官术网_书友最值得收藏!

icon2

2.1 類與對(duì)象

1.類與對(duì)象的概念

面向?qū)ο缶幊痰闹饕枷胧前褬?gòu)成問題的各個(gè)事務(wù)分解成各個(gè)對(duì)象,建立對(duì)象的目的不是為了完成一個(gè)步驟,而是為了描述一個(gè)事物在解決問題中經(jīng)過的步驟和行為。對(duì)象作為程序的基本單元,將程序和數(shù)據(jù)封裝其中,以提高軟件的重用性、靈活性和擴(kuò)展性。類,是創(chuàng)建對(duì)象的模板,一個(gè)類可以創(chuàng)建多個(gè)相同的對(duì)象;對(duì)象,是類的實(shí)例,是按照類的規(guī)則創(chuàng)建的。類與對(duì)象的關(guān)系如圖2-1所示。

圖2-1 類與對(duì)象的關(guān)系

圖2-1中,人和學(xué)生屬于類,張三和學(xué)生李四都是對(duì)象,姓名、身高、地址、年齡、性別、血型都是人這一類的屬性,跑步、吃飯,這都是人這個(gè)類的方法。屬性是一個(gè)變量,用來表示一個(gè)對(duì)象的特征;方法是一個(gè)函數(shù),用來表示對(duì)象的操作。對(duì)象的屬性和方法統(tǒng)稱為對(duì)象的成員。

每一個(gè)實(shí)體都是對(duì)象,有一些對(duì)象是具有相同的結(jié)構(gòu)和特性的。每個(gè)對(duì)象都屬于一個(gè)特定的類型,這個(gè)特定的類型稱為類。正如結(jié)構(gòu)體類型和結(jié)構(gòu)體變量一樣,需要先聲明一個(gè)結(jié)構(gòu)體類型,再用它去定義結(jié)構(gòu)體變量。在C++中也是先聲明一個(gè)類的類型,然后用它去定義若干個(gè)同類型的對(duì)象。可以說,對(duì)象是類類型的一個(gè)變量,類則是對(duì)象的模板。類是抽象的,不占用存儲(chǔ)空間的;而對(duì)象是具體的,占用存儲(chǔ)空間。

類類型的聲明形式如下:


class類名{                    // class,聲明一個(gè)類必須有的關(guān)鍵字
    private:
        私有的數(shù)據(jù)和成員函數(shù);
    public:
        公用的數(shù)據(jù)和成員函數(shù);
};                         // 類的聲明以分號(hào)結(jié)束

其中,private和public稱為成員訪問限定符。

下面再來看下結(jié)構(gòu)體和類的不同之處。例2.1展示了在C++中聲明一個(gè)結(jié)構(gòu)體類型的方法;例2.2則是聲明一個(gè)類的方法。

【例2.1】 聲明一個(gè)結(jié)構(gòu)體類型。


struct SStudent{
    int num;
    char name[20];
    int age;
};
SStudent st_stu1,st_stu2;          // 定義了兩個(gè)結(jié)構(gòu)體變量

【例2.2】 聲明一個(gè)類。


class CStudent{
    int num;
    char name[20];
    int age;               // 這些是數(shù)據(jù)成員,也稱為成員變量
    void display(){          // 這是成員函數(shù)
        cout<<"num:"<<num<<endl;
        cout<<"name:"<<name<<endl;
        cout<<"age:"<<age<<endl;
    }
}; 
CStudent cstu1,cstu2;          // 定義了兩個(gè)對(duì)象

例2.1中聲明了一個(gè)SStudent結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)有三個(gè)數(shù)據(jù)成員:num、name[20]和age;還定義了兩個(gè)SStudent結(jié)構(gòu)體類型的變量st_stu1和st_stu1。例2.2中聲明了一個(gè)CStudent類,類中有三個(gè)數(shù)據(jù)成員和一個(gè)成員函數(shù)display,還定義了兩個(gè)對(duì)象cstu1和cstu2。

從上面結(jié)構(gòu)體的聲明方法和類的聲明方法來看,貌似只有關(guān)鍵字不一樣,結(jié)構(gòu)體的關(guān)鍵字是struct,類的關(guān)鍵字是class。事實(shí)上,聲明類的方法是由聲明結(jié)構(gòu)體類型的方法發(fā)展而來的。但是,struct中的成員訪問權(quán)限默認(rèn)是public,而class中則默認(rèn)是private的。在C語(yǔ)言里,struct中不能定義成員函數(shù),而在C++中,增加了class類型后,擴(kuò)展了struct的功能,struct中也能定義成員函數(shù)了。

【例2.3】 struct中定義成員函數(shù)。


struct SStudent{
public:
    void display(){          // 這是成員函數(shù)
        cout<<"num:"<<num<<endl;
        cout<<"name:"<<name<<endl;
        cout<<"age:"<<age<<endl;
    }                    // 這里沒有分號(hào)
private:
    int num;
    char name[20];
    int age;
};

例2.3中在結(jié)構(gòu)體SStudent中定義了一個(gè)display的public的成員函數(shù),3個(gè)private的成員變量。請(qǐng)注意,一個(gè)成員函數(shù)如果在類中定義,在定義結(jié)束的}之后是不需要加分號(hào)的。

在一個(gè)類中,關(guān)鍵字private和public可以分別出現(xiàn)多次,從每個(gè)部分的有效范圍到出現(xiàn)另一個(gè)訪問限定符或者類體結(jié)束為止。為了使程序清晰,建議大家養(yǎng)成良好的習(xí)慣,使每一種成員訪問限定符在類體中只出現(xiàn)一次,并且先寫public部分,把private部分放在類體的后部,這樣可以使得用戶將注意力集中在能被外界調(diào)用的成員上,使得閱讀者的思路更加清晰。

一個(gè)對(duì)象的聲明方式有以下幾種。

(1)class 類名 對(duì)象名。

(2)類名 對(duì)象名。

這兩種方法是等效的,但顯然第二種方法更為簡(jiǎn)潔與方便。

2.成員函數(shù)

類的成員函數(shù)是函數(shù)的一種,與第1章介紹過的函數(shù)一樣,具有返回值和函數(shù)類型,它與一般函數(shù)的區(qū)別在于,它是屬于類的成員,出現(xiàn)在類體中。它可以被指定為private(私有的)、protected(受保護(hù)的)和public(公用的)。

在使用成員函數(shù)時(shí),要注意它的權(quán)限以及作用域。比如,私有的成員函數(shù)只能被本類中其他成員函數(shù)使用,而不能在類外被調(diào)用。成員函數(shù)中可以使用類中的任何成員,包括私有的和公用的。

成員函數(shù)可以在類體中定義,也可以在類外定義。

【例2.4】 成員函數(shù)在類外被定義。


class CStudent {
public:
    void display();          // 這里需要分號(hào)
private:
    int num;
    char name[20];
    int age;
};
void CStudent::display(){
    cout<<"num:"<<num<<endl;
    cout<<"name:"<<name<<endl;
    cout<<"age:"<<age<<endl;
}

例2.4中在類CStudent外定義了display成員函數(shù)。注意,在類外定義成員函數(shù)時(shí),必須加上類名,予以限定。“::”是作用域限定符或作用域運(yùn)算符,用它聲明函數(shù)是屬于哪個(gè)類的。如果沒有寫類名或者沒有寫類名和作用域限定符,則這個(gè)函數(shù)不屬于任何類,而是一個(gè)普通函數(shù)。成員函數(shù)必須先在類中聲明,然后再在類外定義,即類體的位置應(yīng)在函數(shù)定義之前,否則編譯時(shí)會(huì)出錯(cuò)。

3.類的封裝性

C++中通過類實(shí)現(xiàn)封裝性,把數(shù)據(jù)和這些數(shù)據(jù)有關(guān)的操作封裝在一個(gè)類里。但是,人們?cè)谑褂脮r(shí),往往不關(guān)心類中的具體實(shí)現(xiàn),而只需知道調(diào)用哪個(gè)函數(shù)會(huì)得到什么結(jié)果,能實(shí)現(xiàn)什么功能即可。

為了實(shí)現(xiàn)類對(duì)象的封裝性(數(shù)據(jù)隱藏和提供訪問接口),類類型定義為類成員提供了私有、公有和受保護(hù)的3種基本訪問權(quán)限供用戶選擇,具體內(nèi)容如下所述。

(1)私有成員

1)訪問權(quán)限:只限于類成員訪問。

2)關(guān)鍵字:private。

3)私有段:從private關(guān)鍵字開始至其他訪問權(quán)限聲明之間所有成員組成的代碼段。

4)成員種類:數(shù)據(jù)成員和成員函數(shù)。

(2)公有成員

1)訪問權(quán)限:允許類成員和類外的任何訪問。

2)關(guān)鍵字:public。

3)私有段:從public關(guān)鍵詞開始至其他訪問權(quán)限聲明之間所有成員組成的代碼段。

4)成員種類:數(shù)據(jù)成員和成員函數(shù)。

(3)受保護(hù)成員

1)訪問權(quán)限:允許類成員和派生類成員訪問,不運(yùn)行類外的任何訪問。

2)關(guān)鍵字:protect。

3)私有段:從protect關(guān)鍵詞開始至其他訪問權(quán)限聲明之間所有成員組成的代碼段。

4)成員種類:數(shù)據(jù)成員和成員函數(shù)。

除了限制訪問權(quán)限,在寫代碼時(shí)經(jīng)常要注意“將接口與實(shí)現(xiàn)分離”,這也是隱蔽信息的一個(gè)重要手段。接口與實(shí)現(xiàn)分離,有以下兩個(gè)好處:①如果想修改或者擴(kuò)充類的功能,只需要修改類中的實(shí)現(xiàn),類外部分可以不用修改;②如果發(fā)現(xiàn)類中數(shù)據(jù)成員數(shù)據(jù)有錯(cuò),則只需要在類內(nèi)檢查訪問這些數(shù)據(jù)成員的成員函數(shù)。

一般是將類的聲明放在指定的頭文件中,用戶如果想使用這個(gè)類,直接包含這個(gè)頭文件即可。因?yàn)樵陬^文件中有類的聲明,所以可以直接在程序中用這個(gè)類來定義對(duì)象。為了實(shí)現(xiàn)信息隱蔽,會(huì)把類成員函數(shù)的定義放在另一個(gè)文件中,而不放在頭文件中。

例如,可以將類Student的聲明放在student.h中。

【例2.5】 類的定義與使用。

student.h中的代碼為:


class CStudent{
public:
    void display();
private:
    int num;
    char name[20];
    int age;
};

student.cpp中的代碼為:


#include<iostream>
#include "student.h"          // 這里需要include這個(gè)頭文件,否則無法找到Student
using namespace std; 
void CStudent::display(){          // 這里要注明是Student類的
    cout<<"num:"<<num<<endl;
    cout<<"name:"<<name<<endl;
    cout<<"age:"<<age<<endl;
}

main.cpp中的代碼為:


#include<iostream>
#include "student.h"          // 注意這里是雙引號(hào)
int main(){
    CStudent stu1;          // 定義stu1對(duì)象
    stu1.display();          // 指向stu1對(duì)象的成員函數(shù)
    return 0;
}

執(zhí)行以下這3行命令即可編譯成功:


g++ -c student.cpp 
g++ -c main.cpp 
g++ -o main main.o student.o

編譯后,會(huì)生成main文件,執(zhí)行./main命令,獲得程序的執(zhí)行結(jié)果為:


num:0
name:
age:0

這種結(jié)果是由于還沒有對(duì)數(shù)據(jù)成員進(jìn)行初始化導(dǎo)致的,下面的一節(jié)會(huì)介紹如何初始化一個(gè)對(duì)象。

例2.5中定義了一個(gè)類CStudent,所有的數(shù)據(jù)成員和成員函數(shù)都聲明在頭文件student.h中,而成員函數(shù)的定義則是在.cpp文件student.cpp中。main.cpp中要使用CStudent類時(shí)需要把頭文件student.h包含進(jìn)來。編譯時(shí),編譯器g++會(huì)把main.cpp和student.cpp分別編譯成main.o和student.o,再把main.o和student.o鏈接成可執(zhí)行文件main,圖2-2展示了這一過程。

圖2-2 編譯與鏈接

編譯與鏈接的相關(guān)知識(shí)會(huì)在第4章詳細(xì)展開,這里只作簡(jiǎn)單介紹。

4.構(gòu)造函數(shù)

數(shù)據(jù)成員是不能在類中初始化的,而構(gòu)造函數(shù),正是為此而生,主要用來處理數(shù)據(jù)成員的初始化。它不需要用戶調(diào)用,而是在建立對(duì)象時(shí)自動(dòng)執(zhí)行的。

構(gòu)造函數(shù)的名字必須與類名相同,而不能由用戶任意命名,以便編譯系統(tǒng)能識(shí)別它并把它作為構(gòu)造函數(shù)處理。它是一個(gè)沒有返回值的函數(shù),構(gòu)造函數(shù)在類中定義如例2.6所示。

【例2.6】 構(gòu)造函數(shù)的定義。


class Time{
public:
    Time(){               // 這就是構(gòu)造函數(shù)
        hour=0;
        minute=0;
        second=0;
    }
    set_time();
    get_time();
private:
    int hour,minute,second;
};

構(gòu)造函數(shù)也可以在類外定義,如例2.7所示。

【例2.7】 在類外定義構(gòu)造函數(shù)。


class Time{
public:
    Time();               // 對(duì)構(gòu)造函數(shù)進(jìn)行聲明
    set_time();
    get_time();
private:
    int hour,minute,second;
};
Time::Time(){               // 定義構(gòu)造函數(shù),需要加上類名和域限定符"::"
    hour=0;
    minute=0;
    second=0;
}

在構(gòu)造函數(shù)的函數(shù)體中,不僅可以對(duì)數(shù)據(jù)成員賦值,也可以包含其他語(yǔ)句。不過不提倡在構(gòu)造函數(shù)中加入與初始化無關(guān)的內(nèi)容,以保證程序清晰。如果用戶自己沒有定義構(gòu)造函數(shù),那么C++系統(tǒng)就會(huì)自動(dòng)為其生成一個(gè)構(gòu)造函數(shù),只是這個(gè)構(gòu)造函數(shù)的函數(shù)體是空的,什么也不做,當(dāng)然也不進(jìn)行初始化。

構(gòu)造函數(shù)分為不帶參數(shù)的構(gòu)造函數(shù)與帶參數(shù)的構(gòu)造函數(shù)。不帶參數(shù)的構(gòu)造函數(shù)使該類的每一個(gè)對(duì)象都得到相同的初始值;帶參數(shù)的構(gòu)造函數(shù)則可以方便地實(shí)現(xiàn)對(duì)不同的對(duì)象進(jìn)行不同的初始化。

【例2.8】 帶參數(shù)的構(gòu)造函數(shù)的使用舉例。


