C++面向對象程序設計(陳維興 林小茶)精講


面向對象設計主要特征是程序=對象+消息,對象是基本元素,對象接收到消息后,啟動有關方法完成操作。

面向對象程序設計的基本特征有:抽象、封裝、繼承和多態。

c++支持編譯時的多態和運行時的多態,編譯時的多態通過函數重載實現,運行時的多態通過虛函數實現。

c++通過對c進行擴充,是面向過程程序設計和面向對象設計的混合型程序設計語言。

c++程序一般由類的聲明和類的使用兩大部分組成。

c++對c的擴充:

l  c++除了保留c的進行輸入/輸出操作時常使用的printf()和scanf()函數外,新增標准輸入流對象cin和標准輸出對象cout。cin遇到空格,就認為本數據輸入結束並檢查輸入數據與變量的匹配情況。c++增加了操縱符對輸出數據格式進行控制,如換行操縱符endl。

l  在c語言中全局變量必須聲明在任何函數之前,局部變量必須集中在可執行語句之前,而c++允許變量聲明可與可執行語句交替出現。

l  在c++中,結構名、聯合名、枚舉名都是類型名。可以像定義對象那樣,定義結構體、聯合體、枚舉。不必在前面冠以struct、union、enum。

l  c語言建議為每一個函數建立函數原型,c++必須建立函數原型,以便在編譯時進行類型檢查。

l  c語言常用#define 來定義常量,但是容易出錯,c++推薦用const修飾符。

l  內聯函數以空間換時間的方式降低函數調用時的系統開銷,但要求內聯函數體內不含有負雜的控制語句,也不能過長,免得程序編譯后體量過大。c++中一個函數在類體內則被隱式的指定為內聯函數,也可以用inline顯式的指定內聯函數。

l  可以定義帶有默認參數的函數。

l  用戶可以重載函數,即在同一作用域中,可以定義函數名相同的函數,只要函數參數類型不同或者參數個數不同或者兼而有之。

l  作用域標識符::c語言中函數內如有和全局變量同名的局部變量,則全局變量會被屏蔽無法訪問,而c++中可以在變量名前面使用::表明是使用全局變量。

l  計算機內存會被分為四個區:程序代碼區、全程數據區、棧、堆。只有堆可由用戶分配和釋放。c語言中使用函數malloc函數和free來動態管理內存,c++提供了運算符new、delete來做同樣的工作。

l  引用,c++中引用就是變量的別名,故又稱別名。引用和原變量名指向內存中的同一區域。

l  傳遞函數參數的三種情況,1、將變量名作為函數參數,這是“傳值調用”,是單向傳遞,在函數運行過程中,形參的變化不會傳遞給實參,2、指針變量作為函數參數,這是“傳址調用”,是雙向傳遞,3、引用作為函數參數,也是“傳址調用”雙向傳遞。

l  通常一個函數不能直接用在賦值運算符的左邊,例如

index(2)=25;這是不允許的,但是使用引用返回函數值就可以這么寫了:

int& index(int i)

{

 return a[i];

}

index(2)=25;這是允許的,相當於a[2]=25;要注意的是引用不是一種數據類型,所以不能定義引用的引用、引用的指針、引用的數組。

 

類的構成:類和結構體的擴種形式十分相似,類生命中包括數據和函數,分別稱為數據成員和成員函數,數據成員和成員函數有公有、保護、私有三種訪問權限。一般情況下,類體中只給出成員函數原型,而函數體的定義放在類外。需要注意的是不能在類聲明中給數據成員賦初值。

相同類型的對象可以相互賦值,如a=b,對象之間的賦值,僅僅是對數據成員的賦值,不同的對象的數據成員占據不同的存儲空間而不同對象的成員函數是占有同一個函數代碼段,無法對他們賦值。當類體中存在指針時,使用賦值運算符進行賦值,可能會產生問題,即所謂的“淺拷貝”和“深拷貝”問題,淺拷貝時,因為含有指針,兩個對象指針指向同一塊內存區域,同一個對象調用析構函數時,該塊內存被釋放,當另一個也調用析構函數,同樣要釋放該塊內存,而同一塊內存不能被釋放兩次,這時便會出現問題,要解決該類問題,就需要使用“深拷貝”。

構造函數是特殊的成員函數,其函數名必須和類名相同,可以有任意的參數但不能有返回值甚至void也不行,它不需要用戶調用,在定義對象時自動執行且只執行一次,為對象分配空間,進行初始化。

