C++類的詳解



超女選秀的例子我們玩了很久,為了學習的需要,暫時離開美眉們,我將采用實際項目開發的例子來講解類的更多知識。

在C語言基礎知識中已學習過文件操作,在實際開發中,為了提高效率,我會把文件操作封裝成一個類,類的聲明如下:

// 文件操作類聲明
class CFile
{
private:
  FILE *m_fp;        // 文件指針
  bool  m_bEnBuffer; // 是否啟用緩沖區,true-啟用;false-不啟用

public:
  CFile();   // 類的構造函數
  CFile(bool bEnBuffer);   // 類的構造函數

 ~CFile();   // 類的析構函數

  void EnBuffer(bool bEnBuffer=true);  // 啟、禁用緩沖區

  // 打開文件,參數與fopen相同,打開成功true,失敗返回false          
  bool Open(const char *filename,const char *openmode);

  // 調用fprintf向文件寫入數據
  void Fprintf(const char *fmt, ... );

  // 調用fgets從文件中讀取一行
  bool Fgets(char *strBuffer,const int ReadSize);

  // 關閉文件指針
  void Close();
};

一、類成員的訪問權限

C++通過 public、protected、private三個關鍵字來控制成員變量和成員函數的訪問權限,它們分別表示公有的、受保護的、私有的,被稱為成員訪問限定符。所謂訪問權限,就是類外面的代碼訪問該類中成員權限。

在類的內部,即類的成員函數中,無論成員被聲明為 public、protected 還是private,都是可以互相訪問的,沒有訪問權限的限制。

在類的外部(定義類的代碼之外),只能通過對象訪問public的成員,不能訪問 private、protected屬性的成員。

本節重點介紹 public 和 private,protected 將在以后介紹。

private 后面的成員都是私有的,如m_fp和m_bEnBuffer,直到有 public出現才會變成共有的;public 之后再無其他限定符,所以 public后面的成員都是共有的。

private關鍵字的作用在於更好地隱藏類的內部實現,該向外暴露的接口(能通過對象訪問的成員)都聲明為public,不希望外部知道、或者只在類內部使用的、或者對外部沒有影響的成員,都建議聲明為private。

聲明為 private 的成員和聲明為 public 的成員的次序任意,既可以先出現 private部分,也可以先出現 public 部分。如果既不寫 private 也不寫 public,就默認為private。

在一個類體中,private 和 public可以分別出現多次。每個部分的有效范圍到出現另一個訪問限定符或類體結束時(最后一個右花括號)為止。

您可能會說,將成員變量全部設置為 public 省事,確實,這樣做 99.9%的情況下都不是一種錯誤,我也不認為這樣做有什么不妥;但是,將成員變量設置為private 是一種軟件設計規范,尤其是在大中型項目中,還是請大家盡量遵守這一原則。

二、成員變量的命名

成員變量大都以m_開頭,這是約定成俗的寫法,不是語法規定的內容。以m_開頭既可以一眼看出這是成員變量,又可以和成員函數中的參數名字區分開。

例如成員函數EnBuffer的函數體如下:

// 啟、禁用緩沖區
void CFile::EnBuffer(bool bEnBuffer)
{
  m_bEnBuffer=bEnBuffer;
}

三、構造函數

在CFile類的聲明中,有一些特殊的成員函數CFile(),它就是構造函數(constructor)。

  CFile();   // 類的構造函數
  CFile(bool bEnBuffer);   // 類的構造函數	

構造函數的名字和類名相同,沒有返回值,不能被顯式的調用,而是在創建對象時自動執行。

構造函數具備以下特點:

1)構造函數必須是 public 屬性。

2)構造函數沒有返回值,因為沒有變量來接收返回值,即使有也毫無用處,不管是聲明還是定義,函數名前面都不能出現返回值類型,即使是void 也不允許。

3)構造函數可以有參數,允許重載。一個類可以有多個重載的構造函數,創建對象時根據傳遞的參數來判斷調用哪一個構造函數。

4)構造函數在實際開發中會大量使用,它往往用來做一些初始化工作,對成員變量進行初始化等,注意,不能用memset對整下類進行初始化。

示例

CFile::CFile()   // 類的構造函數
{
  m_fp=0;
  m_bEnBuffer=true;
}

CFile::CFile(bool bEnBuffer)   // 類的構造函數
{
  m_fp=0;
  m_bEnBuffer=bEnBuffer;  
}

四、析構函數

在CFile類的聲明中,還有一個特殊的成員函數~CFile(),它就是析構函數(destructor)。

~CFile();   // 類的析構函數

析構函數的名字在類的名字前加~,沒有返回值,但可以被顯式的調用,在對象銷毀時自動執行,用於進行清理工作,例如釋放分配的內存、關閉打開的文件等,這個用途非常重要,可以防止程序員犯錯。

析構函數具備以下特點:

1)構造函數必須是 public 屬性的。

2)構造函數沒有返回值,因為沒有變量來接收返回值,即使有也毫無用處,不管是聲明還是定義,函數名前面都不能出現返回值類型,即使是void 也不允許。

3)析構函數不允許重載的。一個類只能有一個析構函數。

CFile::~CFile()   // 類的析構函數
{
  Close();  // 調用Close釋放資源
}

五、C++程序也很優雅

很多人說C/C++程序很煩鎖,python程序很優雅,說這話人的很荒謬,那是因為他C/C++並不了解,只要我們願意,可以寫出和python一樣優雅簡潔的代碼,在book210.cpp中,main函數的代碼極為精簡。

示例(book210.cpp)

/*
 * 程序名:book210.cpp,此程序演示用C++類的更多知識。
 * 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

// 文件操作類聲明
class CFile
{
private:
  FILE *m_fp;        // 文件指針
  bool  m_bEnBuffer; // 是否啟用緩沖區,true-啟用;false-不啟用

public:
  CFile();   // 類的構造函數
  CFile(bool bEnBuffer);   // 類的構造函數

 ~CFile();   // 類的析構函數

  void EnBuffer(bool bEnBuffer=true);  // 啟、禁用緩沖區

  // 打開文件,參數與fopen相同,打開成功true,失敗返回false          
  bool Open(const char *filename,const char *openmode);

  // 調用fprintf向文件寫入數據
  void Fprintf(const char *fmt,... );

  // 調用fgets從文件中讀取一行
  bool Fgets(char *strBuffer,const int ReadSize);

  // 關閉文件指針
  void Close();
};

int main(int argc,char *argv[])
{
  if (argc !=2) { printf("請輸入待打開的文件名。\n"); return -1; }

  CFile File;

  if (File.Open(argv[1],"r")==false) { printf("File.Open(%s)失敗。\n",argv[1]); return -1; }
  
  char strLine[301];

  while (true)
  { // 從文件中讀取每一行
    if (File.Fgets(strLine,300)==false) break;

    printf("%s",strLine);   // 把從文件中讀到的內容顯示到屏幕
  }
}

CFile::CFile()   // 類的構造函數
{
  m_fp=0;
  m_bEnBuffer=true;
}

CFile::CFile(bool bEnBuffer)   // 類的構造函數
{
  m_fp=0;
  m_bEnBuffer=bEnBuffer;
}

// 關閉文件指針
void CFile::Close() 
{
  if (m_fp!=0) fclose(m_fp);  // 關閉文件指針
  m_fp=0;
}

CFile::~CFile()   // 類的析構函數
{
  Close();  // 調用Close釋放資源
}

// 啟、禁用緩沖區
void CFile::EnBuffer(bool bEnBuffer)
{
  m_bEnBuffer=bEnBuffer;
}

// 打開文件,參數與fopen相同,打開成功true,失敗返回false          
bool CFile::Open(const char *filename,const char *openmode)
{
  Close();  // 打開新的文件之前,如果已經打開了文件,關閉它。

  if ( (m_fp=fopen(filename,openmode)) == 0 ) return false;

  return true;
}

// 調用fprintf向文件寫入數據
void CFile::Fprintf(const char *fmt,...)
{
  if ( m_fp == 0 ) return;

  va_list ap;
  va_start(arg,ap);
  vfprintf(m_fp,fmt,ap);
  va_end(ap);

  if ( m_bEnBuffer == false ) fflush(m_fp);
}

// 調用fgets從文件中讀取一行
bool CFile::Fgets(char *strBuffer,const int ReadSize)
{
  if ( m_fp == 0 ) return false;

  memset(strBuffer,0,ReadSize);

  if (fgets(strBuffer,ReadSize,m_fp) == 0) return false;

  return true;
}

book210運行的效果就是把文件的內容一行一行的顯示出來,類型linux系統的cat命令。

在這里插入圖片描述

六、類的其它知識

關於類的其它知識,包括this指針、static靜態成員、友元等內容,意義不大,我不介紹了,時間太寶貴,有太多重要的知識要學習,沒必要把時間浪費在這些不痛不癢又沒什么實用價值的知識點上,大家以后有時間了再看也行。

七、可變參數

我們已經介紹過printf、fprintf、sprintf、snprintf函數,它們是一組功能相似的函數,並且有一個共同點,就是函數的參數列表是可以變化的。

函數的聲明如下:

int printf(const char *format, ...);        // 格式化輸出到屏幕
int fprintf(FILE *stream, const char *format, ...);  // 格式化輸出到文件
int sprintf(char *str, const char *format, ...);     // 格式化輸出到字符串
int snprintf(char *str, size_t size, const char *format, ...); // 格式化輸出指定長度的內容到字符串

在實際開發中,我們的自定義函數也會用到可變參數,實現類似上述函數的功能,例如CFile類的Fprintf成員函數。

C語言采用va_start宏、va_end宏和一系列函數來實現可變參數功能。

void CFile::Fprintf(const char *fmt,...)
{
  if ( m_fp == 0 ) return;

  va_list ap;
  va_start(arg,ap);
  vfprintf(m_fp,fmt,ap);
  va_end(ap);

  if ( m_bEnBuffer == false ) fflush(m_fp);
}

以CFile類的Fprintf成員函數為例。

void CFile::Fprintf(const char *fmt,...);     // 可變參數自定義函數的聲明方法

va_list指針、va_start宏、va_end宏用於分析參數,難以理解,大家會用就行,我不詳細介紹。

  va_list ap;
  va_start(ap,fmt);
  vfprintf(m_fp,fmt,ap);     
  va_end(ap);

vfprintf函數把宏分析的結果輸出到文件,還有一系列功能相似的函數,聲明如下:

// 輸出的屏幕
int vprintf(const char *format, va_list ap);
// 輸出到文件
int vfprintf(FILE *stream, const char *format, va_list ap);
// 輸出到字符串
int vsprintf(char *str, const char *format, va_list ap);
// 輸出到字符串,第二個參數指定了輸出結果的長度,類似snprintf函數。
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

八、課后作業

1)編寫示例程序,測試類的類成員的訪問權限。

2)編寫示例程序,測試類的構造函數和它的重載,采用gdb跟蹤構造函數的執行過程。

3)編寫示例程序,測試類的析構函數,采用gdb跟蹤析構造的執行過程。

4)編寫示例程序,實現printf、sprintf和snprintf函數的功能,函數的聲明如下:

int myprintf(const char *format, ...);
int mysprintf(const char *format, ...);
int mysnprintf(const char *format, ...);

5)類定義包括成員變量和成員函數的聲明以及成員函數的定義,在實際開發中,我們通常將公共類的聲明放在頭文件中(如_public.h),成員函數的定義放在程序文件中(如_public.cpp),請按這種方式修改book210.cpp程序,增加_public.h和_public.cpp程序,修改makefile。

九、版權聲明

C語言技術網原創文章,轉載請說明文章的來源、作者和原文的鏈接。
來源:C語言技術網(www.freecplus.net)
作者:碼農有道

如果文章有錯別字,或者內容有錯誤,或其他的建議和意見,請您留言指正,非常感謝!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM