- 后臺(tái)開發(fā):核心技術(shù)與應(yīng)用實(shí)踐
- 徐曉鑫
- 3776字
- 2019-01-03 20:55:33
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); // 不指定第2、3個(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ǔ)類別、生命周期都不同。
- Learning LibGDX Game Development(Second Edition)
- AngularJS入門與進(jìn)階
- Java程序設(shè)計(jì)實(shí)戰(zhàn)教程
- The Modern C++ Challenge
- Vue.js快速入門與深入實(shí)戰(zhàn)
- Dependency Injection in .NET Core 2.0
- OpenStack Cloud Computing Cookbook(Fourth Edition)
- C語(yǔ)言課程設(shè)計(jì)
- Learning Three.js:The JavaScript 3D Library for WebGL
- PLC應(yīng)用技術(shù)(三菱FX2N系列)
- Vue.js 2 Web Development Projects
- Illustrator CS6設(shè)計(jì)與應(yīng)用任務(wù)教程
- 代碼閱讀
- Python語(yǔ)言科研繪圖與學(xué)術(shù)圖表繪制從入門到精通
- Java RESTful Web Service實(shí)戰(zhàn)