SCAU软件开发基础C++复习
C++語言程序設(shè)計(第4版)
文章目錄
- C++語言程序設(shè)計(第4版)
- 第一章 緒論
- 1.1 面向?qū)ο蟮幕靖拍?/li>
- 1.1.1 對象
- 1.1.2 類(數(shù)據(jù)類型)
- 1.1.3 封裝
- 1.1.4 繼承
- 1.1.5 多態(tài)性
- 第二章 C++簡單程序設(shè)計
- 2.1 C++的特點
- 2.2 基本數(shù)據(jù)類型
- 2.3 變量的存儲類型
- 2.4 運算方式和表達式
- 2.4.1 算數(shù)運算式與算數(shù)表達式
- 2.4.2 賦值運算符與賦值表達式
- 2.4.3 逗號運算和逗號表達式
- 2.4.4 邏輯運算與邏輯表達式
- 2.4.5 條件運算符與條件表達式
- 2.4.6 sizeof運算符
- 2.4.7 位運算
- 第三章 函數(shù)
- 3.1 函數(shù)的定義與使用
- 3.2 內(nèi)聯(lián)函數(shù)
- 3.3 帶默認參數(shù)值的函數(shù)
- 3.3.1 有默認參數(shù)值的形參必須放在最后:
- 3.3.2 在相同的作用域內(nèi),不允許在用一個函數(shù)的多個生命中對同一個參數(shù)的默認值重復定義,即使前后定義的值相同也不行:
- 3.4 函數(shù)重載
- 3.5 補充
- 第四章 類與對象
- 4.1 面向?qū)ο蟪绦蛟O(shè)計的基本特點
- 4.1.1 抽象
- 4.1.2 封裝
- 4.1.3 繼承
- 4.1.4 多態(tài)
- 4.2 類和對象
- 4.2.1 類的定義
- 4.2.2 內(nèi)聯(lián)成員函數(shù)
- 4.3構(gòu)造函數(shù)和析構(gòu)函數(shù)
- 4.3.1 構(gòu)造函數(shù)
- 4.3.2 復制構(gòu)造函數(shù)
- 4.3.3 析構(gòu)函數(shù)
- 4.4類的組合
- 4.4.1 組合
- 4.4.2 前向引用聲明
- 4.5 結(jié)構(gòu)體和聯(lián)合體
- 4.5.1 結(jié)構(gòu)體
- 4.5.2 聯(lián)合體
- 第五章 數(shù)據(jù)的共享與保護
- 5.1 標識符的作用域與可見性
- 5.1.1 作用域
- 1.函數(shù)原型作用域:
- 2.局部作用域:
- 3.類作用域:
- 4.命名空間作用域:
- 5.1.2 可見性
- 5.2 對象的生存期
- 5.2.1 靜態(tài)生存期
- 5.2.2 動態(tài)生存期
- 5.3 類的靜態(tài)成員
- 5.3.1 靜態(tài)數(shù)據(jù)成員
- 5.3.2 靜態(tài)函數(shù)成員
- 5.4 類的友元
- 5.4.1 友元函數(shù)
- 5.4.2 友元類
- 5.5 共享數(shù)據(jù)的保護
- 5.5.1 常對象
- 5.5.2 用const修飾的類成員
- 1.常成員函數(shù)
- 2.常數(shù)據(jù)成員
- 3.常飲用
- 5.6 多文件結(jié)構(gòu)和編譯預處理命令
- 5.6.1 C++的一般組織結(jié)構(gòu)
- 5.6.2 外部變量和外部函數(shù)
- 5.6.3標準C++庫
- 第六章 數(shù)組、指針與字符串
- 6.1 數(shù)組
- 6.1.1數(shù)組的聲明
- 6.1.2 數(shù)組的存儲和初始化
- 6.1.3 數(shù)組作為函數(shù)參數(shù)
- 6.1.4 對象數(shù)組
- 6.2 指針
- 6.2.1 內(nèi)存空間的訪問方式
- 6.2.2 指針變量的聲明
- 6.2.3 與地址相關(guān)的運算“*”和“&”
- 6.2.4 指針的賦值
- 指向常量的指針
- 指針類型的常量
- 6.2.5 指針的運算
- 關(guān)系運算
- 賦值運算
- 6.2.6 用指針處理數(shù)組元素
- 6.2.7 指針數(shù)組
- 6.2.8 用指針作為函數(shù)參數(shù)
- 6.2.9 指針型函數(shù)
- 6.2.10 指向函數(shù)的指針
- 6.2.11 對象指針
- 1.一般對象指針的概念
- 2.this指針
- 3.指向類的非靜態(tài)成員的指針
- 4.指向類的靜態(tài)成員的指針
- 6.3 動態(tài)內(nèi)存分配
- 6.3.1動態(tài)申請內(nèi)存操作符new和釋放內(nèi)存操作符delete
- 6.3.2申請和釋放動態(tài)數(shù)組
- 拓展
- 6.3.3 動態(tài)創(chuàng)建多維數(shù)組
- 6.3.4動態(tài)數(shù)組類
- 6.4 用vector創(chuàng)建數(shù)組對象
- 6.5 深復制和淺復制
- 6.5.1 淺復制
- 6.5.2 深復制
- 6.6 字符串
- 6.6.1 用字符數(shù)組存儲和處理字符串
- 用字符數(shù)組表示字符串的缺點
- 6.6.2 string類
- 6.8 深度搜索
- 第七章 繼承與派生
- 7.1 類的繼承與派生
- 7.1.1 概念
- 7.1.2 派生類的定義
- 7.1.3 派生類生成過程
- 1.吸收基類成員
- 2.改造基類成員
- 3.添加新的成員
- 7.2 訪問控制
- 1.公有繼承
- 2.私有繼承
- 3.保護繼承
- 4.私有繼承和保護繼承區(qū)別
- 7.3 類型兼容規(guī)則
- 7.4 派生類的構(gòu)造、析構(gòu)函數(shù)
- 7.4.1 構(gòu)造函數(shù)
- 7.4.2 復制構(gòu)造函數(shù)
- 7.4.3 析構(gòu)函數(shù)
- 7.5 派生類成員的標識與訪問
- 7.5.1 作用域分辨符(::)
- 7.5.2 虛基類
- 7.5.3 虛基類及其派生類構(gòu)造函數(shù)
- 拓展
- 7.8 深度探索
- 第八章 多態(tài)性
- 8.1 多態(tài)性概述
- 8.1.1 多態(tài)的類型
- 8.1.2 多態(tài)的實現(xiàn)
- 8.2 運算符重載
- 8.2.1 運算符重載的規(guī)則
- 兩種重載方式
- 聲明形式
- 8.2.2 運算符重載為成員函數(shù)
- 雙目運算符 B
- 前置單目運算符U
- 后置單目運算符++、--
- 8.2.3 運算符重載為非成員函數(shù)
- 雙目運算符 B
- 前置單目運算符 U
- 后置單目運算符 ++和--
- 8.3 虛函數(shù)
- 8.3.1 一般虛函數(shù)成員
- 運行過程中的多態(tài)必須滿足條件
- 8.3.2 虛析構(gòu)函數(shù)
- 8.4 純虛函數(shù)與抽象類
- 8.7 深度探索
- 第九章 群體類和群體數(shù)據(jù)的組織
- 9.1 函數(shù)模板與類模板
- 9.1.1 函數(shù)模板
- 注意
- 9.1.2 類模板
- 9.2 線性群體
- 9.2.1 線性群體的概念
- 9.2.2 直接訪問群體——數(shù)組類
- 淺復制和深復制
- 與眾不同的運算符
- 指針轉(zhuǎn)換運算符的作用
第一章 緒論
1.1 面向?qū)ο蟮幕靖拍?/h3>
1.1.1 對象
面向?qū)ο蠓椒ㄖ械膶ο?#xff0c;是系統(tǒng)中用來描述客觀事務(wù)的一個主體,它是用來構(gòu)成系統(tǒng)的一個基本單位。對象是由一組屬性和一組行為構(gòu)成的。
1.1.2 類(數(shù)據(jù)類型)
分類所依據(jù)的原則是抽象。面向?qū)ο蠓椒ㄖ械摹邦悺?#xff0c;是具有相同屬性和服務(wù)的一組對象的集合。
1.1.3 封裝
封裝是面向?qū)ο蠓椒ㄖ械囊粋€重要原則,就是把對象的屬性和服務(wù)組合成一個獨立的系統(tǒng)單位,并盡可能隱蔽對象的內(nèi)部細節(jié)。
1.1.4 繼承
特殊類的對象擁有其一般類的全部屬性與服務(wù),稱做特殊類對一般類的繼承。
1.1.5 多態(tài)性
多態(tài)性是指一般類中定義的屬性或行為,被特殊類繼承之后,可以具有不同的數(shù)據(jù)類型或表現(xiàn)出不同的行為。
第二章 C++簡單程序設(shè)計
2.1 C++的特點
C++語言的主要特點是:1.盡量兼容C;2.支持面向?qū)ο蟮姆椒ā?/p>
x.cpp:源程序,x.obj:目標程序,x.exe:執(zhí)行程序。
2.2 基本數(shù)據(jù)類型
| bool | 1 | false,true |
| char | 1 | -128~127 |
| signed char | 1 | -128~127 |
| unsigned char | 1 | 0~255 |
| short(signed short) | 2 | -32768~32767 |
| unsigned short | 2 | 0~65535 |
| int(signed int) | 4 | -2147483648~2147483647 |
| unsigned int | 4 | 0~4294967295 |
| long(signed long) | 4 | -2147483648~2147483647 |
| unsigned long | 4 | 0~4294967295 |
| float | 4 | 3.4x10(-38)~3.4x1038 |
| double | 8 | 1.7x10(-308)~1.7x10308 |
| long double | 8 | 1.7x10(-308)~1.7x10308 |
一般而言,如果對一個整數(shù)所占字節(jié)數(shù)和取值范圍沒有特殊要求,使用int型為宜,因為它通常具有最高的處理效率。
2.3 變量的存儲類型
auto:暫時性存儲。采用堆棧方式分配內(nèi)存空間,其存儲空間可以被若干變量多次覆蓋使用。
register:存放在通用寄存器中。
extern:在所有函數(shù)和程序段中都可以引用。
static:在內(nèi)存中以固定地址存放,整個程序運行期間都有效。
2.4 運算方式和表達式
2.4.1 算數(shù)運算式與算數(shù)表達式
2.4.2 賦值運算符與賦值表達式
2.4.3 逗號運算和逗號表達式
2.4.4 邏輯運算與邏輯表達式
優(yōu)先級分兩級:(<,<=,>,>=) > (==,!=)。
“&&”和“||”具有“短路”特性,如果第一個操作數(shù)求值后為false,則不再對第二個操作數(shù)求值。
2.4.5 條件運算符與條件表達式
表達式1?表達式2:表達式3。
2.4.6 sizeof運算符
sizeof(類型名),用于計算某種類型的對象在內(nèi)存中所占的字節(jié)數(shù)。
2.4.7 位運算
(1)按位與&:a=a&0xfe將a最低位變?yōu)?,c=a&0xff取出a的低字節(jié);
(2)按位或|:a=a|0xff將a最低位變?yōu)?;
(3)按位異或^;
(4)按位取反~;
(5)移位<<或>>:2<<1=4,左邊表達式的值不會被改變。
第三章 函數(shù)
C++繼承了C語言的全部語法,也包括函數(shù)的定義與使用方法。
3.1 函數(shù)的定義與使用
主函數(shù)是程序執(zhí)行的開始點,由主函數(shù)調(diào)用子函數(shù),子函數(shù)還可以調(diào)用其他子函數(shù)。
3.1.1 函數(shù)的定義
形參的作用是實現(xiàn)主調(diào)函數(shù)與被調(diào)函數(shù)之間的聯(lián)系,通常將函數(shù)所處理的數(shù)據(jù)、影響函數(shù)功能的因素或者函數(shù)處理的結(jié)果作為形參。
只有函數(shù)被調(diào)用時才由主調(diào)函數(shù)將實際參數(shù)(實參)賦予形參。
一個函數(shù)可以有返回值,也可以沒有返回值,沒有返回值的時候類型標識符是void,可以不寫return語句,也可以寫一個不帶表達式的return表示結(jié)束當前函數(shù)的調(diào)用。
3.1.2 函數(shù)的調(diào)用
在調(diào)用函數(shù)前,要聲明或定義該函數(shù)。
3.1.3 函數(shù)的參數(shù)傳遞
值傳遞:單向傳遞;
引用傳遞:可以實現(xiàn)雙向傳遞,聲明引用的時候必須同時對它初始化,指向一個已存在的對象,并且不能更改;
常引用作參數(shù)可以保障實參數(shù)據(jù)的安全。
3.2 內(nèi)聯(lián)函數(shù)
聲明時使用關(guān)鍵字 inline。
編譯時在調(diào)用處用函數(shù)體進行替換,節(jié)省了參數(shù)傳遞、控制轉(zhuǎn)移等開銷。
注意:
內(nèi)聯(lián)函數(shù)應(yīng)是比較簡單的函數(shù)。
內(nèi)聯(lián)函數(shù)體內(nèi)不能有循環(huán)語句和switch語句。
內(nèi)聯(lián)函數(shù)的聲明必須出現(xiàn)在內(nèi)聯(lián)函數(shù)第一次被調(diào)用之前。
對內(nèi)聯(lián)函數(shù)不能進行異常接口聲明。
3.3 帶默認參數(shù)值的函數(shù)
3.3.1 有默認參數(shù)值的形參必須放在最后:
int add(int x, int y = 5, int z = 6);//正確
int add(int x = 1, int y = 5, int z);//錯誤
int add(int x = 1, int y, int z = 6);//錯誤
3.3.2 在相同的作用域內(nèi),不允許在用一個函數(shù)的多個生命中對同一個參數(shù)的默認值重復定義,即使前后定義的值相同也不行:
int add(int x = 5,int y = 6); //原型聲明在前 int main() {add(); } int add(int x,int y) { //此處不能再指定默認值return x + y; } int add(int x/* = 5*/,int y/* = 6*/) { //只有定義,沒有原型聲明return x + y; } int main() {add(); }(好習慣:在參數(shù)表中以注釋來說明參數(shù)的默認值)
3.4 函數(shù)重載
定義:兩個以上的函數(shù),具有相同的函數(shù)名,但是形參的個數(shù)或者類型不同,編譯器根據(jù)實參和形參的類型及個數(shù)的最佳匹配,自動確定調(diào)用哪一個函數(shù)。
重載函數(shù)的形參必須不同:個數(shù)不同或類型不同。
當使用具有默認形參值得函數(shù)重載形式時,需要注意防止二義性:
void fun(int length,int width = 2,int height = 33); void fun(int length);在調(diào)用的時候如果使用以下形式就會報錯: fun(1);3.5 補充
在C++中聲明函數(shù)時后面括號內(nèi)為空,表示的是要求的參數(shù)是未知的,如果沒有函數(shù),后面括號內(nèi)應(yīng)該填上void。
第四章 類與對象
4.1 面向?qū)ο蟪绦蛟O(shè)計的基本特點
4.1.1 抽象
抽象是對具體對象(問題)進行概括,抽出這一類對象的公共性質(zhì)并加以描述的過程。
抽象的實現(xiàn):通過類的聲明。
4.1.2 封裝
封裝就是將抽象得到的數(shù)據(jù)和行為(或功能)相結(jié)合,形成一個有機整體,也就是將數(shù)據(jù)與操作數(shù)據(jù)的函數(shù)代碼進行有機的結(jié)合,形成“類”,其中的數(shù)據(jù)和函數(shù)都是類的成員。
實現(xiàn)封裝:類聲明中的{}。
4.1.3 繼承
實現(xiàn):聲明派生類——見第7章
4.1.4 多態(tài)
多態(tài):同一名稱,不同的功能實現(xiàn)方式。
目的:達到行為標識統(tǒng)一,減少程序中標識符的個數(shù)。
實現(xiàn):重載函數(shù)和虛函數(shù)——見第8章
4.2 類和對象
在面向?qū)ο蟪绦蛟O(shè)計中,程序模塊是由類構(gòu)成的。類是對邏輯上相關(guān)的函數(shù)與數(shù)據(jù)的封裝,它是對問題的抽象描述。
4.2.1 類的定義
類包括數(shù)據(jù)成員和函數(shù)成員
數(shù)據(jù)成員的訪問控制屬性有三種:public、protected、private
函數(shù)成員的訪問控制屬性有四種:public、protected、friendly、private
4.2.2 內(nèi)聯(lián)成員函數(shù)
隱式聲明:將函數(shù)體放在類的聲明中。
顯式聲明:使用inline關(guān)鍵字。
4.3構(gòu)造函數(shù)和析構(gòu)函數(shù)
4.3.1 構(gòu)造函數(shù)
構(gòu)造函數(shù)的作用是在對象被創(chuàng)建時使用特定的值構(gòu)造對象,將對象初始化為一個特定的初始狀態(tài)。
在對象創(chuàng)建時被自動調(diào)用。
如果程序中未聲明,則系統(tǒng)自動產(chǎn)生出一個默認的構(gòu)造函數(shù),其參數(shù)列表為空。
構(gòu)造函數(shù)可以是內(nèi)聯(lián)函數(shù)、重載函數(shù)、帶默認參數(shù)值的函數(shù)。
4.3.2 復制構(gòu)造函數(shù)
https://blog.csdn.net/shuaiyoutiao/article/details/121497260?spm=1001.2014.3001.5502
4.3.3 析構(gòu)函數(shù)
析構(gòu)函數(shù)是在對象的生存期即將結(jié)束的時刻被自動調(diào)用的。
析構(gòu)函數(shù)不接受任何參數(shù),但可以是虛函數(shù)。
4.4類的組合
4.4.1 組合
一個類中內(nèi)嵌其他類的對象作為成員,它們之間的關(guān)系是一種包含與被包含的關(guān)系。
聲明形式:
類名::類名(對象成員所需的形參,本類成員形參)
:對象1(參數(shù)),對象2(參數(shù)),…
{
//函數(shù)體其他語句
}
組合類構(gòu)造函數(shù)定義的一般形式是:
類名::類名(形參表):內(nèi)嵌對象1(形參表),內(nèi)嵌對象2(形參表)…
{類的初始化}
#include <iostream> #include <cmath> using namespace std; class Point { //Point類定義 public:Point(int xx = 0, int yy = 0) {x = xx;y = yy;}Point(Point &p);int getX() { return x; }//類外實現(xiàn)int getY() { return y; }//類外實現(xiàn) private:int x, y; }; Point::Point(Point &p) { //復制構(gòu)造函數(shù)的實現(xiàn)x = p.x;y = p.y;cout << "Calling the copy constructor of Point" << endl; } //類的組合 class Line { //Line類的定義 public: //外部接口Line(Point xp1, Point xp2);Line(Line &l);double getLen() { return len; } private: //私有數(shù)據(jù)成員Point p1, p2; //Point類的對象p1,p2double len; }; //組合類的構(gòu)造函數(shù) Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {cout << "Calling constructor of Line" << endl; * double x = static_cast<double>(p1.getX() - p2.getX());//強制類型轉(zhuǎn)化double y = static_cast<double>(p1.getY() - p2.getY());//強制類型轉(zhuǎn)化len = sqrt(x * x + y * y); } Line::Line (Line &l): p1(l.p1), p2(l.p2) {//組合類的復制構(gòu)造函數(shù)cout << "Calling the copy constructor of Line" << endl;len = l.len; } //主函數(shù) int main() {Point myp1(1, 1), myp2(4, 5); //建立Point類的對象Line line(myp1, myp2); //建立Line類的對象Line line2(line); //利用復制構(gòu)造函數(shù)建立一個新對象cout << "The length of the line is: ";cout << line.getLen() << endl;cout << "The length of the line2 is: ";cout << line2.getLen() << endl;return 0; }生成兩個Point類的對象->構(gòu)造Line類的對象line->復制構(gòu)造line2->輸出兩點距離。在整個過程中Point類的復制構(gòu)造函數(shù)被調(diào)用了6次,而且都是在Line類構(gòu)造函數(shù)體之前進行的。
運行結(jié)果如下:
Calling the copy constructor of Point
Calling the copy constructor of Point
Calling the copy constructor of Point
Calling the copy constructor of Point
Calling constructor of Line
Calling the copy constructor of Point
Calling the copy constructor of Point
Calling the copy constructor of Line
The length of the line is: 5
The length of the line2 is: 5
4.4.2 前向引用聲明
例子:請定義兩個類A和B,其中A類中含有B類的對象b1, B類中含有A類的對象a1,編寫兩個類的構(gòu)造函數(shù)和主函數(shù),其中,在A類的構(gòu)造函數(shù)中輸出“b1構(gòu)造成功”, 在B類的構(gòu)造函數(shù)中輸出“a1構(gòu)造成功”;
#include <iostream>#include <cmath>using namespace std;class B;class A{public:A(){cout << "A默認構(gòu)造成功" << endl;}A(int l){cout << "b1構(gòu)造成功" << endl;}private:B *b;//只能聲明指針或引用,如果改為B b則編譯錯誤};class B{public:B(){cout << "B默認構(gòu)造成功" << endl;}B(int l){cout << "a1構(gòu)造成功" << endl;}private:A a;};int main(){A al(1);B b1(1);A a2;}4.5 結(jié)構(gòu)體和聯(lián)合體
4.5.1 結(jié)構(gòu)體
結(jié)構(gòu)體和類的唯一區(qū)別在于,結(jié)構(gòu)體和類具有不同的默認訪問控制屬性,類是private,而結(jié)構(gòu)體是public。
4.5.2 聯(lián)合體
聯(lián)合體的全部數(shù)據(jù)成員共享同一組內(nèi)存單元,所以聯(lián)合體變量成員中至多只有一個有意義。
例:
#include <string> #include <iostream> using namespace std; class ExamInfo { private:string name; //課程名稱enum { GRADE, PASS, PERCENTAGE } mode;//采用何種計分方式union {char grade; //等級制的成績bool pass; //只記是否通過課程的成績int percent; //百分制的成績}; public://三種構(gòu)造函數(shù),分別用等級、是否通過和百分初始化ExamInfo(string name, char grade): name(name), mode(GRADE), grade(grade) { }ExamInfo(string name, bool pass): name(name), mode(PASS), pass(pass) { }ExamInfo(string name, int percent): name(name), mode(PERCENTAGE), percent(percent) { }void show(); } void ExamInfo::show() {cout << name << ": ";switch (mode) {case GRADE: cout << grade; break;case PASS: cout << (pass ? "PASS" : "FAIL"); break;case PERCENTAGE: cout << percent; break;}cout << endl; } int main() {ExamInfo course1("English", 'B');ExamInfo course2("Calculus", true);ExamInfo course3("C++ Programming", 85);course1.show();course2.show();course3.show();return 0; } 運行結(jié)果: English: B Calculus: PASS C++ Programming: 85第五章 數(shù)據(jù)的共享與保護
5.1 標識符的作用域與可見性
5.1.1 作用域
作用域是一個標識符在程序正文中有效的區(qū)域。
1.函數(shù)原型作用域:
C++中最小的作用域,始于“(”,終于“)”,例double area(double radius);
2.局部作用域:
3.類作用域:
類X的成員m具有類作用域,訪問方法有三種:
第一種是如果在X的成員函數(shù)中沒有聲明同名的局部作用域標識符,那么在該函數(shù)內(nèi)可以直接訪問成員m;
第二種是通過表達式x.m或X::m;
第三種是通過ptr->m,ptr為指向X類的一個對象的指針。
4.命名空間作用域:
例子
(具有命名空間作用域的變量也成為全局變量)
#include <iostream> using namespace std; int i; //全局變量,文件作用域 namespace Ns{int j; //在Ns命名空間中的全局變量 } int main() { i = 5; //為全局變量i賦值Ns::j=6; //為全局變量j賦值{ //子塊1using namespace Ns;//使得在當前塊空可以直接引用Ns命名空間的標識符int i; //局部變量,局部作用域i = 7;cout << "i = " << i << endl;//輸出7cout << "j = " << j << endl;//輸出6}cout << “i = ” << i << endl;//輸出5return 0; } 運行結(jié)果: i = 7 j = 6 i = 55.1.2 可見性
程序運行到某一點,能夠引用到的標識符,就是該處可見的標識符。
標識符應(yīng)聲明在先,引用在后。
如果某個標識符在外層中聲明,且在內(nèi)層中沒有同一標識符的聲明,則該標識符在內(nèi)層可見。
對于兩個嵌套的作用域,如果在內(nèi)層作用域內(nèi)聲明了與外層作用域中同名的標識符,則外層作用域的標識符在內(nèi)層不可見。
5.2 對象的生存期
對象從誕生到結(jié)束的這段時間就是它的生存期。
5.2.1 靜態(tài)生存期
如果對象的生存期與程序的運行期相同,則稱它具有靜態(tài)生存期。
方法:在命名空間作用域聲明或在函數(shù)內(nèi)部的局部作用域中聲明時加上static關(guān)鍵字。
局部作用域中的靜態(tài)變量特點:不會隨著每次函數(shù)調(diào)用而產(chǎn)生一個副本,也不會隨著函數(shù)返回而失效,當下一次函數(shù)調(diào)用時,該變量保持上一回的值。
5.2.2 動態(tài)生存期
在局部作用域中具有動態(tài)生存期的對象,習慣上稱為局部生存期對象。局部生存期對象誕生于聲明點,結(jié)束于聲明所在的塊執(zhí)行完畢時。
#include<iostream> using namespace std; int i = 1; // i 為全局變量,具有靜態(tài)生存期。 void other() {static int a = 2;static int b;// a,b為靜態(tài)局部變量,具有全局壽命,局部可見。//只第一次進入函數(shù)時被初始化。int c = 10; // C為局部變量,具有動態(tài)生存期,//每次進入函數(shù)時都初始化。a += 2; i += 32; c += 5;cout<<"---OTHER---\n";cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;b = a; } int main() {static int a;//靜態(tài)局部變量,有全局壽命,局部可見,未指定初值,被賦予0值初始化。int b = -10; // b, c為局部變量,具有動態(tài)生存期。int c = 0;cout << "---MAIN---\n";cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;c += 8; other();cout<<"---MAIN---\n";cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;i += 10; other(); return 0; } 運行結(jié)果: ---MAIN---i: 1 a: 0 b: -10 c: 0 ---OTHER---i: 33 a: 4 b: 0 c: 15 ---MAIN---i: 33 a: 0 b: -10 c: 8 ---OTHER---i: 75 a: 6 b: 4 c: 155.3 類的靜態(tài)成員
靜態(tài)成員可以解決同一個類的不同對象之間數(shù)據(jù)和函數(shù)共享問題。
5.3.1 靜態(tài)數(shù)據(jù)成員
用關(guān)鍵字static聲明
為該類的所有對象共享,靜態(tài)數(shù)據(jù)成員具有靜態(tài)生存期。
必須在類外定義和初始化,用(::)來指明所屬的類。
//5_4.cpp #include <iostream> using namespace std;class Point { //Point類定義 public: //外部接口Point(int x = 0, int y = 0) : x(x), y(y) { //構(gòu)造函數(shù)//在構(gòu)造函數(shù)中對count累加,所有對象共同維護同一個countcount++;}Point(Point &p) { //復制構(gòu)造函數(shù)x = p.x;y = p.y;count++;}~Point() { count--; }int getX() { return x; }int getY() { return y; }void showCount() { //輸出靜態(tài)數(shù)據(jù)成員cout << " Object count = " << count << endl;} private: //私有數(shù)據(jù)成員int x, y;static int count; //靜態(tài)數(shù)據(jù)成員聲明,用于記錄點的個數(shù) };//重要點 int Point::count = 0;//靜態(tài)數(shù)據(jù)成員定義和初始化,使用類名限定int main() { //主函數(shù)Point a(4, 5); //定義對象a,其構(gòu)造函數(shù)回使count增1cout << "Point A: " << a.getX() << ", " << a.getY();a.showCount(); //輸出對象個數(shù)//可換成Point::showCount();Point b(a); //定義對象b,其構(gòu)造函數(shù)回使count增1cout << "Point B: " << b.getX() << ", " << b.getY();b.showCount(); //輸出對象個數(shù)return 0; }運行結(jié)果:Point A: 4, 5 Object count=1Point B: 4, 5 Object count=25.3.2 靜態(tài)函數(shù)成員
靜態(tài)成員函數(shù)可以直接訪問該類的靜態(tài)數(shù)據(jù)和函數(shù)成員,而訪問非靜態(tài)成員,必須通過對象名。
#include <iostream> using namespace std;class Point { //Point類定義 public: //外部接口Point(int x = 0, int y = 0) : x(x), y(y) { //構(gòu)造函數(shù)//在構(gòu)造函數(shù)中對count累加,所有對象共同維護同一個countcount++;} Point(Point &p) { //復制構(gòu)造函數(shù)x = p.x;y = p.y;count++;}~Point() { count--; }int getX() { return x; }int getY() { return y; }static void showCount(Point &p) { //靜態(tài)函數(shù)成員cout << " Object count = " << count << endl;cout << "x = " << p.x << endl;}private: //私有數(shù)據(jù)成員int x, y;static int count; //靜態(tài)數(shù)據(jù)成員聲明,用于記錄點的個數(shù)};int Point::count = 0;//靜態(tài)數(shù)據(jù)成員定義和初始化,使用類名限定int main() { //主函數(shù)Point a(4, 5); //定義對象a,其構(gòu)造函數(shù)回使count增1cout << "Point A: " << a.getX() << ", " << a.getY();Point::showCount(a); //輸出對象個數(shù)和a的xPoint b(a); //定義對象b,其構(gòu)造函數(shù)回使count增1cout << "Point B: " << b.getX() << ", " << b.getY();Point::showCount(a); //輸出對象個數(shù)和a的xreturn 0;}如果把static void showCount(Point &p)改為static void showCount(Point p),則在生成臨時對象的時候count+1
5.4 類的友元
友元破壞了數(shù)據(jù)隱蔽和封裝,但是提高了程序的效率和可讀性。
5.4.1 友元函數(shù)
友元函數(shù)是在類聲明中由關(guān)鍵字friend修飾說明的非成員函數(shù),在它的函數(shù)體中能夠通過對象名訪問 private 和 protected成員。
訪問對象中的成員必須通過對象名。
friend float dist(Point &a, Point &b); 在main中使用: dist(myp1,myp2);5.4.2 友元類
若一個類為另一個類的友元,則此類的所有成員都能訪問對方類的私有成員。
class A {friend class B;//設(shè)置友元類 public:void display() {cout << x << endl;} private:int x; } class B { public:void set(int i);void display(); private:A a;}; void B::set(int i) {a.x=i;//在B的成員函數(shù)中可以訪問a類對象的私有成員 } void B::display() {a.display(); }注意:友元關(guān)系不可傳遞;友元關(guān)系是單向的;友元關(guān)系不可被繼承。
5.5 共享數(shù)據(jù)的保護
對于既需要共享、又需要防止改變的數(shù)據(jù)應(yīng)該聲明為常類型(用const進行修飾)。對于不改變對象狀態(tài)的成員函數(shù)應(yīng)該聲明為常函數(shù)。
5.5.1 常對象
常對象必須初始化,而且不能被更新。
class A {public:A(int i,int j) {x=i; y=j;}...private:int x,y; }; A const a(3,4); //a是常對象,不能被更新(存放在全局變量棧中,不是局部函數(shù)棧)5.5.2 用const修飾的類成員
1.常成員函數(shù)
常成員函數(shù)說明格式:類型說明符 函數(shù)名(參數(shù)表)const;
常成員函數(shù)不更新對象的數(shù)據(jù)成員。
這里,const是函數(shù)類型的一個組成部分,因此在實現(xiàn)部分也要帶const關(guān)鍵字。
const關(guān)鍵字可以被用于參與對重載函數(shù)的區(qū)分:如果僅以const關(guān)鍵字為區(qū)分對成員函數(shù)重載,那么通過非const的對象
通過常對象只能調(diào)用它的常成員函數(shù)。
#include <iostream> #include <cmath> using namespace std; class Point { public:Point(int xx, int yy) { x = xx, y = yy; }int getX()const;//必須聲明常成員函數(shù),否則常對象無法訪問 int getY()const;//同上 double Dis(Point p);double Dis(const Point p) const; private:int x;int y; }; inline int Point::getX()const {return x; } inline int Point::getY()const {return y; } double Point::Dis(Point p) {int xx = x - p.getX();int yy = y - p.getY();return sqrt(xx*xx + yy*yy); } double Point::Dis(const Point p)const {int xx = x - p.getX();int yy = y - p.getY();return sqrt(xx*xx + yy*yy); } int main() {const Point myp1(1, 1);const Point myp2(4, 5);cout << "常對象(1,1) and (4,5) Dis: " << myp1.Dis(myp2) << endl;cout << "下面開始構(gòu)造一般對象" << endl;int x1, y1, x2, y2;cout << "x1 and y1:";cin >> x1 >> y1;cout << "x2 and y2:";cin >> x2 >> y2;Point p1(x1, y1),p2(x2, y2);cout << "一般對象 Dis: " << p1.Dis(p2) << endl;system("pause"); }2.常數(shù)據(jù)成員
使用const說明的數(shù)據(jù)成員。
3.常飲用
聲明形式:const 類型說明符 &引用名;
friend float dist(const Point &p1, const Point &p2);‘ //在main中使用: const Point myp1(1, 1), myp2(4, 5); dist(myp1, myp2);5.6 多文件結(jié)構(gòu)和編譯預處理命令
5.6.1 C++的一般組織結(jié)構(gòu)
在規(guī)模較大的項目中,往往需要多個源程序文件,每個類都有單獨的定義和實現(xiàn)文件,采用將類的定義寫入頭文件中,要使用該類的.cpp文件包含該頭文件的方法,可以對不同的文件進行單獨編寫、編譯,最后再連接,同時充分利用類的封裝特性,在調(diào)試過程中只修改一個類的定義和實現(xiàn),而其余部分不改動。
//文件1,類的定義,Point.h class Point { //類的定義 public: //外部接口Point(int x = 0, int y = 0) : x(x), y(y) { }Point(const Point &p);~Point() { count--; }int getX() const { return x; }int getY() const { return y; }static void showCount(); //靜態(tài)函數(shù)成員 private: //私有數(shù)據(jù)成員int x, y;static int count; //靜態(tài)數(shù)據(jù)成員 }; //文件2,類的實現(xiàn),Point.cpp #include "Point.h" #include <iostream> using namespace std;int Point::count = 0; //使用類名初始化靜態(tài)數(shù)據(jù)成員Point::Point(const Point &p) : x(p.x), y(p.y) { //復制構(gòu)造函數(shù)體count++; }void Point::showCount() {cout << " Object count = " << count << endl; } //文件3,主函數(shù),5_10.cpp #include "Point.h" #include <iostream> using namespace std;int main() {Point a(4, 5); //定義對象a,其構(gòu)造函數(shù)回使count增1cout << "Point A: " << a.getX() << ", " << a.getY();Point::showCount(); //輸出對象個數(shù)Point b(a); //定義對象b,其構(gòu)造函數(shù)回使count增1cout << "Point B: " << b.getX() << ", " << b.getY();Point::showCount(); //輸出對象個數(shù)return 0; }5.6.2 外部變量和外部函數(shù)
//源文件1 int i = 3; //定義變量i void next();//函數(shù)原型聲明int main(){i++;next();return 0; } void next(){i++;other(); }//源文件2 extern int i;//聲明一個在其他文件中定義的變量i void other(){i++; }5.6.3標準C++庫
如果不加入語句“using namespace std;”就需要在每個標識符前加上“std::”
第六章 數(shù)組、指針與字符串
6.1 數(shù)組
6.1.1數(shù)組的聲明
類型說明符 數(shù)組名[ 常量表達式 ] [ 常量表達式 ]…… ;
6.1.2 數(shù)組的存儲和初始化
數(shù)組元素在內(nèi)存中是順序,連續(xù)存儲的。
二維數(shù)組的初始化
將所有數(shù)據(jù)寫在一個{}內(nèi),按順序賦值
例如:static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
分行給二維數(shù)組賦初值
例如:static int a[3][4]
={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
可以對部分元素賦初值
例如:static int a[3][4]={{1},{0,6},{0,0,11}};
列出全部初始值時,第1維下標個數(shù)可以省略
例如:static int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
或:static int a[][4]
={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
6.1.3 數(shù)組作為函數(shù)參數(shù)
使用數(shù)組名傳遞數(shù)據(jù)時,傳遞的是地址。
#include <iostream> using namespace std; void rowSum(int a[][4], int nRow) {for (int i = 0; i < nRow; i++) {for(int j = 1; j < 4; j++)a[i][0] += a[i][j];} } int main() { //主函數(shù)int table[3][4] = {{1, 2, 3, 4},{2, 3, 4, 5}, {3, 4, 5, 6}};//聲明并初始化數(shù)組//輸出數(shù)組元素for (int i = 0; i < 3; i++) {for (int j = 0; j < 4; j++)cout << table[i][j] << " ";cout << endl;}rowSum(table, 3); //調(diào)用子函數(shù),計算各行和//輸出計算結(jié)果for (int i = 0; i < 3; i++) cout << "Sum of row " << i << " is " << table[i][0] << endl;return 0; }6.1.4 對象數(shù)組
聲明:
類名 數(shù)組名[元素個數(shù)];
訪問方法:
通過下標訪問
數(shù)組名[下標].成員名
數(shù)組中每一個元素對象被創(chuàng)建時,系統(tǒng)都會調(diào)用類構(gòu)造函數(shù)初始化該對象。
//Point.h #ifndef _POINT_H #define _POINT_Hclass Point { //類的定義 public: //外部接口Point();Point(int x, int y);~Point();void move(int newX,int newY);int getX() const { return x; }int getY() const { return y; }static void showCount(); //靜態(tài)函數(shù)成員 private: //私有數(shù)據(jù)成員int x, y; }; #endif //_POINT_H//Point.cpp #include <iostream> #include "Point.h" using namespace std; Point::Point() {x = y = 0;cout << "Default Constructor called." << endl; } Point::Point(int x, int y) : x(x), y(y) {cout << "Constructor called." << endl; } Point::~Point() {cout << "Destructor called." << endl; } void Point::move(int newX,int newY) {cout << "Moving the point to (" << newX << ", " << newY << ")" << endl;x = newX;y = newY; } //6-3.cpp #include "Point.h" #include <iostream> using namespace std;int main() {cout << "Entering main..." << endl;Point a[2];for(int i = 0; i < 2; i++)a[i].move(i + 10, i + 20);cout << "Exiting main..." << endl;return 0; }運行結(jié)果:Entering main... Default Constructor called.//調(diào)用無參構(gòu)造函數(shù) Default Constructor called.//調(diào)用無參構(gòu)造函數(shù) Moving the point to (10, 20)//調(diào)用成員函數(shù)move,i=0 Moving the point to (11, 21)//調(diào)用成員函數(shù)move,i=1 Exiting main... Destructor called.//return 0調(diào)用析構(gòu)函數(shù) Destructor called.//return 0調(diào)用析構(gòu)函數(shù)6.2 指針
6.2.1 內(nèi)存空間的訪問方式
通過變量名或地址訪問。
6.2.2 指針變量的聲明
指針變量是用于存放內(nèi)存單元地址的。
指針變量的初始化:
存儲類型 數(shù)據(jù)類型 *指針名=初始地址;
6.2.3 與地址相關(guān)的運算“*”和“&”
*在聲明語句中表示聲明的是指針類型,而在執(zhí)行語句中表示訪問指針對象的內(nèi)容;
&在聲明語句中表示的是聲明的是引用類型,而在執(zhí)行語句中出現(xiàn)在=號后面表示取對象的地址。
6.2.4 指針的賦值
形式:指針名=地址
指針的類型是它所指向變量的類型,而不是指針本身數(shù)據(jù)值的類型,任何一個指針本身的數(shù)據(jù)值都是unsigned long int型。
指向常量的指針
int a; const int *p1 = &a; //p1是指向常量的指針 int b; p1 = &b; //正確,p1本身的值可以改變 *p1 = 1; //編譯時出錯,不能通過p1改變所指的對象指針類型的常量
int a; int * const p2 = &a; p2 = &b; //錯誤,p2是指針常量,值不能改變6.2.5 指針的運算
p1[n1]等價于*(p1 + n1)。
y=*px++ 相當于 y=*(px++) (*和++優(yōu)先級相同,自右向左運算)關(guān)系運算
指向相同類型數(shù)據(jù)的指針之間可以進行關(guān)系運算;
指針和0之間可以進行關(guān)系運算:p==0或p!=0。
賦值運算
向指針變量賦的值必須是地址常量或變量,不能是普通整數(shù)。但可以賦值為整數(shù)0,表示空指針,也就是不指向任何有效地址的指針。
6.2.6 用指針處理數(shù)組元素
聲明與賦值
int a[10], *pa; pa=&a[0]; 或 pa=a; 經(jīng)過上述聲明及賦值后: *pa就是a[0],*(pa+1)就是a[1],... ,*(pa+i)就是a[i]. a[i], *(pa+i), *(a+i), pa[i]都是等效的。 不能寫 a++,因為a是數(shù)組首地址是常量。6.2.7 指針數(shù)組
聲明形式:數(shù)據(jù)類型*數(shù)組名[下標表達式];
通過數(shù)組元素的地址可以輸出二維數(shù)組元素,形式如下:
*(*(array+i)+j)6.2.8 用指針作為函數(shù)參數(shù)
如果需要傳遞的數(shù)據(jù)存放在一個連續(xù)的區(qū)域里,使用指針作為函數(shù)參數(shù)只傳遞數(shù)據(jù)的起始地址,不用傳遞數(shù)據(jù)的值,可以減少開銷。
函數(shù)設(shè)置: void splitFloat(float x, int *intPart, float *fracPart) 主函數(shù)使用: splitFloat(x, &n, &f); //變量地址作為實參如果函數(shù)體中不需要通過指針改變指針所指對象的內(nèi)容,應(yīng)在參數(shù)表中將其聲明為指向常量的指針,這樣是的常對象被取地址后也可以作為該函數(shù)的參數(shù)。
void print(const int *p, int n);6.2.9 指針型函數(shù)
int* max(int a, int b);6.2.10 指向函數(shù)的指針
聲明形式:數(shù)據(jù)類型 (*函數(shù)指針名)(參數(shù)表);
#include <iostream> using namespace std; void printStuff(float) {cout << "This is the print stuff function."<< endl; }void printMessage(float data) {cout << "The data to be listed is "<< data << endl; }void printFloat(float data) {cout << "The data to be printed is "<< data << endl; } const float PI = 3.14159f; const float TWO_PI = PI * 2.0f; int main() { //主函數(shù)void (*functionPointer)(float); //函數(shù)指針printStuff(PI);functionPointer = printStuff; *functionPointer(PI); //函數(shù)指針調(diào)用functionPointer = printMessage;functionPointer(TWO_PI); //函數(shù)指針調(diào)用functionPointer(13.0); //函數(shù)指針調(diào)用functionPointer = printFloat;functionPointer(PI); //函數(shù)指針調(diào)用printFloat(PI);return 0; }6.2.11 對象指針
1.一般對象指針的概念
聲明形式
類名 *對象指針名;
訪問方法
對象指針名->成員名;
#include <iostream> using namespace std; class Point { //類的定義 public: //外部接口Point(int x = 0, int y = 0) : x(x), y(y) { } //構(gòu)造函數(shù)int getX() const { return x; } //返回xint getY() const { return y; } //返回y private: //私有數(shù)據(jù)int x, y; }; int main() { //主函數(shù)Point a(4, 5); //定義并初始化對象aPoint *p1 = &a; //定義對象指針,用a的地址將其初始化cout << p1->getX() << endl; //利用指針訪問對象成員cout << a.getX() << endl; //利用對象名訪問對象成員return 0; }2.this指針
this指針式隱含于每一個類的非靜態(tài)成員函數(shù)中的特殊指針(包括構(gòu)造函數(shù)和析構(gòu)函數(shù)),它用于指向正在被成員函數(shù)操作的對象。
return x;相當于:return this->x;
3.指向類的非靜態(tài)成員的指針
聲明一般形式
類型說明符 類名::*指針名; //聲明指向數(shù)據(jù)成員的指針 類型說明符 (類名::*指針名)(參數(shù)表) //聲明指向函數(shù)成員的指針賦值一般形式
指針名=&類名::數(shù)據(jù)成員名; //數(shù)據(jù)成員指針賦值 指針名=&類名::函數(shù)成員名; //函數(shù)成員指針賦值訪問類對象成員一般形式
/*訪問對象數(shù)據(jù)成員*/ 對象名.*類成員指針名; 或 對象指針名->*類成員指針名/*訪問對象函數(shù)成員*/ (對象名.*類成員指針名)(參數(shù)表) 或 (對象指針名->*類成員指針名)(參數(shù)表) #include <iostream> using namespace std;class Point{ public:Point(int x = 0, int y = 0):x(x), y(y){}int getX() const {return x;}int getY() const {return y;} private:int x, y; }; int main() {Point a(4, 5);Point *p1 = &a;int (Point::*funcPtr)() const = &Point::getX;cout << (a.*funcPtr)() << endl;cout << (p1->*funcPtr)() << endl;cout << a.getX() << endl;cout << p1->getX() << endl;return 0; }簡而言之,就是創(chuàng)建一個指針代理類中的一個成員函數(shù)。
4.指向類的靜態(tài)成員的指針
class Point{public:...static void showCount(){...}static int count; }; int Point::count=0; int main(){int *ptr=&Point::count;void (*funcPtr)()=Point::showCount;... }6.3 動態(tài)內(nèi)存分配
6.3.1動態(tài)申請內(nèi)存操作符new和釋放內(nèi)存操作符delete
#include<iostream> using namespace std; class Point {...}; int main() {cout << "Step one: " << endl;Point *ptr1 = new Point;//調(diào)用缺省構(gòu)造函數(shù)delete ptr1; //刪除對象,自動調(diào)用析構(gòu)函數(shù)cout << "Step two: " << endl;ptr1 = new Point(1,2); delete ptr1; return 0; }6.3.2申請和釋放動態(tài)數(shù)組
分配:new 類型名T [ 數(shù)組長度 ]
(數(shù)組長度可以是任何表達式,在運行時計算)
釋放:delete[] 數(shù)組名p
(釋放指針p所指向的數(shù)組。p必須是用new分配得到的數(shù)組首地址。)
拓展
用new動態(tài)創(chuàng)建一維數(shù)組時,在“[]”后還可以加“()”,但"()"內(nèi)不可以帶任何參數(shù)。
Point *ptr = new Point[2]; //創(chuàng)建對象數(shù)組,數(shù)組每個元素的初始化都是執(zhí)行new Point cout << ptr[0].getX() << endl; Point *ptr = new Point[2]();//創(chuàng)建對象數(shù)組,數(shù)組的初始化都是執(zhí)行new Point() cout << ptr[0].getX() << endl;①若Point的構(gòu)造函數(shù)設(shè)置了默認值,則兩個輸出結(jié)果相同。
Point():x(0),y(0){}②若Point的構(gòu)造函數(shù)沒有設(shè)置默認值,則兩個輸出結(jié)果都是未定義的數(shù)。
Point(){}所以只要定義了構(gòu)造函數(shù),無論new的時候后面加不加“()”,都會調(diào)用自身已有的構(gòu)造函數(shù)。
③若類中不定義構(gòu)造函數(shù),而使用編譯器的缺省構(gòu)造函數(shù),則前者輸出未定義的數(shù),后者輸出0。
④如果是調(diào)用內(nèi)置類型,則和③結(jié)果相同
int *a = new int; cout << a <<endl;//結(jié)果為未定義的數(shù) int *b = new int(); cout << b <<endl;//結(jié)果為06.3.3 動態(tài)創(chuàng)建多維數(shù)組
char (*fp)[3]; fp = new char[2][3];6.3.4動態(tài)數(shù)組類
需知:assert函數(shù)(斷言)當判斷括號內(nèi)條件表達式值不為true時,程序中止,assert只在調(diào)試模式(debug模式)下生效,在發(fā)行模式(release模式)不執(zhí)行任何操作。
#include <iostream> #include <cassert> using namespace std; class Point {...}; class ArrayOfPoints { //動態(tài)數(shù)組類 public:ArrayOfPoints(int size) : size(size) {points = new Point[size];}~ArrayOfPoints() {cout << "Deleting..." << endl;delete[] points; }Point& element(int index) {//返回“引用”可以用來操作封裝數(shù)組對象內(nèi)部的數(shù)組元素。//如果返回“值”則只是返回了一個“副本”,通過“副本”是無法操作原來數(shù)組中的元素的assert(index >= 0 && index < size); return points[index];} private:Point *points; //指向動態(tài)數(shù)組首地址int size; //數(shù)組大小 }; int main() {int count;cout << "Please enter the count of points: ";cin >> count;ArrayOfPoints points(count); //創(chuàng)建對象數(shù)組//通過訪問數(shù)組元素的成員points.element(0).move(5, 0);//通過類訪問數(shù)組元素的成員points.element(1).move(15, 20); return 0; }6.4 用vector創(chuàng)建數(shù)組對象
vector是C++標準庫提供的被封裝的動態(tài)數(shù)組,而且可以具有各種類型。
vector是一個類模板,不是一個類。
使用vector可以很好的檢測下標越界的錯誤。
使用vector定義動態(tài)數(shù)組的形式
vector <元素類型> 數(shù)組對象名(數(shù)組長度); 例: vector<int> arr(5)//建立大小為5的int數(shù)組細節(jié):使用vector定義的數(shù)組對象的所有元素都會被初始化,如果元素類型是基本數(shù)據(jù)類型,則初始化的值都是0;如果元素類型是類類型,則會調(diào)用類的默認構(gòu)造函數(shù)進行初始化,因此,使用vector定義類動態(tài)數(shù)組前,需要保證類具有默認構(gòu)造函數(shù)。
只能設(shè)置所有元素為相同初值,形式如下
vector <元素類型> 數(shù)組對象名(數(shù)組長度,元素初值);使用vector定義的數(shù)組對象名和普通的數(shù)組對象名不同的是,使用vector定義的數(shù)組對象名表示的是數(shù)組對象而不是數(shù)組的首地址,因為數(shù)組對象不是數(shù)組,而是封裝了數(shù)組的對象。
#include <iostream> #include <vector> using namespace std;//計算數(shù)組arr中元素的平均值 double average(const vector<double> &arr) {double sum = 0;for (unsigned i = 0; i < arr.size(); i++)sum += arr[i];return sum / arr.size(); }//arr.size()返回數(shù)組的大小 int main() {unsigned n;cout << "n = ";cin >> n;vector<double> arr(n); //創(chuàng)建數(shù)組對象cout << "Please input " << n << " real numbers:" << endl;for (unsigned i = 0; i < n; i++)cin >> arr[i];cout << "Average = " << average(arr) << endl;return 0; }聲明函數(shù)時使用vector定義的數(shù)組作為形參形式
void outputVector( const vector <int> & ); void inputVector( vector <int> & );6.5 深復制和淺復制
深拷貝和淺拷貝最根本的區(qū)別在于是否真正獲取一個對象的復制實體,而不是引用。
6.5.1 淺復制
淺復制只是創(chuàng)建另外一個指針指向和“復制”的指針相同已存在的地址,并沒有創(chuàng)建副本。
#include <iostream> #include <cassert> using namespace std; class Point {...}; class ArrayOfPoints {...}; int main() {int count;cout << "Please enter the count of points: ";cin >> count;ArrayOfPoints pointsArray1(count); //創(chuàng)建對象數(shù)組pointsArray1.element(0).move(5,10);pointsArray1.element(1).move(15,20);ArrayOfPoints pointsArray2(pointsArray1); //創(chuàng)建副本cout << "Copy of pointsArray1:" << endl;cout << "Point_0 of array2: " << pointsArray2.element(0).getX() << ", "<< pointsArray2.element(0).getY() << endl;cout << "Point_1 of array2: " << pointsArray2.element(1).getX() << ", "<< pointsArray2.element(1).getY() << endl;pointsArray1.element(0).move(25, 30);pointsArray1.element(1).move(35, 40);cout << "After the moving of pointsArray1:" << endl;cout << "Point_0 of array2: " << pointsArray2.element(0).getX() << ", "<< pointsArray2.element(0).getY() << endl;cout << "Point_1 of array2: " << pointsArray2.element(1).getX() << ", "<< pointsArray2.element(1).getY() << endl;return 0; } 運行結(jié)果如下: Please enter the number of points:2 Default Constructor called. Default Constructor called. Copy of pointsArray1: Point_0 of array2: 5, 10 Point_1 of array2: 15, 20 After the moving of pointsArray1: Point_0 of array2: 25, 30 Point_1 of array2: 35, 40 Deleting... Destructor called. Destructor called. Deleting...接下來程序出現(xiàn)異常,也就是運行錯誤。
原因分析:
在程序結(jié)束前,需要調(diào)用pointsArray1和pointsArray2的析構(gòu)函數(shù),但是兩個對象共用同一個內(nèi)存地址,所以該空間被釋放兩次,導致運行錯誤。
6.5.2 深復制
深復制創(chuàng)建一個指針,并且申請了新的內(nèi)存,讓這個指針指向新的內(nèi)存地址,將需要復制的對象的每一個屬性都復制了一遍而且兩個對象的相同屬性使用兩個不同的內(nèi)存地址,也就是說兩個對象之間不存在任何一個公用地址。
#include <iostream> #include <cassert> using namespace std; class Point {...}; class ArrayOfPoints { public:ArrayOfPoints(const ArrayOfPoints& pointsArray);... }; ArrayOfPoints::ArrayOfPoints(const ArrayOfPoints& v) {size = v.size;points = new Point[size];for (int i = 0; i < size; i++)points[i] = v.points[i]; } int main() {//同6.5.1 }6.6 字符串
6.6.1 用字符數(shù)組存儲和處理字符串
//以下三條語句具有等價的作用: char str[8] = { 'p', 'r', 'o', 'g', 'r', 'a', 'm', '\0' }; char str[8] = "program"; char str[] = "program";用字符數(shù)組表示字符串的缺點
① 執(zhí)行連接、拷貝、比較等操作,都需要顯式調(diào)用庫函數(shù),很麻煩。
② 當字符串長度很不確定時,需要用new動態(tài)創(chuàng)建字符數(shù)組,最后要用delete釋放,很繁瑣。
③ 字符串實際長度大于為它分配的空間時,會產(chǎn)生數(shù)組下標越界的錯誤。
6.6.2 string類
string類中封裝了串的屬性并提供了一系列允許訪問這些屬性的函數(shù)。
string();//默認構(gòu)造函數(shù) string(const string& rhs);//復制構(gòu)造函數(shù) string(const char*s);//用指針s指向的字符串常量初始化string類的對象 string(const string& rhs,unsigned int pos,unsigned int n);//從rhs的pos位置開始取n個字符,用來初始化string類的對象 string(const char* s,unsigned int n);//用指針s所指向的字符串中的前n個字符初始化string類的對象 string(unsigned int n,char c);//將參數(shù)c中的字符重復n次,用來初始化string類的對象常用成員函數(shù)介紹
string getline (cin,s);//輸入字符串s string getline (cin,s,",")//輸入字符串s,以,為輸入結(jié)束的標志 string append (const char* s);//將s添加在本字符串尾 string assign (const char* s);//賦值,將s所指向的字符串賦值給本對象 int compare (const string &str) const;//比較本串與str串的大小,a<str返回負值,a>str返回正值,相等返回0細節(jié):嚴格來說,string不是一個獨立的類,而是類模板basic_string的一個特化實例。
6.8 深度搜索
第七章 繼承與派生
7.1 類的繼承與派生
7.1.1 概念
類的繼承,是新的類從已有類那里得到已有的特性——從已有類產(chǎn)生新類的過程就是類的派生。
基類或父類(直接基類、間接基類)——派生類或子類。
7.1.2 派生類的定義
繼承方式規(guī)定了如何訪問從基類繼承的成員,如果沒有給出繼承方式關(guān)鍵字,則默認為private。
單繼承 class 派生類名:繼承方式 基類名{}; 多繼承 class 派生類名:繼承方式1 基類名1,繼承方式2 基類名2,...{};派生類成員指除了從基類繼承來的所有成員之外,新增加的數(shù)據(jù)和函數(shù)成員。
7.1.3 派生類生成過程
1.吸收基類成員
派生類包含了它的全部基類中除構(gòu)造函數(shù)和析構(gòu)函數(shù)之外的所有成員。
2.改造基類成員
①基類成員的訪問控制問題
主要依靠派生類定義時的繼承方式控制。
②對基類數(shù)據(jù)或函數(shù)成員的覆蓋或隱藏。
隱藏:也稱作同名隱藏,派生類聲明一個和某基類成員同名的新成員(成員函數(shù)的話參數(shù)表也相同,參數(shù)不同屬于重載),之后在派生類或通過派生類的對象,只能訪問到派生類中聲明的同名成員,而不能訪問到基類的同名成員。
3.添加新的成員
繼承與派生機制的核心,保證派生類在功能上有所發(fā)展的關(guān)鍵。
7.2 訪問控制
1.公有繼承
基類的public和protected成員的訪問屬性在派生類中保持不變,但基類的private成員不可直接訪問。
派生類中的成員函數(shù)可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員。
通過派生類的對象只能訪問基類的public成員。
#include <iostream> using namespace std; class Point { public:void initPoint(float x=0,float y=0){this->x=x;this->y=y;}//設(shè)置x,y值void movePoint(float offX,float offY){x+=offX;y+=offY;}//移動點函數(shù)float getX(){return x;}float getY(){return y;} private:float x,y; }; class Rectangle:public Point { public:void initRectangle(float x,float y,float w,float h){initPoint(x,y);this->w=w;this->h=h;}float getW(){return w;}float getH(){return h;} private:float w,h; }; int main() {Rectangle rect; //定義Rectangle類的對象rect.initRectangle(2, 3, 20, 10); //設(shè)置矩形的數(shù)據(jù)rect.movePoint(3,2); //移動矩形位置cout << "The data of rect(x,y,w,h): " << endl;cout << rect.getX() <<", " //輸出矩形的特征參數(shù)<< rect.getY() << ", "<< rect.getW() << ", "<< rect.getH() << endl;return 0; }Rectangle類的對象rect可以直接訪問基類的公有成員movePoint(…)、getX()、getY()。
2.私有繼承
基類的public和protected成員都以private身份出現(xiàn)在派生類中,但基類的private成員不可直接訪問。
派生類中的成員函數(shù)可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員。
通過派生類的對象不能直接訪問基類中的任何成員。
#include <iostream> using namespace std; class Point { public:void initPoint(float x=0,float y=0){this->x=x;this->y=y;}//設(shè)置x,y值void movePoint(float offX,float offY){x+=offX;y+=offY;}//移動點函數(shù)float getX(){return x;}float getY(){return y;} private:float x,y; }; class Rectangle:private Point { public:void initRectangle(float x,float y,float w,float h){initPoint(x,y);this->w=w;this->h=h;}void movePoint(float offX,float offY){Point::movePoint(offX,offY);}//新增共有函數(shù)成員調(diào)用基類共有成員函數(shù)float getX(){return Point::getX();}//新增共有函數(shù)成員調(diào)用基類共有成員函數(shù)float getY(){return Point::getY();}//新增共有函數(shù)成員調(diào)用基類共有成員函數(shù)float getW(){return w;}float getH(){return h;} private:float w,h; }; int main() {Rectangle rect; //定義Rectangle類的對象rect.initRectangle(2, 3, 20, 10); //設(shè)置矩形的數(shù)據(jù)rect.movePoint(3,2); //移動矩形位置cout << "The data of rect(x,y,w,h): " << endl;cout << rect.getX() <<", " //輸出矩形的特征參數(shù)<< rect.getY() << ", "<< rect.getW() << ", "<< rect.getH() << endl;return 0; }Rectangle類的對象rect不可以直接訪問基類的公有成員movePoint(…)、getX()、getY(),如果使用公有繼承中的例子,就會出現(xiàn)錯誤:error: ‘Point’ is not an accessible base of ‘Rectangle’|,所以需要在派生類Rectangle中新增成員函數(shù)來調(diào)用基類公有成員函數(shù)。
3.保護繼承
基類的public和protected成員都以protected身份出現(xiàn)在派生類中,但基類的private成員不可直接訪問。
派生類中的成員函數(shù)可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員。
通過派生類的對象不能直接訪問基類中的任何成員。
#include <iostream> using namespace std; class Point { public:void initPoint(float x=0,float y=0){this->x=x;this->y=y;}//設(shè)置x,y值void movePoint(float offX,float offY){x+=offX;y+=offY;}//移動點函數(shù)float getX(){return x;}float getY(){return y;} protected:float z; private:float x,y; }; class Rectangle:protected Point { public:void initRectangle(float x,float y,float w,float h){initPoint(x,y);this->w=w;this->h=h;z=20.1;}void movePoint(float offX,float offY){Point::movePoint(offX,offY);}//移動點函數(shù)float getX(){return Point::getX();}float getY(){return Point::getY();}float getZ(){return z;}//直接派生類可以直接訪問基類保護成員float getW(){return w;}float getH(){return h;} private:float w,h; }; int main() {Point p;p.z=2;//編譯錯誤,error: 'float Point::z' is protected within this context|Rectangle rect; //定義Rectangle類的對象rect.initRectangle(2, 3, 20, 10); //設(shè)置矩形的數(shù)據(jù)rect.movePoint(3,2); //移動矩形位置cout << "The data of rect(x,y,w,h): " << endl;cout << rect.getX() <<", " //輸出矩形的特征參數(shù)<< rect.getY() << ", "<< rect.getW() << ", "<< rect.getH() << endl;cout << "z="<<rect.getZ() << endl;return 0; }4.私有繼承和保護繼承區(qū)別
對派生類來說,在直接派生類中所有成員訪問屬性和私有繼承是一樣的。
假設(shè)a類的派生類b類有了派生類c類:
當b類私有繼承a類,c類的成員和對象都不能訪問間接從a類繼承的成員;
當b類保護繼承a類,那么a類的public和protected成員在b類中都是protected成員,c類有可能訪問間接從a類繼承來的成員
#include <iostream> using namespace std; class Point { public:void initPoint(float x=0,float y=0){this->x=x;this->y=y;}//設(shè)置x,y值void movePoint(float offX,float offY){x+=offX;y+=offY;}//移動點函數(shù)float getX(){return x;}float getY(){return y;} protected:float z; private:float x,y; }; class Rectangle:protected Point { public:void initRectangle(float x,float y,float w,float h){initPoint(x,y);this->w=w;this->h=h;z=20.1;}void movePoint(float offX,float offY){Point::movePoint(offX,offY);}//移動點函數(shù)float getX(){return Point::getX();}float getY(){return Point::getY();}float getW(){return w;}float getH(){return h;} private:float w,h; }; class Square:protected Rectangle { public:void initSquare(float x,float y,float w,float h){initRectangle(x,y,w,h);Rectangle::z=30.1;}void movePoint(float offX,float offY){Rectangle::movePoint(offX,offY);}//移動點函數(shù)float getX(){return Point::getX();}//可以訪問間接基類Point的非私有成員函數(shù)float getY(){return Point::getY();}//可以訪問間接基類Point的非私有成員函數(shù)float getZ(){return z;}//可以訪問間接基類Point的非私有數(shù)據(jù)成員float getW(){return Rectangle::getW();}//可以訪問直接基類Rectangle的非私有成員函數(shù)float getH(){return Rectangle::getH();}}; int main() {Square sq; //定義Square類的對象sq.initSquare(2, 3, 20, 10); //設(shè)置矩形的數(shù)據(jù)sq.movePoint(3,2); //移動矩形位置cout << "The data of rect(x,y,w,h): " << endl;cout << sq.getX() <<", " //輸出矩形的特征參數(shù)<< sq.getY() << ", "<< sq.getW() << ", "<< sq.getH() << endl;cout << "z="<<sq.getZ() << endl;return 0; } 運行結(jié)果: The data of rect(x,y,w,h): 2,3,20,10 z=30.17.3 類型兼容規(guī)則
類型兼容規(guī)則是指在需要基類對象的任何地方,都可以使用公有派生類的對象來替代,分為以下情況:
①派生類的對象可以隱含轉(zhuǎn)換為基類對象。
②派生類的對象可以初始化基類的引用。
③派生類的指針可以隱含轉(zhuǎn)換為基類的指針。
在替代之后,派生類對象可以作為基類的對象使用,只能使用從基類繼承而來的成員。
#include <iostream> using namespace std; class Base1 { //基類Base1定義 public:void display() const {cout << "Base1::display()" << endl;}}; class Base2: public Base1 { //公有派生類Base2定義 public:void display() const {cout << "Base2::display()" << endl;}}; class Derived: public Base2 { //公有派生類Derived定義 public:void display() const {cout << "Derived::display()" << endl;} }; void fun(Base1 *ptr) { //參數(shù)為指向基類對象的指針ptr->display(); //"對象指針->成員名"} int main() { //主函數(shù)Base1 base1; //聲明Base1類對象Base2 base2; //聲明Base2類對象Derived derived; //聲明Derived類對象fun(&base1); //用Base1對象的指針調(diào)用fun函數(shù)fun(&base2); //用Base2對象的指針調(diào)用fun函數(shù),在fun函數(shù)中轉(zhuǎn)換為Base1類的指針,所以只能使用從Base1類繼承的display()fun(&derived); //用Derived對象的指針調(diào)用fun函數(shù),在fun函數(shù)中轉(zhuǎn)換為Base1類的指針,所以只能使用從Base1類繼承的display()return 0; } 運行結(jié)果: Base1::display() Base1::display() Base1::display()7.4 派生類的構(gòu)造、析構(gòu)函數(shù)
7.4.1 構(gòu)造函數(shù)
在繼承時不會繼承基類的構(gòu)造函數(shù),所以就需要在構(gòu)造派生類的對象時,人為地調(diào)用基類的構(gòu)造函數(shù)對于基類的成員對象和新增成員對象初始化。
派生類的構(gòu)造函數(shù)一般語法形式:
派生類名::派生類名(參數(shù)表): 基類名1(基類1初始參數(shù)表),...,基類名n(基類n初始參數(shù)表), 成員對象名1(成員對象1初始化參數(shù)表),...,成員對象名n(成員對象n初始化參數(shù)表){...}先調(diào)用基類的構(gòu)造函數(shù),然后調(diào)用內(nèi)嵌對象的構(gòu)造函數(shù),基類構(gòu)造函數(shù)的調(diào)用是按照派生類定義的順序,調(diào)用順序按照它們被繼承時聲明的順序(從左向右),內(nèi)嵌對象構(gòu)造函數(shù)調(diào)用按照在類中聲明的先后順序。
#include <iostream> using namespace std; class Base1 { //基類Base1,構(gòu)造函數(shù)有參數(shù) public:Base1(int i) { cout << "Constructing Base1 " << i << endl; }}; class Base2 { //基類Base2,構(gòu)造函數(shù)有參數(shù) public:Base2(int j) { cout << "Constructing Base2 " << j << endl; }}; class Base3 { //基類Base3,構(gòu)造函數(shù)無參數(shù) public:Base3() { cout << "Constructing Base3 *" << endl; }}; class Derived: public Base2, public Base1, public Base3 { //派生新類Derived,注意基類名的順序 public: //派生類的公有成員Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b){ }//注意基類名的個數(shù)與順序,//注意成員對象名的個數(shù)與順序 private: //派生類的私有成員對象Base1 member1;Base2 member2;Base3 member3; }; int main() {Derived obj(1, 2, 3, 4);return 0; } 運行結(jié)果: constructing Base2 2//Base2(b) constructing Base1 1//Base1(a) constructing Base3 *//Base3() constructing Base1 3//Base1 member1 constructing Base2 4//Base2 member2 constructing Base3 *//Base3 member37.4.2 復制構(gòu)造函數(shù)
派生類的復制構(gòu)造函數(shù)會自動調(diào)用基類的復制構(gòu)造函數(shù),聲明形式:
Derived::Derived(const Derived &v):Base(v){...}7.4.3 析構(gòu)函數(shù)
析構(gòu)函數(shù)的執(zhí)行順序和構(gòu)造函數(shù)完全相反,先執(zhí)行析構(gòu)函數(shù)的函數(shù)體,然后對派生類新增的類類型的成員對象進行清理,最后對所有從基類繼承來的成員進行清理。
#include <iostream> using namespace std; class Base1 { //基類Base1,構(gòu)造函數(shù)有參數(shù) public:Base1(int i) {this->i=i; cout << "Constructing Base1 " << i << endl; }~Base1() { cout << "Destructing Base1 " << i <<endl; } private:int i; }; class Base2 { //基類Base2,構(gòu)造函數(shù)有參數(shù) public:Base2(int j) {this->j=j; cout << "Constructing Base2 " << j << endl; }~Base2() { cout << "Destructing Base2 " << j <<endl; } private:int j; }; class Base3 { //基類Base3,構(gòu)造函數(shù)無參數(shù) public:Base3() { cout << "Constructing Base3 *" << endl; }~Base3() { cout << "Destructing Base3-*-*-*-" << endl; } }; class Derived: public Base2, public Base1, public Base3 { //派生新類Derived,注意基類名的順序 public: //派生類的公有成員Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) { }//注意基類名的個數(shù)與順序,注意成員對象名的個數(shù)與順序~Derived(){cout << "Destructing Derived" << endl;} private: //派生類的私有成員對象Base1 member1;Base2 member2;Base3 member3; }; int main() {Derived obj(1, 2, 3, 4);return 0; }運行結(jié)果: constructing Base2 2 //Base2(b) constructing Base1 1 //Base1(a) constructing Base3 * //Base3() constructing Base1 3 //Base1 member1(c) constructing Base2 4 //Base2 member2(d) constructing Base3 * //Base3 member3 Destructing Derived //執(zhí)行派生類析構(gòu)函數(shù)的函數(shù)體 Destructing Base3-*-*-*- //清理派生類新增的類類型的成員對象member3.~Base3() Destructing Base2 4 //清理派生類新增的類類型的成員對象member2.~Base2() Destructing Base1 3 //清理派生類新增的類類型的成員對象member1.~Base1() Destructing Base3-*-*-*- //清理從基類繼承來的成員Base3.~Base3() Destructing Base1 1 //清理從基類繼承來的成員Base1(1).~Base1() Destructing Base2 2 //清理從基類繼承來的成員Base2(2).~Base2()7.5 派生類成員的標識與訪問
7.5.1 作用域分辨符(::)
如果派生類中聲明了與基類成員函數(shù)同名的新函數(shù),即使函數(shù)的參數(shù)表不同,從基類繼承的同名函數(shù)的所有重載形式也都會被隱藏,如果要訪問被隱藏的成員,就需要使用作用域分辨符和基類名來限定。
在多繼承中,如果派生類沒有聲明同名成員,且各基類中存在相同名稱的成員,則派生類對象名.成員名或派生類對象指針->成員名,會產(chǎn)生二義性。
細節(jié):派生類中定義了基類同名成員,但是參數(shù)表不同的話,不屬于函數(shù)重載,但是調(diào)用該基類成員也需要基類名::成員名。
#include <iostream> using namespace std; class Base1 { //定義基類Base1 public:int var;void fun() { cout << "Member of Base1" << endl; } }; class Base2 { //定義基類Base2 public:int var;void fun() { cout << "Member of Base2" << endl; } }; class Derived: public Base1, public Base2 { //定義派生類Derived public:int var; //同名數(shù)據(jù)成員 }; int main() {Derived d;Derived *p = &d;d.var = 1; //對象名.成員名標識//d.fun(); //編譯錯誤,d.Base1::var = 2; //作用域操作符標識d.Base1::fun(); //訪問Base1基類成員p->Base2::var = 3; //作用域操作符標識p->Base2::fun(); //訪問Base2基類成員return 0; }運行結(jié)果: Member of Base1 Member of Base2在Derived類中新增兩個成員函數(shù),一個解決二義性,一個與基類同名但參數(shù)表不同,如下
#include <iostream> using namespace std; class Base1 { //定義基類Base1 public:int var;void fun() { cout << "Member of Base1" << endl; } }; class Base2 { //定義基類Base2 public:int var;void fun() { cout << "Member of Base2" << endl; } }; class Derived: public Base1, public Base2 { //定義派生類Derived public:int var; //同名數(shù)據(jù)成員void fun(){Base1::fun();}//或using Base1::fun;void fun(int i){cout << "Member of Derived" << endl;} }; int main() {Derived d;Derived *p = &d;d.var = 1; //對象名.成員名標識d.fun(1); //訪問Derived類成員d.fun(); //通過訪問Derived類成員,訪問Base1基類成員d.Base1::var = 2; //作用域操作符標識d.Base1::fun(); //訪問Base1基類成員p->Base2::var = 3; //作用域操作符標識p->Base2::fun(); //訪問Base2基類成員 return 0; }運行結(jié)果: Member of Derived Member of Base1 Member of Base1 Member of Base2多繼承同名隱藏實例
#include <iostream> using namespace std; class Base0 { //定義基類Base0 public:int var0;void fun0() { cout << "Member of Base0" << endl; }}; class Base1: public Base0 { //定義派生類Base1 public: //新增外部接口int var1;}; class Base2: public Base0 { //定義派生類Base2 public: //新增外部接口int var2;}; class Derived: public Base1, public Base2 {//定義派生類Derived public: //新增外部接口int var;void fun() { cout << "Member of Derived" << endl; }};int main() { //程序主函數(shù)Derived d; //定義Derived類對象dd.Base1::var0 = 2; //使用直接基類d.Base1::fun0();d.Base2::var0 = 3; //使用直接基類d.Base2::fun0();return 0; }運行結(jié)果: Member of Base0 Member of Base0上述派生類對象在內(nèi)存中擁有兩份var0的副本,可以存放不同的值,可以通過Base1和Base2調(diào)用Base0的構(gòu)造函數(shù)初始化,或者使用作用域分辨符通過直接基類名限定類分別訪問。
上述派生類對象在內(nèi)存中始終只有一份fun0的副本,之所以需要通過基類名Base1或Base2加以限定,是因為調(diào)用非靜態(tài)成員函數(shù)總是針對特定的對象,執(zhí)行函數(shù)調(diào)用時需要將指向該類的一個對象的指針作為隱含的參數(shù)傳遞給被調(diào)函數(shù)來初始化this指針,上述例子中,因為有兩個Base0類的子對象,所以作用域分辨符用Base1或Base2限定,哪個派生類的Base0子對象被調(diào)用。
7.5.2 虛基類
為了解決上面的問題,我們將共同基類設(shè)置為虛基類,這時從不同路徑繼承過來的同名數(shù)據(jù)成員在內(nèi)存中就只有一個副本,同一個函數(shù)名也只有一個映射。
虛基類的聲明式在派生類的定義過程中進行的,語法形式為
class 派生類名:virtual 繼承方式 基類名 #include <iostream> using namespace std; class Base0 { //定義基類Base0 public:int var0;void fun0() { cout << "Member of Base0" << endl; }}; class Base1: virtual public Base0 { //定義派生類Base1 public: //新增外部接口int var1;}; class Base2: virtual public Base0 { //定義派生類Base2 public: //新增外部接口int var2;}; class Derived: public Base1, public Base2 {//定義派生類Derived public: //新增外部接口int var;void fun() { cout << "Member of Derived" << endl; }};int main() { //程序主函數(shù)Derived d; //定義Derived類對象dd.var0 = 2; //直接訪問虛基類的數(shù)據(jù)成員d.fun0(); //直接訪問虛基類的函數(shù)成員return 0; }運行結(jié)果: Member of Base0不使用虛基類可以容納更多數(shù)據(jù),使用虛基類更簡潔,內(nèi)存空間更節(jié)省。
7.5.3 虛基類及其派生類構(gòu)造函數(shù)
#include <iostream> using namespace std; class Base0 { //定義基類Base0 public:Base0(int var) : var0(var) { }int var0;void fun0() { cout << "Member of Base0" << endl; }}; class Base1: virtual public Base0 {//定義派生類Base1 public: //新增外部接口Base1(int var) : Base0(var) { }int var1;}; class Base2: virtual public Base0 {//定義派生類Base2 public: //新增外部接口Base2(int var) : Base0(var) { }int var2;}; class Derived: public Base1, public Base2 {//定義派生類Derived public: //新增外部接口Derived(int var) : Base0(var), Base1(var), Base2(var) { }int var;void fun() { cout << "Member of Derived" << endl; }}; int main() { //程序主函數(shù)Derived d(1); //定義Derived類對象dd.var0 = 2; //直接訪問虛基類的數(shù)據(jù)成員d.fun0(); //直接訪問虛基類的函數(shù)成員return 0; }單看代碼,建立Derived類對象d時需要調(diào)用Base0構(gòu)造函數(shù)并初始化var0、Base1和Base2構(gòu)造函數(shù)Base1()和Base2(),那么豈不是要初始化3次從虛基類繼承來的var0。
對此C++給出了解決方法,將建立對象時所指定的類稱為當時的最遠派生類,當此類對象含有從虛基類繼承來的成員,則只由最遠派生類的構(gòu)造函數(shù)調(diào)用虛基類的構(gòu)造函數(shù)進行初始化,而忽略其他類對虛基類構(gòu)造函數(shù)的調(diào)用。
拓展
構(gòu)造一個類的對象的一般順序是:
7.8 深度探索
第八章 多態(tài)性
8.1 多態(tài)性概述
多態(tài)是指同樣的消息被不同類型的對象接收時導致不同的行為。
8.1.1 多態(tài)的類型
面向?qū)ο蟮亩鄳B(tài)性分為四種類型:
專用多態(tài):重載多態(tài)、強制多態(tài)。
通用多態(tài):包含多態(tài)、參數(shù)多態(tài)。
根據(jù)多態(tài)性作用的時機可以分為:編譯時的多態(tài)、運行時的多態(tài)。
8.1.2 多態(tài)的實現(xiàn)
綁定是指將一個標識符名和一個存儲地址聯(lián)系在一起的過程。
編譯時的多態(tài)——綁定工作在編譯連接階段完成的情況稱為靜態(tài)綁定。
運行時的多態(tài)——綁定工作在程序運行階段完成的情況稱為動態(tài)綁定
8.2 運算符重載
運算符重載是對已有的運算符賦予多重含義,使同一個運算符作用與不同類型的數(shù)據(jù)時導致不同的行為。
運算符重載的本質(zhì)就是函數(shù)重載。
8.2.1 運算符重載的規(guī)則
1.只能重載C++中已經(jīng)有的運算符
不能重載的運算符舉例:類屬關(guān)系運算符“.”、成員指針運算符“.*”、作用域分辨符“::”、三目運算符“?:”
2.重載之后運算符的優(yōu)先級和結(jié)合性都不會改變
3.運算符重載是針對新類型數(shù)據(jù)的實際需要,對原有運算符進行適當?shù)母脑?/p>
兩種重載方式
重載為類的非靜態(tài)成員函數(shù)和重載為非成員函數(shù)
聲明形式
函數(shù)類型 operator 運算符(形參) {...... }當運算符重載為成員函數(shù)的時候,函數(shù)的參數(shù)個數(shù)要比原操作個數(shù)少一個(后置“++”,“–”除外),因為第一個操作數(shù)會被作為函數(shù)調(diào)用的目的對象,因此無需出現(xiàn)在參數(shù)表中,函數(shù)體中可以直接訪問第一個操作數(shù)的成員;
當運算符重載為非成員函數(shù)的時候,函數(shù)的參數(shù)個數(shù)和原操作個數(shù)相同,且至少應(yīng)該有一個自定義類型的形參,因為運算符的所有操作數(shù)必須顯式通過參數(shù)傳遞。
8.2.2 運算符重載為成員函數(shù)
雙目運算符 B
如果要重載 B 為類成員函數(shù),使之能夠?qū)崿F(xiàn)表達式 oprd1 B oprd2,其中 oprd1 為A 類對象,則 B 應(yīng)被重載為 A 類的成員函數(shù),形參類型應(yīng)該是 oprd2 所屬的類型。
經(jīng)重載后,表達式 oprd1 B oprd2 相當于 oprd1.operator B(oprd2)。
前置單目運算符U
如果要重載為類的成員函數(shù),使之能夠?qū)崿F(xiàn)表達式U oprd,其中oprd為A類的對象,則U應(yīng)當重載為A類的成員函數(shù),函數(shù)沒有形參。
經(jīng)重載后,表達式U oprd相當于oprd.operator U()。
后置單目運算符++、–
如果要重載為類的成員函數(shù),使之能夠?qū)崿F(xiàn)表達式oprd++或oprd–,其中oprd為A類的對象,則U應(yīng)當重載為A類的成員函數(shù),函數(shù)要帶有一個整型int形參。
經(jīng)重載后,表達式oprd++和oprd–相當于oprd.operator++(0)和oprd.operator–(0)。
#include <iostream> using namespace std; class Clock { //時鐘類定義 public: //外部接口Clock(int hour = 0, int minute = 0, int second = 0);void showTime() const;Clock& operator ++ (); //前置單目運算符重載Clock operator ++ (int); //后置單目運算符重載 private: //私有數(shù)據(jù)成員int hour, minute, second; };Clock::Clock(int hour/* = 0 */, int minute/* = 0 */, int second/* = 0 */) { if (0 <= hour && hour < 24 && 0 <= minute && minute < 60&& 0 <= second && second < 60) {this->hour = hour;this->minute = minute;this->second = second;} elsecout << "Time error!" << endl; } void Clock::showTime() const { //顯示時間函數(shù)cout << hour << ":" << minute << ":" << second << endl; }Clock & Clock::operator ++ () { //前置單目運算符重載函數(shù)second++;if (second >= 60) {second -= 60;minute++;if (minute >= 60) {minute -= 60;hour = (hour + 1) % 24;}}return *this; }Clock Clock::operator ++ (int) { //后置單目運算符重載//注意形參表中的整型參數(shù)Clock old = *this;++(*this); //調(diào)用前置“++”運算符return old; } int main() {Clock myClock(23, 59, 59);cout << "First time output: ";myClock.showTime();cout << "Show myClock++: ";(myClock++).showTime();cout << "Show ++myClock: ";(++myClock).showTime();return 0; }運行結(jié)果: First time output: 23:59:59 Show myClock++: 23:59:59 Show ++myClock: 0:0:1前置單目運算符和后置單目運算符最主要的區(qū)別就在于重載函數(shù)的形參。
(對于函數(shù)參數(shù)表中并未使用的參數(shù),C++允許不給出參數(shù)名,所有后置單目運算符重載的int形參可以只給出類型名)
8.2.3 運算符重載為非成員函數(shù)
函數(shù)的形參代表依自左至右次序排列的各操作數(shù)。
如果在運算符的重載函數(shù)中需要操作某類對象的私有成員或保護成員,可以將此函數(shù)聲明為該類的友元,但不要盲目聲明為類的友元函數(shù)。
如果不聲明為友元函數(shù),則該函數(shù)僅依賴于類的接口,只要類不變,函數(shù)的實現(xiàn)就不需要變;如果聲明為友元函數(shù),該函數(shù)就依賴于類的實現(xiàn),即使類的接口不變,只要類的私有數(shù)據(jù)成員發(fā)生變化,該函數(shù)的實現(xiàn)就必須變化。
雙目運算符 B
重載后,表達式oprd1 B oprd2 等同于operator B(oprd1,oprd2 )
//8_3.cpp #include <iostream> using namespace std;class Complex { //復數(shù)類定義 public: //外部接口Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { } //構(gòu)造函數(shù)friend Complex operator + (const Complex &c1, const Complex &c2); //運算符+重載friend Complex operator - (const Complex &c1, const Complex &c2); //運算符-重載friend ostream & operator << (ostream &out, const Complex &c); //運算符<<重載 private: //私有數(shù)據(jù)成員double real;//復數(shù)實部double imag;//復數(shù)虛部 }; Complex operator + (const Complex &c1, const Complex &c2) { //重載運算符函數(shù)實現(xiàn)return Complex(c1.real + c2.real, c1.imag + c2.imag); } Complex operator - (const Complex &c1, const Complex &c2) { //重載運算符函數(shù)實現(xiàn)return Complex(c1.real - c2.real, c1.imag - c2.imag); } ostream & operator << (ostream &out, const Complex &c) { //重載運算符函數(shù)實現(xiàn)out << "(" << c.real << ", " << c.imag << ")";return out; } int main() { //主函數(shù)Complex c1(5, 4), c2(2, 10), c3; //定義復數(shù)類的對象cout << "c1 = " << c1 << endl;cout << "c2 = " << c2 << endl;c3 = c1 - c2 - c2; //使用重載運算符完成復數(shù)減法cout << "c3 = c1 - c2 - c2 = " << c3 << endl;c3 = 5.0 + c2; //使用重載運算符完成復數(shù)加法cout << "c3 = 5.0 + c2 = " << c3 << endl;return 0; }程序輸出的結(jié)果為: c1 = (5, 4) c2 = (2, 10) c3 = c1 - c2 - c2 = (1, -16) c3 = 5.0 + c2 = (7, 10)“<<”操作符的左操作數(shù)是ostream類型的引用,ostream是cout的一個基類,右操作數(shù)是Complex類型的引用,在執(zhí)行cout<<c1時,調(diào)用operator<<(cout,c1)。該函數(shù)把第一個參數(shù)傳入的ostream對象以引用形式返回,是為了支持形如“cout<<c1<<c2”的連續(xù)輸出,因為第二個“<<”操作符的左操作數(shù)是第一個“<<”操作符的返回結(jié)果,所以需要返回引用的形式。
前置單目運算符 U
重載后,表達式 U oprd 等同于operator U(oprd )
后置單目運算符 ++和–
重載后,表達式 oprd B 等同于operator B(oprd,0 )
8.3 虛函數(shù)
虛函數(shù)是動態(tài)綁定的基礎(chǔ);虛函數(shù)必須是非靜態(tài)的成員函數(shù);虛函數(shù)經(jīng)過派生之后,在類族中就可以實現(xiàn)運行過程中的多態(tài)。
8.3.1 一般虛函數(shù)成員
聲明形式
virtual 函數(shù)類型 函數(shù)名(形參表) {... }虛函數(shù)聲明只能出現(xiàn)在類定義中的函數(shù)原型聲明中,而不能在成員函數(shù)實現(xiàn)的時候。
運行過程中的多態(tài)必須滿足條件
類之間滿足賦值兼容規(guī)則。
賦值兼容規(guī)則簡單分類:派生類的對象可以賦值給基類對象、派生類的對象可以初始化基類的引用、派生類對象的地址可以賦給指向基類的指針。
聲明虛函數(shù)
由成員函數(shù)來調(diào)用或者是通過指針、引用來訪問虛函數(shù)(如果是使用對象名來訪問虛函數(shù),在編譯過程中就可以進行(靜態(tài)綁定))
虛函數(shù)一般不聲明為內(nèi)聯(lián)函數(shù),因為對虛函數(shù)的調(diào)用需要動態(tài)綁定,而對內(nèi)聯(lián)函數(shù)的處理是靜態(tài)的,所以一般虛函數(shù)不能以內(nèi)聯(lián)函數(shù)處理,但如果聲明為內(nèi)聯(lián)函數(shù)也不會引起錯誤。
#include <iostream> using namespace std;class Base1 { //基類Base1定義 public:virtual void display() const; //虛函數(shù) }; void Base1::display() const {cout << "Base1::display()" << endl; }class Base2:public Base1 { //公有派生類Base2定義 public:/*virtual*/void display() const; //覆蓋基類的虛函數(shù) }; void Base2::display() const {cout << "Base2::display()" << endl; } class Derived: public Base2 { //公有派生類 public:/*virtual*/void display() const; //覆蓋基類的虛函數(shù) }; void Derived::display() const {cout << "Derived::display()" << endl; }void fun(Base1 *ptr) { //參數(shù)為指向基類對象的指針ptr->display(); //"對象指針->成員名" }int main() { //主函數(shù)Base1 base1; //定義Base1類對象Base2 base2; //定義Base2類對象Derived derived; //定義Derived類對象fun(&base1);//用Base1對象的指針調(diào)用fun函數(shù)fun(&base2);//用Base2對象的指針調(diào)用fun函數(shù)fun(&derived);//用Derived對象的指針調(diào)用fun函數(shù)return 0; }運行結(jié)果: Base1::display() Base2::display() Derived::display()上述程序滿足運行中的多態(tài)必需三個條件:Base1、Base2和Derived屬于同一個類族,通過公有派生而來,滿足賦值兼容規(guī)則;Base1中聲明了虛函數(shù);程序中通過對象指針調(diào)用成員。
當派生類沒有顯式聲明虛函數(shù)時,系統(tǒng)遵循一下規(guī)則判斷:
滿足以上三個條件,會自動確定為虛函數(shù),這時派生類虛函數(shù)會覆蓋基類虛函數(shù),同時隱藏基類中同名函數(shù)的所有重載形式。
補充:如果fun函數(shù)換成以下形式,會有不同結(jié)果
//情況1 void fun(Base1 *ptr) { //參數(shù)為指向基類對象的指針ptr->Base1::display(); //"對象指針->基類::成員名"調(diào)用被覆蓋的基類成員函數(shù) } //情況2 void fun(Base1 ptr) { //參數(shù)為基類對象ptr.display(); //"對象名.成員名"調(diào)用成員函數(shù) } int main() {fun(base1);//用Base1對象調(diào)用fun函數(shù)fun(base2);//用Base2對象調(diào)用fun函數(shù)fun(derived);//用Derived對象調(diào)用fun函數(shù) }運行結(jié)果都為: Base1::display() Base1::display() Base1::display()補充
對象切片:用派生類對象復制構(gòu)造基類對象的行為。例如
Derived d; Base1 b = d;//調(diào)用Base1的復制構(gòu)造函數(shù)用d構(gòu)造b8.3.2 虛析構(gòu)函數(shù)
C++中不可以聲明虛構(gòu)造函數(shù),但可以聲明虛析構(gòu)函數(shù),如果類的析構(gòu)函數(shù)是虛函數(shù),那么它的所有派生類的析構(gòu)函數(shù)也是虛函數(shù)。
簡單來說,如果有可能通過基類指針刪除派生類對象,就需要讓析構(gòu)函數(shù)是虛函數(shù),否則會產(chǎn)生不確定的結(jié)果。
聲明形式
virtual ~類名();不聲明析構(gòu)函數(shù)為虛函數(shù)
#include <iostream> using namespace std;class Base { public:~Base(); }; Base::~Base() {cout<< "Base destructor" << endl;}class Derived: public Base{ public:Derived();~Derived();private:int *p; }; Derived::Derived() {p = new int(0); } Derived::~Derived() { cout << "Derived destructor" << endl;delete p; }void fun(Base* b) {delete b; }int main() {Base *b = new Derived();fun(b);return 0; }運行結(jié)果: Base destructor此時派生類析構(gòu)函數(shù)不執(zhí)行,內(nèi)存泄漏。
析構(gòu)函數(shù)聲明為虛函數(shù)
#include <iostream> using namespace std;class Base { public:virtual ~Base(); }; Base::~Base() {cout<< "Base destructor" << endl;}class Derived: public Base{ public:Derived();~Derived();private:int *p; }; Derived::Derived() {p = new int(0); } Derived::~Derived() { cout << "Derived destructor" << endl;delete p; }void fun(Base* b) {delete b; }int main() {Base *b = new Derived();fun(b);return 0; }運行結(jié)果: Derived destructor Base destructor內(nèi)存空間被正確釋放,實現(xiàn)了多態(tài)。
8.4 純虛函數(shù)與抽象類
抽象類為類族提供統(tǒng)一的操作界面,可以說,建立抽象類就是為了通過它多態(tài)地使用其中的成員函數(shù)。
抽象類是帶純虛函數(shù)的類,抽象類不能實例化。
聲明形式
virtual 函數(shù)類型 函數(shù)名(參數(shù)表) = 0;聲明為純虛函數(shù)之后,基類中就可以不再給出函數(shù)的實現(xiàn)部分,函數(shù)體由派生類給出。
//8_6.cpp #include <iostream> using namespace std;class Base1 { //基類Base1定義 public:virtual void display() const = 0; //純虛函數(shù) };class Base2: public Base1 { //公有派生類Base2定義 public:void display() const; //覆蓋基類的虛函數(shù) }; void Base2::display() const {cout << "Base2::display()" << endl; } class Derived: public Base2 { //公有派生類Derived定義 public:void display() const; //覆蓋基類的虛函數(shù) }; void Derived::display() const {cout << "Derived::display()" << endl; }void fun(Base1 *ptr) { //參數(shù)為指向基類對象的指針ptr->display(); //"對象指針->成員名" }int main() { //主函數(shù)Base2 base2; //定義Base2類對象Derived derived; //定義Derived類對象fun(&base2); //用Base2對象的指針調(diào)用fun函數(shù)fun(&derived); //用Derived對象的指針調(diào)用fun函數(shù)return 0; }運行結(jié)果: Base2::display() Derived::display()8.7 深度探索
第九章 群體類和群體數(shù)據(jù)的組織
9.1 函數(shù)模板與類模板
參數(shù)化多態(tài)性:將程序所處理的對象的類型參數(shù)化,使得一段程序可以用于處理多種不同類型的對象。
9.1.1 函數(shù)模板
定義形式
template<模板參數(shù)表>//模板參數(shù)表多為typename T或class T 類型名 函數(shù)名(參數(shù)表) {... }例:求絕對值函數(shù)的模板
#include <iostream> using namespace std; template<typename T> T abs(T x) {return x < 0? -x : x; } int main() {int n = -5;double d = -5.5;cout << abs(n) << endl;//abs(n)會讓編譯器生成函數(shù)模板的一個實例int abs(int x),并傳入n的值執(zhí)行此函數(shù)cout << abs(d) << endl;//abs(b)會讓編譯器生成函數(shù)模板的一個實例double abs(double x),并傳入b的值執(zhí)行此函數(shù)return 0; }例
#include <iostream> using namespace std;template <class T> //定義函數(shù)模板 void outputArray(const T *array, int count) {for (int i = 0; i < count; i++)cout << array[i] << " ";cout << endl; }int main() { //主函數(shù)const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20;int a [A_COUNT] = { 1, 2, 3, 4, 5, 6, 7, 8 }; //定義int數(shù)組double b[B_COUNT] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 };//定義double數(shù)組char c[C_COUNT] = "Welcome to see you!";//定義char數(shù)組cout << " a array contains:" << endl;outputArray(a, A_COUNT); //調(diào)用函數(shù)模板cout << " b array contains:" << endl;outputArray(b, B_COUNT); //調(diào)用函數(shù)模板cout << " c array contains:" << endl;outputArray(c, C_COUNT); //調(diào)用函數(shù)模板return 0; }運行結(jié)果: a array contains: 1 2 3 4 5 6 7 8 b array contains: 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 c array contains: W e l c o m e t o s e e y o u !注意
9.1.2 類模板
使用類模板使用戶可以為類聲明一種模式,使得類中的某些數(shù)據(jù)成員、某些成員函數(shù)的參數(shù)、某些成員函數(shù)的返回值,能取任意類型(包括基本類型的和用戶自定義類型)。
語法形式:
template<模板參數(shù)表> class 類名 {... } //在類模板外定義成員函數(shù): template<模板參數(shù)表> 類型名 類名<模板參數(shù)標識符列表>::函數(shù)名(參數(shù)表) //使用模板類建立對象 模板名<模板參數(shù)表>對象名1,...,對象名n例
#include <iostream> #include <cstdlib> using namespace std; struct Student {int id; //學號float gpa; //平均分 }; template <class T> class Store {//類模板:實現(xiàn)對任意類型數(shù)據(jù)進行存取 private:T item; // item用于存放任意類型的數(shù)據(jù)bool haveValue; // haveValue標記item是否已被存入內(nèi)容 public:Store(); // 缺省形式(無形參)的構(gòu)造函數(shù)T &getElem(); //提取數(shù)據(jù)函數(shù)void putElem(const T &x); //存入數(shù)據(jù)函數(shù) };template <class T> //默認構(gòu)造函數(shù)的實現(xiàn) Store<T>::Store(): haveValue(false) { } template <class T> //提取數(shù)據(jù)函數(shù)的實現(xiàn) T &Store<T>::getElem() {//如試圖提取未初始化的數(shù)據(jù),則終止程序if (!haveValue) { cout << "No item present!" << endl;exit(1); //使程序完全退出,返回到操作系統(tǒng)。}return item; // 返回item中存放的數(shù)據(jù) } template <class T> //存入數(shù)據(jù)函數(shù)的實現(xiàn) void Store<T>::putElem(const T &x) {// 將haveValue 置為true,表示item中已存入數(shù)值 haveValue = true; item = x; // 將x值存入item }int main() {Store<int> s1, s2; s1.putElem(3); s2.putElem(-7);cout << s1.getElem() << " " << s2.getElem() << endl;Student g = { 1000, 23 };Store<Student> s3;s3.putElem(g); cout << "The student id is " << s3.getElem().id << endl;Store<double> d;cout << "Retrieving object D... ";cout << d.getElem() << endl //由于d未經(jīng)初始化,在執(zhí)行函數(shù)D.getElement()過程中導致程序終止return 0; }運行結(jié)果: 3 -7 The student id is 1000 Retrieving object D... No item present!9.2 線性群體
9.2.1 線性群體的概念
群體是指由多個數(shù)據(jù)元素組成的集合體。群體可以分為兩個大類:線性群體和非線性群體。
線性群體中的元素次序與其位置關(guān)系是對應(yīng)的。在線性群體中,又可按照訪問元素的不同方法分為直接訪問、順序訪問和索引訪問。
對可直接訪問的線性群體,我們可以直接訪問群體中的任何一個元素,例如通過數(shù)組下標直接訪問數(shù)組元素。對順序訪問的線性群體,只能按照元素的排列順序從頭開始訪問各個元素。
9.2.2 直接訪問群體——數(shù)組類
靜態(tài)數(shù)組缺點:大小在編譯時就已經(jīng)確定,在運行時無法修改。
動態(tài)數(shù)組優(yōu)點:其元素個數(shù)可在程序運行時改變。
vector就是用類模板實現(xiàn)的動態(tài)數(shù)組。
動態(tài)數(shù)組類模板 Array,聲明和實現(xiàn)都在Array.h文件中
#ifndef ARRAY_H #define ARRAY_H #include <cassert>template <class T> //數(shù)組類模板定義 class Array { private:T* list; //用于存放動態(tài)分配的數(shù)組內(nèi)存首地址int size; //數(shù)組大小(元素個數(shù)) public:Array(int sz = 50); //構(gòu)造函數(shù)Array(const Array<T> &a); //拷貝構(gòu)造函數(shù)~Array(); //析構(gòu)函數(shù)Array<T> & operator = (const Array<T> &rhs); //重載"=“T & operator [] (int i); //重載"[]”const T & operator [] (int i) const; operator T * (); //重載到T*類型的轉(zhuǎn)換operator const T * () const;int getSize() const; //取數(shù)組的大小void resize(int sz); //修改數(shù)組的大小 }; template <class T> Array<T>::Array(int sz) {//構(gòu)造函數(shù)assert(sz >= 0);//sz為數(shù)組大小(元素個數(shù)),應(yīng)當非負size = sz; // 將元素個數(shù)賦值給變量sizelist = new T [size]; //動態(tài)分配size個T類型的元素空間 } template <class T> Array<T>::~Array() { //析構(gòu)函數(shù)delete [] list; } //拷貝構(gòu)造函數(shù) template <class T> Array<T>::Array(const Array<T> &a) {size = a.size; //從對象x取得數(shù)組大小,并賦值給當前對象的成員//為對象申請內(nèi)存并進行出錯檢查list = new T[size]; // 動態(tài)分配n個T類型的元素空間for (int i = 0; i < size; i++) //從對象X復制數(shù)組元素到本對象 list[i] = a.list[i]; }//重載"="運算符,將對象rhs賦值給本對象。實現(xiàn)對象之間的整體賦值 template <class T> Array<T> &Array<T>::operator = (const Array<T>& rhs) {if (&rhs != this) { //如果本對象中數(shù)組大小與rhs不同,則刪除數(shù)組原有內(nèi)存,然后重新分配if (size != rhs.size) {delete [] list; //刪除數(shù)組原有內(nèi)存size = rhs.size; //設(shè)置本對象的數(shù)組大小list = new T[size]; //重新分配n個元素的內(nèi)存}//從對象X復制數(shù)組元素到本對象 for (int i = 0; i < size; i++)list[i] = rhs.list[i];}return *this; //返回當前對象的引用 } //重載下標運算符,實現(xiàn)與普通數(shù)組一樣通過下標訪問元素,并且具有越界檢查功能 template <class T> T &Array<T>::operator[] (int n) {assert(n >= 0 && n < size); //檢查下標是否越界return list[n]; //返回下標為n的數(shù)組元素 } template <class T> const T &Array<T>::operator[] (int n) const {assert(n >= 0 && n < size); //檢查下標是否越界return list[n]; //返回下標為n的數(shù)組元素 }? //重載指針轉(zhuǎn)換運算符,將Array類的對象名轉(zhuǎn)換為T類型的指針 template <class T> Array<T>::operator T * () {return list; //返回當前對象中私有數(shù)組的首地址 }template <class T> Array<T>::operator const T * () const {return list; //返回當前對象中私有數(shù)組的首地址 } //取當前數(shù)組的大小 template <class T> int Array<T>::getSize() const {return size; } // 將數(shù)組大小修改為sz template <class T> void Array<T>::resize(int sz) {assert(sz >= 0); //檢查sz是否非負if (sz == size) //如果指定的大小與原有大小一樣,什么也不做return;T* newList = new T [sz]; //申請新的數(shù)組內(nèi)存int n = (sz < size) ? sz : size;//將sz與size中較小的一個賦值給n//將原有數(shù)組中前n個元素復制到新數(shù)組中for (int i = 0; i < n; i++)newList[i] = list[i];delete[] list; //刪除原數(shù)組list = newList; // 使list指向新數(shù)組size = sz; //更新size } #endif //ARRAY_H淺復制和深復制
如果沒有對“=”重載,那么系統(tǒng)自動生成隱含的重載函數(shù),給每個數(shù)據(jù)成員執(zhí)行“=”運算符,也就是淺復制。所以當需要通過顯式定義復制構(gòu)造函數(shù)執(zhí)行深復制的時候,就需要重載賦值運算符。
與眾不同的運算符
重載的“=”和“[]”返回值類型都是對象的引用,因為“=”左值不能為一個對象的值,對其賦值沒有意義,而“[]”經(jīng)常需要作為左值,所以將“[]”返回值類型指定為對象的引用,通過引用可以改變對象的值。
指針轉(zhuǎn)換運算符的作用
#include <iostream> using namespace std;void read(int *p, int n) {for (int i = 0; i < n; i++)cin >> p[i]; } int main() {int a[10];read(a, 10);return 0; }#include "Array.h" #include <iostream> using namespace std;void read(int *p, int n) {for (int i = 0; i < n; i++)cin >> p[i]; } int main() { Array<int> a(10);read(a, 10);return 0; }調(diào)用read函數(shù)的時候系統(tǒng)會嘗試把對象名轉(zhuǎn)換為int*類型,會編譯錯誤,所以需要定義指針類型轉(zhuǎn)換函數(shù)。
//重載指針轉(zhuǎn)換運算符,將Array類的對象名轉(zhuǎn)換為T類型的指針 template <class T> Array<T>::operator T * () {return list; //返回當前對象中私有數(shù)組的首地址 }Array類的應(yīng)用
求范圍2~N中的質(zhì)數(shù),N在程序運行時由鍵盤輸入。
#include <iostream> #include <iomanip> #include "Array.h" using namespace std; int main() {Array<int> a(10); // 用來存放質(zhì)數(shù)的數(shù)組,初始狀態(tài)有10個元素。int n, count = 0;cout << "Enter a value >= 2 as upper limit for prime numbers: ";cin >> n;for (int i = 2; i <= n; i++) {bool isPrime = true;for (int j = 0; j < count; j++)if (i % a[j] == 0) { //若i被a[j]整除,說明i不是質(zhì)數(shù)isPrime = false; break;}if (isPrime) { //如果質(zhì)數(shù)表填滿了,將其空間加倍if (count == a.getSize()) a.resize(count * 2);a[count++] = i;}}for (int i = 0; i < count; i++) cout << setw(8) << a[i];cout << endl;return 0; }運行結(jié)果: Enter a value >= 2 as upper limit for prime numbers:1002 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97現(xiàn)對象之間的整體賦值
template
Array &Array::operator = (const Array& rhs) {
if (&rhs != this) {
//如果本對象中數(shù)組大小與rhs不同,則刪除數(shù)組原有內(nèi)存,然后重新分配
if (size != rhs.size) {
delete [] list; //刪除數(shù)組原有內(nèi)存
size = rhs.size; //設(shè)置本對象的數(shù)組大小
list = new T[size]; //重新分配n個元素的內(nèi)存
}
//從對象X復制數(shù)組元素到本對象
for (int i = 0; i < size; i++)
list[i] = rhs.list[i];
}
return this; //返回當前對象的引用
}
//重載下標運算符,實現(xiàn)與普通數(shù)組一樣通過下標訪問元素,并且具有越界檢查功能
template
T &Array::operator[] (int n) {
assert(n >= 0 && n < size); //檢查下標是否越界
return list[n]; //返回下標為n的數(shù)組元素
}
template
const T &Array::operator[] (int n) const {
assert(n >= 0 && n < size); //檢查下標是否越界
return list[n]; //返回下標為n的數(shù)組元素
}?
//重載指針轉(zhuǎn)換運算符,將Array類的對象名轉(zhuǎn)換為T類型的指針
template
Array::operator T * () {
return list; //返回當前對象中私有數(shù)組的首地址
}
?
template
Array::operator const T * () const {
return list; //返回當前對象中私有數(shù)組的首地址
}
//取當前數(shù)組的大小
template
int Array::getSize() const {
return size;
}
// 將數(shù)組大小修改為sz
template
void Array::resize(int sz) {
assert(sz >= 0); //檢查sz是否非負
if (sz == size) //如果指定的大小與原有大小一樣,什么也不做
return;
T newList = new T [sz]; //申請新的數(shù)組內(nèi)存
int n = (sz < size) ? sz : size;//將sz與size中較小的一個賦值給n
//將原有數(shù)組中前n個元素復制到新數(shù)組中
for (int i = 0; i < n; i++)
newList[i] = list[i];
delete[] list; //刪除原數(shù)組
list = newList; // 使list指向新數(shù)組
size = sz; //更新size
}
#endif //ARRAY_H
調(diào)用read函數(shù)的時候系統(tǒng)會嘗試把對象名轉(zhuǎn)換為int*類型,會編譯錯誤,所以需要定義指針類型轉(zhuǎn)換函數(shù)。
//重載指針轉(zhuǎn)換運算符,將Array類的對象名轉(zhuǎn)換為T類型的指針 template <class T> Array<T>::operator T * () {return list; //返回當前對象中私有數(shù)組的首地址 }Array類的應(yīng)用
求范圍2~N中的質(zhì)數(shù),N在程序運行時由鍵盤輸入。
#include <iostream> #include <iomanip> #include "Array.h" using namespace std; int main() {Array<int> a(10); // 用來存放質(zhì)數(shù)的數(shù)組,初始狀態(tài)有10個元素。int n, count = 0;cout << "Enter a value >= 2 as upper limit for prime numbers: ";cin >> n;for (int i = 2; i <= n; i++) {bool isPrime = true;for (int j = 0; j < count; j++)if (i % a[j] == 0) { //若i被a[j]整除,說明i不是質(zhì)數(shù)isPrime = false; break;}if (isPrime) { //如果質(zhì)數(shù)表填滿了,將其空間加倍if (count == a.getSize()) a.resize(count * 2);a[count++] = i;}}for (int i = 0; i < count; i++) cout << setw(8) << a[i];cout << endl;return 0; }運行結(jié)果: Enter a value >= 2 as upper limit for prime numbers:1002 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97總結(jié)
以上是生活随笔為你收集整理的SCAU软件开发基础C++复习的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: glassfish基本使用
- 下一篇: C++多线程传参详解