版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第2章類(class)2.1類的概念 2.2隱藏實(shí)現(xiàn)
2.3訪問(wèn)控制
2.4訪問(wèn)控制 本章小結(jié)
習(xí)題
2.1類的概念面向?qū)ο蟮某绦蛟O(shè)計(jì)是一種程序設(shè)計(jì)技術(shù)。面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言的意義是:某種程序設(shè)計(jì)語(yǔ)言特別提供了一些機(jī)制,以很好地支持使用該語(yǔ)言進(jìn)行面向?qū)ο蟮某绦蛟O(shè)計(jì)。我們能在C中寫(xiě)出面向?qū)ο蟮某绦?,但這會(huì)非常的困難,因?yàn)樗恢苯又С诌@些機(jī)制。設(shè)計(jì)C++就是為了支持?jǐn)?shù)據(jù)抽象、面向?qū)ο蟮某绦蛟O(shè)計(jì)和通用型程序設(shè)計(jì),以及在這些風(fēng)格約束下的傳統(tǒng)的C程序設(shè)計(jì)技術(shù)。從本章開(kāi)始,我們將介紹這些風(fēng)格。本章介紹類——?C++里的數(shù)據(jù)抽象,它與第1章介紹的內(nèi)部類型同樣方便。2.1.1面向?qū)ο笏枷朊嫦驅(qū)ο笳Z(yǔ)言中的類就是對(duì)真實(shí)世界中的種類的抽象描述。例如,“人類”就是真實(shí)世界中的一個(gè)種類,那么,什么是人類?怎么來(lái)描述人類?首先來(lái)看看人類所具有的一些特征,這個(gè)特征包括屬性(一些參數(shù)、數(shù)值,也就是后面要介紹的類的成員變量)以及方法(一些行為,他能干什么,也就是后面要介紹的類的方法)。每個(gè)人都具有身高、體重、年齡、血型等“屬性”以及會(huì)勞動(dòng)、會(huì)直立行走、會(huì)用自己的頭腦去創(chuàng)造工具等“方法”。人之所以能區(qū)別于其他類型的動(dòng)物,是因?yàn)槊總€(gè)人都具有人這個(gè)群體的屬性與方法。“人類”只是一個(gè)抽象的概念,它僅僅是一個(gè)概念,它是不存在的實(shí)體,但是所有具備“人類”這個(gè)群體的屬性與方法的對(duì)象都叫人。這個(gè)對(duì)象“人”是實(shí)際存在的實(shí)體。每個(gè)人都是人這個(gè)群體的一個(gè)對(duì)象。老虎為什么不是人?因?yàn)樗痪邆淙诉@個(gè)群體的基本特征(屬性與方法)?——老虎不會(huì)直立行走、不會(huì)使用工具等等,所以說(shuō)老虎不是人。由此可見(jiàn),類描述了一組有相同特性和相同行為的對(duì)象。在面向?qū)ο蟪绦蛑?,類?shí)際上就是數(shù)據(jù)類型,如整數(shù)、小數(shù)等。整數(shù)也有一組特性和行為:整數(shù)的特性為int,整數(shù)的行為有?+、-、×、/?等事件行為。面向過(guò)程的語(yǔ)言與面向?qū)ο蟮恼Z(yǔ)言的區(qū)別就在于,面向過(guò)程的語(yǔ)言不允許程序員自己定義數(shù)據(jù)類型,而只能使用程序中內(nèi)置的(已經(jīng)定義好的)數(shù)據(jù)類型。為了模擬真實(shí)世界,為了更好地解決問(wèn)題,程序員往往需要自己創(chuàng)建解決問(wèn)題所必需的數(shù)據(jù)類型,比如前面講的“人”這個(gè)數(shù)據(jù)類型。2.1.2類與對(duì)象的定義抽象數(shù)據(jù)類型就是將類以屬性和方法組合的方式進(jìn)行的抽象描述。面向?qū)ο蟮木幊陶Z(yǔ)言最大的特色就是可以定義自己所需的抽象數(shù)據(jù)類型,也就是上一節(jié)提到的類,以更好地解決問(wèn)題。下面回顧一下“類”、“對(duì)象”、“屬性”、“方法”之間的關(guān)系。就像前面所說(shuō)的,人這個(gè)“類”是什么也做不了的,因?yàn)椤叭祟悺敝皇且粋€(gè)抽象的概念,它不是實(shí)實(shí)在在的“東西”,而這個(gè)“東西”就是所謂的對(duì)象。只有人這個(gè)“對(duì)象”才能去工作。而類是對(duì)象的描述,對(duì)象從類中產(chǎn)生,對(duì)象具有類所描述的所有屬性與方法。比如電視機(jī)都有工作原理圖,那么什么叫電視機(jī)呢?只要能夠?qū)崿F(xiàn)電視工作原理圖的所有功能的物體,都叫電視機(jī)??墒?,電視機(jī)原理圖是不能工作的,也即這個(gè)原理圖不能收看節(jié)目,只有電視機(jī)這個(gè)“實(shí)體”,即所謂的“對(duì)象”才能收看節(jié)目。也就是說(shuō),從類生成對(duì)象之后才真正有意義,才能開(kāi)始工作。此時(shí),電視機(jī)擁有電視原理圖所描述的所有屬性與方法。每一個(gè)實(shí)體都是對(duì)象。有一些對(duì)象具有相同的結(jié)構(gòu)和特性。每個(gè)對(duì)象都屬于一個(gè)特定的類型。在C++中對(duì)象的類型稱為類(class)。類代表了某一批對(duì)象的共性和特征。類是對(duì)象的抽象,而對(duì)象是類的具體實(shí)例(instance)。
C語(yǔ)言中的結(jié)構(gòu)體struct是類的基礎(chǔ),類就是由struct發(fā)展而來(lái)的。例如,C++中用戶可以這樣定義“人”(person)這個(gè)類://Person.husingnamespacestd;
classPerson{stringname; //姓名
intage; //年齡
charsex; //性別
//…};
可以看到“人”這個(gè)類是由多種內(nèi)部類型的數(shù)據(jù)組成的。在這個(gè)例子中,它包括stringname(姓名,字符型)、intage(年齡,整數(shù)型)和charsex(性別,字符型)三個(gè)數(shù)據(jù)類型。這個(gè)類是內(nèi)部類型的數(shù)據(jù)的集合,它和C語(yǔ)言中的結(jié)構(gòu)體struct很相似。但并不是所有的類都如此,某些類中可能會(huì)包含自定義的數(shù)據(jù)類型,并且那些包含自定義的數(shù)據(jù)類型的類在C++編程中是很常見(jiàn)的。
注意:類定義后面要加分號(hào)(;),而C里的struct定義后面不加。類和對(duì)象的關(guān)系就如同結(jié)構(gòu)體類型和結(jié)構(gòu)體變量的關(guān)系,結(jié)構(gòu)體中需要先聲明一個(gè)結(jié)構(gòu)體類型,然后用它去定義結(jié)構(gòu)體變量。在C++中也是先聲明一個(gè)類類型,然后用它去定義若干個(gè)同類型的對(duì)象。對(duì)象就是類類型的一個(gè)變量。
類定義的一般語(yǔ)法格式如下:
class類名
{public:
數(shù)據(jù)成員,成員函數(shù)的說(shuō)明;
prutected:
數(shù)據(jù)成員,成員函數(shù)的說(shuō)明;
Private:
數(shù)據(jù)成員,成員函數(shù)的說(shuō)明;
};
先定義類,然后就可以定義對(duì)象。對(duì)象定義的一般語(yǔ)法格式如下:類名對(duì)象1,對(duì)象2;2.1.3成員變量及成員函數(shù)類的成員函數(shù)是函數(shù)的一種,它的用法和作用與C語(yǔ)言中的函數(shù)基本上是一樣的,它與一般函數(shù)的區(qū)別是:它屬于一個(gè)類的成員,出現(xiàn)在類體中;它可以被指定為private(私有的)、public(公用的)或protected(受保護(hù)的)。在使用類函數(shù)時(shí),要注意調(diào)用它的權(quán)限以及它的作用域。下面來(lái)看看寫(xiě)的類能夠干什么以及是怎么工作的。例如,有一個(gè)員工叫“peter”,修改他的名字后對(duì)其進(jìn)行輸出。
【程序2.1】//Person.cpp#include<iostream>;#include<string>;usingnamespacestd;classPerson{ stringname; //姓名
intage; //年齡
charsex; //性別
};intmain(){ Personpeter; ="peter"; cout<<<<endl; return0;}
首先看看自定義的數(shù)據(jù)類型Person,在應(yīng)用時(shí)它和int類型的數(shù)據(jù)相似,兩者都需要?jiǎng)?chuàng)建變量(對(duì)象peter),只不過(guò)前者是自己定義的,而后者是基本數(shù)據(jù)類型。main()函數(shù)是整個(gè)函數(shù)的入口,int表示main()函數(shù)將要返回一個(gè)整型值,一般0表示程序正常結(jié)束,非0表示程序異常結(jié)束;也可以分別用EXIT_SUCCESS和EXIT_FAILURE表示0和非0值,這樣更易讀。“Personpeter;”定義一個(gè)Person類型的對(duì)象peter;“="peter";”試圖修改peter的name屬性,將其賦值為"peter";“cout<<<<endl;”試圖輸出peter的name屬性,endl表示換行。
編譯這段代碼,發(fā)現(xiàn)編譯器提示以下信息:
errorC2248:'name':cannotaccessprivatememberdeclaredinclass'Person'errorC2248:'name':cannotaccessprivatememberdeclaredinclass'Person'
定位出錯(cuò)的語(yǔ)句,發(fā)現(xiàn)錯(cuò)誤產(chǎn)自以下語(yǔ)句:
="peter";cout<<<<endl;
提示信息的意思是,程序無(wú)法訪問(wèn)私有(private)變量name。上述第一句試圖修改name屬性,第二句試圖獲得name屬性并輸出。因此,錯(cuò)誤信息是兩條。產(chǎn)生錯(cuò)誤的原因是:類的屬性包括私有屬性和公有屬性。私有屬性是無(wú)法通過(guò)對(duì)象名直接獲得的,即類似于這種訪問(wèn)方式是無(wú)效的。而公有屬性可以通過(guò)對(duì)象名直接訪問(wèn)。那么,Person類中的屬性是私有的還是公有的呢?似乎沒(méi)有特別說(shuō)明過(guò)。此處需要注意的是,在C++中,默認(rèn)的類的屬性的修飾符是private。如何修改這個(gè)錯(cuò)誤呢?只需要將屬性定義為公有屬性就可以了。修改后的代碼如下:【程序2.2】classPerson{public: stringname; //姓名
intage; //年齡
charsex; //性別
};
這樣錯(cuò)誤就被改正了。重新編譯程序并運(yùn)行,運(yùn)行結(jié)果如下:
由程序2.2可以看出,只是在屬性前加了語(yǔ)句“public:”,這樣它后面的屬性就被定義為公有屬性。前面介紹過(guò),類是屬性與方法的集合。為了實(shí)現(xiàn)數(shù)據(jù)的封裝,提高數(shù)據(jù)的安全性,一般應(yīng)該把類的屬性聲明為私有的,而把類的方法聲明為公有的。這樣,對(duì)象能夠直接調(diào)用類中定義的所有方法,當(dāng)對(duì)象想要修改或得到自己的屬性時(shí)就必須調(diào)用已定義的專用的方法才能夠?qū)崿F(xiàn)。因此,我們提倡“對(duì)象調(diào)用方法,方法改變屬性”。也許有人會(huì)說(shuō),直接定義屬性為公有的,就可以省去寫(xiě)公有方法的麻煩了。乍一看似乎有道理,其實(shí)不然。這樣的設(shè)計(jì)原則是有好處的,比如,類的屬性一般都有自己的取值范圍或變化規(guī)則,用成員方法來(lái)訪問(wèn)就可以在成員方法里面加入這些限制和規(guī)則,使程序更加健壯。更極端的情況是某一屬性允許被訪問(wèn)但不允許被修改,這樣的屬性聲明為私有屬性,只提供一個(gè)訪問(wèn)該屬性的成員函數(shù)即可。例如上一節(jié)定義的Person類,將其中所有的屬性都修改為private,即定義為私有屬性,對(duì)這些屬性的讀取和修改只能用用戶提供的方法。對(duì)Person類的修改如下:
【程序2.3】//Person.husingnamespacestd;classPerson{private: stringname; //姓名 intage; //年齡
charsex; //性別
public: voidsetName(stringiniName) {//這個(gè)方法是修改員工的姓名
name=iniName; } stringgetName(){//這個(gè)方法是得到員工的姓名
returnname; } intgetAge(){//只允許訪問(wèn),提供get方法。而不允許修改,不提供set方法。
returnage; }};//Person.cpp#include<iostream>;#include"person.h";usingnamespacestd;intmain(){Personpeter; //定義一個(gè)Person的對(duì)象(最好用中文)peter.setName("peter"); //將對(duì)象名賦給perterstringp=peter.getName(); //獲取對(duì)象名
cout<<p<<endl;return0;}
運(yùn)行結(jié)果如下:
現(xiàn)在,Person這個(gè)類中包含許多屬性和方法。此時(shí),不能直接用創(chuàng)建好的對(duì)象調(diào)用它的屬性來(lái)進(jìn)行修改。因?yàn)樗膶傩允莗rivate類型的,是不能夠被直接引用從而進(jìn)行修改的。要想修改姓名就要用對(duì)象調(diào)用setName()方法,而要得到姓名就要調(diào)用getName()方法。這就是“對(duì)象調(diào)用方法,方法改變屬性”。下面來(lái)看看修改后的person.cpp文件。“#include<iostream>;”的意思是說(shuō)下面的程序要用到iostream中已經(jīng)定義好的類來(lái)工作,比如屏幕輸出類的對(duì)象cout;“#include"person.h"”的意思是下面要用到定義的Person類了;“Personpeter;”定義一個(gè)Person類型的對(duì)象peter,“peter.setName("peter");”設(shè)定了peter這個(gè)對(duì)象的屬性name的值為"peter";“stringp=peter.getName();”獲得peter的屬性name的值,并賦給p變量,最后用“cout<<p<<endl;”打印出這個(gè)名字。
程序中對(duì)peter這個(gè)對(duì)象進(jìn)行操作才會(huì)有實(shí)際意義。千萬(wàn)不要有這種想法:“試圖對(duì)類進(jìn)行操作!”這是毫無(wú)意義的。對(duì)象peter擁有了類所描述的所有的屬性及方法,下面一一列舉:
/*所有的Person對(duì)象都擁有這些屬性。每創(chuàng)建一個(gè)對(duì)象就會(huì)重新分配一塊內(nèi)存來(lái)存放相應(yīng)對(duì)象的這些屬性。每個(gè)對(duì)象都有自己“獨(dú)特”的一份。*/
private:stringname; //員工姓名
intage; //員工年齡
charsex; //員工性別/*所有的Person對(duì)象都擁有這些方法。但在內(nèi)存中只有一份*/public:voidsetName(stringiniName){//這個(gè)方法修改姓名
name=iniName;}stringgetName(){//這個(gè)方法得到姓名
returnname;}intgetAge(){//這個(gè)方法得到年齡
returnage;}
實(shí)際上在創(chuàng)建peter這個(gè)對(duì)象時(shí)計(jì)算機(jī)只給這個(gè)對(duì)象的所有的屬性分配了內(nèi)存,而并沒(méi)有給方法分配內(nèi)存。方法只有一個(gè),是屬于所有的對(duì)象的,所以無(wú)論創(chuàng)建了多少個(gè)對(duì)象,計(jì)算機(jī)只會(huì)為一個(gè)方法分配一塊內(nèi)存。所有的方法其實(shí)都是一樣的,不必為每個(gè)對(duì)象都拷貝方法,但不同對(duì)象的相同屬性的值是不一樣的,所以必須給每個(gè)對(duì)象的屬性都分配內(nèi)存來(lái)保存這些不同的數(shù)據(jù)。
對(duì)于上面的實(shí)例,它已經(jīng)能完成絕大部分工作了,但它還是不完善的,還有許多的細(xì)節(jié)需要去完善。也許有的讀者已經(jīng)注意到了,當(dāng)創(chuàng)建完“peter”這個(gè)對(duì)象時(shí),這個(gè)對(duì)象的所有屬性都是空的,也就是說(shuō),這個(gè)對(duì)象的姓名是未定的、年齡是未定的、性別是未定的。而想把這些屬性都添加上去,就還要用對(duì)象調(diào)用相應(yīng)的方法,去一個(gè)一個(gè)地修改。有沒(méi)有什么好方法能夠在創(chuàng)建對(duì)象的同時(shí)完成對(duì)屬性的初始化呢?答案是肯定的,這就需要所謂的構(gòu)造函數(shù)。構(gòu)造函數(shù)是類中最特殊的函數(shù),它與析構(gòu)函數(shù)的功能正好相反。構(gòu)造函數(shù)具有以下特征:
(1)它沒(méi)有返回值類型;(2)它的名稱與類的名稱必須完全相同;
(3)可以對(duì)構(gòu)造函數(shù)進(jìn)行重載;
(4)它在創(chuàng)建對(duì)象時(shí)自動(dòng)被調(diào)用。構(gòu)造函數(shù)可對(duì)類中的屬性進(jìn)行初始化工作。上面的程序沒(méi)有自己定義構(gòu)造函數(shù),在這種情況下,系統(tǒng)會(huì)自動(dòng)定義一個(gè)“默認(rèn)構(gòu)造函數(shù)”。默認(rèn)構(gòu)造函數(shù)是沒(méi)有參數(shù)的構(gòu)造函數(shù)。如果程序員定義了構(gòu)造函數(shù),那么系統(tǒng)就不會(huì)再為你的程序添加一個(gè)默認(rèn)構(gòu)造函數(shù)了。(通常提倡自己定義構(gòu)造函數(shù),而不用系統(tǒng)的默認(rèn)構(gòu)造函數(shù)。)【程序2.4】//Person.hclassPerson{private: stringname; //姓名
intage; //年齡
charsex; //員工性別
public: Person(){//這個(gè)就是默認(rèn)構(gòu)造函數(shù)
name="peter"; //設(shè)置姓名 age=20; //設(shè)置年齡
sex="M"; //設(shè)置性別
} voidsetName(stringiniName){//這個(gè)方法是修改員工的姓名
name=iniName; } stringgetName(){//這個(gè)方法是得到員工的姓名
returnname; }intgetAge(){//只允許訪問(wèn),提供get方法。而不允許修改,不提供set方法
returnage;} //…};
在創(chuàng)建“peter”這個(gè)對(duì)象的同時(shí),它所有的屬性也被初始化了。顯然,這大大地提高了工作效率,但是,它還是不符合要求。想想看,如果現(xiàn)在創(chuàng)建這個(gè)類型的第二個(gè)對(duì)象,會(huì)發(fā)生什么事情?實(shí)際上,除了對(duì)象的“名”(這個(gè)名稱不是對(duì)象屬性中的name屬性,而是對(duì)象本身的名稱)不一樣外,其所有的“屬性值”都一樣。比如,現(xiàn)在創(chuàng)建第二個(gè)對(duì)象john,會(huì)發(fā)現(xiàn)這個(gè)對(duì)象的所有屬性和peter這個(gè)對(duì)象的所有屬性完全相同。因此只能再用對(duì)象的方法去改變寫(xiě)屬性了。很顯然,這種方法不太好,需要一種在創(chuàng)建對(duì)象時(shí)為對(duì)象的屬性賦予“想要的值”的方法。默認(rèn)構(gòu)造函數(shù)就顯得無(wú)能為力了。需要的是帶參數(shù)的構(gòu)造函數(shù),在創(chuàng)建對(duì)象時(shí),把參數(shù)傳給構(gòu)造函數(shù),這樣就能完成上述功能?!境绦?.5】//Person.hclassPerson{private: stringname; //姓名
intage; //年齡
charsex; //員工性別
//…public:Person(stringiniName,intiniAge,chariniSex){//看這個(gè)構(gòu)造函數(shù)
name=iniName; //設(shè)置姓名
age=iniAge; //設(shè)置年齡
sex=iniSex; //設(shè)置性別
} //…};
這樣一來(lái),在創(chuàng)建對(duì)象的同時(shí)就可以賦予想要的值,很顯然,這是方便而有效的。例如,“peter=Person("peter",20,'M');”,這樣,所有的工作都完成了(在創(chuàng)建對(duì)象的同時(shí)賦予了想要的“初值”)。2.2隱藏實(shí)現(xiàn)在整個(gè)設(shè)計(jì)和編碼過(guò)程中,都應(yīng)該貫穿著隱藏實(shí)現(xiàn)的思想,這是一個(gè)很重要的概念。將程序員分成類的創(chuàng)建者(classcreator,即創(chuàng)建新的數(shù)據(jù)類型的人)和客戶程序員(clientprogrammer,即使用這些類編程的人)能幫助更好地理解這種思想??蛻舫绦騿T最需要的是直接使用一個(gè)已經(jīng)實(shí)現(xiàn)功能的類,對(duì)他們來(lái)講,也許不知道這個(gè)類是如何實(shí)現(xiàn)的,但是這個(gè)類為他們提供了使用方法,使得他們能夠簡(jiǎn)潔地去使用一個(gè)現(xiàn)成的東西。而對(duì)于創(chuàng)建者來(lái)說(shuō),源程序一般是不會(huì)向客戶提供的,他們只給客戶提供使用方法。就像手機(jī)一樣,他們不會(huì)給客戶說(shuō)明這部手機(jī)是如何制造出來(lái)的,而客戶也只是關(guān)心這部手機(jī)該如何使用,創(chuàng)建者可以將它們的實(shí)現(xiàn)隱藏起來(lái)。這樣,創(chuàng)建者就有了自己的權(quán)利,也不用擔(dān)心程序會(huì)被惡意破壞和修改。當(dāng)客戶使用創(chuàng)建者的類時(shí),客戶的許多功能也許就是基于創(chuàng)建者的功能之上的,也許還會(huì)創(chuàng)建出更多的類,而這些類又可能會(huì)被其他人所使用。而最基本的東西,就是創(chuàng)建者原先提供給客戶的類。創(chuàng)建者必須去保護(hù)它,維護(hù)它的正確性,這樣才不會(huì)造成更大的錯(cuò)誤?!敖涌凇?Interface)是已創(chuàng)建好的類和客戶程序員的橋梁,客戶程序員必須遵照接口的規(guī)定去使用它。在實(shí)現(xiàn)的背后必然存放著某些代碼和數(shù)據(jù),這些代碼與那些隱藏起來(lái)的數(shù)據(jù)叫做“隱藏的實(shí)現(xiàn)”??蛻舫绦騿T只需使用接口去發(fā)送請(qǐng)求,隱藏起來(lái)的這些程序會(huì)幫他完成剩下的問(wèn)題,而客戶不用去關(guān)心程序是如何實(shí)現(xiàn)的。在程序內(nèi)部,有許多數(shù)據(jù)是不想讓客戶使用的,可能是因?yàn)檫@個(gè)數(shù)據(jù)是一個(gè)輔助作用,只是為了完成自己的功能而創(chuàng)建的,它們沒(méi)有功能可以向客戶提供,所以也不必要讓客戶知道。也可能是因?yàn)檫@個(gè)數(shù)據(jù)是不應(yīng)該被使用的,使用它會(huì)造成錯(cuò)誤,可能程序提供了其他的函數(shù)來(lái)使用它以避免錯(cuò)誤,這樣它更不應(yīng)該對(duì)客戶開(kāi)放。在這里舉個(gè)簡(jiǎn)單的例子,讓讀者初步體會(huì)隱藏實(shí)現(xiàn)的作用和意義。在這里,對(duì)Person類做了簡(jiǎn)化。先看程序2.6?!境绦?.6】#include<iostream>;#include<string>;usingnamespacestd;classPerson{private: intage; //年齡
public: voidsetAge(intinitAge){ age=initAge; }};intmain(){ Personpeter; Person*p=&peter; peter.setAge(21); void*add1=(void*)p; int*add2=(int*)add1; *add2=33; cout<<*add2<<endl; return0;}
運(yùn)行結(jié)果如下:
可以看到,在這個(gè)例子中,Person類只有setAge()成員方法,而沒(méi)有g(shù)etAge()成員方法。這樣設(shè)計(jì)的目的是不希望類的使用者獲得成員屬性age。然而,通過(guò)一種特殊的方法(指針),不但獲得了私有屬性age的值,而且還將age的屬性值修改為33。這種特殊的方法將在下一節(jié)“訪問(wèn)控制”中詳細(xì)討論。這種指針的方法之所以能夠成功,很大程度上是因?yàn)轭惖氖褂谜呖梢粤私忸惖脑敿?xì)的定義內(nèi)容。在這個(gè)例子中,使用者知道Person類中含有一個(gè)int型的age屬性,所以才能夠通過(guò)整型的指針(int*)訪問(wèn)和修改私有屬性age。為了避免這種情況出現(xiàn),可以將實(shí)現(xiàn)部分與接口部分分開(kāi),并將實(shí)現(xiàn)部分盡可能地隱藏起來(lái)。將程序2.6修改如下:【程序2.7】//Person.h#ifndefPERSON_H#definePERSON_HclassPerson{ structInformation; Information*p;public: voidinitialize(); intgetAge();voidsetAge(int);};#endif//PERSON_H//Person.cpp#include"Person.h";structPerson::Information{ intage;};voidPerson::initialize(){ p=newInformation; p->age=31;}intPerson::getAge(){ returnp->age;}voidPerson::setAge(intinitAge){ p->age=initAge;}
//UsePerson.cpp#include"Person.h"intmain(){ Personpeter; peter.initialize(); peter.setAge(10); peter.getAge(); return0;}
在這個(gè)例子中,將Person類分成了兩部分,分別是接口部分即Person.h和實(shí)現(xiàn)部分即Person.cpp。在頭文件Person.h中,只包含公共的接口和一個(gè)指針,這個(gè)指針指向一個(gè)沒(méi)有完全定義的類。所有程序員都能看到下面這行:
structInformation;
這只是一個(gè)不完全的類型說(shuō)明或類聲明,它沒(méi)有提供有關(guān)該Information的任何細(xì)節(jié)。而類的實(shí)現(xiàn)部分被隱藏在Person.cpp中,并且Person.cpp不需要向程序的使用者提供。這樣使得安全性和可擴(kuò)展性都得到了提高。在任何項(xiàng)目中,都應(yīng)該盡可能地從隱藏實(shí)現(xiàn)的角度去考慮問(wèn)題。C++語(yǔ)言采用三個(gè)顯式(明確)關(guān)鍵字即public、private和protected來(lái)設(shè)置類邊界。這樣能很好地保護(hù)和開(kāi)放資源。這些關(guān)鍵字的使用和含義都是相當(dāng)直觀的,它們決定了誰(shuí)能使用后續(xù)的定義內(nèi)容。“public”(公共)意味著后續(xù)的定義能被任何人所使用,將其開(kāi)放出去?!皃rivate”(私有)意味著除類的創(chuàng)建者以及類的內(nèi)部函數(shù)成員,其他任何方式都不能訪問(wèn)后續(xù)的定義信息。private很好的保護(hù)了私有變量,使得它們不被其他類所訪問(wèn)?!皃rotected”(受保護(hù)的)與“private”相似,不同的是一個(gè)繼承的類可訪問(wèn)受保護(hù)的成員,但不能訪問(wèn)私有成員。有關(guān)繼承的詳細(xì)內(nèi)容將在第9章介紹。2.3訪問(wèn)控制在C++中,用戶可以說(shuō)明成員數(shù)據(jù)和成員函數(shù)的訪問(wèn)級(jí)別。共有三種訪問(wèn)級(jí)別:公共的(public)、保護(hù)的(protected)和私有的(private)。它們用于在結(jié)構(gòu)中設(shè)置邊界,被稱為訪問(wèn)說(shuō)明符(accessspecifier)。無(wú)論什么時(shí)候使用訪問(wèn)說(shuō)明符,后面都必須加上一個(gè)冒號(hào)。那么,設(shè)置這些訪問(wèn)控制的理由是什么?理由有兩點(diǎn):一是信息的隱蔽,即以后將要提到的封裝,將類的內(nèi)部實(shí)現(xiàn)和外部接口分開(kāi),這樣當(dāng)客戶程序員要使用這個(gè)類時(shí)不需要關(guān)心類的內(nèi)部實(shí)現(xiàn)而只需要了解該類的接口。而且對(duì)類進(jìn)行維護(hù)時(shí)只需修改類的實(shí)現(xiàn)部分,基本上不用修改接口。二是數(shù)據(jù)的保護(hù),即將程序員認(rèn)為的類中的重要的信息保護(hù)起來(lái),以免其他程序的不恰當(dāng)?shù)脑L問(wèn)或不恰當(dāng)?shù)男薷摹?.3.1private
具有private訪問(wèn)控制級(jí)別的成員是完全保密的,即只能通過(guò)指向當(dāng)前類(不包括派生類)的this指針才可以訪問(wèn),其他環(huán)境均無(wú)法直接訪問(wèn)這個(gè)成員。如果有人企圖訪問(wèn)一個(gè)私有成員,就會(huì)產(chǎn)生一個(gè)編譯錯(cuò)誤。
例如下面的Person類。
【程序2.8】classPerson{private:stringname;public: stringgetName(){ returnname; //類的成員可以訪問(wèn)類的私有成員
} voidsetName(stringinitName){//類的成員可以訪問(wèn)類的私有成員
name=initName; }};
下面定義該類的一個(gè)實(shí)例:
Personperson;
其成員訪問(wèn)的合法性如下:
; //非法,name為Person的私有成員類Person的私有成員name可以通過(guò)提供給外部訪問(wèn)的接口getName()和setName()來(lái)訪問(wèn)。2.3.2protected
具有protected訪問(wèn)控制級(jí)別的成員是半公開(kāi)的,外界無(wú)法直接訪問(wèn)這個(gè)控制級(jí)別的成員,但是派生類的this指針可以獲得訪問(wèn)能力。protected與private基本相似,只有在繼承時(shí)有較大的區(qū)別。繼承的類可以訪問(wèn)protected成員,但是不能訪問(wèn)private成員。有關(guān)繼承的詳細(xì)內(nèi)容,將在第9章介紹。2.3.3public
具有public訪問(wèn)控制級(jí)別的成員是完全公開(kāi)的,任何環(huán)境下都可以通過(guò)對(duì)象訪問(wèn)這個(gè)成員。同樣稍微更改一下程序2.8中的Person類,改動(dòng)后如程序2.9所示。
【程序2.9】classPerson{public: stringsex;public: stringgetSex(){ returnsex;//類的成員可以訪問(wèn)類的公有成員
} voidsetSex(stringinitSex){//類的成員可以訪問(wèn)類的公有成員
If(initSex!="male"&&initSex!="female"){ cout<<"Wronginput,Pleaseinputmaleorfemale!"<<endl; }else{sex=initSex; } }};
同樣定義該類的一個(gè)實(shí)例:
Personperson;
其成員訪問(wèn)的合法性如下:
person.sex; //合法,sex為Person的公有成員類Person的公有成員sex還可以通過(guò)提供給外部訪問(wèn)的接口getSex()和setSex()來(lái)訪問(wèn)。
綜合以上三個(gè)例子可以看出,訪問(wèn)指示符決定了跟在它后面的名稱的訪問(wèn)級(jí)別,一直影響到下一個(gè)訪問(wèn)指示符出現(xiàn)或類說(shuō)明結(jié)束為止?,F(xiàn)在可以用一段簡(jiǎn)單的并且完整的程序來(lái)體現(xiàn)訪問(wèn)控制的作用。如下面的Person.cpp,為了避免累贅和重復(fù),只注釋了main函數(shù)中的語(yǔ)句。
【程序2.10】//Person.cpp//展示訪問(wèn)控制的作用
#include<iostream>#include<string>usingnamespacestd;classPerson{public: stringsex;protected: intage;private: stringname;public: Person(){}stringgetSex() { returnsex; } intgetAge() { returnage; } stringgetName() { returnname; }voidsetSex(stringinitSex) {//類的成員可以訪問(wèn)類的公有成員
if(initSex!="male"&&initSex!="female") { cout<<"Wronginput,Pleaseinputmaleorfemale!"<<endl; }else{ sex=initSex; } }voidsetAge(intinitAge) { age=initAge; } voidsetName(stringinitName) { name=initName; }};intmain(){ Personperson; //定義Person的一個(gè)對(duì)象(實(shí)例) person.sex="male"; //Person的實(shí)例person可以直接訪問(wèn)Person中的公有成員sex //person.age=20; //Person的實(shí)例person不能直接訪問(wèn)Person中的保護(hù)成員age person.setAge(20);//Person的實(shí)例person可以通過(guò)setAge()方法訪問(wèn)私有成員age//="Tom"; //Person的實(shí)例person不能直接訪問(wèn)Person中的私有成員name person.setName("Tom");//Person的實(shí)例person可以通過(guò)setName()方法訪問(wèn)私有成員name cout<<"Thevalueofsexis"<<person.getSex()<<endl; cout<<"Thevalueofageis"<<person.getAge()<<endl;//Person的實(shí)例person可以通過(guò)getAge()方法訪問(wèn)保護(hù)成員agecout<<"Thevalueofnameis"<<person.getName()<<endl;//Person的實(shí)例person可以通過(guò)getName()方法訪問(wèn)私有成員name return0;}
運(yùn)行結(jié)果如下:
值得注意的是,訪問(wèn)控制可以阻止用戶對(duì)對(duì)象進(jìn)行的無(wú)規(guī)劃的使用。但是在顯式地使用類型轉(zhuǎn)換時(shí),這種保護(hù)也會(huì)失去作用。如在上面的Person.cpp中假設(shè)沒(méi)有提供getAge()這個(gè)方法時(shí),也可以通過(guò)以下的方式得到Age的值。同理,name的值也可以用這種方法得到。由于這種方法用到了強(qiáng)制類型轉(zhuǎn)換,因此要謹(jǐn)慎使用。
Person*personPointer=&person;//取得Person實(shí)例在內(nèi)存中的地址
void*add1=(void*)personPointer;//先將地址強(qiáng)制轉(zhuǎn)換為void*型int*add2=(int*)add1;//先將地址強(qiáng)制轉(zhuǎn)換為int*型
cout<<"Nowthevalueofageis"<<*(add2+4)<<endl;//即使age是Person中的保護(hù)成員,仍然可以得到age的值以上程序片段在MicrosoftVisualC++中完全可以替代Person.cpp中的語(yǔ)句:
cout<<"Thevalueofageis"<<person.getAge()<<endl;
如果讀者使用的是其他的編譯器如GNU的gcc編譯器或者其他的操作系統(tǒng)如Linux,指針add2的偏移也許會(huì)不同,在調(diào)試時(shí)需要做相應(yīng)的改動(dòng)。
一般來(lái)說(shuō),公有的成員是類的對(duì)外接口,而私有和保護(hù)成員是類的內(nèi)部實(shí)現(xiàn)并且不希望外部了解和訪問(wèn)的。需要區(qū)分類的成員對(duì)類的實(shí)例(instant)的可見(jiàn)性和對(duì)類的成員函數(shù)的可見(jiàn)性的不同。類的成員函數(shù)可以訪問(wèn)類的所有的成員,不存在任何限制。而其實(shí)例對(duì)類的成員的訪問(wèn)則要受到上述所說(shuō)的訪問(wèn)控制符的制約。2.4訪問(wèn)控制出于信息隱藏的目的,類的成員一般被定義為私有的,并通過(guò)公有的成員函數(shù)提供對(duì)類私有數(shù)據(jù)的訪問(wèn)。但是有些時(shí)候,可能希望類外的函數(shù)可以訪問(wèn)類的保護(hù)成員。為了解決這個(gè)問(wèn)題,可以將這些函數(shù)定義為友元(friend)函數(shù)。友元包括友元函數(shù)和友元類。友元可以提高類的訪問(wèn)效率,但會(huì)破壞類的封裝性。2.4.1友元函數(shù)有時(shí)候,希望通過(guò)類外部的某個(gè)函數(shù)去操作類的屬性。比如希望在這個(gè)函數(shù)中修改Person類的屬性值,并以特定的格式輸出Person類的信息。不希望在Person類中定義這樣的輸出函數(shù),而導(dǎo)致類變得臃腫和不易維護(hù)?!境绦?.11】#include<iostream>#include<string>usingnamespacestd;classPerson{private: stringname; intage;public: Person(stringinitName,intinitAge){ name=initName; age=initAge; } stringgetName(){ returnname; } intgetAge(){ returnage; }};//函數(shù)定義在類的外部
voidinformation(Personp){ p.age=20; cout<<<<"'sageis"<<p.age<<endl;}intmain(){ Personp("WangWei",23); cout<<p.getName()<<"'sageis"<<p.getAge()<<endl; information(p); return0;}
編譯這段代碼,出現(xiàn)下面的錯(cuò)誤提示:
errorC2248:'age':cannotaccessprivatememberdeclaredinclass'Person'
錯(cuò)誤產(chǎn)生在語(yǔ)句“p.age=20;”,原因是函數(shù)information不能訪問(wèn)Person類中的私有成員age。在C++的設(shè)計(jì)規(guī)則中,類外部的函數(shù)是無(wú)法對(duì)類的屬性進(jìn)行操作的。但是,友元函數(shù)是一個(gè)例外。友元函數(shù)可以超脫類的訪問(wèn)控制,具有訪問(wèn)類中所有數(shù)據(jù)成員的能力。友元通過(guò)關(guān)鍵字friend聲明。它們不受其在類體中被聲明的public、private和protected區(qū)的影響。修改上面的代碼,把友元函數(shù)的聲明放在了類定義的末尾部分?!境绦?.12】classPerson{private: stringname; intage;public: Person(stringinitName,intinitAge){ name=initName; age=initAge; }stringgetName(){ returnname; } intgetAge(){ returnage; } friendvoidinformation(Personp);//友元函數(shù)的聲明
};
運(yùn)行結(jié)果如下:
可以看到,程序中通過(guò)兩種方式對(duì)Person類的私有成員name和age進(jìn)行了訪問(wèn)。第一種通過(guò)成員函數(shù)訪問(wèn)成員變量,由語(yǔ)句“cout<<p.getName()<<"'sageis"<<p.getAge()<<endl;”完成。第二種通過(guò)友元函數(shù)修改成員變量并進(jìn)行輸出,由語(yǔ)句“information(p);”完成。函數(shù)information被類Person聲明為友元函數(shù),通過(guò)這種方式函數(shù)information獲得了對(duì)類Person中所有成員的訪問(wèn)權(quán)限。注意,友元函數(shù)并不能看做是類的成員函數(shù),它只是個(gè)被聲明為類的友元的普通函數(shù)。2.4.2嵌套友元下面回顧一下2.2.3節(jié)關(guān)于隱藏實(shí)現(xiàn)的例子,Person類的定義如下:
classPerson{ structInformation; Information*p;public: voidinitialize(); intgetAge(); voidsetAge(int);};
仔細(xì)觀察,在Person類中嵌套了一個(gè)結(jié)構(gòu)體Information。但是,結(jié)構(gòu)體不能封裝方法,可操作性較低。然而,類可以定義自己的方法。如果把結(jié)構(gòu)體Information改為類會(huì)如何呢?分析程序2.13的代碼:
【程序2.13】#include<iostream>#include<string>usingnamespacestd;classPerson{private: stringname;intage; public: Person(stringinitName,intinitAge){ name=initName; age=initAge; }
stringgetName(){ returnname; }voidsetName(stringinitName){ name=initName; }
intgetAge(){ returnage; } voidsetAge(intinitAge){ age=initAge; }classInformation{ private: Person*p; public: Information(Person*initp){ p=initp; } voidinfor(){ cout<<p->name<<"'sageis"<<p->age<<endl;} };};
在分析這段程序之前,先介紹一些嵌套類的相關(guān)知識(shí)。在一個(gè)類中定義的類稱為嵌套類,包含嵌套類的類稱為外圍類。例如,類B定義在類A的內(nèi)部,則類B為嵌套類,類A為外部類。這樣做的好處是隱藏類名,提高類的抽象能力,并且強(qiáng)調(diào)了兩個(gè)類的主從關(guān)系。
可以看到,類Information定義在類Person中。類Person是外圍類,類Information是嵌套類。類Information擁有一個(gè)私有成員Person*p,p是指向Person對(duì)象的指針變量。函數(shù)infor試圖通過(guò)指針p輸出Person對(duì)象的私有成員變量name和age。編譯這段代碼,將會(huì)出現(xiàn)以下錯(cuò)誤信息:
'name':cannotaccessprivatememberdeclaredinclass'Person''age':cannotaccessprivatememberdeclaredinclass'Person'
錯(cuò)誤產(chǎn)生在語(yǔ)句“cout<<p->name<<"'sageis"<<p->age<<endl;”中,這里提示編程人員函數(shù)information不具有訪問(wèn)Person私有成員的權(quán)限。這是因?yàn)樵贑++中,嵌套類的成員函數(shù)對(duì)外圍類的成員沒(méi)有訪問(wèn)權(quán)限,反之亦然。
如果將嵌套類聲明為外圍類的友元類,結(jié)果會(huì)如何呢?將代碼修改如下:
【程序2.14】classPerson{private: …public: … classInformation; friendInformation; classInfo
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025高考數(shù)學(xué)考點(diǎn)剖析精創(chuàng)專題卷七-空間向量與立體幾何【含答案】
- 糖尿病視網(wǎng)膜病變病例討論(共30張課件)
- 江西省贛州市興國(guó)縣高興鎮(zhèn)高興小學(xué)-主題班會(huì)-網(wǎng)絡(luò)安全教育【課件】
- 二零二五年短視頻平臺(tái)場(chǎng)推廣服務(wù)協(xié)議2篇
- 第2課《濟(jì)南的冬天》課時(shí)提高練2024-2025學(xué)年語(yǔ)文七年級(jí)上冊(cè)
- 高績(jī)效團(tuán)隊(duì)的成功秘密就在會(huì)議里!講解材料
- 四年級(jí)語(yǔ)文上冊(cè)第七單元習(xí)作寫(xiě)信習(xí)題課件2新人教版
- 二零二五版交通事故醫(yī)療費(fèi)用賠償協(xié)議3篇
- 2024年濟(jì)寧職業(yè)技術(shù)學(xué)院高職單招職業(yè)技能測(cè)驗(yàn)歷年參考題庫(kù)(頻考版)含答案解析
- 2024年浙江東方職業(yè)技術(shù)學(xué)院高職單招職業(yè)適應(yīng)性測(cè)試歷年參考題庫(kù)含答案解析
- 2024年人力資源年度工作總結(jié)參考(2篇)
- DB52T 1776.1-2023 耕地質(zhì)量等別評(píng)價(jià) 第1部分:評(píng)價(jià)規(guī)范
- BIM工程師年終總結(jié)
- 釘釘OA辦公系統(tǒng)操作流程培訓(xùn)
- 新生兒科年度護(hù)理質(zhì)控總結(jié)
- 2024秋季新教材人教版體育與健康一年級(jí)上冊(cè)課件:1我們愛(ài)運(yùn)動(dòng)
- 領(lǐng)導(dǎo)年終總結(jié)匯報(bào)工作
- 《工貿(mào)企業(yè)有限空間作業(yè)安全規(guī)定》知識(shí)培訓(xùn)
- CQI-23模塑系統(tǒng)評(píng)估審核表-中英文
- 2024年大型游樂(lè)設(shè)施操作(Y2)特種作業(yè)取證(廣東)考試復(fù)習(xí)題庫(kù)(含答案)
- 高層次人才座談會(huì)發(fā)言稿
評(píng)論
0/150
提交評(píng)論