c++提供了除賦值運算符之外的初始化數據成員的方法,即成員初始化列表。對於用const修飾的數據成員,或是引用類型的數據成員,是不允許用賦值語句直接賦值的,只能用成員初始化列表進行初始化。
析構函數是另一種特殊成員函數,通常用於撤銷對象時的一些清理工作,如釋放分配給對象的內存空間等。析構函數和構造函數名字相同,但在前面加一個波浪號(~),析構函數沒有參數也沒有返回值,而且不能重載。因此一個類中只有一個析構函數。當撤銷對象時,編譯系統會自動調用析構函數。

拷貝構造函數,它的作用是在建立一個新對象時,使用一個已經存在的對象去初始化這個新對象,如point p1(p2)或point p1=p2;拷貝構造函數只有一個參數,並且是同類對象的引用,每個類都必須有一個拷貝構造函數。

因為成員函數代碼是同類所有對象共用,為了使成員函數辨別出當前調用自己的是哪個對象,c++引入了自引用指針this,每當創建一個對象,系統就把this指針指向該對象,當調用成員函數時,系統把this指針作為一個隱含參數傳遞給該函數。

對象中的數據成員有自己獨立的存儲空間,互不相干,但有時希望不同的對象可以共享一個或幾個數據成員,實現同類的不同對象間數據共享,於是有了靜態成員的概念。無論建立多少類的對象,都只有一份靜態數據成員的拷貝,從而實現同類的不同對象間的數據共享,靜態成員屬於類,所以可以用類直接訪問。靜態成員函數不是為了對象間的溝通,而是為了處理靜態數據成員。靜態成員函數沒有this指針,所以一般不訪問非靜態成員,如果要訪問非靜態成員,則需顯式的通過對象名.非靜態成員名來訪問。

有了靜態成員來實現同類不同對象間的數據共享,同樣為了實現類間的數據共享,c++引入了友元這一概念。友元是一扇通向私有成員的后門。

一個類的友元函數不是該類的成員,它可以是不屬於任何類的非成員函數,也可以是另一個類的成員函數。定義時需在友元函數前添加關鍵字friend。一個類的友元函數可以訪問該類的私有成員,但它畢竟不是該類成員,所以在定義和調用該類時不必像成員函數那樣在函數名前加上“類名::”。它也沒有this指針,需要顯式的通過“對象名.數據成員”才能訪問數據成員。成員函數只能訪問它所屬的類,但一個函數如果被定義為多個類的友元函數,那它可以訪問這些類的所有數據。使用友元函數時必須要慎重。

可以將一個類比如y定義為另一個類x的友元類,那么y類中的所有成員函數都是x類的友元函數。

繼承就是從先輩處得到屬性和行為特征,較好的解決了代碼重用的問題。默認為私有繼承,還有公有繼承、保護繼承。基類的構造函數和析構函數不能被繼承,當創建派生類對象時,先調用基類的構造函數,在調用派生類的構造函數,撤銷派生類的對象時,順序相反。在沒有虛函數的情況下,如果派生類中定義了與基類成員同名的成員,稱派生類成員覆蓋了基類成員,如要在派生類中使用基類同名成員,必須使用“基類名::同名成員”的方式顯式指定,如果在對象中調用就用“對象名.基類名::同名成員”。

當一個派生類有多個基類時,成為多繼承。多繼承要注意構造函數的書寫,同時多繼承帶來一個問題:當派生類y有兩個基類a和b,同時a和b有一個共同的基類c,這樣y就會繼承兩個c的拷貝,當要訪問c的成員時,必須顯式的指定是a還是b的成員,以免產生二義性,為了解決這個問題,c++引入虛基類的概念。上例中可以把c聲明為a和b的虛基類,即:class a:virtual 繼承方式 c和class b:virtual 繼承方式 c這樣從a和b繼承的y只會繼承c一次。

不同數據類型數據之間的自動轉換和賦值,稱為賦值兼容。基類和派生類對象之間也存有賦值兼容關系:在基類對象可以使用的地方都可以用派生類的對象來替代。派生類對象可以賦值給基類對象,指向基類對象的指針可以指向基類的公有派生類,但不能指向私有派生類。

所謂多態性就是不同對象收到相同的消息,產生不同的動作。連編是把函數名和函數體的程序代碼連接(聯系)在一起的過程。靜態連編在編譯階段就完成了,函數調用速度快,效率高,但缺乏靈活性,動態連編在運行階段完成,在程序運行時才去確定調用哪個函數,降低了程序運行效率但增強了靈活性。c++是編譯型語言,仍采用靜態連編,好在c++引入了“虛函數”從而實現了靜態連編和動態連編相結合。虛函數是基類中的成員函數,前面加有關鍵字virtual,並在派生類中被重載,在派生類中被重新定義時其函數原型包括返回類型、函數名、參數個數、參數類型的順序,都必須和基類中的原型完全相同。

虛函數使用的基礎是賦值兼容規則,而賦值兼容規則成立的前提條件是派生類從其基類公有派生,所以要想通過定義虛函數來實現動態多態性派生類就必須是從基類公有派生。內聯函數不能是虛函數,因為內聯函數不能在運行中動態確定其位置,所以即使虛函數在類的內部定義。編譯時仍將它看成非內聯的。

基類往往表示一種抽象的概念,此時在基類中預留一個函數名,具體功能留給派生類根據需要去定義,這樣一個虛函數就成為純虛函數,格式如下:virtual 返回類型 函數名(參數表)=0;含有純虛函數的類稱為抽象類。抽象類的目的就是用它去建立派生類,抽象類不能實例化為對象。也不允許從非抽象類派生出抽象類,抽象類也不能做函數參數類型、返回類型或顯式轉換的類型。

運算符的重載,需要寫一個運算符函數,比如要重載”+”號,就要寫一個名為operator+的函數。運算符重載函數有兩種形式,一種是定義為它要操作的類的成員函數,另一種是定義為該類的友元函數。友元運算符重載函數如下:

class x{

……

friend 返回類型 operator運算符(形參表);

……

}

注意友元函數不是類x的成員不能直接訪問x的成員,要顯式指定“x.成員名”也沒有this指針。並不是所有的運算符都可以定義為友元運算符重載函數,如賦值運算符“=”,下標運算符“[]”,函數調用運算符“()”等。

對於x=x1+x2;c++解釋為x=operator+(x1,x2);

成員運算符重載函數格式如下:

class x{

……

 返回類型 operator運算符(形參表);

……

}

 

因為成員函數可以在函數體內直接訪問類x的成員,而且有this指針隱含的指向當前對象,所以對於雙目運算符,只需一個形參就可以了。

對於x=x1+x2;c++解釋為x=x1.operator+(x2);

一般來說,雙目運算符可以被重載為友元函數活成員函數,但也有例外只能用友元函數,比如一個類的對象和一個整數或其他類對象相加的成員函數:

如果是x和整數i相加 x=x1+i;是正確的,因為c++解釋為x=x1.operator+(i);

但x=i+x1;卻出錯,因為c++解釋為x=i.operator+(x1);但是i是整數,並沒有成員函數operator+所以編譯出錯。

為了解決這一問題,只能定義兩個友元函數:

class x{

……

friend 返回類型 operator運算符(x& x1,int i);

friend 返回類型 operator運算符(int i, x& x1);

 

……

}

利用函數重載來解決運算符兩邊操作數交換的問題。

對於“++”和“--”等分前綴用法后綴用法的運算符用“int”區分,沒有int是前綴用法,有是后綴用法。

class x{

……

friend 返回類型 operator運算符(x& x1);//前綴

friend 返回類型 operator運算符( x& x1,int);//后綴

 

……

}

為了解決“淺拷貝”帶來的“指針懸掛”問題,可以重載賦值運算符”=”,引入深拷貝。

類型轉換分為標准類型(如int,float,double,char等)間的轉換和類類型和標准類型間的轉換。准類型間的轉換c++已經自帶了方法轉換,不需用戶在寫方法。

類類型和標准類型間的轉換有兩種方法1、通過轉換構造函數進行類型轉換2、通過類型轉換函數進行類型轉換。

在程序設計的過程中經常出現這樣的情況:兩個或多個函數的函數體完全相同,差別僅在於它們的參數類型不同,為了提高代碼的可重用性和可維護性,c++提出了模板概念。

在c語言中可以用宏定義#define,但是宏定義避開了類型檢查,容易出錯。

模板可以實現參數類型參數化,即把數據類型定義為參數,從而實現代碼重用。模板分為函數模板和類模板,他們分別用來建立模板函數和模板類。

函數模板是建立一個通用函數,其函數返回類型和參數類型不具體指定,用一個虛擬的類型來代表,這個通用函數就是函數模板,系統調用函數時根據實參的類型取代模板中虛擬類型。

格式如下:

template  <typename 類型參數>

返回類型 函數名(模板形參表)

{

函數體

}

或者

template <class 類型參數>

返回類型 函數名(模板形參表)

{

函數體

}

一般為了與類聲明中的class區分,一般用第一種格式,c++中的類型參數一般是t、type等。,typename和class用來表名后面的參數是一個虛擬的類型名。函數模板需要實例化一個模板函數才能調用,當編譯系統發現一個函數調用

函數名(模板實參表)

就會根據模板實參表中的類型生成一個函數即模板函數。模板函數中的函數體與函數模板函數體相同。函數模板可以和同名的非模板函數重載,調用順序是先尋找一個參數完全匹配的非模板函數,如果有就調用沒有就尋找函數模板將其實例化,如實例化模板函數成功就調用它。

