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