#include<iostream>
using namespace std;
#define pi 3.1415
class Circle{
public:
    Circle(int r);          // 形參列表
    double Area();
private:
    int radius;               // 數(shù)據(jù)成員
};
Circle::Circle(int r){
    radius=r; 
}
double Circle::Area(){
    return pi*radius*radius;
}
int main(){
    Circle cir1(10);
    cout<<"cir1's area: "<<cir1.Area()<<endl;
    Circle cir2(1);
    cout<<"cir2's area: "<<cir2.Area()<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


cir1's area: 314.15
cir2's area: 3.1415

例2.8中的構(gòu)造函數(shù)帶了一個(gè)整型的參數(shù),并在構(gòu)造函數(shù)中將r參數(shù)賦值給了成員變量radius。定義對(duì)象時(shí)可利用構(gòu)造函數(shù)直接對(duì)成員變量賦值。

另外,C++還提供另一種初始化數(shù)據(jù)成員的方法:參數(shù)初始化表。這種方法不在函數(shù)體內(nèi)對(duì)數(shù)據(jù)成員初始化,而是在函數(shù)首部實(shí)現(xiàn)。例2.8中的構(gòu)造函數(shù)可以改寫為下列形式:


Circle::Circle(int r):radius(r){}     // 后面沒有分號(hào)

在C++中,一個(gè)類可以同時(shí)定義多個(gè)構(gòu)造函數(shù),以提供不同的初始化方法。這些構(gòu)造函數(shù)的參數(shù)個(gè)數(shù)不同或參數(shù)的類型不同,這就是構(gòu)造函數(shù)的重載。

【例2.9】 構(gòu)造函數(shù)重載的使用。


#include <iostream>
using namespace std;
class Box{
public:
    Box();                         // 聲明一個(gè)無參的構(gòu)造函數(shù)
    /*聲明一個(gè)有參的構(gòu)造函數(shù),并用參數(shù)的初始化列表對(duì)數(shù)據(jù)成員初始化*/
    Box(int h,int w,int l):height(h),width(w),length(l){}
    int volume();
private:
    int height,width,length;
};
Box::Box(){                    // 定義無參的構(gòu)造函數(shù)
    height=1;
    width=2;
    length=3;
}
int Box::volume(){
    return height*width*length;
}
int main(){
    Box box1;                    // 不指定實(shí)參
    cout<<"box1's volume: "<<box1.volume()<<endl;
    Box box2(2,5,10);               // 指定實(shí)參
    cout<<"box2's volume: "<<box2.volume()<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


box1's Volume: 6
box2's Volume: 100

例2.9中定義了兩個(gè)構(gòu)造函數(shù),一個(gè)是無參的,一個(gè)是有參的,兩個(gè)函數(shù)的函數(shù)名一樣,但參數(shù)格式不一樣,所以是函數(shù)重載。若定義對(duì)象時(shí)不指定實(shí)參,則調(diào)用無參的構(gòu)造函數(shù)。

調(diào)用構(gòu)造函數(shù)時(shí)不必給出實(shí)參的構(gòu)造函數(shù),稱為默認(rèn)構(gòu)造函數(shù)。無參的構(gòu)造函數(shù)屬于默認(rèn)構(gòu)造函數(shù)。一個(gè)類只能有一個(gè)默認(rèn)構(gòu)造函數(shù)。即使一個(gè)類中有多個(gè)構(gòu)造函數(shù),但建立對(duì)象時(shí),都只執(zhí)行其中一個(gè)構(gòu)造函數(shù)。

構(gòu)造函數(shù)中的參數(shù)的值,可以通過實(shí)參傳遞,也可以指定為某些默認(rèn)值。

【例2.10】 構(gòu)造函數(shù)默認(rèn)參數(shù)的使用。


#include <iostream>
using namespace std;
class Box{
public:
    Box(int h=2,int w=2,int l=2);     // 在聲明構(gòu)造函數(shù)時(shí)指定默認(rèn)參數(shù)
    int volume();
private:
    int height,width,length;
};
Box::Box(int h,int w,int len){          // 在定義函數(shù)時(shí)可以不指定默認(rèn)參數(shù)
    height=h;
    width=w;
    length=len;
}
int Box::volume(){
    return height*width*length;
}
int main(){
    Box box1(1);                    // 不指定第23個(gè)實(shí)參
    cout<<"box1's volume: "<<box1.volume()<<endl;
    Box box2(1,3);               // 不指定第3個(gè)實(shí)參
    cout<<"box2's volume: "<<box2.volume()<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


box1's volume:4
box2's volume:6

例2.10中定義了一個(gè)帶有默認(rèn)參數(shù)的構(gòu)造函數(shù),是在聲明時(shí)指定默認(rèn)參數(shù),而定義時(shí)則可以不指定默認(rèn)參數(shù)。定義對(duì)象時(shí),可以傳0~3個(gè)參數(shù),傳了幾個(gè)參數(shù),就替換前面的幾個(gè)參數(shù),其余都還是使用默認(rèn)參數(shù)。

使用默認(rèn)參數(shù)的好處在于:調(diào)用構(gòu)造函數(shù)時(shí)就算沒有提供參數(shù)也不會(huì)出錯(cuò),且對(duì)每一個(gè)對(duì)象能有相同的初始化狀況。

不過,應(yīng)該在聲明構(gòu)造函數(shù)默認(rèn)值時(shí)指定默認(rèn)參數(shù)值,而不能只在定義構(gòu)造函數(shù)時(shí)指定默認(rèn)參數(shù)值。如果構(gòu)造函數(shù)中的參數(shù)全指定了默認(rèn)值,則在定義對(duì)象時(shí),可給一個(gè)實(shí)參或多個(gè)實(shí)參,也可以不給實(shí)參。

一個(gè)類中如果定義了全是默認(rèn)參數(shù)的構(gòu)造函數(shù)后,就不能再定義重載構(gòu)造函數(shù)了。假設(shè)Box類定義了以下3個(gè)構(gòu)造函數(shù):


Box(int =10,int =10,int =10);
Box();
Box(int,int);

若有以下定義語(yǔ)句,思考注釋中的問題:


Box box1;          // 是調(diào)用上面的第一個(gè)默認(rèn)參數(shù)的構(gòu)造函數(shù),還是第二個(gè)默認(rèn)構(gòu)造函數(shù)?
Box box2(15,30);     // 是調(diào)用上面的第一個(gè)默認(rèn)參數(shù)的構(gòu)造函數(shù),還是第三個(gè)構(gòu)造函數(shù)?

5.析構(gòu)函數(shù)

析構(gòu)函數(shù)的名字是類名的前面加一個(gè)“~”符號(hào)。在C++中,“~”符號(hào)是位取反運(yùn)算符,類似地,析構(gòu)函數(shù)的作用與構(gòu)造函數(shù)相反。它也不需要用戶來調(diào)用它,不過,它是在對(duì)象聲明周期結(jié)束時(shí)自動(dòng)執(zhí)行的。

程序執(zhí)行析構(gòu)函數(shù)的時(shí)機(jī)有以下4種。

(1)如果在函數(shù)中定義了一個(gè)對(duì)象,當(dāng)這個(gè)函數(shù)調(diào)用結(jié)束時(shí),對(duì)象會(huì)被釋放,且在對(duì)象釋放前會(huì)自動(dòng)執(zhí)行析構(gòu)函數(shù)。

(2)static局部對(duì)象在函數(shù)調(diào)用結(jié)束時(shí)對(duì)象不釋放,所以也不執(zhí)行析構(gòu)函數(shù),只有在main函數(shù)結(jié)束或調(diào)用exit函數(shù)結(jié)束程序時(shí),才調(diào)用static局部對(duì)象的析構(gòu)函數(shù)。

(3)全局對(duì)象則是在程序流程離開其作用域(如main函數(shù)結(jié)束或調(diào)用exit函數(shù))時(shí),才會(huì)執(zhí)行該全局對(duì)象的析構(gòu)函數(shù)。

(4)用new建立的對(duì)象,用delete釋放該對(duì)象時(shí),會(huì)調(diào)用該對(duì)象的析構(gòu)函數(shù)。

析構(gòu)函數(shù)的作用不是刪除對(duì)象,而是在撤銷對(duì)象占用的內(nèi)存前完成一些清理工作,使得這些內(nèi)存可以供新對(duì)象使用。析構(gòu)函數(shù)的作用也不限于釋放資源方面,它還可以被用來執(zhí)行用戶希望在最后一次使用對(duì)象之后所執(zhí)行的任何操作。

【例2.11】 析構(gòu)函數(shù)的使用方法舉例。


#include<iostream>
using namespace std;
class Box{
public:
    Box(int h=2,int w=2,int l=2);
    ~Box(){                         // 析構(gòu)函數(shù)
        cout<<"Destructor called."<<endl;     // 析構(gòu)函數(shù)里的內(nèi)容
    }
    int volume(){
        return height*width*length;
    }
private:
    int height,width,length;
};
Box::Box(int h,int w,int len){
    height=h;
    width=w;
    length=len;
}
int main(){
    Box box1;
    cout<<"The volume of box1 is "<<box1.volume()<<endl;
    cout<<"hello."<<endl;
    return 0;
}

程序的運(yùn)行結(jié)果:


The volume of box1 is 8
hello.
Destructor called.

例2.11中定義了類Box的析構(gòu)函數(shù),析構(gòu)函數(shù)中輸出Destructor called.,也就是析構(gòu)函數(shù)被執(zhí)行時(shí)就會(huì)輸出這條消息。由程序的執(zhí)行結(jié)果可以看出,析構(gòu)函數(shù)是對(duì)象釋放內(nèi)存前執(zhí)行的。

如果用戶沒有編寫析構(gòu)函數(shù),編譯系統(tǒng)會(huì)自動(dòng)生成一個(gè)默認(rèn)的析構(gòu)函數(shù),但不進(jìn)行任何操作,所以許多簡(jiǎn)單的類中沒有用顯式的析構(gòu)函數(shù)。

6.靜態(tài)數(shù)據(jù)成員

有時(shí)需要為某個(gè)類的所有對(duì)象分配一個(gè)單一的存儲(chǔ)空間。在C語(yǔ)言中,可以使用全局變量,但這樣很不安全。全局?jǐn)?shù)據(jù)可以被任何人修改,而且在一個(gè)項(xiàng)目中,它很容易和其他名字沖突。如果可以把數(shù)據(jù)當(dāng)成全局變量那樣去存儲(chǔ),但又被隱藏在類的內(nèi)部,而且清楚地與這個(gè)類相聯(lián)系,這種處理方法就是最理想的。這個(gè)可以用類的靜態(tài)數(shù)據(jù)成員來實(shí)現(xiàn)。類的靜態(tài)成員擁有一塊單獨(dú)的存儲(chǔ)區(qū),而不管創(chuàng)建了多少個(gè)該類的對(duì)象。所有這些對(duì)象的靜態(tài)數(shù)據(jù)成員都共享這一塊靜態(tài)存儲(chǔ)空間,這就為這些對(duì)象提供了一種互相通信的方法。靜態(tài)數(shù)據(jù)成員是屬于類的,它只在類的范圍內(nèi)有效,可以是public、private或protected的范圍。

因?yàn)椴还墚a(chǎn)生了多少對(duì)象,類的靜態(tài)數(shù)據(jù)成員都有著單一的存儲(chǔ)空間,所以存儲(chǔ)空間必須定義在一個(gè)單一的地方。如果一個(gè)靜態(tài)數(shù)據(jù)成員被聲明而沒有被定義,鏈接器會(huì)報(bào)告一個(gè)錯(cuò)誤:“定義必須出現(xiàn)在類的外部而且只能定義一次”。因此靜態(tài)數(shù)據(jù)成員的聲明通常會(huì)放在一個(gè)類的實(shí)現(xiàn)文件中。舉例如下所示。

xxx.h類型文件中:


class base{
public:
    static int var;          // 聲明靜態(tài)數(shù)據(jù)成員
};

xxx.cpp類型文件中:


int base::var=10;               // 定義靜態(tài)數(shù)據(jù)成員,不必在初始化語(yǔ)句里加上static

在頭文件中定義(初始化)靜態(tài)成員容易引起重復(fù)定義的錯(cuò)誤,比如這個(gè)頭文件同時(shí)被多個(gè).cpp文件所包含的時(shí)候。即使加上#ifndef#define#endif或者#pragma once也不行。

C++靜態(tài)數(shù)據(jù)成員被類的所有對(duì)象所共享,包括該類的派生類的對(duì)象。派生類對(duì)象與基類對(duì)象共享基類的靜態(tài)數(shù)據(jù)成員。靜態(tài)的數(shù)據(jù)成員在內(nèi)存中只占一份空間。如果改變它的值,則在各個(gè)對(duì)象中這個(gè)數(shù)據(jù)成員的值同時(shí)都改變了,這樣可以節(jié)約空間,提高效率。下面程序展示了修改基類的靜態(tài)數(shù)據(jù)成員,同時(shí)影響派生類對(duì)象和基類對(duì)象的情況。

【例2.12】 靜態(tài)的數(shù)據(jù)成員基類和派生類對(duì)象共享。


#include<iostream>
using namespace std;
class Base{
public:
    static int var;
};
int Base::var=10;
class Derived:public Base{
};
int main(){
    Base base1;
    base1.var++;               // 通過對(duì)象名引用
    cout<<base1.var<<endl;     // 輸出11
    Base base2;
    base2.var++;
    cout<<base2.var<<endl;     // 輸出12
    Derived derived1;
    derived1.var++; 
    cout<<derived1.var<<endl;     // 輸出13
    Base::var++;               // 通過類名引用
    cout<<derived1.var<<endl;     // 輸出14
    return 0;
}

程序的執(zhí)行結(jié)果是:


11
12
13
14

例2.12中在基類Base中定義了一個(gè)靜態(tài)數(shù)據(jù)成員var,類Derived繼承了類Base。無論是通過基類對(duì)象,或者是派生類對(duì)象,都可以改變靜態(tài)數(shù)據(jù)成員var的值。

如果只聲明了類而未定義對(duì)象,類的一般數(shù)據(jù)成員是不占內(nèi)存空間的,只有在定義對(duì)象時(shí)才會(huì)為對(duì)象的數(shù)據(jù)成員分配空間。但是靜態(tài)數(shù)據(jù)成員不屬于某一個(gè)對(duì)象,所以在為對(duì)象所分配的空間中不包括靜態(tài)數(shù)據(jù)成員所占的空間,靜態(tài)數(shù)據(jù)成員是在所有對(duì)象之外單獨(dú)開辟一段空間來存放。只要在類中定義了靜態(tài)數(shù)據(jù)成員,即使不定義對(duì)象,也為靜態(tài)數(shù)據(jù)成員分配了空間,它可以被引用。

在一個(gè)類中可以有一個(gè)或多個(gè)靜態(tài)數(shù)據(jù)成員,所有對(duì)象都共享這些靜態(tài)數(shù)據(jù)成員,都可以引用它。

如果在一個(gè)函數(shù)中定義了靜態(tài)變量,在函數(shù)結(jié)束時(shí)該靜態(tài)變量并不被釋放,仍然存在并保留其值。靜態(tài)數(shù)據(jù)成員也類似,它不隨對(duì)象的建立而分配空間,也不隨對(duì)象的撤銷而釋放。靜態(tài)數(shù)據(jù)成員是程序在編譯時(shí)被分配空間,到程序結(jié)束時(shí)釋放空間。

靜態(tài)數(shù)據(jù)成員可以通過對(duì)象名引用,也可以通過類名來引用。

7.靜態(tài)成員函數(shù)

與數(shù)據(jù)成員類似,成員函數(shù)也可以定義為靜態(tài)的,在類中聲明函數(shù)的前面加static關(guān)鍵字就成了靜態(tài)成員函數(shù),如:


static int volume();

和靜態(tài)數(shù)據(jù)成員一樣,靜態(tài)成員函數(shù)也是類的一部分,而不是對(duì)象的一部分。如果要在類外調(diào)用公用的靜態(tài)成員函數(shù),要用類名和域運(yùn)算符“::”,如:


Box::volume( );

實(shí)際上也允許通過對(duì)象名調(diào)用靜態(tài)成員函數(shù),如:


a.volume( );

但這并不意味著此函數(shù)是屬于對(duì)象a的,而只是用a的類型而已。

與靜態(tài)數(shù)據(jù)成員不同,靜態(tài)成員函數(shù)的作用不是為了對(duì)象之間的溝通,而是為了能處理靜態(tài)數(shù)據(jù)成員。

當(dāng)調(diào)用一個(gè)對(duì)象的成員函數(shù)(非靜態(tài)成員函數(shù))時(shí),系統(tǒng)會(huì)把該對(duì)象的起始地址賦給成員函數(shù)的this指針。而靜態(tài)成員函數(shù)并不屬于某一對(duì)象,它與任何對(duì)象都無關(guān),因此靜態(tài)成員函數(shù)沒有this指針。既然它沒有指向某一對(duì)象,也就無法對(duì)一個(gè)對(duì)象中的非靜態(tài)成員進(jìn)行默認(rèn)訪問(即在引用數(shù)據(jù)成員時(shí)不指定對(duì)象名)。

可以說,靜態(tài)成員函數(shù)與非靜態(tài)成員函數(shù)的根本區(qū)別是:非靜態(tài)成員函數(shù)有this指針,而靜態(tài)成員函數(shù)沒有this指針。由此決定了靜態(tài)成員函數(shù)不能訪問本類中的非靜態(tài)成員。

靜態(tài)成員函數(shù)可以直接引用本類中的靜態(tài)數(shù)據(jù)成員,因?yàn)殪o態(tài)成員同樣是屬于類的,可以直接引用。在C++程序中,靜態(tài)成員函數(shù)主要用來訪問靜態(tài)數(shù)據(jù)成員,而不訪問非靜態(tài)成員。

假如在一個(gè)靜態(tài)成員函數(shù)中有以下語(yǔ)句:


cout<<height<<endl;     // height已聲明為static,則引用本類中的靜態(tài)成員,合法
cout<<width<<endl;     // width是非靜態(tài)數(shù)據(jù)成員,不合法

但是,并不是絕對(duì)不能引用本類中的非靜態(tài)成員,只是不能進(jìn)行默認(rèn)訪問,因?yàn)闊o法知道應(yīng)該去找哪個(gè)對(duì)象。如果一定要引用本類的非靜態(tài)成員,應(yīng)該加對(duì)象名和成員運(yùn)算符“.”,如:


cout<<a.width<<endl;     // 引用本類對(duì)象a中的非靜態(tài)成員

假設(shè)a已定義為Box類對(duì)象,且在當(dāng)前作用域內(nèi)有效,則此語(yǔ)句合法。不過,最好養(yǎng)成這樣的習(xí)慣:只用靜態(tài)成員函數(shù)引用靜態(tài)數(shù)據(jù)成員,而不引用非靜態(tài)數(shù)據(jù)成員。這樣思路更清晰、邏輯更清楚,不易出錯(cuò)。

【例2.13】 靜態(tài)成員函數(shù)的使用方法舉例。


#include<iostream>
using namespace std;
class CStudent{
public:
    CStudent (int n,int s):num(n),score(s){}     // 定義構(gòu)造函數(shù)
    void total();
    static double average();
private:
    int num;
    int score;
    static int count;
    static int sum;                    // 這兩個(gè)數(shù)據(jù)成員是所有對(duì)象共享的
};
int CStudent::count=0;                    // 定義靜態(tài)數(shù)據(jù)成員
int CStudent::sum=0;
void CStudent::total(){                    // 定義非靜態(tài)成員函數(shù)
    sum+=score;                         // 非靜態(tài)數(shù)據(jù)成員函數(shù)中可使用靜態(tài)數(shù)據(jù)成
                                   // 員、非靜態(tài)數(shù)據(jù)成員
    count++;
}
double CStudent::average(){               // 定義靜態(tài)成員函數(shù)
    return sum*1.0/count;                    // 可以直接引用靜態(tài)數(shù)據(jù)成員,不用加類名
}
int main(){
    CStudent stu1(1,100);
    stu1.total();                         // 調(diào)用對(duì)象的非靜態(tài)成員函數(shù)
    CStudent stu2(2,98);
    stu2.total();
    CStudent stu3(3,99);
    stu3.total();
    cout<< CStudent::average()<<endl;          // 調(diào)用類的靜態(tài)成員函數(shù),輸出99
}

程序的執(zhí)行結(jié)果是:


99

例2.13中聲明了一個(gè)CStudent類,類中有靜態(tài)成員函數(shù)average、靜態(tài)數(shù)據(jù)成員count和sum。靜態(tài)數(shù)據(jù)成員count和sum必須在類的外部定義,在非靜態(tài)成員函數(shù)total中可以使用靜態(tài)數(shù)據(jù)成員、非靜態(tài)數(shù)據(jù)成員,而在靜態(tài)成員函數(shù)中則可以不加類名直接引用靜態(tài)數(shù)據(jù)成員。

8.對(duì)象的存儲(chǔ)空間

很多C++書籍中都介紹過一個(gè)對(duì)象需要占用多大的內(nèi)存空間,最權(quán)威的結(jié)論是:非靜態(tài)成員變量總和加上編譯器為了CPU計(jì)算做出的數(shù)據(jù)對(duì)齊處理和支持虛函數(shù)所產(chǎn)生的負(fù)擔(dān)的總和。下面分別看看數(shù)據(jù)成員、成員函數(shù)、構(gòu)造函數(shù)、析構(gòu)函數(shù)、虛函數(shù)的空間占用情況。

先來看看一個(gè)空類的存儲(chǔ)空間是多少個(gè)Byte呢?可以看例2.14的程序。

【例2.14】 空類存儲(chǔ)空間的計(jì)算。


#include<iostream>
using namespace std;
class CBox{
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;// 輸出1
    return 0;
}

程序的執(zhí)行結(jié)果是:


1

例2.14中定義了一個(gè)空類CBox,里面既沒有數(shù)據(jù)成員,也沒有成員函數(shù)。程序執(zhí)行結(jié)果顯示它的大小為1。

空類型對(duì)象中不包含任何信息,應(yīng)該大小為0。但是當(dāng)聲明該類型的對(duì)象的時(shí)候,它必須在內(nèi)存中占有一定的空間,否則無法使用這些對(duì)象。至于占用多少內(nèi)存,由編譯器決定。C++中每個(gè)空類型的實(shí)例占1Byte空間。

【例2.15】 只有成員變量的類的存儲(chǔ)空間計(jì)算。


#include<iostream>
using namespace std;
class CBox{
    int length,width,height;
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


12

例2.15中,類CBox中只有3個(gè)成員變量,由于整型變量占4Byte,所以對(duì)象所占的空間就是12Byte。那靜態(tài)成員變量是否也占存儲(chǔ)空間呢?

【例2.16】 有成員變量和靜態(tài)成員變量的類的存儲(chǔ)空間計(jì)算。


#include<iostream>
using namespace std;
class CBox{
    int length,width,height;
    static int count;
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


12

例2.16中,類CBox中有3個(gè)普通數(shù)據(jù)成員和1個(gè)靜態(tài)數(shù)據(jù)成員,比例2.14中多了一個(gè)靜態(tài)數(shù)據(jù)成員,但是程序的執(zhí)行結(jié)果還是12,也就證明了靜態(tài)數(shù)據(jù)成員是不占對(duì)象的內(nèi)存空間的。

【例2.17】 類中只有1個(gè)成員函數(shù)的存儲(chǔ)空間計(jì)算。


#include<iostream>
using namespace std;
class CBox{
    int foo();
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


1

例2.17中類CBox中只有一個(gè)成員函數(shù),類CBox的對(duì)象boxobj的大小卻只有1Byte,和空類對(duì)象是一樣的,所以可以得出,成員函數(shù)是不占空間的。

【例2.18】 類中構(gòu)造函數(shù)、析構(gòu)函數(shù)的空間占用情況。


#include<iostream>
using namespace std;
class CBox{
public:
    CBox(){};
    ~CBox(){};
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


1

例2.18中類CBox中只有構(gòu)造函數(shù)和析構(gòu)函數(shù),類CBox的對(duì)象boxobj的大小也只有1Byte,和空類對(duì)象是一樣的,所以可以得出,構(gòu)造函數(shù)和析構(gòu)函數(shù)也是不占空間的。

【例2.19】 類中有虛的析構(gòu)函數(shù)的空間計(jì)算。


#include<iostream>
using namespace std;
class CBox{
public:
    CBox(){};
    virtual ~CBox(){};
};
int main(){
    CBox boxobj;
    cout<<sizeof(boxobj)<<endl;          // 輸出4
    return 0;
}

程序的執(zhí)行結(jié)果是:


8

例2.19中,類CBox中有1個(gè)構(gòu)造函數(shù)和1個(gè)虛的析構(gòu)函數(shù),程序的執(zhí)行結(jié)果是8。事實(shí)上,編譯器為了支持虛函數(shù),會(huì)產(chǎn)生額外的負(fù)擔(dān),這正是指向虛函數(shù)表的指針的大小。(指針變量在64位的機(jī)器上占8Byte。)如果一個(gè)類中有一個(gè)或者多個(gè)虛函數(shù),沒有成員變量,那么類相當(dāng)于含有一個(gè)指向虛函數(shù)表的指針,占8Byte。

【例2.20】 繼承空類和多重繼承空類存儲(chǔ)空間的計(jì)算。


#include<iostream>
using namespace std;
class A{
};
class B{
};
class C:public A{
};
class D:public virtual B{
};
class E:public A,public B{
};
int main(){
    A a;
    B b;
    C c;
    D d;
    E e;
    cout<<"sizeof(a):"<<sizeof(a)<<endl;
    cout<<"sizeof(b):"<<sizeof(b)<<endl;
    cout<<"sizeof(c):"<<sizeof(c)<<endl;
    cout<<"sizeof(d):"<<sizeof(d)<<endl;
    cout<<"sizeof(e):"<<sizeof(e)<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果是:


sizeof(a):1
sizeof(b):1
sizeof(c):1
sizeof(d):8
sizeof(e):1

例2.20中定義了一個(gè)空類A和B,類C繼承了類A,類D繼承了虛基類B,類E繼承了類A和類B。這些類的對(duì)象所占的空間都是1Byte。由此可見,單一繼承的空類空間也是1,多重繼承的空類空間還是1,但是虛繼承涉及虛表(虛指針),所以sizeof(d)=8。

綜上所述,每個(gè)對(duì)象所占用的存儲(chǔ)空間只是該對(duì)象的非靜態(tài)數(shù)據(jù)成員的總和,其他都不占用存儲(chǔ)空間,包括成員函數(shù)和靜態(tài)數(shù)據(jù)成員。函數(shù)代碼是存儲(chǔ)在對(duì)象空間之外的,而且,函數(shù)代碼段是公用的,即如果對(duì)同一個(gè)類定義了10個(gè)對(duì)象,這些對(duì)象的成員函數(shù)對(duì)應(yīng)的是同一個(gè)函數(shù)代碼段,而不是10個(gè)不同的函數(shù)代碼段。

9.this指針

每個(gè)對(duì)象中的數(shù)據(jù)成員都分別占有存儲(chǔ)空間,如果對(duì)同一個(gè)類定義了n個(gè)對(duì)象,則有n組同樣大小的空間以存放n個(gè)對(duì)象中的數(shù)據(jù)成員。不同對(duì)象都調(diào)用同一個(gè)函數(shù)代碼段。那么,當(dāng)不同對(duì)象的成員函數(shù)引用數(shù)據(jù)成員時(shí),怎么能保證所引用的是所指定的對(duì)象的數(shù)據(jù)成員呢?

假設(shè),對(duì)于上述例子中定義的Box類,定義了3個(gè)同類對(duì)象a、b、c。如果有a.volume(),應(yīng)該是引用對(duì)象a中的height、width和length,以計(jì)算出箱子a的體積;如果有b.volume(),應(yīng)該是引用對(duì)象b中的height、width和length,計(jì)算出箱子b的體積。而現(xiàn)在都用同一個(gè)函數(shù)段,系統(tǒng)怎樣使它分別引用a或b中的數(shù)據(jù)成員呢?

在每一個(gè)成員函數(shù)中都包含一個(gè)特殊的指針,這個(gè)指針的名字是固定的,稱為this指針。它是指向本類對(duì)象的指針,它的值是當(dāng)前被調(diào)用的成員函數(shù)所在的對(duì)象的起始地址。例如,當(dāng)調(diào)用成員函數(shù)a.volume時(shí),編譯系統(tǒng)就把對(duì)象a的起始地址賦給this指針,在成員函數(shù)引用數(shù)據(jù)成員時(shí),就按照this的指向找到對(duì)象a的數(shù)據(jù)成員。例如volume函數(shù)要計(jì)算height*width*length的值,實(shí)際上是執(zhí)行:


(this->height)*(this->width)*(this->length)

由于當(dāng)前this指向a,因此相當(dāng)于執(zhí)行:


(a.height)*(a.width)*( a.length)

這就計(jì)算出箱子a的體積。同樣如果有b.volume(),編譯系統(tǒng)就把對(duì)象b的起始地址賦給成員函數(shù)volume的this指針,顯然計(jì)算出來的是箱子b的體積。

this指針是隱式使用的,它是作為參數(shù)被傳遞給成員函數(shù)。本來,成員函數(shù)volume的定義如下:


int Box::volume(){
    return (height*width*length);
}

C++把它處理為:


int Box::volume(Box *this){
    return (this->height * this->width * this->length);
}

即在成員函數(shù)的形參表列中增加一個(gè)this指針。在調(diào)用該成員函數(shù)時(shí),實(shí)際上是用以下方式調(diào)用的:


a.volume(&a);

將對(duì)象a的地址傳給形參this指針,然后按this的指向去引用其他成員。

需要說明的是,這些都是編譯系統(tǒng)自動(dòng)實(shí)現(xiàn)的,不必人為地在形參中增加this指針,也不必將對(duì)象a的地址傳給this指針,但在需要時(shí)也可以顯式地使用this指針。

例如在Box類的volume函數(shù)中,下面兩種表示方法都是合法的、相互等價(jià)的:


return (height * width * length);               // 隱含使用this指針
return (this->height * this->width * this->length);     // 顯式使用this指針

可以用*this表示被調(diào)用的成員函數(shù)所在的對(duì)象,*this就是this所指向的對(duì)象,即當(dāng)前的對(duì)象。例如在成員函數(shù)a.volume()的函數(shù)體中,如果出現(xiàn)*this,它就是對(duì)象a。上面的return語(yǔ)句也可寫成:


return((*this).height * (*this).width * (*this).length); 

注意

*this兩側(cè)的括號(hào)不能省略,不能寫成*this.height。因?yàn)槌蓡T運(yùn)算符“.”的優(yōu)先級(jí)別高于指針運(yùn)算符“*”,因此,*this.height就相當(dāng)于*(this.height),而this.height是不合法的,編譯會(huì)出錯(cuò)。

所謂“調(diào)用對(duì)象a的成員函數(shù)f”,實(shí)際上是在調(diào)用成員函數(shù)f時(shí)使this指針指向?qū)ο骯,從而訪問對(duì)象a的成員。在使用“調(diào)用對(duì)象a的成員函數(shù)f”時(shí),應(yīng)當(dāng)對(duì)它的含義有正確的理解。

this指針有以下特點(diǎn)。

(1)只能在成員函數(shù)中使用,在全局函數(shù)、靜態(tài)成員函數(shù)中都不能使用this。

(2)this指針是在成員函數(shù)的開始前構(gòu)造,并在成員函數(shù)的結(jié)束后清除。

(3)this指針會(huì)因編譯器不同而有不同的存儲(chǔ)位置,可能是棧、寄存器或全局變量。

(4)this是類的指針。

(5)因?yàn)閠his指針只有在成員函數(shù)中才有定義,所以獲得一個(gè)對(duì)象后,不能通過對(duì)象使用this指針,所以也就無法知道一個(gè)對(duì)象的this指針的位置。不過,可以在成員函數(shù)中指定this指針的位置。

(6)普通的類函數(shù)(不論是非靜態(tài)成員函數(shù),還是靜態(tài)成員函數(shù))都不會(huì)創(chuàng)建一個(gè)函數(shù)表來保存函數(shù)指針,只有虛函數(shù)才會(huì)被放到函數(shù)表中。

10.類模板

有時(shí),兩個(gè)或多個(gè)類的功能是相同的,但僅僅因?yàn)閿?shù)據(jù)類型不同,就要分別定義兩個(gè)類,如下面的例2.21聲明了一個(gè)類。

【例2.21】 操作整數(shù)的類。


class Operation_int{
public:
    Operation_int(int a,int b):x(a),y(b){}
    int add(){
        return x+y;
    }
    int subtract(){
        return x-y;
    }
private:
    int x,y;
};

這個(gè)類的作用是兩個(gè)整數(shù)的加減,如果要對(duì)兩個(gè)浮點(diǎn)數(shù)做加減,就又得新定義一個(gè)類,比如例2.22所示。

【例2.22】 操作浮點(diǎn)數(shù)的類。


class Operation_double{
public:
    Operation_double(double a, double b):x(a),y(b){}
    double add(){
        return x+y;
    }
    double subtract(){
        return x-y;
    }
private:
    double x,y;
};

因?yàn)閰?shù)的類型不同,所以不能復(fù)用,這也使得代碼量劇增。為了解決這類問題,C++中提供了類模板的功能。可以先聲明一個(gè)通用的類模板,這個(gè)類模板可以有一個(gè)或多個(gè)虛擬的類型參數(shù),對(duì)以上例子中的兩個(gè)類,可以定義如例2.23這樣的類模板。

【例2.23】 操作兩個(gè)數(shù)的類模板。


template<class T>          // 聲明一個(gè)模板,虛擬類型名為T
class Operation {
public:
    Operation (T a, T b):x(a),y(b){}
    T add(){
        return x+y;
    }
    T subtract(){
        return x-y;
    }
private:
    T x,y;
};

例2.23這個(gè)類模板與上面的類相比,有以上兩個(gè)不同點(diǎn)。

(1)聲明類模板時(shí)增加了下面這一行代碼:


template <class 類型參數(shù)名>

其中,template是聲明類模板時(shí)必須寫的關(guān)鍵字,意思是“模板”。關(guān)鍵字class后的類型參數(shù)名可以是任意的合法標(biāo)識(shí)符,本例中T就是一個(gè)類型參數(shù)名。

(2)原有的類型名int換成虛擬類型參數(shù)名T。

在建立類對(duì)象時(shí),如果將實(shí)際類型指定為int型,編譯系統(tǒng)就會(huì)用int取代T;如果指定為double,編譯系統(tǒng)就會(huì)用double取代T。

聲明一個(gè)類模板的對(duì)象時(shí),要用實(shí)際類型名去取代虛擬的類型,這樣才能使它變成一個(gè)實(shí)際的對(duì)象,如:


Operation <int> opobj(1,2);

在類模板名之后的尖括號(hào)里指定實(shí)際的類型名,這樣在編譯時(shí),編譯系統(tǒng)就用int取代類模板中的類型參數(shù)T,這樣就把類模板具體化了,或者說實(shí)際化了。例2.24中實(shí)現(xiàn)了一個(gè)類模板,利用它可以分別對(duì)兩個(gè)整數(shù)和兩個(gè)浮點(diǎn)數(shù)進(jìn)行加、減操作。

【例2.24】 用類模板實(shí)現(xiàn)對(duì)兩個(gè)數(shù)的加、減操作。


#include <iostream>
using namespace std;
template<class T>                              // 聲明一個(gè)模板,虛擬類型名為T
class Operation {
public:
    Operation (T a, T b):x(a),y(b){}
    T add(){
        return x+y;
    }
    T subtract(){
        return x-y;
    }
private:
    T x,y;
};
int main(){
    Operation <int> op_int(1,2);
    cout<<op_int.add()<<" "<<op_int.subtract()<<endl;     // 輸出3-1
    Operation <double> op_double(1.2,2.3);
    cout<<op_double.add()<<" "<<op_double.subtract()<<endl;     // 輸出3.5-1.1
    return 0;
}

程序的執(zhí)行結(jié)果是:


3 -1
3.5 -1.1

例2.24中聲明了一個(gè)類模板,可以對(duì)兩個(gè)相同類型的數(shù)進(jìn)行加、減操作:如果是傳入兩個(gè)整數(shù),則對(duì)兩個(gè)整數(shù)進(jìn)行加減;如果傳入兩個(gè)浮點(diǎn)數(shù),則對(duì)兩個(gè)浮點(diǎn)數(shù)進(jìn)行加減。

如果類模板的成員函數(shù)是在類外定義的,則需要這么寫:


template<class T>
T Operation <T> :: add(){
    return x+y;
}

綜上所述,可以這樣聲明和使用類模板。

(1)先寫一個(gè)實(shí)際的類。

(2)將此類中準(zhǔn)備改變的類型名改用一個(gè)自己指定的虛擬類型名。

(3)在類聲明前面加入一行,格式為:template<class虛擬類型參數(shù)>。

(4)用類模板定義對(duì)象時(shí)用以下形式:


類模板名<實(shí)際類型名>對(duì)象名;類模板名<實(shí)際類型名>對(duì)象名(實(shí)參表列)

(5)如果在類模板外定義成員函數(shù),應(yīng)寫成類模板形式:


template <class 虛擬類型參數(shù)>函數(shù)類型 類模板名<虛擬類型參數(shù)>::成員函數(shù)名(函數(shù)形參表列) {}

類模板是對(duì)一批僅數(shù)據(jù)成員類型不同的類的抽象,只要為這一批類所組成的整個(gè)類家族創(chuàng)造一個(gè)類模板,即給出一套程序代碼,就可以用來生成多種具體的類,從而大大提高編程的效率。

11.析構(gòu)函數(shù)與構(gòu)造函數(shù)的執(zhí)行順序

前面講了析構(gòu)函數(shù)執(zhí)行的時(shí)機(jī),下面再來對(duì)比下構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用時(shí)間和調(diào)用順序。首先看下面包含構(gòu)造函數(shù)和析構(gòu)函數(shù)的C++程序的執(zhí)行結(jié)果,以此來判斷二者執(zhí)行的順序。

【例2.25】 構(gòu)造函數(shù)和析構(gòu)函數(shù)的執(zhí)行順序?qū)嵗?/p>


#include<iostream>
using namespace std;
class CBox{
public:
    CBox (int h,int w,int l){
    height=h;
    width=w;
    length=l;
    cout<<"Constructor called."<<endl;               // 構(gòu)造函數(shù)被執(zhí)行時(shí)輸出信息
    }
    ~CBox (){                              // 析構(gòu)函數(shù)
     cout<<"Destructor called."<<length<<endl;          // 析構(gòu)函數(shù)被執(zhí)行時(shí)輸出
    }
    int volume(){
        return height*width*length;
    }
private:
    int height,width,length;
};
int main(){
    CBox box1(1,2,3);
    cout<<box1.volume()<<endl;
    CBox box2(2,3,4);
    cout<<box2.volume()<<endl;
    return 0;
}

程序的執(zhí)行結(jié)果為:


Constructor called.
6
Constructor called.
24
Destructor called.4
Destructor called.3

例2.25中聲明了類CBox,類中有一個(gè)構(gòu)造函數(shù)和一個(gè)析構(gòu)函數(shù),當(dāng)構(gòu)造函數(shù)運(yùn)行時(shí)會(huì)輸出一句話,方便判斷構(gòu)造函數(shù)執(zhí)行的時(shí)機(jī);同樣的,當(dāng)析構(gòu)函數(shù)運(yùn)行時(shí)也會(huì)輸出一句話,方便判斷析構(gòu)函數(shù)執(zhí)行的時(shí)機(jī)。

在一般情況下,調(diào)用析構(gòu)函數(shù)的次序正好與調(diào)用構(gòu)造函數(shù)的次序相反:最先被調(diào)用的構(gòu)造函數(shù),其對(duì)應(yīng)的(同一對(duì)象中的)析構(gòu)函數(shù)最后被調(diào)用;而最后被調(diào)用的構(gòu)造函數(shù),其對(duì)應(yīng)的析構(gòu)函數(shù)最先被調(diào)用。如上所示,先執(zhí)行box2的析構(gòu)函數(shù),再執(zhí)行box1的析構(gòu)函數(shù)。可以簡(jiǎn)記為:先構(gòu)造的后析構(gòu),后構(gòu)造的先析構(gòu),它相當(dāng)于一個(gè)棧,先進(jìn)后出。但是,并不是在任何情況下都是按這一原則處理的。對(duì)象可以在不同的作用域中定義,可以有不同的存儲(chǔ)類別,這些都會(huì)影響調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)的時(shí)機(jī)。下面歸納一下什么時(shí)候調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。

(1)在全局范圍中定義的對(duì)象(即在所有函數(shù)之外定義的對(duì)象),它的構(gòu)造函數(shù)在文件中的所有函數(shù)(包括main函數(shù))執(zhí)行之前調(diào)用。但如果一個(gè)程序中有多個(gè)文件,而不同的文件中都定義了全局對(duì)象,則這些對(duì)象的構(gòu)造函數(shù)的執(zhí)行順序是不確定的。當(dāng)main函數(shù)執(zhí)行完畢或調(diào)用exit函數(shù)時(shí)(此時(shí)程序終止),調(diào)用析構(gòu)函數(shù)。

(2)如果定義的是局部自動(dòng)對(duì)象(如在函數(shù)中定義對(duì)象),則在建立對(duì)象時(shí)調(diào)用其構(gòu)造函數(shù)。如果函數(shù)被多次調(diào)用,則在每次建立對(duì)象時(shí)都要調(diào)用構(gòu)造函數(shù)。在函數(shù)調(diào)用結(jié)束、對(duì)象釋放時(shí)先調(diào)用析構(gòu)函數(shù)。

(3)如果在函數(shù)中定義靜態(tài)(static)局部對(duì)象,則只在程序第一次調(diào)用此函數(shù)建立對(duì)象時(shí)調(diào)用構(gòu)造函數(shù)一次,在調(diào)用結(jié)束時(shí)對(duì)象并不釋放,因此也不調(diào)用析構(gòu)函數(shù),只在main函數(shù)結(jié)束或調(diào)用exit函數(shù)結(jié)束程序時(shí),才調(diào)用析構(gòu)函數(shù)。例如,在一個(gè)函數(shù)中定義了以下兩個(gè)對(duì)象:


void func(){
    Box box1;          // 定義自動(dòng)局部對(duì)象
    static Box box2;     // 定義靜態(tài)局部對(duì)象
}

在調(diào)用func函數(shù)時(shí),先調(diào)用box1的構(gòu)造函數(shù),再調(diào)用box2的構(gòu)造函數(shù)。在func調(diào)用結(jié)束時(shí),box1是要釋放的(因?yàn)樗亲詣?dòng)局部對(duì)象),因此要調(diào)用box1的析構(gòu)函數(shù)。而box2是靜態(tài)局部對(duì)象,在func調(diào)用結(jié)束時(shí)并不需要釋放,因此不需要調(diào)用stud2的析構(gòu)函數(shù),直到程序結(jié)束釋放stud2時(shí),才調(diào)用stud2的析構(gòu)函數(shù)。由此可以看到,stud2是后調(diào)用構(gòu)造函數(shù)的,但并不先調(diào)用其析構(gòu)函數(shù),原因是兩個(gè)對(duì)象的存儲(chǔ)類別、生命周期都不同。

主站蜘蛛池模板: 商水县| 黄石市| 沙洋县| 尉氏县| 大竹县| 昔阳县| 临沂市| 华坪县| 长白| 正镶白旗| 清新县| 佛山市| 扶绥县| 临夏市| 吴忠市| 手机| 灯塔市| 松溪县| 遵化市| 深水埗区| 绥芬河市| 申扎县| 安丘市| 武平县| 德惠市| 广宗县| 关岭| 江安县| 尉氏县| 台东市| 古交市| 左贡县| 四平市| 梁山县| 哈巴河县| 平邑县| 沈丘县| 济阳县| 马龙县| 沁源县| 扬州市|