對於類的聲明也可以采用類似的方法,使用類模板可以簡化那些功能相同而數據類型不同的類的聲明。格式如下:

template <typename 類型參數>

class 類名{

類成員聲明

};

template <class 類型參數>

class 類名{

類成員聲明

};

類模板定義對象的格式是:

類名<數據實際類型> 對象名(參數表);

c++支持c語言的輸入輸出系統之外,還定義了一套面向對象的輸入輸出系統。“流”是數據從一個源留到目的的抽象,負責數據的生產者和消費者之間建立聯系並管理數據的流動。i/o流類庫中各種類的聲明包含在相應的頭文件中如iostream、fstream、strstream、iomanip。

類ios是流的基類是抽象類。流類定義的對象稱為流對象,c++中有幾個預定義好的流對象:標准輸入流對象cin、標准輸出流對象cout、非緩沖型標准出錯流對象cerr和緩沖型標准出錯流對象clog。

cout中常用的成員函數:cout.put(a)輸出一個字符a;

cin中常用的成員函數:cin.get(ch)功能是從輸入流讀取一個字符(包括空白符)賦給字符型變量ch;cin.getline(字符數組,字符個數n,終止標識符)或cin.getline(字符指針,字符個數n,終止標識符)功能是從輸入流讀取n-1個字符,如果提前讀到終止標識符就提前結束最后總要插入一個字符串結束標志’\n’。cin.ignore(n,終止字符)功能是跳過輸入流中的n個(默認1個)字符或遇到指定的終止字符(默認是eof)提前結束跳躍。

流基類ios中定義了一些進行輸入輸出格式控制的成員函數,查看書籍,除此之外c++提供了另一種輸入輸出格式控制的方法,稱為操縱符,查閱書籍,用戶也可以自定義操縱符。格式如下:

輸出流:

ostream &操縱符名 (ostream &stream)

{

自定義代碼

return stream;

}

輸入流:

istream &操縱符名 (istream &stream)

{

自定義代碼

return stream;

}

文件流用來處理外存文件,根據文件中數據的組織形式,文件可分為兩類:文本文件和二進制文件。文本文件又稱ASCii文件,一個字節存放一個ASCII代碼代表一個字符,二進制文件則是內存中的存儲形式原樣寫到外存中形成的文件,比如整數100,在文本文件中是以‘1’、‘0’、‘0’三個字符的ASCII的代碼存放的,占3個字節,而在二進制文件中就是100的二進制形式01100100存放的,占一個字節。

C++進行文件操作的一般步驟:1、為要進行操作的文件建立一個文件流對象,2、打開文件,如果不存在就創建該文件,3、進行讀寫操作,4、關閉文件。用到的類ifsteam(用於文件輸入)、ofsteam(文件輸出)、fsteam(文件輸入輸出)。

成員函數:

文件流對象.open(文件名,使用方式):以特定方式打開文件

文件流對象.open():關閉文件

文件流對象<<數據:寫入文件

文件流對象>>變量:讀取數據

文件流對象.read(char  *buf,int len):讀取len個字符到buf數組

文件流對象.write(const char  *buf,int len):將buf數組中len個字符寫入文件。

文件流對象.eof():檢測是否到達文件尾。

隨機讀寫函數:看書籍。

命名空間是由程序設計者命名的一個內存區域,用來處理程序中同名沖突問題。定義格式如下:

Namespace 空間名

{

代碼

}

C語言中沒有命名空間,如果C++使用的帶擴展名.h的頭文件,不必使用命名空間。如果C++使用的不帶擴展名.h的頭文件就要指定頭文件所在的命名空間。

程序中的常見錯誤分為:編譯時的錯誤和運行時的錯誤。編譯時的錯誤主要是語法錯誤,運行過程的錯誤統稱為異常,對異常的處理就是異常處理。

C++中處理異常的辦法是:如果執行一個函數出現異常,可以不在本函數處理而是甩給上一級也就是函數的調用者,一直可以逐級上傳一直到最高級,如果最高級也無法處理,系統會調用系統函數terminate(),有它調用abort終止程序。

C++異常處理機制有檢查、拋出和捕獲三個部分:try(檢查)、catch(捕獲)、throw(拋出)

格式如下:

Throw 表達式:

Int  i;

Throw  i;因為i是整型變量,所以throw拋出的是整型異常;

Throw拋出的異常會由catch捕獲。

Try

{

被檢查的語句

}

Catch(異常類型聲明1)

{

進行異常處理的語句1

}

Catch(異常類型聲明2)

{

進行異常處理的語句2

}

……

Try和catch必須配套使用。

本書主要內容就是這些。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM