程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++ 面向對象(一)—— 類(Classes)

C++ 面向對象(一)—— 類(Classes)

編輯:關於C++

類(class)是一種將數據和函數組織在同一個結構裡的邏輯方法。定義類的關鍵字為class ,其功能與C語言中的struct類似,不同之處是class可以包含函數,而不像struct只能包含數據元素。

類定義的形式是:

    class class_name {
        permission_label_1:
            member1;
        permission_label_2:
            member2;
        ...
    } object_name;
  

 

其中 class_name 是類的名稱 (用戶自定義的類型) ,而可選項object_name 是一個或幾個對象(object)標識。Class的聲明體中包含成員members,成員可以是數據或函數定義,同時也可以包括允許范圍標志 permission labels,范圍標志可以是以下三個關鍵字中任意一個:private:, public: 或 protected:。它們分別代表以下含義:

private :class的private成員,只有同一個class的其他成員或該class的“friend” class可以訪問這些成員。protected :class的protected成員,只有同一個class的其他成員,或該class的“friend” class,或該class的子類(derived classes) 可以訪問這些成員。public :class的public成員,任何可以看到這個class的地方都可以訪問這些成員。

如果我們在定義一個class成員的時候沒有聲明其允許范圍,這些成員將被默認為 private范圍。

例如:

 class CRectangle {

            int x, y;
        public:
            void set_values (int,int);
            int area (void);
    } rect;
    

 

上面例子定義了一個class CRectangle 和該class類型的對象變量rect 。這個class 有4個成員:兩個整型變量 (x 和 y) ,在private 部分 (因為private 是默認的允許范圍);以及兩個函數, 在 public 部分:set_values() 和 area(),這裡只包含了函數的原型(prototype)。

注意class名稱與對象(object)名稱的不同:在上面的例子中,CRectangle 是class 名稱 (即用戶定義的類型名稱),而rect 是一個CRectangle類型的對象名稱。它們的區別就像下面例子中類型名 int和 變量名a 的區別一樣:

int a;

int 是class名稱 (類型名) ,而a 是對象名 object name (變量)。

在程序中,我們可以通過使用對象名後面加一點再加成員名稱(同使用C structs一樣),來引用對象rect 的任何public成員,就像它們只是一般的函數或變量。例如:

rect.set_value (3,4);
myarea = rect.area();

但我們不能夠引用 x 或 y ,因為它們是該class的 private 成員,它們只能夠在該class的其它成員中被引用。暈了嗎?下面是關於class CRectangle的一個復雜的例子:

    // classes example
    #include 
    class CRectangle {
            int x, y;
        public:
            void set_values (int,int);
            int area (void) {return (x*y);}
    };
    
    void CRectangle::set_values (int a, int b) {
        x = a;
        y = b;
    }
    
    int main () {
        CRectangle rect;
        rect.set_values (3,4);
        cout << "area: " << rect.area();
    }			
			
area: 12

上面代碼中新的東西是在定義函數set_values().使用的范圍操作符(雙冒號:: )。它是用來在一個class之外定義該class的成員。注意,我們在CRectangle class內部已經定義了函數area() 的具體操作,因為這個函數非常簡單。而對函數set_values() ,在class內部只是定義了它的原型prototype,而其實現是在class之外定義的。這種在class之外定義其成員的情況必須使用范圍操作符::。

范圍操作符 (::) 聲明了被定義的成員所屬的class名稱,並賦予被定義成員適當的范圍屬性,這些范圍屬性與在class內部定義成員的屬性是一樣的。例如,在上面的例子中,我們在函數set_values() 中引用了private變量x 和 y,這些變量只有在class內部和它的成員中才是可見的。

在class內部直接定義完整的函數,和只定義函數的原型而把具體實現放在class外部的唯一區別在於,在第一種情況中,編譯器(compiler) 會自動將函數作為inline 考慮,而在第二種情況下,函數只是一般的class成員函數。

我們把 x 和 y 定義為private 成員 (記住,如果沒有特殊聲明,所有class的成員均默認為private ),原因是我們已經定義了一個設置這些變量值的函數 (set_values()) ,這樣一來,在程序的其它地方就沒有辦法直接訪問它們。也許在一個這樣簡單的例子中,你無法看到這樣保護兩個變量有什麼意義,但在比較復雜的程序中,這是非常重要的,因為它使得變量不會被意外修改 (這裡意外指的是從object的角度來講的意外)。

使用class的一個更大的好處是我們可以用它來定義多個不同對象(object)。例如,接著上面class CRectangle的例子,除了對象rect之外,我們還可以定義對象rectb :

    // class example
    #include 
    
    class CRectangle {
            int x, y;
        public:
            void set_values (int,int);
            int area (void) {return (x*y);}
    };
    
    void CRectangle::set_values (int a, int b) {
        x = a;
        y = b;
    }
    
    int main () {
        CRectangle rect, rectb;
        rect.set_values (3,4);
        rectb.set_values (5,6);
        cout << "rect area: " << rect.area() << endl;
        cout << "rectb area: " << rectb.area() << endl;
    }
			
rect area: 12
rectb area: 30

注意: 調用函數rect.area() 與調用rectb.area()所得到的結果是不一樣的。這是因為每一個class CRectangle 的對象都擁有它自己的變量 x 和 y,以及它自己的函數set_value() 和 area()。

這是基於對象( object) 和 面向對象編程 (object-oriented programming)的概念的。這個概念中,數據和函數是對象(object)的屬性(properties),而不是像以前在結構化編程 (structured programming) 中所認為的對象(object)是函數參數。在本節及後面的小節中,我們將討論面向對象編程的好處。

在這個具體的例子中,我們討論的class (object的類型)是CRectangle,有兩個實例(instance),或稱對象(object):rect 和 rectb,每一個有它自己的成員變量和成員函數。


構造函數和析構函數 (Constructors and destructors)

對象(object)在生成過程中通常需要初始化變量或分配動態內存,以便我們能夠操作,或防止在執行過程中返回意外結果。例如,在前面的例子中,如果我們在調用函數set_values( ) 之前就調用了函數area(),將會產生什麼樣的結果呢?可能會是一個不確定的值,因為成員x 和 y 還沒有被賦於任何值。

為了避免這種情況發生,一個class 可以包含一個特殊的函數:構造函數 constructor,它可以通過聲明一個與class同名的函數來定義。當且僅當要生成一個class的新的實例 (instance)的時候,也就是當且僅當聲明一個新的對象,或給該class的一個對象分配內存的時候,這個構造函數將自動被調用。下面,我們將實現包含一個構造函數的CRectangle :

    // class example
    #include 
    
    class CRectangle {
        int width, height;
      public:
        CRectangle (int,int);
        int area (void) {return (width*height);}
    };
    
    CRectangle::CRectangle (int a, int b) {
        width = a;
        height = b;
    }
    
    int main () {
        CRectangle rect (3,4);
        CRectangle rectb (5,6);
        cout << "rect area: " << rect.area() << endl;
        cout << "rectb area: " << rectb.area() << endl;
    }    
			
rect area: 12
rectb area: 30

正如你所看到的,這個例子的輸出結果與前面一個沒有區別。在這個例子中,我們只是把函數set_values換成了class的構造函數constructor。注意這裡參數是如何在class實例 (instance)生成的時候傳遞給構造函數的:

CRectangle rect (3,4);
CRectangle rectb (5,6);

同時你可以看到,構造函數的原型和實現中都沒有返回值(return value),也沒有void 類型聲明。構造函數必須這樣寫。一個構造函數永遠沒有返回值,也不用聲明void,就像我們在前面的例子中看到的。

析構函數Destructor 完成相反的功能。它在objects被從內存中釋放的時候被自動調用。釋放可能是因為它存在的范圍已經結束了(例如,如果object被定義為一個函數內的本地(local)對象變量,而該函數結束了);或者是因為它是一個動態分配的對象,而被使用操作符delete釋放了。

析構函數必須與class同名,加水波號tilde (~) 前綴,必須無返回值。

析構函數特別適用於當一個對象被動態分別內存空間,而在對象被銷毀的時我們希望釋放它所占用的空間的時候。例如:

    // example on constructors and destructors
    #include 
    
    class CRectangle {
        int *width, *height;
      public:
        CRectangle (int,int);
        ~CRectangle ();
        int area (void) {return (*width * *height);}
    };
    
    CRectangle::CRectangle (int a, int b) {
        width = new int;
        height = new int;
        *width = a;
        *height = b;
    }
    
    CRectangle::~CRectangle () {
        delete width;
        delete height;
    }
    
    int main () {
        CRectangle rect (3,4), rectb (5,6);
        cout << "rect area: " << rect.area() << endl;
        cout << "rectb area: " << rectb.area() << endl;
        return 0;
    }
			
rect area: 12
rectb area: 30

構造函數重載(Overloading Constructors)

像其它函數一樣,一個構造函數也可以被多次重載(overload)為同樣名字的函數,但有不同的參數類型和個數。記住,編譯器會調用與在調用時刻要求的參數類型和個數一樣的那個函數(Section 2.3, Functions-II)。在這裡則是調用與類對象被聲明時一樣的那個構造函數。

實際上,當我們定義一個class而沒有明確定義構造函數的時候,編譯器會自動假設兩個重載的構造函數 (默認構造函數"default constructor" 和復制構造函數"copy constructor")。例如,對以下class:

   class CExample {
     public:
       int a,b,c;
       void multiply (int n, int m) { a=n; b=m; c=a*b; };
   };
   

 

沒有定義構造函數,編譯器自動假設它有以下constructor 成員函數:

Empty constructor

它是一個沒有任何參數的構造函數,被定義為nop (沒有語句)。它什麼都不做。

CExample::CExample () { };Copy constructor

它是一個只有一個參數的構造函數,該參數是這個class的一個對象,這個函數的功能是將被傳入的對象(object)的所有非靜態(non-static)成員變量的值都復制給自身這個object。

  CExample::CExample (const CExample& rv) {
       a=rv.a;  b=rv.b;  c=rv.c;
   }
   

 

必須注意:這兩個默認構造函數(empty construction 和 copy constructor )只有在沒有其它構造函數被明確定義的情況下才存在。如果任何其它有任意參數的構造函數被定義了,這兩個構造函數就都不存在了。在這種情況下,如果你想要有empty construction 和 copy constructor ,就必需要自己定義它們。

當然,如果你也可以重載class的構造函數,定義有不同的參數或完全沒有參數的構造函數,見如下例子:

    // overloading class constructors
    #include 
    
    Class CRectangle {
        int width, height;
      public:
        CRectangle ();
        CRectangle (int,int);
        int area (void) {return (width*height);}
    };
    
    CRectangle::CRectangle () {
        width = 5;
        height = 5;
    }
    
    CRectangle::CRectangle (int a, int b) {
        width = a;
        height = b;
    }
    
    int main () {
        CRectangle rect (3,4);
        CRectangle rectb;
        cout << "rect area: " << rect.area() << endl;
        cout << "rectb area: " << rectb.area() << endl;
    }
			
rect area: 12
rectb area: 25

在上面的例子中,rectb 被聲明的時候沒有參數,所以它被使用沒有參數的構造函數進行初始化,也就是width 和height 都被賦值為5。

注意在我們聲明一個新的object的時候,如果不想傳入參數,則不需要寫括號():

CRectangle rectb;// right
CRectangle rectb();// wrong!

類的指針(Pointers to classes)

類也是可以有指針的,要定義類的指針,我們只需要認識到,類一旦被定義就成為一種有效的數據類型,因此只需要用類的名字作為指針的名字就可以了。例如:

CRectangle * prect;

是一個指向class CRectangle類型的對象的指針。

就像數據機構中的情況一樣,要想直接引用一個由指針指向的對象(object)中的成員,需要使用操作符 ->。這裡是一個例子,顯示了幾種可能出現的情況:

    // pointer to classes example
    #include 
    
    class CRectangle {
        int width, height;
      public:
        void set_values (int, int);
        int area (void) {return (width * height);}
    };
    
    void CRectangle::set_values (int a, int b) {
        width = a;
        height = b;
    }
    
    int main () {
        CRectangle a, *b, *c;
        CRectangle * d = new CRectangle[2];
        b= new CRectangle;
        c= &a;
        a.set_values (1,2);
        b->set_values (3,4);
        d->set_values (5,6);
        d[1].set_values (7,8);
        cout << "a area: " << a.area() << endl;
        cout << "*b area: " << b->area() << endl;
        cout << "*c area: " << c->area() << endl;
        cout << "d[0] area: " << d[0].area() << endl;
        cout << "d[1] area: " << d[1].area() << endl;
        return 0;
    }
			
a area: 2
*b area: 12
*c area: 2
d[0] area: 30
d[1] area: 56

以下是怎樣讀前面例子中出現的一些指針和類操作符 (*, &, ., ->, [ ]):

*x 讀作: pointed by x (由x指向的)&x 讀作: address of x(x的地址)x.y 讀作: member y of object x (對象x的成員y)(*x).y 讀作: member y of object pointed by x(由x指向的對象的成員y)x->y 讀作: member y of object pointed by x (同上一個等價)x[0] 讀作: first object pointed by x(由x指向的第一個對象)x[1] 讀作: second object pointed by x(由x指向的第二個對象)x[n] 讀作: (n+1)th object pointed by x(由x指向的第n+1個對象)

在繼續向下閱讀之前,一定要確定你明白所有這些的邏輯含義。如果你還有疑問,再讀一遍這一笑節,或者同時參考 小節 "3.3, 指針(Pointers)" 和 "3.5, 數據結構(Structures)".


由關鍵字struct和union定義的類

類不僅可以用關鍵字class來定義,也可以用struct或union來定義。

因為在C++中類和數據結構的概念太相似了,所以這兩個關鍵字struct和class的作用幾乎是一樣的(也就是說在C++中struct定義的類也可以有成員函數,而不僅僅有數據成員)。兩者定義的類的唯一區別在於由class定義的類所有成員的默認訪問權限為private,而struct定義的類所有成員默認訪問權限為public。除此之外,兩個關鍵字的作用是相同的。

union的概念與struct和class定義的類不同, 因為union在同一時間只能存儲一個數據成員。但是由union定義的類也是可以有成員函數的。union定義的類訪問權限默認為public。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved