站在有編程經驗的視角,快速全面瞭解C++

目 錄
第一章 認識C++的對象 3
1.1 初識C++的函數和對象 3
1.2 認識C++語言面向過程編程的特色 4
1.3 程序的編輯、編譯和運行的基本概念 5
第二章 從結構到類的演變 6
2.1 結構的演化 6
2.2 從結構演變一個簡單的類 6
2.3 面向過程與面向對象 6
2.4 C++面向對象程序設計的特色 6
2.5 使用類和對象 7
2.6 string對象數組與泛型算法 7
第3章 函數和函數模板 7
3.1 函數的參數及其傳遞方式 7
3.2 深刻討論函數返回值 8
3.3 內聯函數 9
3.4 函數重載和默認參數 9
3.5 函數模板 9
第4章 類和對象 10
4.1 類及其實例化 10
4.2 構造函數 11
4.3 析構函數 12
4.4 調用複製構造函數的綜合實例 13
4.5 成員函數重載及默認參數 13
4.6 this指針 13
4.7 一個類的對象做爲另外一個類的成員 13
4.8 類和對象的性質 13
4.9 面向對象的標記圖 14
4.10 面向對象編程的文件規範 15
第五章 特殊函數和成員 16
5.1 對象成員的初始化 16
5.2 靜態成員 17
5.3 友元函數 17
5.4 const對象 18
5.5 數組和類 19
5.6 指向類成員函數的指針 19
5.7 求解一元二次方程 20
第六章 繼承和派生 20
6.1 繼承和派生的基本概念 20
6.2 單一繼承 20
6.3 多重繼承 22
6.4 二義性及其支配規則 22
第七章 類模板與向量 22
7.1 類模板 22
7.2 向量與泛型算法 23
7.3 出圈遊戲 25
第八章 多態性和虛函數 25
8.1 多態性 25
8.2 虛函數 25
8.3 多重繼承與虛函數 27
8.4 類成員函數的指針與多態性 27
第9章 運算符重載及流類庫 27
9.1 運算符重載 27
9.2 流類庫 28
9.3 文件流 31
第10章 面向對象設計實例 32
10.1 過程抽象和數據抽象 32
10.2 發現對象並創建對象層 33
10.3 定義數據成員和成員函數 33
10.4 如何發現基類和派生類結構 34
10.5 接口繼承與實現繼承 34
10.6 設計實例 35ios

第一章認識C++的對象c++

//c++ hello world 示例
#include <iostream>
using namespace std;
int main()
{
    cout << "Hello, World!" << endl;
    return 0;
}

1.1 初識C++的函數和對象
通常稱現實世界中客觀存在的事物爲對象。
1.混合型語言
C++程序以.cpp做爲文件擴展名,而且必須有一個且只能有一個名爲mian(不是C++的關鍵字)的主函數。真正的面向對象的語言沒有主函數,C++保留了這個面向過程的主函數,因此稱之爲混合型語言。
2.靈活的註釋方式程序員

/* c++註釋方式 */
//我是註釋
#if 0
    我是註釋內容,但考試不會考到:)
#endif

3.使用輸出和輸入對象
C++將數據從一個對象流向另外一個對象的流動的抽象稱爲「流」。從流中獲取數據的操做稱爲提取操做。向流中添加數據的操做稱爲插入操做。
cin用來處理標準輸入,即鍵盤輸入。cin通常和流提取運算符>>結合使用。
cout用來處理標準輸出,即屏幕輸出。cout通常和流插入運算符<<結合使用。算法

char something[50];
char something2[50];
cin >> something;
cout << "你輸入的是:" << something << endl;
//endl 用於在行末添加一個換行符

cin >> something >> something2;
/* 至關於
cin >> something;
cin >> something2;
*/
cout << "你輸入的第一件事:" << something << endl;
cout << "你輸入的第二件事:" << something2 << endl;

4.使用命名空間
所謂命名空間(namespace)是一種將程序庫名稱封裝起來的方法,它提升了程序的性能和可靠性。
C++新標準就是將標準類庫中的頭文件與通常的頭文件(須要使用擴展名「.h」)區分開來。固然,也能夠本身定義符合標準庫的頭文件,使用這種頭文件時,也須要同時使用命名空間語句。
若是仍然使用C庫中的頭文件,則須要使用擴展名「.h」形式,例如<math.h>和<stdio.h>。若是使用C++提供的頭文件,則不須要使用擴展名「.h」,例如,<string>。注意C++庫中替代C庫中的頭文件的正確名稱,例如可使用<cmath>替代<math.h>。編程

5.對象的定義及初始化
定義對象包括爲它命名並賦予它數據類型。數組

int num;
char something[50];
int main()
{
    // 一些代碼;
    return 0;
}

6.函數原型及其返回值
函數都須要有類型說明。int main() 指出main是整數類型,返回值由return後面的表達式決定,且表達式的值必須與聲明函數的類型一致。
C++使用變量的基本規則是:必須先聲明,後使用,對函數調用也是如此。
7.const修飾符和預處理程序
C語言通常使用"#define"定義常量,在C++中,建議使用const代替宏定義。const是放在語句定義以前的,所以能夠進行類型判別。
用關鍵字const修飾的標識符是一類特殊的常量,稱爲符號常量,或const變量。使用const容許編譯器對其進行類型檢查並可改善程序的可讀性。
C++語言可使用宏定義。無參數的宏做爲常量,而參數的宏則能夠提供比函數更高的效率。但預處理只是進行簡單的文本代替而不進行語法檢查,因此會存在一些問題。
由於被const修飾的變量的值在程序中不能被改變,因此在聲明符號常量是,必須對符號常量進行初始化,除非這個變量使用extern修飾的外部變量。安全

#include <iostream>
using namespace std;

//不管是define仍是const定義的常量名稱,使用大寫對與普通標識符作區分是很好的編程實踐 
#define LENGTH 10   
//注意const定義常量時的不一樣寫法,包括類型、等號、語句後的分號等。
const int WIDTH = 5; 
#define NEWLINE '\n'
 
int main()
{
 
   int area;  
   
   area = LENGTH * WIDTH;
   cout << area;
   cout << NEWLINE;
   return 0;
}

C++語言預處理程序不是C++編譯程序的一部分,它負責分析處理幾種特殊的語句,這些語句被稱爲預處理語句。顧名思義,預處理程序對這幾種特殊語句的分析處理是在編譯程序的其餘部分以前進行的。爲了與通常的C++程序語句相區別,全部預處理語句都以位於行首的符號「#」開始。
預處理語句有3種,分別是宏定義、文件包含和條件編譯。
預處理程序把全部出現的、被定義的名字所有替換成對應的「字符序列」。#define中的名字與C++中的標識符有相同的形式,爲了區別,每每用大寫字母來表示(標識符用小寫字母),這也適合const語句。
文件引用使用雙引號仍是尖括號,其含義並不同。採用尖括號引用系統提供的包含文件,C++編譯系統將首先在C++系統設定的目錄中尋找包含文件,若是沒有找到,就到指定的目錄中去尋找。採用雙引號引用本身定義的包含文件(通常都放在本身定義的指定目錄中),這將通知C++編譯器在用戶當前的目錄下或指定的目錄下尋找包含文件。指定的目錄沒必要在同一個邏輯盤中。數據結構

//標準規定,包含C++提供的標準頭文件或系統頭文件時應使用尖括號,包含自定義頭文件時可以使用雙引號。
#include <system_lib>
#inclue "my_lib"

8.程序運行結果
9.程序書寫格式
1.2 認識C++語言面向過程編程的特色
C++語言的標準模板庫(Standard Templete Library,STL)提供了與要操做的元素類型無關的算法,不只使許多複雜問題迎刃而解,並且也將許多面向對象的程序設計問題轉化成基於對象的面向過程編程。
1.2.1 使用函數重載
C++容許爲一個函數定義幾個版本,從而使一個函數名具備多種功能,這稱爲函數重載。
1.2.2 新的基本數據類型及其注意事項
void是無類型標識符,只能聲明函數的返回值類型,不能聲明變量。C++語言還比C語言多了bool(布爾)型。C++標準只限定int和short至少要有16位,而long至少32位,short不得長於int,int不得長於long。
地址運算符「&」用來取對象存儲的首地址。
C++語言中的整數常量有4種類型:十進制常量、長整型常量、八進制常量和十六進制常量,並用前綴和後綴進行分類標識。app

//c++中整數常量
85         // 十進制
0213       // 八進制 
0x4b       // 十六進制 
30         // 整數 
30u        // 無符號整數 
30l        // 長整數 
30ul       // 無符號長整數

1.2.3 動態分配內存
在使用指針時,若是不使用對象地址初始化指針,能夠本身給它分配地址。對於值存儲一個基本類型數據的指針,申請的方式以下:
new 類型名[size] //申請能夠存儲size個該數據類型的對象
再也不使用時,簡單地使用「delete指針名」便可釋放已經申請的存儲空間。
示例:框架

#include <iostream>
using namespace std;
 
int main ()
{
   double* pvalue  = NULL; // 初始化爲 null 的指針
   pvalue  = new double;   // 爲變量請求內存
 
   *pvalue = 29494.99;     // 在分配的地址存儲值
   cout << "Value of pvalue : " << *pvalue << endl;
 
   delete pvalue;         // 釋放內存
 
   return 0;
}

1.2.4 引用
別名的地址就是原來對象的地址,選定命名時使用「引用」運算符「&」,再選用數據類型與之配合。引用的聲明方式以下:
數據類型& 別名=對象名;
所謂「引用」就是將一個新標識符和一塊已經存在的存儲區域相關聯。所以,使用引用時沒有分配新的存儲區域,它自己並非新的數據類型。
引用一般用於函數的參數表中或者做爲函數的返回值。對引用實質性的理解應抓住以下兩點:
(1)引用實際上就是變量的別名,使用引用就如同直接使用變量同樣。引用與變量名在使用的形式上是徹底同樣的,引用只是做爲一種標識對象的手段,不能直接聲明對數組的引用,也不能聲明引用的引用。
(2)引用的做用與指針有類似之處,它會對內存地址上存在的變量進行修改,但它不佔用新的地址,從而節省開銷。

#include <iostream>
 
using namespace std;
 
int main ()
{
   // 聲明簡單的變量
   int    i;
   double d;
 
   // 聲明引用變量
   int&    r = i;
   double& s = d;
   
   i = 5;
   cout << "Value of i : " << i << endl;
   cout << "Value of i reference : " << r  << endl;
 
   d = 11.7;
   cout << "Value of d : " << d << endl;
   cout << "Value of d reference : " << s  << endl;
   
   return 0;
}

1.2.5 對指針使用const限定符
能夠用const限定符強制改變訪問權限。
1.左值和右值
左值是指某個對象的表達式。
2.指向常量的指針
指向常量的指針是在很是量指針聲明前面使用const,例如:
const int *p;
它告訴編譯器,「p」是常量,不能將「p」做爲左值進行操做,即限定了「*p=」的操做,因此稱爲指向常量的指針。
3.常量指針
把const限定符放在*號的右邊,是使指針自己稱爲一個const指針。
int x=5;
int * const p=&x;
不能改變p的指向,但能夠經過間接引用運算符「」改變其值,例如語句「p=56;」將上面的x的值改變爲56。
4.指向常量的常量指針
也能夠聲明指針和指向的對象都不能改動的「指向常量的常量指針」,這時必需要初始化指針。例如:
int x=2;
const int * const p=&x;
告訴編譯時,*p和p都是常量,都不能做爲左值。
1.2.6 泛型算法應用於普通數組
所謂泛型算法,就是提供的操做與元素的類型無關。
1.2.7 數據的簡單輸入輸出格式
C++提供了兩種格式控制方式:一種是使用ios _base類提供的接口;另外一種是使用一種稱爲操控符的特殊函數,它的特色是可直接包含在輸出和輸入的表達式中,所以更爲方便,不帶形式參數的操控符定義在頭文件<iostream>中,帶形式參數的操控符定義在頭文件<iomanip>中。使用它們時,一是要正確包含它們,二是隻有與符號「<<」或「>>」鏈接時才起做用,三是無參數的操控符函數不能帶有「()」號。

#include <iomanip>
#include <iostream>
using namespace std;

int main ()
{
    //iomanip對輸入輸出流添加更好的控制
    cout << setw(5) << 255 << endl;
    cout << setw( 3 ) << 1 << setw( 3 ) << 10 << setw( 3 ) << 100 << endl; 
}

/*輸出結果:
*  255
*  1 10100
*/

1.3 程序的編輯、編譯和運行的基本概念
用C++語言寫成的程序稱爲源程序,源程序必須通過C++編譯程序翻譯成機器語言才能執行。要獲得一個用C++語言設計的、名爲myapp.exe的可執行文件,其過程可分爲以下幾步:
(1)先使用編輯器編輯一個C++程序mycpp.cpp,又稱其爲C++的源程序。
(2)而後使用C++編譯器對這個C++程序進行編譯,產生文件mycpp.obj
(3)再使用鏈接程序(又稱Link),將mycpp.obj變成mycpp.exe
所謂集成環境,就是將C++語言編輯、編譯、鏈接和運行程序都集成到一個綜合環境中。
第二章 從結構到類的演變
2.1 結構的演化
類是從結構演變而來,開始稱爲「帶類的C」。這種演變就是從讓結構含有函數開始的。
2.1.1 結構發生質的演變
1.函數與數據共享
2.封裝性
2.1.2 使用構造函數初始化結構對象
2.2 從結構演變一個簡單的類
2.3 面向過程與面向對象
所謂「面向過程」,就是沒必要了解計算機的內部邏輯,而把精力主要集中在對如何求解問題的算法和過程的描述上,經過編寫程序把解決問題的步驟告訴計算機。
所謂函數,就是模塊的基本單位,是對處理問題的一種抽象。
結構化程序設計使用的是功能抽象,面向對象程序設計不只能進行功能抽象,並且能進行數據抽象。「對象」其實是功能抽象和數據抽象的統一。(有沒有感受有點是在學馬克思,類和對象的抽象其實是通常和特殊的關係,在馬克思裏面也有相似的概念。計算機的發展實際上也是借鑑了不少當代的先進理論。)
面向對象的程序設計方法不是以函數過程和數據結構爲中心,而是以對象表明求解問題的中心環節。它追求的是現實問題空間與軟件系統解空間的近似和直接模擬(或者說軟件世界對現實世界的抽象)。
軟件開發是對給定問題求解的過程。從認識論的角度看,能夠歸爲兩項主要的活動:認識與描述。
軟件開發者將被開發的整個業務範圍稱做「問題域」(problem domain),「認識」就是在所要處理的問題域範圍內,經過人的思惟,對該問題域客觀存在的事物以及對所要解決的問題產生正確的認識和理解,包括弄清事物的屬性,行爲及其彼此之間的關係並找出解決問題的方法。
「描述」是指用一種語言把人們對問題域中事物的認識、對問題及其解決方法的認識描述出來。最終的描述必須使用一種可以被機器讀得懂的語言,即編程語言。
2.4 C++面向對象程序設計的特色
和傳統的程序設計方法相比,面向對象的程序設計具備抽象、封裝、繼承和多態性等關鍵要素。
2.4.1 對象
C++可以使用對象名、屬性和操做三要素來描述對象。
2.4.2 抽象和類
抽象是一種從通常的觀點看待事物的方法,即集中於事物的本質特徵,而不是具體細節或具體實現。
類的概念來自於人們認識天然、認識社會的過程。在這一過程當中,人們主要使用由特殊到通常的概括法和由通常到特殊的演繹法。在概括的過程當中,是從一個個具體的事物中把共同的特徵抽取出來,造成一個通常的概念,這就是「歸類」;在演繹的過程當中,把同類事物,根據不一樣的特徵分紅不一樣的小類,這就是「分類」。對於一個具體的類,它有許多具體的個體,這些個體叫作「對象」。
類的做用是定義對象。
所謂「一個類的全部對象具備相同的屬性」,是指屬性的個數、名稱、數據類型相同,各個對象的屬性值則能夠互不相同,而且隨着程序的執行而變化。
2.4.3 封裝
將類封裝起來,也是爲了保護類的安全。所謂安全,就是限制使用類的屬性和操做。
對象內部數據結構這種不可訪問性稱爲信息(數據)隱藏。
封裝就是把對象的屬性和操做結合成一個獨立的系統單位,並儘量隱蔽對象的內部細節。
在類中,封裝是經過存取權限實現的。
2.4.4 繼承
繼承是一個類能夠得到另外一個類的特性的機制,繼承支持層次概念。
經過繼承,低層的類只須定義特定於它的特徵,而共享高層的類中的特徵。
2.4.5 多態性
不一樣的對象能夠調用相同名稱的函數,但可致使徹底不一樣的行爲的現象稱爲多態性。
2.5 使用類和對象
2.5.1 使用string對象
所謂方法,書就是供這類對象使用的成員函數。對象使用本身的成員函數的方法是經過「.」運算符,格式以下:
對象名.成員函數
2.5.2 使用string類的典型成員函數實例
2.5.3 使用complex對象
C++標準程序庫提供complex類定義複數對象。
2.5.4 使用對象小結
注意:類是抽象出一類物質的共同特徵,模板則是概括出不一樣類型事物的共同操做。
2.6 string對象數組與泛型算法
第3章 函數和函數模板
3.1 函數的參數及其傳遞方式
C語言函數參數的傳遞方式只有傳值一種,傳值又分爲傳變量值和傳變量地址值兩種狀況。比較複雜的是結構變量,結構變量的值是指結構域中全部的變量值。在C++中,像int、float、double、char、bool等簡單數據類型的變量,也是對象。類對象通常都包括數據成員和成員函數,若是在C++沿用C語言的說法,則對象的值就是對象全部數據成員的值,約定參數傳遞中傳遞「對象值」是指對象的數據成員值,傳遞「對象地址值」是指對象的首地址值。C++函數還可使用傳遞對象的「引用」方式,即它的函數參數有兩種傳遞方式:傳值和傳引用。傳引用其實就是傳對象的地址,全部也稱傳地址方式。
參數傳遞中不要混淆傳地址值和傳地址的區別。傳地址值傳遞的是值,不是地址;傳地址傳的是地址不是地址值。傳遞對象地址值是使用對象指針作爲參數;傳遞地址時使用對象引用做爲參數。
3.1.1 對象做爲函數參數
將對象做爲函數參數,是將實參對象的值傳遞給形參對象,這種傳遞是單向的。形參擁有實參的備份,當在函數中改變形參的值時,改變的是這個備份中的值,不會影響原來實參的值。
3.1.2 對象指針做爲函數參數
將指向對象的指針做爲函數參數,形參是對象指針(指針能夠指向對象的地址),實參是對象的地址值。
注意:不要非要在主程序裏產生指針,而後再用指針做爲參數。函數原型參數的類型是指針,能夠指針讓它指向對象地址,即:
string *s1=&str1;
是徹底正確的,使用&str1標識的是取對象str1的地址值,因此&str1直接做爲參數便可。
3.1.3 引用做爲函數參數
可使用「引用」做爲函數的參數(引用形參)。這時函數並無對形參對象初始化,即沒有指定形參對象是哪一個對象的別名。在函數調用是,實參對象名傳給形參對象名,形參對象名就成爲實參對象名的別名。實參對象和形參對象表明同一個對象,因此改變形參對象的值就是改變實參對象的值。
實際上,在虛實結合時是把實參對象的地址傳給形參對象,使形參對象的地址取實參對象的地址,從而使形參對象和實參對象共享同一個單元。這就是地址傳遞方式。
經過使用引用參數,一個函數能夠修改另一個函數內的變量。由於引用對象不是一個獨立的對象,不單獨佔用內存單元,而對象指針要另外開闢內存單元(其內容是地址),因此傳引用比傳指針更好。
注意:雖然系統向形參傳遞的是實參的地址而不是實參的值,但實參必須使用對象名。
3.14 默認參數
默認參數就是不要求程序員設定該參數,而由編譯器在須要時給該參數賦默認值。當程序員須要傳遞特殊值時,必須顯式地指明。默認參數是在函數原型中說明的,默認參數能夠多於1個,但必須放在參數序列的後部。
若是一個默認參數須要指明一個特定值,則在其以前全部的參數都必須賦值。
3.1.5 使用const保護數據
用const修飾傳遞參數,意思是通知函數,它只能使用參數而無權修改它。這主要是爲了提升系統的自身安全。C++中廣泛採用這種方法。
3.2 深刻討論函數返回值
C++函數的返回值類型能夠是除數組之外的任何類型。非void類型的函數必須向調用者返回一個值。數組只能返回地址。當函數返回值是指針或引用對象時,須要特別注意:函數返回所指的對象必須繼續存在,所以不能將函數內部的局部對象做爲函數的返回值。
3.2.1 返回引用的函數
函數能夠返回一個引用,將函數說明爲返回一個引用的主要目的是爲了將該函數用在賦值運算符的左邊。函數原型的表示方法以下:
數據類型 &函數名(參數列表);
3.2.2 返回指針的函數
函數的返回值能夠是存儲某種類型數據的內存地址,稱這種函數爲指針函數。它們的通常定義形式以下:
類型標識符 *函數名(參數列表);
在C++中,除了內存分配失敗以外,new不會返回空指針,而且沒有任何對象的地址爲零。指針所指向的對象的生存期不該低於該指針的生存期。
3.2.3 返回對象的函數
3.2.4 函數返回值做爲函數的參數
若是用函數返回值做爲另外一個函數的參數,這個返回值必須與參數類型一致。
3.3 內聯函數
使用關鍵字inline說明的函數稱內聯函數。在C++中,除具備循環語句、switch語句的函數不能說明爲內聯函數外,其餘函數均可以說明爲內聯函數。使用內聯函數能加快程序執行速度,但若是函數體語句多,則會增長程序代碼的大小。使用小的內聯函數在代碼速度和大小上能夠取得折衷,其餘狀況下取決於程序員是追求代碼速度,仍是追求代碼的規模。
因爲編譯器必須知道內聯函數的函數體,才能進行內聯替換,所以,內聯函數必須在程序中第一次調用此函數的語句出現以前定義。
3.4 函數重載和默認參數
函數重載可使一個函數名具備多種功能,即具備「多種形態」,這種特性稱爲多態性。
C++的多態性又被直觀地稱爲「一個名字,多個函數」。源代碼只指明函數調用,而不說明具體調用哪一個函數。編譯器的這種鏈接方式稱爲動態聯編或遲後聯編。在動態聯編中,直到程序運行才能肯定調用哪一個函數(動態聯編須要虛函數的支持)。若是編譯器在編譯時,能根據源代碼調用固定的函數標識符,並用物理地址代替它們,這就稱爲靜態聯編或先期聯編。靜態聯編是在程序被編譯時進行的。
使用默認參數,就不能對少於參數個數的函數進行重載。另外,僅有函數返回值不一樣也是區分不了重載函數的。當使用默認參數設計類的構造函數時,要特別注意這一問題。
3.5 函數模板
1.引入函數模版
因爲函數在設計時沒有使用實際的類型,而是使用虛擬的類型參數,故其靈活性獲得增強。當用實際的類型來實例化這種函數時,就好像按照模版來製造新的函數同樣,因此稱這種函數爲函數模板。將函數模版與某個具體數據類型連用,就產生了模板函數,又稱這個過程爲函數模板實例化,這種形式就是類型參數化。
2.函數模板的參數
對於一個默認調用,能從函數參數推斷出模板參數的能力是其中最關鍵的一環。要想省去顯式調用的麻煩,條件是由這個調用的函數參數表可以唯一地標識出模板參數的一個集合。
3.使用顯式規則和關鍵字typename
C++專門定義一個僅僅用在模板中的關鍵字typename,它的用途之一是代替template參數列表中的關鍵字class。

template<typename T>
int compare(const T& left, const T& right) {
    if (left < right) {
        return -1; 
    }
    if (right < left) {
        return 1; 
    }
    return 0;
}

compare<int>(1, 2); //使用模板函數

第4章 類和對象
4.1 類及其實例化
對象就是一類物體的實例,將一組對象的共同特徵抽象出來,從而造成「類」的概念。
4.1.1 定義類
像C語言構造結構同樣,類也是一種用戶本身構造的數據類型並遵循C++的規定。
類要先聲明後使用,無論聲明內容是否相同,聲明同一個名字的兩個類是錯誤的,類是具備唯一標識符的實體;在類中聲明的任何成員不能使用extern、auto和register關鍵字進行修飾;類中聲明的變量屬於該類,在某些狀況下,變量也能夠被該類的不一樣實例所共享。
類和其餘數據類型不一樣的是,組成這種類型的不只能夠有數據,並且能夠有對數據進行操做的函數,他們分別叫作類的數據成員和類的成員函數,並且不能在類聲明中對數據成員使用表達式進行初始化。
1.類聲明
類聲明以關鍵字class開始,其後跟類名。類所聲明的內容用花括號括起來,右花括號後的分號做爲類關鍵字聲明語句的結束標誌。這一對花括號之間的內容稱爲類體;
訪問權限用於控制對象的某個成員在程序中的可訪問性,若是沒有使用關鍵字,則全部成員默認聲明爲private權限。
2.定義成員函數
類中聲明的成員函數用來對數據成員進行操做,還必須在程序中實現這些成員函數。
定義成員函數的通常形式以下:
返回類型 類名::成員函數名(參數列表)
{
成員函數的函數體//內部實現
}
其中「::」是做用域運算符,「類名」是成員函數所屬類的名字,「::」用於表名其後的成員函數是屬於這個特定的類。換言之,「類名::成員函數名」的意思就是對屬於「類名」的成員函數進行定義,而「返回類型」則是這個成員函數返回值的類型。
也可使用關鍵字inline將成員函數定義爲內聯函數。
若是在聲明類的同時,在類體內給出成員函數的定義,則默認爲內聯函數。
3.數據成員的賦值
不能在類體內給數據成員賦值。在類體外就更不容許了。
數據成員的具體值是用來描述對象的屬性的。只有產生了一個具體的對象,這些數據值纔有意義。若是在產生對象時就使對象的數據成員具備指定值,則稱爲對象的初始化。
4.1.2 使用類的對象
對象和引用都使用運算符「.」訪問對象的成員,指針則使用「- >」運算符。
暫不涉及尚未介紹的保護成員,能夠概括出以下規律:
(1)類的成員函數能夠直接使用本身類的私有成員(數據成員和成員函數)
(2)類外面的函數不能直接訪問類的私有成員(數據成員和成員函數)
(3)類外面的函數只能經過類的對象使用該類的公有成員函數。
在程序運行時,經過爲對象分配內存來建立對象。在建立對象時,使用類做爲樣板,故稱對象爲類的實例。
定義類對象指針的語法以下:
類名* 對象指針名;
對象指針名=對象的地址;
也能夠直接進行初始化。
類名* 對象指針名=對象的地址;
類對象的指針能夠經過「->」運算符訪問對象的成員,即:
對象指針名->對象成員名
4.1.3 數據封裝
面向對象的程序設計是經過爲數據和代碼創建分塊的內存區域,以便提供對程序進行模塊化的一種程序設計方法,這些模塊能夠被用作樣板,在須要時在創建其副本。根據這個定義,對象是計算機內存中的一塊區域,經過將內存分塊,每一個模塊(即對象)在功能上保持相對獨立。
對象被視爲能作出動做的實體,對象使用這些動做完成相互之間的做用。換句話說,對象就像在宿主計算機上擁有數據和代碼並能相互通訊的具備特定功能的一臺較小的計算機。
4.2 構造函數
C++有稱爲構造函數的特殊成員函數,它可自動進行對象的初始化。
初始化和賦值是不一樣的操做,當C++語言預先定義的初始化和賦值操做不能知足程序的要求時,程序員能夠定義本身的初始化和賦值操做。
4.2.1 默認構造函數
當沒有爲一個類定義任何構造函數的狀況下,C++編譯器總要自動創建一個不帶參數的構造函數。
4.2.2 定義構造函數
1.構造函數的定義和使用方法
構造函數的名字應與類名同名。並在定義構造函數時不能指定返回類型,即便void類型也不能夠。
當聲明一個外部對象時,外部對象只是引用在其餘地方聲明的對象,程序並不爲外部對象說明調用構造函數。若是是全局對象,在main函數執行以前要調用它們的構造函數。
2.自動調用構造函數
程序員不能在程序中顯式地調用構造函數,構造函數是自動調用的。例如構造一個Point類的對象a,不能寫成「Point a.Point(x,y) ;」,只能寫成「Point a(x,y) ;」。編譯系統會自動調用Point(x,y)產生對象a並使用x和y將其正確地初始化。
能夠設計多個構造函數,編譯系統根據對象產生的方法調用相應的構造函數,構造函數是在產生對象的同時初始化對象的。
4.2.3 構造函數和預算符new
運算符new用於創建生存期可控的對象,new返回這個對象的指針。因爲類名被視爲一個類型名,所以,使用new創建動態對象的語法和創建動態變量的語法相似,其不一樣點是new和構造函數一塊兒使用。
使用new創建的動態對象只能用delete刪除,以便釋放所佔空間。應養成及時釋放再也不使用的內存空間的習慣。
4.2.4 構造函數的默認參數
若是程序定義本身的有參數構造函數,又想使用無參數形式的構造函數,解決的方法時間相應的構造函數所有使用默認的參數設計。
4.2.5 複製構造函數
引用在類中一個很重要的用途是用在複製構造函數中。一是一類特殊並且重要的函數,一般用於使用已有的對象來創建一個新對象。
在一般狀況下,編譯器創建一個默認複製構造函數,默認複製構造函數採用拷貝方式使用已有的對象來創建新對象,因此又直譯爲拷貝構造函數。程序員能夠本身定義複製構造函數,對類A而言,複製構造函數的原型以下:
A::A(A&)
從這個函數原型來看,首先它是一個構造函數,由於這畢竟是在創造一個新對象。其次,他的參數有些特別,是引用類本身的對象,即用一個已有的對象來創建新對象。使用引用是從程序的執行效率角度考慮的。爲了避免改變原有對象,更普通的形式是像下面這樣使用const限定:
A::A(const A &)
像調用構造函數同樣,若是自定義了複製構造函數,編譯器只調用程序員爲它設計的賦值構造函數。
在C++中,在一個類中定義的成員函數能夠訪問該類任何對象的私有成員。
4.3 析構函數
在對象消失時,應使用析構函數釋放由構造函數分配的內存。構造函數、賦值構造函數和析構函數是構造型成員函數的基本成員,應深入理解他們的做用並熟練掌握其設計方法。
4.3.1 定義析構函數
由於調用析構函數也是由編譯器來完成的,因此編譯器必須總能知道應調用哪一個函數。最容易、也最符合邏輯的方法是指定這個函數的名稱與類名同樣。爲了與析構函數區分,在析構函數的前面加上一個「~」號(仍然稱析構函數與類同名)。在定義析構函數時,不能指定任何返回類型,即便指定void類型返回類型也不行。析構函數也不能指定參數,可是能夠顯示地說明參數爲void,即形如A::~A(void)。從函數重載的角度分析,一個類也只能定義一個析構函數且不能指明參數,以便編譯系統自動調用。
析構函數在對象的生存期結束時被自動調用。當對象的生存期結束時,程序爲這個對象調用析構函數,而後回收這個對象佔用的內存。全局對象和靜態對象的析構函數在程序運行結束以前調用。
類的對象數組的每一個元素調用一次析構函數。全局對象數組的析構函數在程序結束以前被調用。
4.3.2 析構函數與運算符delete
運算符delete與析構函數一塊兒工做。當使用運算符delete刪除一個動態對象時,他首先爲這個動態對象調用析構函數,而後再釋放這個動態對象佔用的內存,這和使用new創建動態對象的過程正好相反。
當使用delete釋放動態對象數組時,必須告訴這個動態對象數組有幾個元素對象,C++使用「[ ]」來實現。即語句
delete[ ] ptr ; //注意不要寫錯爲delete ptr[]
當程序前後建立幾個對象時,系統按後建先析構的原則析構對象。當使用delete調用析構函數時,則按delete的順序析構。
4.3.3 默認析構函數
若是在定義類時沒有定義析構函數,C++編譯器也爲它產生一個函數體爲空的默認析構函數。
4.4 調用複製構造函數的綜合實例
4.5 成員函數重載及默認參數
4.6 this指針
使用this指針,保證了每一個對象能夠擁有本身的數據成員,但處理這些數據成員的代碼能夠被全部的對象共享。
C++規定,當一個成員函數被調用是,系統自動向它傳遞一個隱含的參數,該參數是一個指向調用該函數的對象的指針,從而使成員函數知道該對哪一個對象進行操做。在程序中,可使用關鍵字this來引用該指針。this指針是C++實現封裝的一種機制,它將對象和該對象調用的成員函數鏈接在一塊兒,在外部看來,每個對象都擁有本身的成員函數。
除非有特殊須要,通常狀況下都省略符號「this ->」,而讓系統進行默認設置。
4.7 一個類的對象做爲另外一個類的成員
4.8 類和對象的性質
4.8.1 對象的性質
(1)同一個類的對象之間能夠相互賦值。
(2)可以使用對象數組。
(3)可以使用指向對象的指針,使用取地址運算符&將一個對象的地址置於該指針中。
注意,指向對象的指針的算術運算規則與C語言的同樣,但指向對象的指針不能取數據成員的地址,也不能去成員函數的地址。
(4)對象能夠用做函數參數。
(5)對象做爲函數參數時,可使用對象、對象引用和對象指針。
(6)一個對象能夠作爲另外一個類的成員。
4.8.2 類的性質
1.使用類的權限
爲了簡單具體,討論數據成員爲私有,成員函數爲公有的狀況。
(1)類自己的成員函數可使用類的全部成員(私有和公有成員)。
(2)類的對象只能訪問公有成員函數。
(3)其餘函數不能使用類的私有成員,也不能使用公有成員函數,它們只能經過類的對象使用類的公有成員函數。
(4)雖然一個能夠包含另一個類的對象,但這個類也只能經過被包含類的對象使用那個類的成員函數,經過成員函數使用數據成員。
2.不徹底的類聲明
類不是內存中的物理實體,只有當使用類產生對象時,才進行內存分配,這種對象創建的過程稱爲實例化。
應當注意的是:類必須在其成員使用以前先進行聲明。
class MembersOnly; //不徹底的類聲明
MenbersOnly *club; //定義一個全局變量類指針
第一條語句稱爲不徹底類聲明,它用於在類沒有徹底定義以前就引用該類的狀況。
不徹底聲明的類不能實例化,不然會出現編譯錯誤;不徹底聲明僅用於類和結構,企圖存取沒有徹底聲明的類成員,也會引發編譯錯誤。
3.空類
儘管類的目的是封裝代碼和數據,它也能夠不包括任何聲明。
class Empty{};
4.類做用域
聲明類時所使用的一對花括號造成所謂的類的做用域。在類做用域中聲明的標識符只在類中可見。
若是該成員函數的實現是在類定義以外給出的,則類做用域也包含類中成員函數的做用域。
類中的一個成員名可使用類名和做用域運算符來顯式地指定,這稱爲成員名限定。
4.9 面向對象的標記圖
4.9.1 類和對象的UML標記圖
4.9.2 對象的結構與鏈接
只有定義和描述了對象之間的關係,各個對象才能構成一個總體的、有機的系統模型,這就是對象的結構和連結關係。對象結構是指對象之間的分類(繼承)關係和組成(聚合)關係,統稱爲關聯關係。對象之間的靜態關係是經過對象屬性之間的鏈接反映的,稱爲實例鏈接。對象行爲之間的動態關係是經過對象行爲(信息)之間的依賴關係表現的,稱之爲消息鏈接,實例鏈接和消息鏈接統稱爲鏈接。
1.分類關係及其表示
C++中的分類結構是繼承(基類/派生類)結構,UML使用一個空三角形表示繼承關係,三角形指向基類。
2.對象組成關係及其表示
組成關係說明的結構是總體與部分關係。C++中最簡單的是包含關係。C++語言中的「聚合」隱含了兩種實現方式,第一種方式是獨立地定義,能夠屬於多個總體對象,並具備不一樣生存期。這種所屬關係是能夠動態變化的,稱之爲彙集。使用空心菱形表示它們之間的關係。第二種方式是用一個類的對象做爲一種廣義的數據類型來定義總體對象的一個屬性,構成一個嵌套對象。在這種狀況下,這個類的對象只能隸屬於唯一的總體對象並與它同生同滅,稱這種狀況爲「組合」,它們之間的關聯關係比第一種強,具備管理組成部分的責任,使用實心菱形表示。
3.實例鏈接及其表示
實例鏈接反映對象之間的靜態關係,例如車和駕駛員的關係,這種雙邊關係在實現中能夠經過對象(實例)的屬性表達出來。實例鏈接有一對1、一對多、多對多3種鏈接方式。
4.消息鏈接及其表示
消息鏈接描述對象之間的動態關係。即若一個對象在執行本身的操做時,須要經過消息請求另外一個對象爲它完成某種服務,則說第一個對象與第二個對象之間存在着消息鏈接。消息鏈接是有方向的,使用一條帶箭頭的實線表示,從消息的發送者指向消息的接收者。
4.9.3 使用實例
4.9.4 對象、類和消息
對象的屬性是指描述對象的數據成員。數據成員能夠是系統或程序員定義的數據類型。對象屬性的集合稱爲對象的狀態。
對象的行爲是定義在對象屬性上的一組操做的集合。操做(函數成員)是響應消息而完成的算法,表示對象內部實現的細節。對象的操做集合體現了對象的行爲能力。
對象的屬性和行爲是對象定義的組成要素,分別表明了對象的靜態和動態特徵。由以上分析的例子可見,不管對象是簡單的或是負責的,通常具備如下特徵:
(1)有一個狀態,由與其相關的屬性集合所表徵。
(2)有唯一標識名,能夠區別於其餘對象。
(3)有一組操做方法,每一個操做決定對象的一種行爲。
(4)對象的狀態只能被本身的行爲所改變。
(5)對象的操做包括自身操做(施加於自身)和施加於其餘對象的操做。
(6)對象之間以消息傳遞的方式進行通訊。
(7)一個對象的成員仍能夠是一個對象。
4.10 面向對象編程的文件規範
通常要求將類的聲明放在頭文件中,很是簡單的成員函數能夠在聲明中定義(默認內聯函數形式),實現放在.cpp文件中。在.cpp文件中,將頭文件包含進去。主程序單獨使用一個文件,這就是多文件編程規範。
4.10.1 編譯指令
C++的源程序可包含各類編譯指令,以指示編譯器對源代碼進行編譯以前先對其進行預處理。全部的編譯指令都以#開始,每條編譯指令單獨佔用一行,同一行不能有其餘編譯指令和C++語句(註釋例外)。編譯指令不是C++的一部分,但擴展了C++編程環境的使用範圍,從而改善程序的組織和管理。
1.嵌入指令
嵌入指令#include指示編譯器將一個源文件嵌入到帶有#include指令的源文件中該指令所在的位置處。尖括號或雙引號中的文件名可包含路徑信息。例如:

include<userprog.h>

注意:因爲編譯指令不是C++的一部分,所以,在這裏表示反斜槓時只使用一個反斜槓。
2.宏定義

define指令定義一個標識符及串,在源程序中每次遇到該標識符時,編譯器均用定義的串代替之。該標識符稱爲宏名,而將替換過程稱之爲宏替換。#define指令用以進行宏定義,其通常形式以下:

define 宏名 替換正文

「宏名」必須是一個有效的C++標識符,「替換正文」可爲任意字符組成的字符序列。「宏名」和「替換正文」之間至少有一個空格。注意,宏定義由新行結束,而不以分號結束。若是給出了分號,則它也被視做爲替換正文的一部分。當替換正文要書寫在多行上時,除最後一行外,每行的行尾要加上一個反斜線,表示宏定義繼續到下一行。
因宏定義有許多不安全因素,對須要使用無參數宏的場合,應該儘可能使用const代替宏定義。
在程序的一個地方定義的宏名,若是不想使其影響到程序的其餘地方,能夠在再也不使用時用#undef刪除。
3.條件編譯指令
條件編譯指令是#if、#else、#elif和#endif,它們構成相似於C++的if選擇結構,其中#endif表示一條指令結束。
編譯指令#if用於控制編譯器對源程序的某部分有選擇地進行編譯。該部分從#if開始,到#endif結束。若是#if後的常量表達式爲真,則編譯這部分,不然就不編譯該部分,這時,這部分代碼至關於被從源文件中刪除。
編譯指令#else在#if測試失效的狀況下創建另一種選擇。能夠在#else分支中使用編譯指令#error輸出出錯信息。#error使用的形式以下:

error 出錯信息

「出錯信息」是一個字符序列。當遇到#error指令時,編譯器顯示其後面的「出錯信息」,並停止對程序的編譯。
編譯指令可嵌套,嵌套規則和編譯器對其的處理方式與C++的if預計嵌套狀況相似。
4.define操做符
關鍵字defined不是指令,而是一個預處理操做符,用於判斷一個標識符是否已經被#define定義。若是標識符identifier已被#define定義,則defined(identifier)爲真,不然爲假。
條件編譯指令#ifdef和#ifndef用於測試其後的標識符是否被#define定義,若是已經被定義,則#ifdef測試爲真,#ifndef測試爲假;若是沒有被定義,則#ifdef測試爲假,#ifndef測試爲真。
4.10.2 在頭文件中使用條件編譯
第五章 特殊函數和成員
5.1 對象成員的初始化
能夠在一個類中說明具備某個類的類型的數據成員,這些成員成爲對象成員。在類A中說明對象成員的通常形式以下:
class A
{
類名1 成員名1;
類名2 成員名2;
……
類名n 成員名n;
};
說明對象成員是在類名以後給出對象成員的名字。爲初始化對象成員,A類的構造函數要調用這些對象成員所在類的構造函數,A類的構造函數的定義形式以下:
A::A(參數表0):成員1(參數表1),成員2(參數表2),成員n(參數表n)
{//其餘操做}
冒號「:」後由逗號隔開的項目組成員初始化列表,其中的參數表給出了爲調用相應成員所在類的構造函數時應提供的參數。參數列表中的參數都來自「參數表0」,可使用任意複雜的表達式,其中能夠有函數調用。若是初始化列表某項的參數表爲空,則列表中相應的項能夠省略。
對象成員構造函數的順序取決於這些對象成員在類中說明的順序,與他們在成員初始化列表中給出的順序無關。析構函數的調用順序與構造函數正好相反。
5.2 靜態成員
簡單成員函數是指聲明中不含const、volatile、static關鍵字的函數。若是類的數據成員或成員函數使用關鍵字static進行修飾,這樣的成員稱爲靜態數據成員或靜態成員函數,統稱爲靜態成員。
靜態數據成員只能說明一次,若是在類中僅對靜態數據成員進行聲明,則必須在文件做用域的某個地方進行定義。在進行初始化以前,必須進行成員名限定。
注意:因爲static不是函數中的一部分,因此在類聲明以外定義靜態成員函數時,不使用static。在類中定義的靜態成員函數是內聯的。
類中的任何成員函數均可以訪問靜態成員。由於靜態成員函數沒有this指針,因此靜態成員函數只能經過對象名(或指向對象的指針)訪問該對象的非靜態成員。
靜態成員函數與通常函數有以下不一樣:
(1)能夠不指向某個具體的對象,只與類名連用。
(2)在沒有創建對象以前,靜態成員就已經存在。
(3)靜態成員是類的成員,不是對象的成員。
(4)靜態成員爲該類的全部對象共享,它們被存儲於一個公用的內存中。
(5)沒有this指針,因此除非顯式地把指針傳給它們,不然不能存取類的數據成員。
(6)靜態成員函數不能說明爲虛函數。
(7)靜態成員函數不能直接訪問非靜態函數。
靜態對象具備以下性質:
(1)構造函數在代碼執行過程當中,第一次遇到它的時候變量定義時被調用,但直到整個程序結束以前僅調用一次。
(2)析構函數在整個程序退出以前被調用,一樣也只調用一次。
5.3 友元函數
有時兩個概念上相近的類要求其中一個類能夠無限制地存取另外一個類的成員。
友元函數解決了這類難題。友元函數能夠存取私有成員、公有成員和保護成員。其實,友元函數能夠是一個類或函數,還沒有定義的類也能夠做爲友元引用。
1.類自己的友元函數
爲本類聲明一個友元函數,這時,雖然在類中說明它,但它並非類的成員函數,因此能夠在類外面像普通函數那樣定義這個函數。
雖然友元函數是在類中說明的,但函數的定義通常在類以外,做用域的開始點在它的說明點,結束點和類的做用域相同。
友元說明能夠出現於類的私有或公有部分。由於友元說明也必須出現於類中,因此應將友元看做類的接口的一部分。使用它的主要目的是提升程序效率。友元函數能夠直接訪問類對象的私有程序,於是省去調用類成員函數的開銷。它的另外一個優勢是:類的設計者沒必要在考慮號該類的各類可能使用狀況以後再設計這個類,而能夠根據須要,經過使用友元增長類的接口。但使用友元的主要問題是:它容許友元函數訪問對象的私有成員,這破壞了封裝和數據隱藏,致使程序的可維護性變差,所以在使用友元時必須權衡得失。
注意:友元函數能夠在類中聲明時定義。若是在類外定義,不能再使用friend關鍵字。
2.將成員函數用做友元
一個類的成員函數(包括構造函數和析構函數)能夠經過使用friend說明爲另外一個類的友元。
3.將一個類說明爲另外一個類的友元
能夠將一個類說明爲另外一個類的友元。這時,整個類的成員函數均具備友元函數的性能。聲明友元關係簡化爲「friend class 類名;」
須要注意的是,友元關係是不傳遞的,即當說明類A是類B的友元,類B又是類C的友元時,類A卻不是類C的友元。這種友元關係也不具備交換性,即當說明類A是類B的友元時,類B不必定是類A的友元。
當一個類要和另外一個類協同工做時,使一個類稱爲另外一個類的友元是頗有用的。
友元聲明與訪問控制無關。友元聲明在私有區域進行或在公有區域進行是沒有太大區別的。雖然友元函數的聲明能夠置於任何部分,但一般置於能強調其功能的地方以使其直觀。對友元函數聲明的唯一限制是該函數必須出如今類聲明內的某一部分。
5.4 const對象
能夠在類中使用const關鍵字定義數據成員和成員函數或修飾一個對象。一個const對象只能訪問const成員函數,不然將產生編譯錯誤。
1.常量成員
常量成員包括常量數據成員、靜態常數據成員和常引用。靜態常數據成員仍保留靜態成員特徵,須要在類外初始化。常數據成員和常引用只能經過初始化列表來得到初值。
2.常引用做爲函數參數
使用引用做爲參數,傳送的是地址。但有時僅但願將參數的值提供給函數使用,並不容許函數改變對象的值,這時可使用常引用做爲參數。
3.常對象
在對象名前使用const聲明常量對象,但聲明時必須同時進行初始化,並且不能被更新。定義的語法以下:
類名 const 對象名(參數表);
4.常成員函數
能夠聲明一個成員函數爲const函數。一個const對象能夠調用const函數,但不能調用非const成員函數。const放在函數聲明以前意味着返回值是常量,但這不符合語法。必須將關鍵字const放在參數列表以後,才能說明該函數是一個const成員函數。聲明常成員函數的格式以下:
類型標識符 函數名(參數列表) const;
爲了保證不只聲明const成員函數,並且確實也定義爲const函數,程序員在定義函數時必須重申const聲明。也就是說,const已經成爲這種成員函數識別符的一部分,編譯器和鏈接程序都要檢查const。定義格式以下:
類型標識符 類名::函數名(參數列表)const{//函數體}
const位於函數參數表以後,函數體以前。也能夠在類中用內聯函數定義const函數。
類型標識符 函數名(參數列表)const{//函數體}
在定義成員函數時,函數體以前加上const能夠防止覆蓋函數改變數據成員的值。
在常成員函數裏,不能更新對象的數據成員,也不能調用該類中沒有用const修飾的成員函數。若是將一個對象說明爲常對象,則經過該對象只能調用它的const成員函數,不能調用其餘成員函數。
注意:用const聲明static成員函數沒有什麼做用。在C++中聲明構造函數和析構函數時使用const關鍵字均是非法的,但有些C++的編譯程序並不給出出錯信息。volatile關鍵字的使用方法與const相似,但因其不多用,此處不介紹。
5.5 數組和類
編譯器調用適當的構造函數創建數組的每個份量。若是找不到合適的構造函數,則產生錯誤信息。
5.6 指向類成員函數的指針
對象是一個完整的實體。爲了支持這一封裝,C++包含了指向類成員的指針。能夠用普通指針訪問內存中給定類型的任何對象,指向類成員的指針則用來訪問某個特定類的對象中給定類型的任何成員。
C++既包含指向類數據成員函數的指針,又包含指向成員函數的指針。他提供一種特殊的指針類型,指針指向類的成員,而不是指向該類的一個對象中該成員的一個實例,這種指針稱爲指向類成員的指針。不過,指向類數據成員的指針要求數據成員是公有的,用途不大,因此不予介紹,僅簡單介紹指向類成員函數的指針。
類並非對象,但有時可將其視爲對象來使用。能夠聲明並使用指向對象成員函數的指針。指向對象的指針是比較傳統的指針,假設類A的成員函數爲「void fa(void);」,如要創建一個指針pafn,它能夠指向任何無參數和無返回值的類A的成員函數:
void(A::* pafn)(void);
雖然「::」是做用域分辨符,但在這種狀況下,「A::」最好讀成「A的成員函數」,這時,從內往外看,此聲明可讀做:pafn是一個指針,指向類A的成員函數,此成員函數既無參數,也無返回值。下面的例子說明了pafn如何被賦值並用以調用函數fa:
pafn=A::fa; //指向類A的成員函數fa的指針pafn
a x; //類A的對象x
A *px=&x; //指向類A對象x的指針px
(x.*pafn)(); //調用類A的對象x的成員函數fa
(px->pafn)(); //調用類A的對象x的指針px指向的成員函數fa
指向類A中參數類型列表爲list,返回類型爲type的成員函數的指針聲明形式以下:
type(A::* pointer)(list);
若是類A的成員函數fun的原型與pointer所指向的函數的原型同樣,則語句
pointer=A::fun;
將該函數的地址(這個地址不是真實地址,而是在A類中全部對象的便宜)置給了pointer。在使用指向類成員函數的指針訪問對象的某個成員函數時,必須指定一個對象。用對象名或引用調用pointer所指向的函數時,使用運算符「.」,使用指向對象的指針調用pointer所指向的成員函數時,使用運算符「->」。5.7 求解一元二次方程第六章 繼承和派生6.1 繼承和派生的基本概念這種經過特殊化已有的類來創建新類的過程,叫作「類的派生」,原來的類叫作「基類」,新創建的類則叫作「派生類」。另外一方面,從類的成員角度看,派生類自動地將基類的全部成員做爲本身的成員,這叫作「繼承」。基類和派生類又能夠分別叫作「父類」和「子類」,有時也稱爲「通常類」和「特殊類」。從一個或多個之前定義的類(基類)產生新類的過程稱爲派生,這個新類稱爲派生類。派生的新類同時也能夠增長或者從新定義數據和操做,這就產生了類的層次性。類的繼承是指派生類繼承基類的數據成員和成員函數。繼承經常使用來表示類屬關係,不能將繼承理解爲構成關係。當從現有的類中派生出新類時,派生類能夠有如下幾種變化:(1)增長新的成員(數據成員或成員函數)(2)從新定義已有的成員函數(3)改變基類成員的訪問權限C++派生類使用兩種基本的面向對象技術:第一種稱爲性質約束,即對基類的性質加以限制;第二種稱爲性質擴展,即增長派生類的性質。C++中有兩種繼承:單一繼承和多重繼承。對於單一繼承,派生類只能有一個基類;對於多重繼承,派生類能夠有多個基類。6.2 單一繼承6.2.1 單一繼承的通常形式在C++,聲明單一繼承的通常形式以下:class 派生類名:訪問控制 基類名{private:成員聲明列表protected:成員聲明列表public:成員聲明列表};這裏和通常的類聲明同樣,用關鍵字class聲明一個新的類。冒號後面的部分指示這個新類是哪一個基類的派生類。所謂「訪問控制」是指如何控制基類成員在派生類中的訪問屬性。它是3個關鍵字public、private、protected中的一個。一對大括號「{}」是用來聲明派生類本身的成員的。6.2.2 派生類的構造函數和析構函數定義派生類的構造函數的通常形式以下:派生類名::派生類名(參數表0) : 基類名(參數表){...//函數體}冒號後「基類名(參數表)」稱爲成員初始化列表,參數表給出所調用的基類構造函數所須要的實參。實參的值能夠來自「參數表0」,或由表達式給出。構造函數(包括析構函數)是不被繼承的,因此一個派生類只能調用它的直接基類的構造函數。當定義派生類的一個對象時,首先調用基類的構造函數,對基類成員進行初始化,而後執行派生類的構造函數,若是某個基類還是一個派生類,則這個過程遞歸執行。該對象消失時,析構函數的執行順序和執行構造函數的順序正好相反。6.2.3 類的保護成員C++語言規定,使用公有方式產生的派生類成員函數能夠直接訪問基類中定義的或從另外一個基類繼承來的公有成員,但不能訪問基類的私有成員。在類的聲明中,關鍵字protected以後聲明的是類的保護成員。保護成員具備私有成員和公有成員的雙重角色;對派生類的成員函數而言,它是公有成員,能夠被訪問;而對其餘函數而言則還是私有成員,不能被訪問。6.2.4 訪問權限和賦值兼容規則1.公有派生和賦值兼容規則在公有派生的狀況下,基類成員的訪問權限在派生類中保持不變。這就意味着:(1)基類的公有成員在派生類中依然是公有的。(2)基類的保護成員在派生類中依然是保護的。(3)基類的不可訪問的和私有的成員在派生類中也仍然時不可訪問的。當但願類的某些成員可以被派生類所訪問,而又不能被其餘的外界函數訪問的時候,就應當把它們定義爲保護的。所謂賦值兼容規則是指在公有派生狀況下,一個派生類的對象能夠做爲基類的對象來使用的狀況。注意:靜態成員能夠被繼承,這時基類對象和派生類的對象共享該靜態成員。2.「isa」和「has-a」的區別類與類之間的關係有兩大類:一是繼承和派生問題,二是一個類使用另外一個類的問題。後者的簡單用途是把另外一個類的對象做爲本身的數據成員或者成員函數的參數。對於繼承,首先要掌握公有繼承的賦值兼容規則,理解公有繼承「就是一個(isa)」的含義。分層也能夠叫作包含、嵌入或者聚合。公有繼承的意思是「isa」。與此相反,分層的意思是指「has-a(有一個)」。3.公有繼承存取權限表注意:靜態成員能夠被繼承,這時,基類對象和派生類的對象共享該靜態成員。4.私有派生經過私有派生,基類的私有和不可訪問成員在派生類中是不可訪問的,而公有和保護成員這時就成了派生類的私有成員,派生類的對象不能訪問繼承的基類成員,必須定義公有的成員函數做爲接口。更重要的是,雖然派生類的成員函數可經過定義自定義的函數訪問基類的成員,但將派生類做爲基類在繼續派生時,這時即便使用公有派生,原基類公有成員在新的派生類中也將是不可訪問的。5.保護派生派生也可使用protected。這種派生使原來的權限都降一級使用,即private變爲不可訪問;protected變爲private;public變爲protected。由於限制了數據成員和成員函數的訪問權限,因此用的比較少。它與private繼承的主要區別在於下一級的派生中。6.3 多重繼承一個類從多個基類派生的通常形式以下:class 類名1:訪問控制 類名2,訪問控制 類名3,...,訪問控制 類名n{...//定義派生類本身的成員};6.4 二義性及其支配規則對基類成員的訪問必須是無二義性的,如使用一個表達式的含義能解釋爲可訪問多個基類中的成員,則這種對基類成員的訪問就是不肯定的,稱這種訪問具備二義性。6.4.1 做用域分辨符和成員名限定從類中派生其餘類可能致使幾個類使用同一個成員函數名或數據成員名。程序必須確切地告訴編譯器使用哪一個版本的數據成員或成員函數。若是基類中的名字在派生類中再次聲明,則派生類中的名字隱藏了基類中的相應名字。C++能夠迫使編譯器「看到」當前做用域的外層部分,存取那些被隱藏的名字,這是由做用域分辨運算符「::」實現的(簡稱做用域運算符)。這一過程叫作做用域分辨。做用域分辨操做的通常形式以下:類名::標識符「類名」能夠是任意基類或派生類名,「類標識符」是該類中聲明的任一成員名。6.4.2 派生類支配基類的同名函數基類的成員和派生類新增的成員都具備類做用域,基類在外層,派生類在內層。若是這時派生類定義了一個和基類成員函數同名的新成員函數(由於參數不一樣屬於重載,因此這裏是指具備相同參數表的成員函數),派生類的新成員函數就覆蓋了外層的同名成員函數。在這種狀況下,直接使用成員函數名只能訪問派生類的成員函數,只有使用做用域分辨,才能訪問基類的同名成員函數。派生類D中的名字N支配基類B中同名的名字N,稱爲名字支配規則。第七章 類模板與向量7.1 類模板若是將類看做包含某些數據類型的框架,而後將這些數據類型從類中分離出來造成一個通用的數據類型T,爲這個數據類型T設計一個操做集,而且容許原來那些數據類型的類都能使用這個操做集,這將避免由於類的數據類型不一樣而產生的重複性設計,其實,類型T並非類,而是對類的描述,常稱之爲類模板。在編譯時,由編譯器將類模板與某種特定數據類型聯繫起來,就產生一個特定的類(模板類)。利用類模板能大大簡化程序設計。7.1.1 類模板基礎知識1.類模板的成分及語法關鍵字class在這裏的含義是「任意內部類型或用戶定義類型」,但T也多是結構或類。對於函數模版及類模板來講,模板層次結構的大部份內容都是同樣的,然而在模板聲明以後,對類而言便顯示出了根本性的差別。爲了窗機類模板,在類模板參數表以後應該有類聲明。在類中能夠像使用其餘類型(如int或double)那樣使用模板參數。類模板聲明的通常方法以下:template <類模板參數> class 類名{//類體};2.類模板的對象類模板也稱爲參數化類。初始化類模板時,只要傳給它指定的數據類型,編譯器就用指定類型替代模板參數產生相應的模板類。用類模板定義對象的通常格式以下:類名<模板實例化參數類型> 對象名(構造函數實參列表);類名<模板實例化參數類型> 對象名;//默認或者無參數構造函數使用模板類時,當給模板實例化參數類型一個指定的數據類型時,編譯器自動用這個指定數據類型替代模板參數。在類體外面定義成員函數時,必須用template重寫類模板聲明。通常格式以下:template <模板參數>返回類型 類名<模板類型參數>::成員函數名(函數參數列表){//函數體}<模板類型參數> 是指template的「< >」內使用class(或typename)聲明的類型參數,構造函數和析構函數沒有返回類型。模板實例化參數類型包括數據類型和值。編譯器不能從構造函數參數列表推斷出模板實例化參數類型,因此必須顯式地給出對象的參數類型。7.1.2 類模板的派生與繼承類模板也能夠繼承,繼承的方法與普通的類同樣。聲明模板類繼承以前,必須從新聲明類模板。模板類的基類和派生類均可以是模板(或非模板)類。類模板的派生與繼承很複雜,本書僅簡單介紹模板類繼承非模板類和從模板類派生一個類模板兩種狀況。能夠用一個非模板類爲一組模板提供一種共同的實現方法。7.2 向量與泛型算法在數組生存期內,數組的大小是不會改變的。向量是一維數組的類版本,它與數組類似,其中的元素項老是連續存儲的,但它和數組不一樣的是:向量中存儲元素的多少能夠在運行中根據須要動態地增加或縮小。向量是類模板,具備成員函數。7.2.1 定義向量列表向量(vector)類模板定義在頭文件vector中,它提供4種構造函數,用來定義由各元素組成的列表。用length表示長度,數據類型用type表示,對象名爲name。向量定義的賦值運算符「=」,容許同類型的向量列表相互賦值,而無論它們的長度如何。向量能夠改變賦值目標的大小,使它的元素數目與賦值源的元素數目相同。向量的第一個元素也是從0開始。不能使用列表初始化向量,但能夠先初始化一個數組,而後把數組的內容複製給向量。7.2.2 泛型指針「與操做對象的數據類型相互獨立」的算法稱爲泛型算法。也就是說,泛型算法提供了許多可用於向量的操做行爲,而這些算法和想要操做的元素類型無關。這是藉助一對泛型指針來實現的。向量具備指示第一元素的標記begin和指示結束的標記end,也就是標識要進行操做的元素空間。若是begin不等於end,算法便會首先做用於begin所指元素,並將begin前進一個位置,而後做用於當前begin所指元素,如此繼續進行,直到begin等於end爲止。由於end是最後一個元素的下一個位置,因此元素存在的範圍是半開區間[begin,end)。在向量中,泛型指針是在底層指針的行爲之上提供一層抽象化機制,取代程序原來的「指針直接操做方式」。假設用T表示向量的參數化數據類型,iterator在STL裏面是一種通用指針,它在向量中的做用至關於T*。用iterator聲明向量的正向泛型指針的通常形式以下:vector<type> :: iterator泛型指針名;對向量的訪問能夠是雙向的,圖中的rbegin和rend是提供給逆向泛型指針的開始和結束標誌。逆向泛型指針的加操做是使它向rend方向移動,減操做向rbegin移動。聲明逆向泛型指針使用reverse_iterator。聲明的方法以下:vector<數據類型> :: reverse_iterator 指針名;7.2.3 向量的數據類型向量除了可使用基本數據類型以外,還可使用構造類型,只要符合構成法則便可。7.2.4 向量最基本的操做方法1.訪問向量容量信息的方法(1)size():返回當前向量中已經存放的對象的個數(2)max_size():返回向量能夠容納最多對象的個數,通常是操做系統的尋址空間所需容納的對象的個數。這個參數不是用戶指定的,它取決於硬件結構。(3)capacity():返回無需再次分配內存就能容納的對象個數。它的初始值爲程序員最初申請的元素個數。當存放空間已滿,又增長一個元素時,它在原來的基礎上自動翻倍擴充空間,以便存放更多的元素。通俗地講,也就是已申請的空間。這三者的關係以下。(4)empty():當前向量爲空時,返回true。2.訪問向量中對象的方法(1)front():返回向量中的第一個對象。(2)back():返回向量中的最後一個對象。(3)operator[](size_type,n):返回向量中的第n+1個對象(下標爲n的向量元素)。3.在向量中插入對象的方法(1)push_back(const T&):向向量尾部插入一個對象。(2)insert(iterator it,const T&):向it所指向的向量位置前插入一個對象。(3)insert(iterator it,size_type n,const T&X):向it所指向量位置前插入n個值爲X的對象。4.在向量中刪除對象的方法(1)pop_back(const T&):刪除向量中最後一個對象。(2)erase(iterator it):刪除it所指向的容器對象。(3)clear():刪除向量中的全部對象,empty()返回true。7.3 出圈遊戲第八章 多態性和虛函數8.1 多態性靜態聯編所支持的多態性稱爲編譯時的多態性。當調用重載函數時,編譯器能夠根據調用時使用的實參在編譯時就肯定下來應調用哪一個函數。動態聯編所支持的多態性稱爲運行時的多態性,這由虛函數來支持。虛函數相似於重載函數,但與重載函數的實現策略不一樣,即對虛函數的調用使用動態聯編。8.1.1 靜態聯編中的賦值兼容性及名字支配規律對象的內存地址空間中只包含數據成員,並不存儲有關成員函數的信息。這些成員函數的地址翻譯過程與其對象的內存地址無關。聲明的基類指針只能指向基類,派生類指針只能指向派生。它們的原始類型決定它們只能調用各類的同名函數area。8.1.2 動態聯編的多態性當編譯系統編譯含有虛函數的類時,將爲它創建一個虛函數表,表中的每個元素都指向一個虛函數的地址。此外,編譯器也爲類增長一個數據成員,這個數據成員是一個指向該虛函數表的指針,一般稱爲vptr。虛函數的地址翻譯取決於對象的內存地址。編譯器爲含有虛函數類的對象首先創建一個入口地址,這個地址用來存放指向虛函數表的指針vptr,而後按照類中虛函數的聲明次序,一一填入函數指針。當調用虛函數時,先經過vptr找到虛函數表,而後再找出虛函數的真正地址。派生類能繼承基類的虛函數表,並且只要是和基類同名(參數也相同)的成員函數,不管是否使用virtual聲明,它們都自動稱爲虛函數。若是派生類沒有改寫繼承基類的虛函數,則函數指針調用基類的虛函數。若是派生類改寫了基類的虛函數,編譯器將從新爲派生類的虛函數創建地址,函數指針會調用改寫過的虛函數。虛函數的調用規則是:根據當前對象,優先調用對象自己的成員函數。這和名字支配規律相似,不過虛函數是動態聯編的,是在執行期「間接」調用實際上欲聯編的函數。8.2 虛函數一旦基類定義了虛函數,該基類的派生類中的同名函數也自動稱爲虛函數。8.2.1 虛函數的定義虛函數只能是類中的一個成員函數,但不能是靜態成員,關鍵字virtual用於類中該函數的聲明中。當在派生類中定義了一個同名的成員函數時,只要該成員函數的參數個數和相應類型以及它的返回類型與基類中同名的虛函數徹底同樣,則不管是否爲該成員使用virtual,它都將成爲一個虛函數。8.2.2 虛函數實現動態性的條件關鍵字virtual指示C++編譯器對調用虛函數進行動態聯編。這種多態性是程序運行到須要的語句處才動態肯定的,因此稱爲運行時的多態性。不過,使用虛函數並不必定產生多態性,也不必定使用動態聯編。例如,在調用中對虛函數使用成員名限定,能夠強制C++對該函數的調用使用靜態聯編。產生運行時的多態性有以下3個前提:(1)類之間的繼承關係知足賦值兼容性規則。(2)改寫了同名函數。(3)根據賦值兼容性規則使用指針(或引用)。因爲動態聯編是在運行時進行的,相對於靜態聯編,它的運行效率比較低,但它可使程序員對程序進行高度抽象,設計出可擴充性好的程序。8.2.3 構造函數和析構函數調用虛函數在構造函數和析構函數中調用虛函數採用靜態聯編,即他們所調用的虛函數是本身的類或基類中定義的函數,但不是任何在派生類中重定義的虛函數。目前推薦的C++標準不支持虛構造函數。因爲析構函數不容許有參數,所以一個類只能有一個虛析構函數。虛析構函數使用virtual說明。只要基類的析構函數被說明爲虛函數,則派生類的析構函數,不管是否使用virtual進行說明,都自動地成爲虛函數。delete運算符和析構函數一塊兒工做(new和構造函數一塊兒工做),當使用delete刪除一個對象時,delete隱含着對析構函數的一次調用,若是析構函數爲虛函數,則這個調用採用動態聯編。通常來講,若是一個類中定義了虛函數,析構函數也應說明爲虛函數,尤爲是在析構函數要完成一些有意義的任務時,例如,釋放內存。若是基類的析構函數爲虛函數,則在派生類爲定義析構函數時,編譯器所生成的析構函數也爲虛函數。8.2.4 純虛函數與抽象類在許多狀況下,不能再基類中爲虛函數給出一個有意義的定義,這時能夠將它說明爲純虛函數,將其定義留給派生類去作。說明純虛函數的通常形式以下:class 類名{virtual 函數類型 函數名(參數列表)=0;};一個類能夠說明多個純虛函數,包含有純虛函數的類稱爲抽象類。一個抽象類只能做爲基類來派生新類,不能說明抽象類的對象。但能夠說明指向抽象類對象的指針(或引用)。從一個抽象類派生的類必須提供純虛函數的實現代碼,或在該派生類中仍將它說明爲純虛函數,不然編譯器將給出錯誤信息。這說明了純虛函數的派生類還是抽象類。若是派生類給了某類全部純虛函數的實現,則該派生類再也不是抽象類。若是經過同一個基類派生一系列的類,則將這些類總稱爲類族。抽象類的這一特色保證了進度類族的每一個類都具備(提供)純虛函數所要求的行爲,進而保證了圍繞這個類族所創建起來的軟件能正常運行,避免了這個類族的用戶因爲偶然失誤而影響系統正常運行。抽象類至少含有一個虛函數,並且至少有一個虛函數是純虛函數,以便將它與空的虛函數區分開來。下面是兩種不一樣的表示方法:virtual void area()=0;virtual void area(){}在成員函數內能夠調用純虛函數。由於沒有爲純虛函數定義代碼,因此在構造函數或虛構函數內調用一個純虛函數將致使程序運行錯誤。8.3 多重繼承與虛函數8.4 類成員函數的指針與多態性在派生類中,當一個指向基類成員函數的指針指向一個虛函數,而且經過指向對象的基類指針(或引用)訪問這個虛函數時,仍發生多態性。第9章 運算符重載及流類庫9.1 運算符重載9.1.1 重載對象的賦值運算符編譯器在默認狀況下爲每一個類生成一個默認的賦值操做,用於同類的兩個對象之間相互賦值。默認的含義是逐個爲成員賦值,即將一個對象的成員的值賦給另外一個對象相應的成員,這種賦值方式對於有些類多是不正確的。C++的關鍵字「operator」和運算符一塊兒使用就表示一個運算符函數。例如「operator +」表示重載「+」運算符。9.1.2 運算符重載的實質C++是由函數組成的,在C++內部,任何運算都是經過函數來實現的。由於任何運算都是經過函數來實現的,因此運算符重載其實就是函數重載,要重載某個運算符,只要重載相應的函數就能夠了。與以往稍有不一樣的是,須要使用新的關鍵字「operator」,它和C++的一個運算符連用,構成一個運算符函數名,例如「operator+」.經過這種構成方法就能夠像重載普通函數那樣重載運算符函數operator+()。因爲C++已經爲各類基本數據類型定義了該運算函數,因此只須要爲本身定義的類型重載operator+()就能夠了。通常地,爲用戶定義的類型重載運算符都要求可以訪問這個類型的私有成員,因此只有兩條路可走:要麼將運算符重載爲這個類型的成員函數,要麼將運算符重載爲這個類型的友元。C++的運算符大部分均可以重載,不能重載的只有. 、:: 、* 和 ?: 。前面三個是由於在C++中都有特定的含義,不許重載以免沒必要要的麻煩;「?:」則是由於不值得重載。另外,「sizeof」和「#」不是運算符,於是不能重載,而=、()、[ ] 、->這4個運算符只能用類運算符來重載。9.1.3 <<、>>和++運算符重載實例其實,插入符「<<」和提取符「>>」的重載也與其餘運算符重載同樣,但操做符的左邊是流對象的別名而不是被操做的對象,運算符跟在流對象的後面,它們要直接訪問類的私有數據,並且流是標準類庫,用戶只能繼承不能修改,更不能是流庫的成員,因此它們必須做爲類的友元重載。插入符函數的通常形式以下:ostream &operator<<(ostream & output,類名 &對象名){return output;}output是類ostream對象的引用,它是cout的別名,即ostream&output=cout。調用參數時,output引用cout(即cout的別名)。顯然,插入符函數的第2個參數使用引用方式比直接使用對象名的可讀性要好一些。提取符函數的通常形式以下:istram &operator>>(istream & input,類名&對象名){return input;}input是類istream對象的引用。它是cin的別名,即istream&input=cin。調用參數時,input引用cin(即cin的別名)。另外,提取符函數須要返回新的對象值,因此應該使用引用,即「類名&對象名」,不能使用「類名 對象名」。插入符函數不改變對象的值,因此兩種方法均可以。顯然,運算符「<<」重載函數有兩個參數,第1個是ostream類的一個引用,第2個是自定義類型的一個對象。這個重載方式是友元重載。這個函數的返回類型是一個ostream類型的引用,在函數中實際返回的是該函數的第一個參數,這樣作是爲了使得「<<」可以連續使用。有些C++編譯器不區分前綴或後綴運算符,這時只能經過對運算符函數進行重載來反映其爲前綴或後綴運算符。注意不能本身定義新的運算符,只能是把C++原有的運算符用到本身設計的類上面去。同時,通過重載,運算符並不改變原有的優先級,也不改變它所需的操做數目。當不涉及到定義的類對象時,它仍然執行系統預約義的運算,只有用到本身定義的對象上,才執行新定義的操做。應該根據須要進行運算符重載。不排除在某些特殊狀況下會有一些特殊的須要,但大多數狀況下不會將運算符「+」重載爲兩個複數相減的運算(儘管有能力這麼作)。通常老是要求運算符重載合乎習慣。9.1.4 類運算符和友元運算符的區別若是運算符所需的操做數(尤爲是第一個操做數)但願進行隱式類型轉換,則運算符應經過友元來重載。另外一方面,若是一個運算符的操做須要修改類對象的狀態,則應當使用類運算符,這樣更符合數據封裝的要求。但參數是引用仍是對象,則要根據運算符在使用中可能出現的狀況來決定。若是對象做爲重載運算符函數的參數,則可使用構造函數將常量轉換成該類型的對象。若是使用引用做爲參數,由於這些常量不能做爲對象名使用,因此編譯系統就要報錯。9.1.5 下標運算符「[ ]」的重載運算符「[ ]」只能用類運算符來重載。9.2 流類庫C++的流類庫由幾個進行I/O操做的基礎類和幾個支持特定種類的源和目標的I/O操做的類組成。9.2.1 流類庫的基礎類在C++中,輸入輸出時同流來完成的。C++的輸出操做將一個對象的狀態轉換成一個字符序列,輸出到某個地方。輸入操做也是從某個地方接收到一個字符序列,而後將其轉換成一個對象的狀態所要求的格式。這看起來很像數據在流動,因而把接收輸出數據的地方叫作目標,把輸入數據來自的地方叫作源。而輸入和輸出操做能夠當作字符序列在源、目標以及對象之間的流動。C++將與輸入和輸出有關的操做定義爲一個類體系,放在一個系統庫裏,以備用戶調用。這個執行輸入和輸出操做的類體系就叫作流類,提供這個流類實現的系統庫就叫作流類庫。C++的流類庫由幾個進行I/O操做的基礎類和幾個支持特定種類源和目標的I/O操做類組成。在C++中,若是在多條繼承路徑上有一個匯合處,則稱這個匯合處的基類爲公共基類(ios符合條件)。由於能夠經過不一樣的訪問路徑訪問這個基類,從而使公共的基類會產生多個實例,這樣會引發二義性。若是想使這個公共的基類只產生一個實例,則能夠將這個基類說明爲虛基類。ios類就是isrream類和ostream類的虛基類,用來提供對流進行格式化I/O操做和錯誤處理的成員函數。用關鍵字virtual可將公共基類說明爲虛基類,虛基類的定義很難處理,這就是爲何最初的C++語言沒有能支持多重繼承的緣由。從ios類公有派生的istream和ostream兩個類分別提供對流進行提取操做和插入操做的成員函數,而iostream類經過組合istream類和ostream類來支持對一個流進行雙向(也就是輸入和輸出)操做,它並無提供新的成員函數。C++流類庫預約義了4個流,它們是cin、cout、cerr、clog。事實上,能夠將cin視爲類istream的一個對象,而將cout視爲類ostream的對象。流是一個抽象概念,當實際進行I/O操做時,必須將流和一種具體的物理設備(好比鍵盤)聯接起來。C++的流類庫預約義的4個流所聯接起來的具體設備爲:cin 與標準輸入設備相聯接cout 與標準輸出設備相聯接cerr 與標準錯誤輸出設備相聯接(非緩衝方式)clog 與標準錯誤輸出設備相聯接(緩衝方式)9.2.2 默認輸入輸出的格式控制關於數值數據,默認方式可以自動識別浮點數並用最短的格式輸出,還能夠將定點數分紅整數和小數部分。特別要注意字符的讀入規則。對單字符來說,它將捨去空格,直到讀到字符爲止。對於單字符對象a,b和c,「cin>>a>>b>>c;」能將連續的3個字符分別正確地賦給相應對象。對字符串來說,它從讀到第一個字符開始,到空格符結束。對於字符數組,使用數組名來總體讀入。但對於字符指針,儘管爲它動態分配了地址,也只能採起逐個賦值的方法,它不只不以空格結束,反而捨棄空格(讀到字符才計數)。由於字符串沒有結束位,因此將字符串做爲總體輸出時,有效字符串後面將出現亂碼。不過,能夠手工增長表示字符串的結束符「0」來消除亂碼。當用鍵盤同時給一個單字符對象和一個字符串對象賦值時,不要先給字符串賦值。若是先給它賦值,應該強行使用結束符。Bool(布爾型)在VC6.0中把輸入的0識別爲false,其餘的值均識別爲1。輸出時,只有0和1兩個值。若是默認輸入輸出格式不能知足本身的要求,就必須重載它們。9.2.3 使用ios_base類1.ios_base類簡介ios_base類派生ios類,ios類又是istream類和ostream類的虛基類。表9.1 常量名及含義常量名 含義skipws 跳過輸入中的空白left 輸出數據按輸出域左邊對齊輸出right 輸出數據按輸出域右邊對齊輸出intermal 在指定任何引導標誌或基以後填充字符dec 轉換基數爲十進制形式oct 轉換基數爲八進制形式hex 轉換基數爲十六進制形式showbase 輸出帶有一個表示制式的字符showpoint 浮點輸出時必須帶有一個小數點和尾部的0uppercase 十六進制數值輸出使用大寫A~F,科學計數顯示使用大寫字母Eshowpos 在正數前添加一個「+」號scientific 使用科學計數法表示浮點數fixed 使用定點形式表示浮點數untibuf 每次插入以後,ostream::osfx刷新該流的緩衝區。默認緩衝單元爲cerrboolalpha 把邏輯值輸出爲true和false(不然輸出爲1和0)adjustfield 對齊方式域(與left、right、internal配合使用,例如ios_base::adjustfield)basefield 數字方式域(與dex、oct、hex配合使用,例如ios_base::basefield)floatfield 浮點方式域(與fix、scientific配合使用,例如ios_base::floatfield)表9.2 處理標誌的成員函數及其做用成員函數 做 用long flags(long) 容許程序員設置標誌字的值,並返回之前所設置的標誌字long flags() 僅返回當前的標誌字long setf(long,long) 用於設置標誌字的某一位,第2個參數指定所要操做的位,第1個參數指定爲該參數所設置的值long setf(long) 用來設置參數指定的標誌位long unsetf(long) 清除參數指定的標誌位int width(int) 返回之前設置顯示數據的域寬int width() 只返回當前域寬(默認寬度爲0)char fill(char) 設置填充字符,設置的寬度小時,空餘的位置用填充字符來填充,默認條件下是空格。這個函數返回之前設置的填充字符char fill() 得到當前的填充字符int precision(int) 返回之前設置的精度(小數點後的小數位數)int precision 返回當前設置的精度2.直接使用格式控制表9.1中的名字能夠直接用在系統提供的輸入輸出流中,而且有些是成對的。加no前綴標識取消原操做。用width(int)設置寬度的效果只對一次輸入或輸出有效,在完成一次輸入或輸出以後,寬度設置自動恢復爲0(表示按實際數據寬度輸入輸出)。在設置輸入時,實際輸入的字符串最大長度爲n-1(寬度n計入結束符)。setw(int)只對緊跟其後的輸出有效。3.使用成員函數在使用成員函數進行格式控制的時候,setf用來設置,unsetf用來恢復默認設置。它們被流對象調用,使用表9.1的常量設置。9.3 文件流在C++裏,文件操做是經過流來完成的。C++總共有輸入文件流、輸出文件流和輸入輸出文件流3種,並已將它們標準化。要打開一個輸入文件流,須要定義一個ifstream類型的對象;要打開一個輸出文件流,須要定義一個ofstream類型的對象;若是要打開輸入輸出文件流,則要定義一個fstream類型的對象。這3種類型都定義在頭文件<fstream>裏。9.3.1 使用文件流流類具備支持文件的能力,在創建和使用文件時,就像使用cin和cout同樣方便。能夠總結出對文件進行操做的方法以下:(1)打開一個相應的文件流.(2)把這個流和相應的文件關聯起來。由於ifstream、ofstream和fstream這3個類都具備自動打開文件的構造函數,而這個構造函數就具備open()的功能。所以,事實上能夠用一條語句:ofstream myStream("myText.txt");來完成上述兩步。若是指定文件路徑,路徑中的「」號必須使用轉義字符表示。(3)操做文件流。綜上所述,流是I/O流類的中心概念。流是一種抽象,它負責在數據的生產者和消費者之間創建聯繫並管理數據的流動,程序將流對象看作是文件對象的化身。一個輸出流對象是信息流動的目標,ofstream是最重要的輸出流。一個輸入流對象是數據流動的源頭,ifstream是最重要的輸入流。一個iostream對象能夠是數據流動的源或目的,fstream就是從它派生的。其實,文件流的對象是字節流,並且文本文件和二進制文件均是字節流。將文件與流關聯起來,就是使用「>>」和「<<」對文件進行讀寫。但不能使用空格,這是由「>>」和「<<」原來的性質所決定的。9.3.2 幾個典型流成員函數1.輸出流的open函數open函數的原型以下:void open(char const *,int filemode,int =filebuf::openprot);它有3個參數,第一個是要打開的文件名,第2個是文件的打開方式,第3個是文件的保護方式,通常都使用默認值。第2個參數能夠取以下所示的值:ios_base::in 打開文件進行讀操做,這種方式可避免刪除現存文件的內容ios_base::out 打開文件進行寫操做,這是默認模式ios_base::ate 打開一個已有的輸入或輸出文件並查找到文件尾ios_base::app 打開文件以便在文件尾部添加數據ios_base::binary 指定文件以二進制方式打開,默認爲文本方式ios_base::trunc 如文件存在,將其長度截斷爲零並清除原有內容除ios_base::app方式以外,在其餘幾種方式下,文件剛剛打開是,指示當前讀寫位置的文件指針都定位於文件的開始位置,而ios_base::app使文件當前的寫指針定位於文件尾。這幾種方式也能夠經過「或」運算符「|」同時使用。2.輸入流類的open函數使用默認構造函數創建對象,而後調用open成員函數打開文件。輸入流的打開模式以下所示:ios_base::in 打開文件用於輸入(默認)ios_base::binary 指定文件以二進制方式打開,默認爲文本方式3.close成員函數close成員函數用來關閉與一個文件流相關聯的磁盤文件。若是沒有關閉該文件,由於文件流是對象,因此在文件流對象的生存期結束時,將自動關閉該文件。不過,應養成及時關閉再也不使用的文件的習慣,以免誤操做或者干擾而產生的錯誤。4.錯誤處理函數在對一個流對象進行I/O操做時,可能會產生錯誤。當錯誤發生時,可使用文件流的錯誤處理成員函數進行錯誤類型判別。這些成員函數的做用見表9.3。可使用這些函數檢查當前流的狀態,例如:if(cin.good()) cin>>data;函數clear更多地是用於在已知流發生錯誤的狀況下清除流的錯誤狀態,也能夠用於設置流的錯誤狀態。除非發生致命錯誤(hardfail),不然可使用函數clear清除流的錯誤狀態。「!」運算符已通過了重載,它與fail函數執行相同的功能,所以表達式if(!cout)等價於if(cout.fail()),if(cout)等價於if(!cout.fail())。表9.3 成員函數及其功能函 數 功 能bad() 若是進行非法操做,返回true,不然返回falseclear() 設置內部錯誤狀態,若是用缺省參量調用則清除全部錯誤位eof() 若是提取操做已經到達文件尾,則返回true,不然返回falsegood() 若是沒有錯誤條件和沒有設置文件結束標誌,返回true,不然返回falsefail() 與good相反,操做失敗返回false,不然返回trueis_open() 斷定流對象是否成功地與文件關聯,如果,返回true,不然返回false9.3.3 文件存取綜合實例 第10章 面向對象設計實例10.1 過程抽象和數據抽象抽象是造成概念的必要手段,它是從許多事物中捨棄個別的、非本質性的特徵,抽取共同及本質性的特徵的過程。對於分析而言,抽象原則具備兩方面的意義:(1)儘管問題域中的事物很複雜,但分析員並不須要瞭解和描述其所有特徵,只須要分析研究與系統目標有關的事物及其本質特徵。對於那些與系統目標無關的特徵和許多具體的細節,即便有所瞭解,也應該捨棄。(2)經過捨棄個體事物在細節上的差別,抽取其共同特徵能夠獲得一批事物的抽象概念。過程抽象是指任何一個完成肯定功能的操做序列,其使用者均可以把它看作一個單一的實體,儘管實際上它多是由一系列更低級的操做完成的。運用過程抽象,軟件開發者能夠把一個複雜的功能分解爲一些子功能,過程抽象對於在對象範圍內組織對象的成員函數是頗有用的。數據抽象是指根據施加於數據之上的操做來定義數據類型,並限定數據的值只能由這些操做來修改和觀察。10.2 發現對象並創建對象層軟件開發者將被開發的整個業務範圍稱做「問題域」,能夠按以下步驟考慮發現對象並創建對象層。1.將問題域和系統責任做爲出發點2.正確運用抽象原則在OOA中正確地運用抽象原則,首先要捨棄那些與系統責任無關的事物,只注意與系統責任有關的事物。其次,對於與系統責任有關的事物,也不是把他們的任何特徵都在相應的對象中表達出來,而是要捨棄那些與系統責任無關的特徵。判斷事物是否與系統責任有關的關鍵問題,一是該事物是否爲系統提供了一些有用的信息(是否須要系統爲它保存和管理某些信息);二是它是否向系統提供了某些服務(是否須要系統描述它的某些行爲)。3.尋找候選對象的基本方法尋找候選對象的基本方法的主要策略是從問題域、系統邊界和系統責任三方面找出可能有的候選對象。4.審查和篩選對象5.異常狀況的檢查和調整通常認爲下述狀況都算異常狀況,則須要進行調整。(1)類的數據成員或成員函數不適合該類的所有對象。(2)不一樣類的數據成員或成員函數相同或類似。(3)對同一事物的重複描述。10.3 定義數據成員和成員函數爲了發現對象的數據成員,首先應考慮借鑑以往的OOA結果,看看相同或類似的問題域是否有已開發的OOA模型,儘量複用其中同類對象數據成員的定義。而後重點研究當前問題域和系統責任,針對本系統應該設置的每一類對象,按照問題域的實際狀況,以系統責任爲目標進行正確地抽象,從而找出每一類對象應有的數據成員。1.尋找數據成員的通常方法2.審查與篩選數據成員對於初步發現的數據成員,要進行審查和篩選。即對每一個數據成員提出如下問題:(1)這個數據成員是否體現了以系統責任爲目標的抽象。(2)這個數據成員是否描述了這個對象自己的特徵。(3)該屬性是否符合人們平常的思惟習慣。(4)這個數據成員是否是能夠經過繼承獲得。(5)是否能夠從其餘數據成員直接導出的數據成員。3.定義成員函數使用中要特別注意區分紅員函數、非成員函數和友元函數三者。10.4 如何發現基類和派生類結構1.學習當前領域的分類學知識分析員應該學習一些與當前問題域有關的分類學知識,,由於問題域現行的分類方法(若是有),每每比較正確地反映了事物的特徵、類別以及各類概念的通常性與特殊性。2.按照常識考慮事物的分類從不一樣的角度考慮問題域中事物的分類,能夠造成一些創建基類與派生類結構的初步設想,從而啓發本身發現一些確實須要的基類與派生類結構。3.構建基類與派生類按照基類與派生類結構的兩種定義,可引導咱們從兩種不一樣的思路去發現基類與派生類結構。一種思路是把每個類看作是一個對象集合,分析這些集合之間的包含關係。另外一種思路是看一個類是否是具備另外一個類的所有特徵。這包括兩種狀況:一是創建這些類時已經計劃讓某個類繼承另外一個類的所有成員,此時應創建基類與派生類結構來落實這種繼承;另外一種狀況是起初只是孤立地創建每個類,如今發現一個類中定義的成員(數據成員和成員函數)所有在另外一個類中從新出現了,此時應該考慮創建基類與派生類結構,把後者做爲前者的派生類,以簡化定義。4.考察類的成員對系統中的每一個類,從如下兩個方面考察它們的成員;一是看一個類的成員是否適合這個類的所有對象。若是某些數據成員和成員函數只能適合該類的一部分對象,說明應該從這個類中劃分出一些派生類,創建基類與派生類關係。另外一方面檢查是否有兩個(或更多的)類含有一些共同的數據成員和成員函數。若是有,則考慮若把這些共同的數據成員和成員函數提取出來後可否構成一個在概念上是包含原來那些類的基類,組成一個基類與派生類結構。還要對發現的結構進行審查、調整和簡化,處理異常狀況,才能創建合適的結構。10.5 接口繼承與實現繼承如今假設設計一個能夠供其餘類繼承的基類,派生類使用公有繼承方式。公有繼承其實是由兩個不一樣部分組成的,即函數接口的繼承和函數實現的繼承。可歸納成以下兩點:(1)繼承的老是成員函數的接口。對於基類是正確的任何事情,對於它的派生類必須也是正確的。(2)聲明純虛函數的目的是使派生類僅僅繼承函數接口,而純虛函數的實現則由派生類去完成。(3)聲明虛函數的目的是使派生類既能繼承基類對此虛函數的實現,又能繼承虛函數提供的接口。(4)聲明實函數的目的是使派生類既能繼承基類對此實函數的實現,又能繼承實函數提供的接口。1.純虛函數純虛函數最顯著的兩個特徵是:(1)它們必須由繼承它的非抽象類從新說明(2)它們在抽象類中沒有定義2.虛函數虛函數的狀況不一樣於純虛函數,派生類繼承虛函數的接口,虛函數通常只提供一個可供派生類選擇的實現。之因此聲明虛函數,目的在於使派生類既繼承函數接口,也繼承虛函數的實現。3.實函數一個實成員函數指定它本身在派生類中保持不變。所以,聲明實函數的目的是使派生類既繼承這個函數的實現,也繼承其函數接口。4.避免從新定義繼承的實函數雖然在派生類中能夠重定義基類的同名實函數,可是從使用安全角度來看,爲了提升程序質量,在實際應用中應避免這樣作。10.6 設計實例

相關文章
相關標籤/搜索