1. 程式人生 > >C++ 類 & 對象

C++ 類 & 對象

復制對象 存在 functions 表示法 1.2 函數 基本類 oid 遇到

C++ 在 C 語言的基礎上增加了面向對象編程,C++ 支持面向對象程序設計。類是 C++ 的核心特性,通常被稱為用戶定義的類型。

類用於指定對象的形式,它包含了數據表示法和用於處理數據的方法。類中的數據和方法稱為類的成員。函數在一個類被稱為類的成員。

C++ 類定義

定義一個類,本質上是定義一個數據類型的藍圖。這實際上並沒有定義任何數據,但它定義了類的名稱意味著什麽,也就是說,它定義了類的對象包括了什麽,以及可以在這個對象上執行哪些操作。

類定義是以關鍵字 class 開頭,後跟類的名稱。類的主體是包含在一對花括號中。類定義後必須跟著一個分號或一個聲明列表。如下所示:

class Box

{

public:

  double length; // 盒子的長度

  double breadth; // 盒子的寬度

  double height; // 盒子的高度

};

定義 C++ 對象

類提供了對象的藍圖,所以基本上,對象是根據類來創建的。聲明類的對象,就像聲明基本類型的變量一樣。

如下所示:Box Box1; // 聲明 Box1,類型為 Box

訪問數據成員

類的對象的公共數據成員可以使用直接成員訪問運算符 (.) 來訪問。

需要註意的是,私有的成員和受保護的成員不能使用直接成員訪問運算符 (.) 來直接訪問。

類 & 對象詳解

概念描述
類成員函數 類的成員函數是指那些把定義和原型寫在類定義內部的函數,就像類定義中的其他變量一樣。
類訪問修飾符 類成員可以被定義為 public、private 或 protected。默認情況下是定義為 private。
構造函數 & 析構函數 類的構造函數是一種特殊的函數,在創建一個新的對象時調用。類的析構函數也是一種特殊的函數,在刪除所創建的對象時調用。
C++ 拷貝構造函數 拷貝構造函數,是一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。
C++ 友元函數 友元函數可以訪問類的 private 和 protected 成員。
C++ 內聯函數 通過內聯函數,編譯器試圖在調用函數的地方擴展函數體中的代碼。
C++ 中的 this 指針 每個對象都有一個特殊的指針 this,它指向對象本身。
C++ 中指向類的指針 指向類的指針方式如同指向結構的指針。實際上,類可以看成是一個帶有函數的結構。
C++ 類的靜態成員 類的數據成員和函數成員都可以被聲明為靜態的。

C++ 類成員函數

類的成員函數是指那些把定義和原型寫在類定義內部的函數,就像類定義中的其他變量一樣。類成員函數是類的一個成員,它可以操作類的任意對象,可以訪問對象中的所有成員。

使用成員函數來訪問類的成員,而不是直接訪問這些類的成員:

class Box
{
   public:
      double length;         // 長度
      double breadth;        // 寬度
      double height;         // 高度
      double getVolume(void);// 返回體積
};

成員函數可以定義在類定義內部,或者單獨使用範圍解析運算符 :: 來定義。在類定義中定義的成員函數把函數聲明為內聯的,即便沒有使用 inline 標識符。可以按照如下方式定義 Volume() 函數:

class Box
{
   public:
      double length;      // 長度
      double breadth;     // 寬度
      double height;      // 高度
   
      double getVolume(void)
      {
         return length * breadth * height;
      }
};

也可以在類的外部使用範圍解析運算符 :: 定義該函數,如下所示:

double Box::getVolume(void)
{
    return length * breadth * height;
}

在這裏,需要強調一點,在 :: 運算符之前必須使用類名。調用成員函數是在對象上使用點運算符(.),這樣它就能操作與該對象相關的數據,如下所示:

Box myBox;          // 創建一個對象

myBox.getVolume();  // 調用該對象的成員函數

C++ 類訪問修飾符

數據封裝是面向對象編程的一個重要特點,它防止函數直接訪問類類型的內部成員。類成員的訪問限制是通過在類主體內部對各個區域標記 public、private、protected 來指定的。關鍵字 public、private、protected 稱為訪問修飾符。

一個類可以有多個 public、protected 或 private 標記區域。每個標記區域在下一個標記區域開始之前或者在遇到類主體結束右括號之前都是有效的。成員和類的默認訪問修飾符是 private。

公有(public)成員

公有成員在程序中類的外部是可訪問的。可以不使用任何成員函數來設置和獲取公有變量的值。

私有(private)成員

私有成員變量或函數在類的外部是不可訪問的,甚至是不可查看的。只有類和友元函數可以訪問私有成員。

默認情況下,類的所有成員都是私有的。

實際操作中,一般會在私有區域定義數據,在公有區域定義相關的函數,以便在類的外部也可以調用這些函數。

保護(protected)成員

保護成員變量或函數與私有成員十分相似,但有一點不同,保護成員在派生類(即子類)中是可訪問的。

繼承中的特點

有public, protected, private三種繼承方式,它們相應地改變了基類成員的訪問屬性。

  • 1.public 繼承:基類 public 成員,protected 成員,private 成員的訪問屬性在派生類中分別變成:public, protected, private

  • 2.protected 繼承:基類 public 成員,protected 成員,private 成員的訪問屬性在派生類中分別變成:protected, protected, private

  • 3.private 繼承:基類 public 成員,protected 成員,private 成員的訪問屬性在派生類中分別變成:private, private, private

但無論哪種繼承方式,上面兩點都沒有改變:

  • 1.private 成員只能被本類成員(類內)和友元訪問,不能被派生類訪問;

  • 2.protected 成員可以被派生類訪問。

C++ 類構造函數 & 析構函數

類的構造函數

類的構造函數是類的一種特殊的成員函數,它會在每次創建類的新對象時執行。

構造函數的名稱與類的名稱是完全相同的,並且不會返回任何類型,也不會返回 void。構造函數可用於為某些成員變量設置初始值。

class Line

{

public:

  void setLength( double len );

  double getLength( void );

  Line(); // 這是構造函數

private:

  double length;

};

帶參數的構造函數

默認的構造函數沒有任何參數,但如果需要,構造函數也可以帶有參數。這樣在創建對象時就會給對象賦初始值。

class Line

{

public:

  void setLength( double len );

  double getLength( void );

  Line(double len); // 這是構造函數

private:

  double length;

};

使用初始化列表來初始化字段

使用初始化列表來初始化字段:Line::Line( double len): length(len)。

假設有一個類 C,具有多個字段 X、Y、Z 等需要進行初始化,同理地,您可以使用上面的語法,只需要在不同的字段使用逗號進行分隔:C::C( double a, double b, double c): X(a), Y(b), Z(c)

類的析構函數

類的析構函數是類的一種特殊的成員函數,它會在每次刪除所創建的對象時執行。

析構函數的名稱與類的名稱是完全相同的,只是在前面加了個波浪號(~)作為前綴,它不會返回任何值,也不能帶有任何參數。析構函數有助於在跳出程序(比如關閉文件、釋放內存等)前釋放資源。

C++ 拷貝構造函數

拷貝構造函數是一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。拷貝構造函數通常用於:

  • 通過使用另一個同類型的對象來初始化新創建的對象。

  • 復制對象把它作為參數傳遞給函數。

  • 復制對象,並從函數返回這個對象。

如果在類中沒有定義拷貝構造函數,編譯器會自行定義一個。如果類帶有指針變量,並有動態內存分配,則它必須有一個拷貝構造函數。拷貝構造函數的最常見形式如下:

classname (const classname &obj) { // 構造函數的主體 }

在這裏,obj 是一個對象引用,該對象是用於初始化另一個對象的。

class Line

{

public:

  int getLength( void );

  Line( int len ); // 簡單的構造函數

  Line( const Line &obj); // 拷貝構造函數

  ~Line(); // 析構函數

private:

  int *ptr;

};

拷貝構造函數

幾個原則:

C++ primer p406 :拷貝構造函數是一種特殊的構造函數,具有單個形參,該形參(常用const修飾)是對該類類型的引用。當定義一個新對象並用一個同類型的對象對它進行初始化時,將顯示使用拷貝構造函數。當該類型的對象傳遞給函數或從函數返回該類型的對象時,將隱式調用拷貝構造函數。

C++支持兩種初始化形式:

拷貝初始化 int a = 5; 和直接初始化 int a(5); 對於其他類型沒有什麽區別,對於類類型直接初始化直接調用實參匹配的構造函數,拷貝初始化總是調用拷貝構造函數,也就是說:

A x(2);  //直接初始化,調用構造函數
A y = x;  //拷貝初始化,調用拷貝構造函數

必須定義拷貝構造函數的情況:

只包含類類型成員或內置類型(但不是指針類型)成員的類,無須顯式地定義拷貝構造函數也可以拷貝;有的類有一個數據成員是指針,或者是有成員表示在構造函數中分配的其他資源,這兩種情況下都必須定義拷貝構造函數。

什麽情況使用拷貝構造函數:

類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:

  • (1)一個對象以值傳遞的方式傳入函數體
  • (2)一個對象以值傳遞的方式從函數返回
  • (3)一個對象需要通過另外一個對象進行初始化。

C++ 友元函數

類的友元函數是定義在類外部,但有權訪問類的所有私有(private)成員和保護(protected)成員。盡管友元函數的原型有在類的定義中出現過,但是友元函數並不是成員函數。

友元可以是一個函數,該函數被稱為友元函數;友元也可以是一個類,該類被稱為友元類,在這種情況下,整個類及其所有成員都是友元。

如果要聲明函數為一個類的友元,需要在類定義中該函數原型前使用關鍵字 friend,如下所示:

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};

C++ 內聯函數

C++ 內聯函數是通常與類一起使用。如果一個函數是內聯的,那麽在編譯時,編譯器會把該函數的代碼副本放置在每個調用該函數的地方。

對內聯函數進行任何修改,都需要重新編譯函數的所有客戶端,因為編譯器需要重新更換一次所有的代碼,否則將會繼續使用舊的函數。

如果想把一個函數定義為內聯函數,則需要在函數名前面放置關鍵字 inline,在調用函數之前需要對函數進行定義。如果已定義的函數多於一行,編譯器會忽略 inline 限定符。

在類定義中的定義的函數都是內聯函數,即使沒有使用 inline 說明符。

Tip: 只有當函數只有 10 行甚至更少時才將其定義為內聯函數.

定義: 當函數被聲明為內聯函數之後, 編譯器會將其內聯展開, 而不是按通常的函數調用機制進行調用.

優點: 當函數體比較小的時候, 內聯該函數可以令目標代碼更加高效. 對於存取函數以及其它函數體比較短, 性能關鍵的函數, 鼓勵使用內聯.

缺點: 濫用內聯將導致程序變慢. 內聯可能使目標代碼量或增或減, 這取決於內聯函數的大小. 內聯非常短小的存取函數通常會減少代碼大小, 但內聯一個相當大的函數將戲劇性的增加代碼大小. 現代處理器由於更好的利用了指令緩存, 小巧的代碼往往執行更快。

結論: 一個較為合理的經驗準則是, 不要內聯超過 10 行的函數. 謹慎對待析構函數, 析構函數往往比其表面看起來要更長, 因為有隱含的成員和基類析構函數被調用!

另一個實用的經驗準則: 內聯那些包含循環或 switch 語句的函數常常是得不償失 (除非在大多數情況下, 這些循環或 switch 語句從不被執行).

有些函數即使聲明為內聯的也不一定會被編譯器內聯, 這點很重要; 比如虛函數和遞歸函數就不會被正常內聯. 通常, 遞歸函數不應該聲明成內聯函數.(遞歸調用堆棧的展開並不像循環那麽簡單, 比如遞歸層數在編譯時可能是未知的, 大多數編譯器都不支持內聯遞歸函數). 虛函數內聯的主要原因則是想把它的函數體放在類定義內, 為了圖個方便, 抑或是當作文檔描述其行為, 比如精短的存取函數.

C++ this 指針

在 C++ 中,每一個對象都能通過 this 指針來訪問自己的地址。this 指針是所有成員函數的隱含參數。因此,在成員函數內部,它可以用來指向調用對象。

友元函數沒有 this 指針,因為友元不是類的成員。只有成員函數才有 this 指針。

#include <iostream>
 
using namespace std;

class Box
{
   public:
      // 構造函數定義
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      int compare(Box box)
      {
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   if(Box1.compare(Box2))
   {
      cout << "Box2 is smaller than Box1" <<endl;
   }
   else
   {
      cout << "Box2 is equal to or larger than Box1" <<endl;
   }
   return 0;
}

C++ 指向類的指針

一個指向 C++ 類的指針與指向結構的指針類似,訪問指向類的指針的成員,需要使用成員訪問運算符 ->,就像訪問指向結構的指針一樣。與所有的指針一樣,您必須在使用指針之前,對指針進行初始化。

C++ 類的靜態成員

可以使用 static 關鍵字來把類成員定義為靜態的。當我們聲明類的成員為靜態時,這意味著無論創建多少個類的對象,靜態成員都只有一個副本。

靜態成員在類的所有對象中是共享的。如果不存在其他的初始化語句,在創建第一個對象時,所有的靜態數據都會被初始化為零。

不能把靜態成員的初始化放置在類的定義中,但是可以在類的外部通過使用範圍解析運算符 :: 來重新聲明靜態變量從而對它進行初始化,

靜態函數成員

如果把函數成員聲明為靜態的,就可以把函數與類的任何特定對象獨立開來。靜態成員函數即使在類對象不存在的情況下也能被調用,靜態函數只要使用類名加範圍解析運算符 :: 就可以訪問。

靜態成員函數只能訪問靜態數據成員,不能訪問其他靜態成員函數和類外部的其他函數。

靜態成員函數有一個類範圍,他們不能訪問類的 this 指針。可以使用靜態成員函數來判斷類的某些對象是否已被創建。

#include <iostream>
 
using namespace std;

class Box
{
   public:
      static int objectCount;
      // 構造函數定義
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次創建對象時增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // 長度
      double breadth;    // 寬度
      double height;     // 高度
};

// 初始化類 Box 的靜態成員
int Box::objectCount = 0;

int main(void)
{
  
   // 在創建對象之前輸出對象的總數
   cout << "Inital Stage Count: " << Box::getCount() << endl;

   Box Box1(3.3, 1.2, 1.5);    // 聲明 box1
   Box Box2(8.5, 6.0, 2.0);    // 聲明 box2

   // 在創建對象之後輸出對象的總數
   cout << "Final Stage Count: " << Box::getCount() << endl;

   return 0;
}

C++ 類 & 對象