




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第一章C++基礎1.1C++概述1.2類和對象1.3繼承和派生1.4多態(tài)和虛函數1.1.1C++程序創(chuàng)建使用C++高級語言編寫的程序稱為源程序。由于計算機執(zhí)行由0和1組成的二進制指令(稱為機器代碼),因而C++源程序是不能被計算機直接執(zhí)行的,必須轉換成機器代碼才能被計算機執(zhí)行。這個轉換過程就是編譯器對源代碼進行編譯和連接的過程。如圖1.1所示。源代碼編譯器目標代碼連接程序可執(zhí)行代碼庫其他代碼圖1.1C++程序創(chuàng)建過程
1.1C++概述
1.1C++概述VisualC++是Microsoft公司推出的目前使用極為廣泛的基于Windows平臺的可視化編程環(huán)境。VisualC++6.0是在以往版本不斷更新的基礎上形成的,由于其功能強大、靈活性好、完全可擴展以及具有強有力的Internet支持,在各種以C/C++語言為內核的Windows開發(fā)工具中脫穎而出,成為目前流行的Windows應用程序開發(fā)的集成開發(fā)環(huán)境,如圖1.2所示。圖1.2VisualC++6.05SP6開發(fā)環(huán)境1.1C++概述1.創(chuàng)建工作文件夾創(chuàng)建VisualC++6.0的工作文件夾“D:\VisualC++應用”,以后所有創(chuàng)建的C++程序都在此文件夾下,這樣既便于管理,又容易查找。在文件夾“D:\VisualC++應用”下再創(chuàng)建一個子文件夾“第1章”用于存放第1章中的C++程序;對于第2章程序就存放在子文件夾“第2章”中,依此類推。
2.啟動VisualC++6.0圖1.3“每日提示”對話框選擇“開始”→“程序”→“MicrosoftVisualStudio6.0”→“MicrosoftVisualC++6.0”,運行VisualC++6.0。第一次運行時,將顯示如圖1.3的“每日提示”對話框。單擊[下一條]按鈕,可看到有關各種操作的提示。如果在[啟動時顯示提示]復選框中單擊鼠標,去除復選框的選中標記“”,那么下一次運行VisualC++6.0,將不再出現此對話框。單擊[關閉]按鈕關閉此對話框,進入VisualC++6.0開發(fā)環(huán)境。圖1.3“每日提示”對話框1.1C++概述3.添加C++程序
(1)單擊標準工具欄上的“新建”()按鈕,打開一個新的文檔窗口,在這個窗口中輸入下列C++代碼。[例Ex_Simple]一個簡單的C++程序/*第一個簡單的C++程序*/#include<iostream.h>intmain(){ doubler,area; //定義變量
cout<<"輸入圓的半徑:";//顯示提示信息
cin>>r; //從鍵盤上輸入變量r的值
area=3.14159*r*r; //計算面積
cout<<"圓的面積為:"<<area<<"\n";//輸出面積
return0; //指定返回值}1.1C++概述(2)選擇“文件”→“保存”菜單或按快捷鍵Ctrl+S或單擊標準工具欄的“”按鈕,彈出“保存為”文件對話框。將文件定位到“D:\VisualC++應用\第1章”文件夾中,文件名指定為“Ex_Simple.cpp”(注意擴展名.cpp不能省略,cpp是CPlusPlus的縮寫,即“C++”的意思)。4.編譯和運行(1)單擊編譯工具條上的生成工具按鈕“”或直接按快捷鍵F7,系統(tǒng)彈出一個對話框,詢問是否為該程序創(chuàng)建默認的活動工作區(qū)間文件夾,單擊[是]按鈕,系統(tǒng)開始對Ex_Simple進行編譯、連接,同時在輸出窗口中顯示編連的有關信息,當出現:表示Ex_Simple.exe可執(zhí)行文件已經正確無誤地生成了。(2)單擊編譯工具條上的運行工具按鈕“”或直接按快捷鍵Ctrl+F5,就可以運行剛剛生成的Ex_Simple.exe了,結果彈出這樣的控制臺窗口(其屬性已被修改過):1.1C++概述此時等待用戶輸入一個數。當輸入10并按Enter鍵后,控制臺窗口顯示為:其中,“Pressanykeytocontinue”是VisualC++自動加上去的,表示Ex_Simple運行后,按一個任意鍵將返回到VisualC++開發(fā)環(huán)境,這就是C++程序的創(chuàng)建、編連和運行過程。上述生成的程序又可叫“控制臺應用程序”,它是指那些需要與傳統(tǒng)DOS操作系統(tǒng)保持程序的某種兼容,同時又不需要為用戶提供完善界面的程序。簡單地講,就是指在Windows環(huán)境下運行的DOS程序??刂婆_窗口就是一個DOS屏幕!1.1C++概述1.1.2C++程序結構每一個C++程序源文件通常是以.cpp(cplusplus,C++)為擴展名,它是由編譯預處理指令、數據或數據結構定義以及若干個函數組成。下面就以Ex_Simple.cpp的程序代碼(如圖1.4所示)來分析C++程序的組成和結構:1.main函數代碼中,main表示主函數,由于每一個程序執(zhí)行時都必須從main開始,而不管該函數在整個程序中的具體位置,因此每一個C++程序或由多個源文件組成的C++項目都必須包含一個且只有一個main函數。圖1.4Ex_Simple.cpp的程序代碼1.1C++概述2.頭文件包含行號為3的代碼是C++文件包含#include的編譯指令,稱為預處理指令。#include后面的iostream.h是C++編譯器自帶的文件,稱為C++庫文件,它定義了標準輸入/輸出流的相關數據及其操作,由于程序用到了輸入/輸出流對象cin和cout,因而需要用#include將其合并到程序中,又由于它們總是被放置在源程序文件的起始處,所以這些文件被稱為頭文件(HeaderFile)。C++編譯器自帶了許多這樣的頭文件,每個頭文件都支持一組特定的“工具”,用于實現基本輸入輸出、數值計算、字符串處理等方面的操作。
3.注釋通常,必要的注釋內容應包含:
●在源文件頭部進行必要的源程序的總體注釋:版權說明、版本號、生成日期、作者、內容、功能、與其它文件的關系、修改日志等,頭文件的注釋中還應有函數功能簡要說明。
●在函數的頭部進行必要的函數注釋:函數的目的/功能、輸入參數、輸出參數、返回值、調用關系(函數、表)等。
●其他的少量注釋。如全局變量的功能、取值范圍等。千萬不要陳述那些一目了然的內容,否則會使注釋的效果適得其反。需要說明的是,C++中的“/*...*/”是用來實現多行的注釋,它是將由“/*”開頭到“*/”結尾之間所有內容均視為注釋,稱為塊注釋。塊注釋(“/*...*/”)的注解方式可以出現在程序中的任何位置,包括在語句或表達式之間。而“//”只能實現單行的注釋,它是將“//”開始一直到行尾的內容作為注釋,稱為行注釋。1.1C++概述1.1.3C++程序組成下面再看2個C++程序:
[例Ex_Simple1]在屏幕上輸出一個由星號形成的三角形//輸出星號的三角形陣列
#include<iostream.h>voidDoDraw(intnum); //聲明一個全局函數intmain(){ intnum=5; //定義并初始化變量
DoDraw(num);//函數的調用
return0; //指定返回值}voidDoDraw(intnum) //函數的定義{ for(inti=0;i<num;i++) //循環(huán)語句{ for(intj=0;j<=i;j++) cout<<'*'; cout<<'\n'; }}1.1C++概述本程序包括兩個函數:主函數main和被調用的函數DoDraw。DoDraw函數是在屏幕上輸出星號的三角形陣列,這個陣列的行數以及每行星號的個數由num決定。程序運行的結果如下:在以后的C++程序運行結果中,本書不再完整顯示其控制臺窗口,也不再顯示“Pressanykeytocontinue”,僅將控制臺窗口中運行結果部分裁剪下來列出,并加以單線陰影邊框,本書作此約定。1.1C++概述
[例Ex_Simple2]用類的概念重寫例Ex_Draw#include<iostream.h>classCDrawArray //定義一個類{public: voidDoDraw(intnum); //聲明類的公有成員函數};voidCDrawArray::DoDraw(intnum)//成員函數的實現{ for(inti=0;i<num;i++) { for(intj=0;j<=i;j++) cout<<'*'; cout<<'\n'; }}intmain(){ intnum=5; CDrawArraymyDraw; //定義類的一個對象
myDraw.DoDraw(num); //調用此對象的成員函數
return0; //指定返回值}1.2類和對象面向對象的程序設計有三個主要特征:封裝、繼承和多態(tài)。
(1)封裝。封裝是將數據和代碼捆綁到一起,避免了外界的干擾和不確定性。在C++中,封裝是通過類來實現的。類是用來描述具有相同的屬性和方法的對象的集合,它定義了該集合中每個對象所共有的屬性和方法。
(2)繼承。繼承是讓某個類型的對象獲得另一個類型的對象的特性。在C++面向對象程序設計中,繼承是指一個子類繼承父類(或稱為基類)的特征。通過繼承可以實現代碼的重用:從已存在的類派生出的一個新類將自動具有原來那個類的特性,同時,它還可以擁有自己的新特性。
(3)多態(tài)。對于相同的消息,不同的對象具有不同的反應的能力。多態(tài)機制使具有不同內部結構的對象可以共享相同的外部接口,通過這種方式減少代碼的復雜度??傊?,面向對象的程序設計是將問題抽象成許多類,將數據與對數據的操作封裝在一起,各個類之間可以存在著繼承關系,對象是類的實例,程序是由對象組成。因此,在C++面向對象程序設計,首先設計類,定義類的屬性和可執(zhí)行的操作(方法),然后設計使用這些類的對象的程序。這種從低級(如類)到高級(如程序)的處理過程稱為自下向上的編程方式。
1.2類和對象1.2.2類的聲明C++中,聲明一個類的一般格式如下:class<類名> //聲明部分{ private:
[<私有型數據和函數>] public:
[<公有型數據和函數>] protected:
[<保護型數據和函數>]};<各個成員函數的實現> //實現部分類體1.2類和對象成員函數是用來對數據成員進行操作,又稱為方法。注意,類體中最后一個花括號后面的分號“;”不能省略。在VisualC++中,常用大寫的C字母開始的標識符作為類名,C用來表示類(Class),以與對象、函數及其他數據類型的名稱相區(qū)別。當類的成員函數的定義是在類體外部完成時,必須用作用域運算符“::”來告知編譯系統(tǒng)該函數所屬的類。此時,成員函數的定義格式如下:<函數類型><類名>::<函數名>(<形式參數表>){ …}函數體1.2類和對象
[例Ex_StuScoreClass]一個類的定義示例需要說明的是:(1)類中的數據成員的數據類型可以是任意的,包含整型、浮點型、字符型、數組、指針和引用等,也可以是另一個類的對象。例如:
classCOne{…};classCTwo{…private:
COnea; //數據成員a是已定義的COne類對象};1.2類和對象(2)由于類是一種數據類型,系統(tǒng)并不會為其分配內存空間,所以在定義類中的數據成員時,不能對其進行初始化,也不能指定除static之外的任何存儲類型。例如類CStuscore中,下面的定義是錯誤的:
classCStuscore{…private: floatfScore[3]={80.0,90.0,78.0};//錯誤:不能在直接對數據成員進行初始化
autointn; //錯誤:不合法的存儲類型};(3)訪問權限關鍵詞public、private、protected在類中使用先后次序無關緊要,且可使用多次。每個訪問權限關鍵詞為類成員所確定的訪問權限是從該關鍵詞開始到下一個關鍵詞為止。如在CStuscore類中,私有數據成員是用兩個private來分開寫成二個部分。同樣,若將公有成員函數用兩個public分開寫成二個部分,仍是正確的。1.2類和對象(4)在進行類設計時,通常將數據成員的聲明為私有的,而將大多數成員函數聲明成公有的。這樣,類以外的代碼不能直接訪問類的訪問權限私有數據,從而實現了數據的封裝。而公有成員函數可為內部的私有數據成員提供外部接口,但接口實現的細節(jié)在類外又是不可見的,這就是C++類的優(yōu)點之一。(5)一般來說,程序員更多關心的是public成員,因此常將public成員寫在類體中的前面,而將private成員寫在類體中的后面。若在類體內沒有指定成員的訪問權限,則默認的訪問權限為私有的(private)。(6)當程序比較大時,應盡量將類單獨存放在一個文件中或將類的聲明放在頭文件中而將成員函數的實現放在與頭文件同名的.cpp文件中(這也是VisualC++6.0的編程方式)。1.2類和對象
1.2.3對象的定義和初始化類對象有3種定義方式:聲明之后定義、聲明之時定義和一次性定義。例如:
classA{…};Aa; //聲明之后定義classB{…}b,c; //聲明之時定義class{…}d,e; //一次性定義但由于“類”比任何數據類型都要復雜得多,為了提高程序的可讀性,真正將“類”當成一個密閉、“封裝”的盒子(接口),在程序中應盡量使用對象的聲明之后定義方式,即按下列格式進行:<類名><對象名表>; 1.2類和對象其中,類名是已聲明過的類的標識符,對象名可以有一個或多個,多個時要用逗號隔開。被定義的對象既可以是一個普通對象,也可以是一個數組對象或指針對象。例如:CStuscoreone,*Stu,Stus[2];這時,one是類CStuscore的一個普通對象,Stu和Stus分別是該類的一個指針對象和對象數組。若對象是一個指針,則還可像指針變量那樣進行初始化,例如:CStuscore*two=&one;可見,在程序中,對象的使用和變量是一樣的,只是對象還有成員的訪問等手段。1.2.4對象成員的訪問一個對象的成員就是該對象的類所定義的數據成員和成員函數。訪問對象的成變量和成員函數與訪問一般變量和函數的方法是一樣的,只不過須在成員前面加上對象名和成員運算符“.”,其表示方式如下:<對象名>.<成員變量><對象名>.<成員函數>(<參數表>) 1.2類和對象例如:
cout<<one.getName()<<endl; //調用對象one中的成員函數getName,然后輸出其結果cout<<Stus[0].getNo()<<endl; //調用對象數組元素Stus[0]中的成員函數getNo,然后輸出需要說明的是,一個類對象只能訪問該類的公有型成員,而對于私有型成員則不能訪問,例如getName和getNo等公有成員可以由對象通過上述方式來訪問,但strName、strStuNo、fScore等私有成員不能被對象來訪問。若對象是一個指針,則對象的成員訪問形式如下:<對象名>-><成員變量><對象名>-><成員函數>(<參數表>) 1.2類和對象“->”是另一個表示成員的運算符,它與“.”運算符的區(qū)別是:“->”用來表示指向對象的指針的成員,而“.”用來表示一般對象的成員。需要說明的是,下面的兩種表示是等價的(對于成員函數也適用):<對象指針名>-><成員變量>(*<對象指針名>).<成員變量> 例如:
CStuscore*two=&one;cout<<(*two).getName()<<endl; //Acout<<two->getName()<<endl; //與A等價需要說明的是,類外通常是指在子類(后面會討論)中或其對象等的一些場合,對于訪問權限public、private和protected來說,只有在子類中或用對象來訪問成員時,它們才會起作用。在用類外對象來訪問成員時,只能訪問public成員,而對private和protected均不能訪問。對類中的成員訪問或通過該類對象來訪問成員都不受訪問權限的限制。
1.2類和對象1.2.5構造函數和析構函數(1)構造函數前面已提及,在類的定義中是不能對數據成員進行初始化的。為了能給數據成員設置某些初值,這時就要使用類的特殊成員函數——構造函數。構造函數的最大特點是在對象建立時它會被自動執(zhí)行,因此用于變量、對象的初始化代碼一般放在構造函數中。C++規(guī)定,一個類的構造函數必須與相應的類同名,它可以帶參數,也可以不帶參數,與一般的成員函數定義相同,可以重載,也可以有默認的形參值。例如:程序運行的結果為:分析和說明:(1)代碼中,為類CPerson定義了3個重載的構造函數(程序中用A、B、C標明)。這些構造函數的重載必須按其規(guī)定進行定義:要么參數個數不同;要么參數個數相同,但參數類型不能相同。其中,構造函數CPerson(floath,floatw=120),不僅設置了形參w的默認值,而且還將該構造函數的聲明在類中進行,其定義在類體外實現。1.2類和對象(2)主函數main中,對象one的初始化等價于one.CPerson("DING"),因而調用的是B構造函數,此時對象的私有數據成員name設定了初值“DING”,而height和weight初值沒有指定,它們的初值取決于對象的存儲類型,可能是默認值或無效值。(3)對象two的初始化等價于two.CPerson(170,130),因而調用的是C構造函數,此時對象的私有數據成員height和weight初值分別設定為170、130,而name初值沒有指定,它可能是默認值或無效值。(4)對象three的初始化等價于three.CPerson("DING",170,130),因而調用的是A構造函數,此時對象的私有數據成員name、height和weight初值分別設定為"DING"、170和130。在C++函數聲明時,自右向左可以為一個或多個形參指定默認的參數值,這樣在調用時,可以不給出具體的實際參數值,而按其指定的默認值工作。如CPerson(floath,floatw=120)構造函數,在定義對象時,若有CPersonother(170)。則使得height=170,而weight等于默認值120。1.2類和對象雖然構造函數的定義方式與一般成員函數沒有什么區(qū)別,但要注意:①構造函數名必須與類同名。只有約定了構造函數名,系統(tǒng)在生成類的對象時,才能自動調用類的構造函數。②定義的構造函數不能指定其返回值的類型,也不能指定為void類型。事實上,由于構造函數主要用于對象數據成員的初始化,因而無須返回函數值,也就無須有返回類型。③若要用類定義對象,則構造函數必須是公有型成員函數,否則類無法實例化。若類僅用于派生其它類,則構造函數可定義為保護型成員函數。④當構造函數重載以及設定構造函數默認形參值時,要避免出現二義性。例如:
CPerson(char*str,floath=170,floatw=130) //A{ strcpy(name,str); height=h; weight=w;}CPerson(char*str) //B{ strcpy(name,str);}1.2類和對象則當“CPersonother(“DING”);”時,即“other.CPerson(“DING”);”,因編譯無法確定是上述哪一個構造函數的調用,從而出現編譯錯誤。⑤如果沒有定義任何構造函數,則編譯自動為類隱式生成一個不帶任何參數的默認構造函數,由于函數體是空塊,因此默認構造函數不進行任何操作,僅僅為了對象創(chuàng)建時的語法需要。例如,對于CPerson類來說,默認構造函數的形式如下:
CPerson() //默認構造函數的形式{}默認構造函數的目的是使下列對象定義形式合法:
CPersonone; //即:one.CPerson();會自動調用默認構造函數此時,由于對象one沒指定任何初值,因而編譯會自動調用類中隱式生成的默認構造函數對其初始化。⑥當類定義中指定了構造函數,則隱式的默認構造函數不再存在,因此,若對于前面定義的CPerson類來說,若有:
CPersonfour; //錯誤1.2類和對象2.析構函數與構造函數相對應的是析構函數(Destructor)。析構函數是C++類中另一個特殊的成員函數,它只是在類名稱前加上一個“~”符號(邏輯非),以與構造函數功能相反。其格式如下:<~類名>(){…}這樣,數據成員(尤其是用new為其開辟的內存空間)的釋放代碼就可放入析構函數的函數體中,以便對象消失后自動調用。需要說明的是:(1)每一個類最多只能有一個析構函數,且應為public,否則類實例化后無法自動調用析構函數進行釋放,但不能被重載,沒有任何參數,也不返回任何值,函數名前也不能有任何關鍵詞(包括void)。例如:
classCPerson{public: ...
~CPerson() { } //析構函數
...};1.2類和對象(2)與類的其他成員函數一樣,析構函數的定義也可在類體外進行,但必須指明它所屬的類,且在類體中還必須有析構函數的聲明。例如:
classCPerson{public: ...
~CPerson(); //析構函數的聲明
...};CPerson::~CPerson() //在類體外進行析構函數的定義{…}(3)與默認構造函數類似,若類的聲明中沒有定義析構函數時,則編譯也會自動生成一個隱式的不做任何操作的默認析構函數。1.2.6new和delete由于一個類的成員的數據類型可以是任何有效的合法的類型,因而若數據類型為指針時,則這樣的成員稱為指針成員,但此時要注意指針成員的潛在危險。例如,若有一個類CName,用來描述一個字符串名稱:1.2類和對象由于“CNameone(p);”調用的是B重載構造函數,從而使得私有指針成員strName指向等于p的指向。而p是指向new開辟的內存空間,其內容為“DING”,一旦p指向的內存空間刪除后,p的指向就變得不確定了,此時strName指向也不確定,所以此時運行結果為:
顯然,輸出的是一個無效的字符串。因此,為了保證類的封裝性,類中的指針成員所指向的內存空間必須在類中自行獨立開辟和釋放。因此,類CName應改成下列代碼:這樣,主函數中的代碼才會正確的運行結果:
1.2類和對象1.2.7對象賦值和拷貝構造函數1.對象賦值在C++中,一個類的對象的初值設定可以有多種形式。例如,對于前面的類CName來說,則可有下列對象的定義方式:
CNameo1; //通過A顯式默認構造函數設定初值CNameo2(“DING”); //通過B重載構造函數設定初值等都是合法有效的。但是若有:
o1=o2; //通過賦值語句設定初值則雖合法,因為同類型的變量可以直接用“=”賦值,但運行后卻會出現程序終止,這是為什么呢?這是因為對于“CNameo1;”這種定義方式,編譯會自動調用相應的默認構造函數,此時顯式的默認構造函數使私有指針成員strName為空值;而“o1=o2;”中,C++賦值運算符的操作是將右操作對象的內容拷貝(復制)到左操作對象的內存空間中,由于左操作對象o1中的strName沒有指向任何內存空間,因此試圖將數據拷貝到一個不存在的內存空間中,程序必然異常終止。所以“o1=o2;”看上去合法,但實際上是不可行的。C++還常用下列形式的初始化來將另一個對象作為對象的初值:1.2類和對象<類名><對象名1>(<對象名2>)例如:
CNameo2(“DING”); //A:通過構造函數設定初值CNameo3(o2); //B:通過指定對象設定初值B語句是將o2作為o3的初值,同o2一樣,o3這種初始化形式要調用相應的構造函數,但此時找不到相匹配的構造函數,因為CName類沒有任何構造函數的形參是CName類對象。事實上,CName還隱含一個特殊的默認構造函數,其原型為CName(constCName&),這種特殊的默認構造函數稱為默認拷貝構造函數。在C++中,每一個類總有一個默認拷貝構造函數,其目的是保證B語句中對象初始化形式的合法性,其功能就等價于“CNameo3=o2;”。但語句“CNameo3(o2);”與語句“o1=o2;”一樣,也會出現程序終止,其原因和“o1=o2;”原因一樣。但是,若有類CData:1.2類和對象
classCData{public: CData(intdata=0) { m_nData=data; } ~CData() {} intgetData() { returnm_nData; }private: intm_nData; };則下列初始化形式卻都是合法有效的:
CDataa(3); //通過重載構造函數設定初值CDatab(a); //通過默認拷貝構造函數設定初值,
//等價于CDatab=a;cout<<a.getData()<<endl; //輸出3cout<<b.getData()<<endl; //輸出31.2類和對象可見,同變量一樣,在C++中類對象的初始化也可以有2種方式:賦值方式和默認拷貝方式。這兩種方式是等價的,例如:CDatab(a);和CDatab=a;是等價的。2.分析和比較
解決CName對象初始化所進行的內容拷貝問題,在C++中有2種手段,一是給“=”運算符賦予新的操作,稱為運算符重載;二是重新定義或重載默認拷貝構造函數。3.淺拷貝前面已說過,每一個C++類都有一個隱式的默認拷貝構造函數,其目的是保證對象拷貝初始化方式的合法性,其功能是將一個已定義的對象所在的內存空間的內容依次拷貝到被初始化的對象的內存空間中。這種僅僅將內存空間的內容拷貝的方式稱為淺拷貝。也就是說,默認拷貝構造函數是淺拷貝方式。
4.深拷貝事實上,對于數據成員有指針類型的類來說,均會出現如CName類的問題,由于默認拷貝構造函數無法解決,因此必須自己定義一個拷貝構造函數,在進行數值拷貝之前,為指針類型的數據成員另辟一個獨立的內存空間。由于這種拷貝還需另辟內存空間,因而稱其為深拷貝。1.2類和對象5.拷貝構造函數拷貝構造函數是一種比較特殊的構造函數,除遵循構造函數的聲明和實現規(guī)則外,還應按下列格式進行定義。<類名>(參數表){}可見,拷貝構造函數的格式就是帶參數的構造函數。由于拷貝操作實質是類對象空間的引用,因此C++規(guī)定,拷貝構造函數的參數個數可以1個或多個,但左起的第1個參數必須是類的引用對象,它可以是“類名&對象”或是“const類名&對象”形式,其中“類名”是拷貝構造函數所在類的類名。也就是說,對于CName的拷貝構造函數,可有下列合法的函數原型:
CName(CName&x); //x為合法的對象標識符CName(constCName&x); CName(CName&x,…); //“…”表示還有其他參數CName(constCName&x,…); 1.2類和對象
需要說明的是,一旦在類中定義了拷貝構造函數,則隱式的默認拷貝構造函數和隱式的默認構造函數就不再有效了。例如,下面的示例是在前面CName的基礎上添加拷貝構造函數的定義,程序如下:
[例Ex_CopyCon]使用拷貝構造函數代碼中,類CName定義了兩個拷貝構造函數A和B,其中A稱為顯式的默認拷貝構造函數,B稱為重載拷貝構造函數,它還帶有字符指針參數,用來將新對象的數據成員字符指針strName指向一個開辟的動態(tài)內存空間,然后將另一個對象one的內容復制到strName中,最后調用cstring頭文件定義的庫函數strcat將字符指針參數add指向的字符串連接到strName中。程序運行的結果如下:
1.2類和對象1.2.8this指針
[例Ex_This]使用this指針
類CPoint中,使用this指針的成員函數是Copy,此時this指針指向類自己,在成員函數Copy中,由于語句“*this=one”等到對象調用時才會執(zhí)行,因而當在main函數調用“pt1.Copy(pt2);”時,this指針指向對象pt1,此時“*this=one”是將one的內容拷貝到類對象pt1中,這樣就使得pt1的數據成員的值等于pt2的數據成員的值。因此main函數中最后的語句“pt1.print();”輸出的結果就是等于pt2的結果。程序運行的結果如下:
1.2類和對象事實上,當成員函數的形參名與該類的成員變量名同名時,則必須用this指針來顯式區(qū)分,例如:
classCPoint{public: CPoint(intx=0,inty=0) {this->x=x; this->y=y; } voidOffset(intx,inty) {(*this).x+=x; (*this).y+=y; } voidPrint()const {cout<<"Point("<<x<<","<<y<<")"<<endl; }private intx,y;};代碼中,類CPoint中的私有數據成員x、y和構造函數、Offset成員函數的形參同名,正是因為成員函數體中使用了this指針,從而使函數中的賦值語句合法有效,且含義明確。否則,如果沒有this指針,則構造函數中的賦值語句就變?yōu)榱恕皒=x;y=y;”,顯然是不合法的。1.3繼承和派生1.3.1繼承的特性在C++中,類的繼承具有下列特性:(1)單向性。類的繼承是有方向的。例如,若A類是子類B的父類,則只有子類B繼承了父類A中的屬性和方法,在B類中可以訪問A類的屬性和方法,但在父類A中卻不訪問子類B的任何屬性和方法。而且,類的繼承還是單向的。例如,若A類繼承了B類,反之,此時B類不能再繼承A類。同樣,若A類是B類的基類,B類是C類的基類,此時C類不能是A類的基類。(2)傳遞性。若A類是B類的基類,B類是C類的基類,則基類A中的屬性和方法傳遞給了子類B以后,通過子類B也傳遞給子類C,這是類繼承的傳遞性。正是因為繼承的傳遞性,才使子類自動獲得基類的屬性和方法。(3)可重用性。自然界中存活在同物種具有遺傳關系的層次通常是有限的,而C++中的類卻不一樣,類的代碼可一直保留。這樣,當基類的代碼構造完之后,其下一代的派生類的代碼往往新增一些屬性和方法,這些一代一代派生下去,整個類的代碼越來越完善。若將若干代的類代碼保存在一個頭文件中,而在新的程序文件中包含進來,然后定義一個派生類,則這樣的派生類就具有前面所有代基類的屬性和方法,而不必從頭開始重新定義和設計,從而節(jié)略了大量的代碼??梢?,類的繼承機制也體現了代碼重用或軟件重用的思想。1.3繼承和派生1.3.2派生類的定義在C++中,一個派生類的定義可按下列格式:class<派生類名>:[<繼承方式1>]<基類名1>,[<繼承方式2>]<基類名2>,…{[<派生類的成員>]};基類列表從格式可以看出:(1)一個派生類和一個一般類的定義格式基本相同,惟一區(qū)別就在于:派生類定義時派生類名后面是由冒號“:”引導的基類列表。(2)基類列表中,若指定基類只有一個,則這樣的派生類是單繼承方式,若有多個基類,則為多繼承方式。當有多個基類時,基類名之間要用逗號分隔。(3)各基類名之前一般需要指定其繼承方式,用來限定派生類繼承基類屬性和方法的使用權限。C++繼承方式有3種:public(公有)、private(私有)及protected(保護),若繼承方式沒有指定,則被默認指定為private(私有)方式。1.3繼承和派生基類必須是在派生類定義前已作過定義的類,若是在派生類后面定義,而僅僅在派生類定義前作基類的提前聲明,則是不合法的。例如,下面的代碼:
classCBase; //基類CBase作提前聲明classCDerived:publicCBase //錯誤:CBase未定義{ intz;};classCBase //基類的定義{ intx,y;};1.3.3繼承方式(1)公有繼承。公有繼承(public)方式具有這樣特點:在派生類中,基類的公有成員、保護成員和私有成員的訪問屬性保持不變。在派生類中,只有基類的私有成員是無法訪問的。也就是說,基類的私有成員在派生類中被隱藏了,但不等于說基類的私有成員不能由派生類繼承。派生類對象只能訪問派生類和基類的公有(public)成員。1.3繼承和派生2.私有繼承。私有繼承(private)方式具有這樣特點:在派生類中,基類的公有成員、保護成員和私有成員的訪問屬性都將變成私有(private),且基類的私有成員在派生類中被隱藏。因此,私有繼承方式下,在派生類中仍可訪問基類的公有(public)和保護(protected)成員。由于基類的所有成員在派生類中都變成私有,因此基類的所有成員在派生類的子類中都是不可見的。換句話說,基類的成員在派生類的子類中已無法發(fā)揮基類的作用,實際上相當于終止基類的繼續(xù)派生。正因為如此,實際應用中私有繼承的使用情況一般比較少見。另外,派生類對象只能訪問派生類的共有成員,而不能訪問基類的任何成員。3.保護繼承。保護繼承(protected)方式具有這樣特點:在派生類中,基類的公有成員、保護成員的訪問屬性都將變成保護的,同樣,基類的私有成員在派生類中也是被隱藏的。同私有繼承一樣,在保護繼承方式下,派生類中仍可訪問基類的公有成員和保護成員。但派生類對象只能訪問派生類的共有成員,而不能訪問基類的任何成員。下面來看一個示例,它是反映類CPerson和類CStudent的公有(public)繼承關系。在學生類CStudent中,它的數據成員可以有:姓名、學號、性別、年齡、三門課程的成績、總分和平均分。以往設計這個類時,是將這些數據成員一并聲明在CStudent類中,這樣做的缺點是顯然的,因為若還需要一個教職員工類CStaff,而該類的數據成員也需要姓名、性別和年齡,則還需要重新定義這些數據,顯然類代碼起不到優(yōu)化和重用的作用。如果將其中的姓名、性別和年齡的數據成員作為基類CPerson的數據成員,處理這些數據的方法也在基類完成。這樣通過繼承,使得CStudent和CStaff類同時擁有基類的數據成員及其方法,從而大大簡化了類CStaff和CStudent的代碼,這在復雜的類設計中尤為重要。1.3繼承和派生
[例Ex_PublicDerived]派生類的公有繼承示例分析和說明:(1)基類CPerson包含:私有數據成員name(姓名)、sex(性別)和age(年齡),公有構造函數CPerson、成員函數SetNameAndSex(用于重新設置name和sex),保護成員函數SetAge(用來重新設置age)、ShowInfo(用來顯示這些信息)。這樣,用基類CPerson創(chuàng)建對象后,對象只能調用共有成員函數SetNameAndSex,而對于私有和保護成員是無法訪問的。(2)派生類CStudent的成員除了自身的成員外,實際上還包含了基類CPerson的所有成員。由于CStudent類公有繼承了CPerson類,因此可以在派生類中訪問基類的公有構造函數CPerson、成員函數SetNameAndSex和保護成員函數SetAge、ShowInfo。在派生類CStudent的構造函數的對象初始化列表中,就是通過調用基類的構造函數CPerson(name,age,sex)對隱藏在派生類CStudent中的基類私有數據成員進行初始化。除此之外,派生類CStudent中的公有成員函數ShowAll中還調用了基類CPerson的保護成員函數ShowInfo。表1.1列出了派生類CStudent的所有成員及其訪問權限。(3)在main函數中,A語句是調用派生類CStudent的構造函數對one對象進行初始化,由于CStudent的構造函數還調用了基類CPerson的構造函數,因而使派生類CStudent對象one的數據成員name、age、sex和stuno得到了初值。B語句是調用了派生類CStudent的公有成員函數SetScore來設定派生類CStudent對象one的數據成員score、ave和total。這樣,對象one的所有數據成員都有了初值。1.3繼承和派生表1.1公有繼承的派生類CStudent的成員基類基類成員派生類成員派生類中訪問派生類對象訪問CPersonnameprivatenameprivateX(不可以)XsexsexageageCPerson()publicCPerson()public√(可以)√SetNameAndSex()SetNameAndSex()SetAge()protectedSetAge()protected√XShowInfo()ShowInfo()stunoprivate√Xscore、ave、totalCStudent()public√√SetScore()SetNoAndAge()ShowAll()
1.3繼承和派生程序運行的結果如下:1.3.4派生類數據成員初始化C++規(guī)定,派生類中對象成員初值的設定應在初始化列表中進行,因此一個派生類的構造函數的定義可有下列格式:成員初始化列表<派生類名>(形參表):基類1(參數表),基類2(參數表),…,基類n(參數表),對象成員1(參數表),對象成員2(參數表),…,對象成員n(參數表){}1.3繼承和派生<派生類名>(形參表):基類1(參數表),基類2(參數表),…,基類n(參數表),對象成員1(參數表),對象成員2(參數表),…,對象成員n(參數表){}說明:(1)在派生類構造函數的成員初始化列表中,既可有基類拷貝的數據成員的初始化,也可有派生類中對象成員的初始化。當然,派生類的數據成員也可在成員初始化列表中進行初始化,但數據成員的初始化形式必須是“數據成員名(參數)”的形式。(2)在成員初始化列表中,多個成員初始化之間必須用逗號分隔。(3)派生類中的各數據成員的初始化次序總體是:首先是基類拷貝成員的初始化,然后才是派生類自己的數據成員初始化。(4)基類拷貝成員的初始化次序與它在成員初始化列表中的次序無關。在單繼承中,它取決于繼承層次的次序,即優(yōu)先初始化上層類的對象。而在多繼承中,基類成員的初始化次序取決于派生類聲明時指定繼承時的基類的先后次序。(5)派生類自身數據成員的初始化次序也與在成員初始化列表中的次序無關,它們取決于在派生類中聲明的先后次序。
1.3繼承和派生例如,一個長方體類CCuboid,它從基類矩形類CRect派生而來?;怌Rect的數據成員是2個CPoint類對象ptLT和ptRB,分別表示矩形的左上角點和右下角點的位置。派生類CCuboid自身的數據成員有表示高度的fHeight,以及表示底面中點位置的CPoint對象ptCenter。如圖1.5所示。具體程序如下:fHeightptRBptLTptCenterCCuboidCRect圖1.5CCuboid類的結構
[例Ex_ClassDerived]派生類的構造和析構示例
程序運行的結果如下:1.3繼承和派生分析:由上述程序代碼可知,派生類CCuboid除了自身的CPoint類對象成員ptCenter和float型數據成員fHeight外,還有CRect基類拷貝的數據成員。下面說明“CCuboidone(5,5,30,30,50);”的構造過程:(1)在CCuboid類中查找對象one匹配的構造函數“CCuboid(intx1,inty1,intx2,inty2,intheight)”,然后傳遞實參,使得x1=5,y1=5,x2=30,y2=30,height=50,流程轉到CCuboid構造函數處(即代碼中的A處)。(2)按CCuboid類聲明繼承時指定的基類次序進行基類拷貝的構造。因CCuboid類是單繼承,基類是CRect,因此這里僅構造CRect基類拷貝。(3)查找CCuboid中的成員初始化列表中有無基類拷貝的構造代碼。若沒有,則流程轉到基類的默認構造函數處;若有,則流程轉到相匹配的基類構造函數處。顯然,這里的流程是轉到“CRect(x1,y1,x2,y2)”處(即代碼中的B處),然后傳遞實參,使得x1=5,y1=5,x2=30,y2=30。(4)由于基類CRect中定義了2個CPoint對象成員ptLT和ptRB,且在CRect構造函數的成員初始化列表中還有其初始化代碼,因此根據它們在CRect類的定義次序先進行ptLT的初始化。(5)根據ptLT在初始化列表中的初始化形式查找匹配的CPoint構造函數版本“CPoint(intx=0,inty=0)”,然后傳遞實參,使得x=5,y=5,流程轉到CPoint構造函數處(即代碼中的C處)。1.3繼承和派生(6)由于CPoint類僅有私有數據成員xPos和yPos,且構造函數中也沒有成員初始化列表。故系統(tǒng)為CPoint類的數據成員開辟內存空間后,直接執(zhí)行CPoint類構造函數中的代碼,結果使得ptLT中的成員xPos和yPos分別為5和5,輸出“CPoint構造函數”。
(7)ptLT構造后,流程回溯到基類CRect構造函數的成員初始化列表中(即第4步),進行CRect類的下一個數據成員ptRB的初始化,即執(zhí)行第5和第6步。故程序運行結果中一開始輸出2次“CPoint構造函數”。(8)基類CRect構造函數的成員初始化列表中的代碼執(zhí)行完成后,系統(tǒng)開始執(zhí)行CRect構造函數中的代碼,結果輸出“CRect構造函數”。此時,CCuboid類對象的CRect基類拷貝中的ptLT和ptRB的值分別為(5,5)和(30,30)。
(9)CRect基類拷貝初始化后,流程回溯到CCuboid類構造函數的成員初始化列表中。因不再有其它基類拷貝的初始化,故系統(tǒng)根據CCuboid類的數據成員的定義次序進行初始化。(10)首先為CCuboid類的CPoint對象成員ptCenter開辟內存空間,然后在CCuboid類構造函數的成員初始化列表中查找ptCenter的初始化代碼“ptCenter((x1+x2)/2,(y1+y2)/2)”,并根據其初始化形式查找匹配的CPoint構造函數版本,傳遞實參,執(zhí)行構造函數中的代碼。結果使得ptCenter的值為(17,17),輸出“CPoint構造函數”。(11)流程再次回溯到CCuboid類構造函數的成員初始化列表中,根據CCuboid類的數據成員的定義次序進行fHeight成員的初始化。(12)為CCuboid類的fHeight數據成員開辟內存空間,然后在CCuboid類構造函數的成員初始化列表中查找fHeight的初始化代碼“fHeight(height)”,結果使得fHeight=50,然后執(zhí)行CCuboid類構造函數中的代碼,輸出“CCuboid構造函數”。CCuboid類對象one構造完畢,流程轉到“one.ShowAll();”處。1.3繼承和派生1.3.5基類成員的訪問前面已提及:派生類對象和派生類中的成員函數對基類的訪問是不同的。那么,在派生類或派生類對象中究竟有哪些訪問基類成員的方式呢?(1)假設,派生類B公有繼承了基類A,A中的公有成員為m,則在派生類B及其對象中訪問基類A成員m的方式有:(2)若派生類B中無任何和A基類成員m同名的成員時,則可在派生類B中直接引用m。若有同名成員存在,則在派生類B中須指定成員所屬的類,即訪問形式為“A::m”。若派生類B對象oB是一個普通對象,當派生類B中無任何和A基類成員m同名的成員時,則通過oB訪問基類成員m的形式為“oB.m”。若派生類B中有同名成員m存在,則通過oB訪問基類成員m的形式為“oB.A::m”。(3)若派生類B對象poB是一個指針對象,當派生類B中無任何和A基類成員m同名的成員時,則通過poB訪問基類成員m的形式為“poB->m”,若派生類B中有同名成員m存在,則通過poB訪問基類成員m的形式為“poB->A::m”。1.4多態(tài)和虛函數1.4.1多態(tài)概述多態(tài)(Polymorphism),是指不同類型的對象接收相同的消息時產生不同的行為。這里的消息主要是指對類的成員函數的調用,而不同的行為是指成員函數的不同實現。多態(tài)是一種普遍存在的現象,如水有三種形態(tài):固態(tài)(冰)、液態(tài)(水)、氣態(tài)(汽),又如算術“加”運算可根據不同的操作數,如1+1、1+0.5、1.5+0.5等,具有多種形式。靜態(tài)聯(lián)編是指這種聯(lián)編在編譯階段完成的,由于聯(lián)編過程是在程序運行前完成的,故又稱為早期聯(lián)編。動態(tài)聯(lián)編是指這種聯(lián)編要在程序運行時動態(tài)進行,所以又稱為晚期聯(lián)編。在C++中,函數重載是靜態(tài)聯(lián)編的具體實現方式。調用重載函數時,編譯根據調用時參數類型與個數在編譯時實現靜態(tài)聯(lián)編,將調用地址與函數名進行綁定。這里先來看一個示例,類的層次關系如圖1.6所示(圖中,方框表示類,箭頭表示繼承關系,它指向基類):
[例Ex_PolyFunc]多態(tài)函數的靜態(tài)聯(lián)編CShape圖1.6類層次圖CTriangleCCircle1.4多態(tài)和虛函數分析和說明:(1)代碼中,基類CShape(形狀類)派生了三角形類CTriangle和圓形類CCircle?;惡团缮愔卸加幸粋€同名的成員函數area,用來求形狀的面積。由于開始時,基類CShape形狀不確定,因此成員函數area返回的值為0.0。而在各具體的形狀類中,成員函數area返回的值為各形狀的具有面積。(2)在main主函數中,由于tri是三角形類CTriangle對象,按“最近優(yōu)先”原則,A語句中tri.area()調用的是CTriangle類的成員函數area,結果為6。類似的,B語句中cir.area()調用的是CCircle類的成員函數area,結果為78.5398。
(3)C語句中,由于s1是CShape對象指針,當它指向CTriangle對象tri時,那么此時s1->area()究竟是調用哪一個類中的area函數呢?由于基類CShape中的成員函數area是一個普通成員,當被派生類CTriangle繼承后,就會在派生類CTriangle中產生CShape類的拷貝,因此,此時s1指向的是對象tri中的CShape類拷貝。故s1->area()調用的是CShape類的成員函數area,結果為0。由于s1->area()調用在編譯時就能確定是基類還是派生類的成員函數,因而它是靜態(tài)聯(lián)編。類似的,D語句中的s2.area()調用的也是CShape類的成員函數area,結果也是為0。
1.4多態(tài)和虛函數程序運行的結果如下:
1.4.2虛函數定義虛函數是用關鍵字virtual來修飾基類中的public或protected的成員函數。當在派生類中進行重新定義后,就可在此類層次中具有該成員函數的不同版本。在程序執(zhí)行過程中,依據基類對象指針所指向的派生類對象,或通過基類引用對象所引用的派生類對象,才能確定哪一個版本被激活,從而實現動態(tài)聯(lián)編。在基類中,虛函數定義的一般格式如下:virtual<函數類型><函數名>(<形式參數表>){ <若干語句>}函數體1.4多態(tài)和虛函數需要說明的是:(1)雖然虛函數定義只是在一般函數定義前添加了關鍵字virtual,但虛函數必須是類中的成員函數。(2)可把析構函數定義為虛函數,但不能將構造函數定義為虛函數。通常在釋放基類中及其派生類中的動態(tài)申請的存儲空間時,也要把析構函數定義為虛函數,以便實現撤消對象時的多態(tài)性。(3)虛函數在派生類重新定義時參數的個數和類型以及函數類型必須和基類中的虛函數完全匹配,這一點和函數重載完全不同。并且,虛函數派生下去仍是虛函數,且可省略virtual關鍵字。下面的示例是將上例基類CShape中的area成員函數定義成虛函數,其它未作改動。
[例Ex_VirtualFunc]虛函數的動態(tài)聯(lián)編程序運行的結果如下:
1.4多態(tài)和虛函數1.4.3虛函數的內部機制程序運行時就會根據基類對象所獲取的派生類對象將派生類對象的VTable和vptr復制給基類,并由基類來調用,從而實現了類成員函數的動態(tài)聯(lián)編。可見,動態(tài)聯(lián)編時需要指定派生類對象的地址,因而必須通過基類指針或引用對象才能激活虛函數的動態(tài)聯(lián)編機制。需要說明的是:派生類中重寫的虛函數應與基類的虛函數完全一樣,包括虛函數的返回值類型也應一樣。在ANSI/ISOC++中,對于一般函數來說,函數返回值類型是不能作為一般函數的重載區(qū)分內容;但對于虛函數而言,函數返回值類型不同的虛函數被認為兩個不一樣的虛函數。一旦派生類中重寫的虛函數與基類的虛函數不一樣時,派生類的VTable和vptr將無法復制給基類,因此當該派生類對象地址傳給基類指針對象時,基類指針對象所引用的虛函數調用是基類本身
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- T-ZSA 232-2024 特種巡邏機器人通.用技術要求
- T-ZJHQ 0003-2024 高等學校生活垃圾分類工作規(guī)范
- 2025年度電子商務平臺數據分析與報告合同模板
- 二零二五年度解除婚約合同范本:婚約解除后的財產清算、債務處理及子女監(jiān)護協(xié)議
- 2025年度鋼板租賃與回收利用合同
- 二零二五年度金融機構資金轉入風險管理合同
- 2025年度智慧能源管理系統(tǒng)擔保人履約保證合同
- 二零二五年度企業(yè)綠色金融項目補貼協(xié)議
- 二零二五年度情人協(xié)議書:浪漫愛情生活規(guī)劃合同范本
- 石壕吏:歷史背景與社會問題分析教學教案
- 濕式氣柜培訓
- 2023年高考真題-化學(福建卷) 含解析
- 欄桿拆除及更換施工方案
- 10我們愛和平(第1課時)(說課稿)2023-2024學年統(tǒng)編版道德與法治六年級下冊
- 《國際貿易實務(英文版)》(英文課件) -Ch 6 International Cargo Transport-Ch 11 Cross-border Commerce
- 新條令.新亮點-內務條令解讀
- 中醫(yī)適宜技術-中藥熱奄包
- 林海雪原課件6張
- 銀發(fā)經濟產業(yè)發(fā)展規(guī)劃
- 防火涂料質量保證書
- 礦產資源開發(fā)合同備忘錄范本
評論
0/150
提交評論