介紹
北京大學程序設計與算法系列之C++程序設計,視頻:Coursera、嗶哩嗶哩。
說明
本課堂筆記目的是方便自己和大家複習以及查閱使用,所以課件內容偏多,請見諒。如有修改意見,歡迎大家提出。
程序設計與算法:C++程序設計
- 介紹
- 說明
- C++程序設計
- P3 01 :函數指針
- P4 02 命令行參數
- P5 03 位運算
- P6 04 引用
- P7 05 const 關鍵字和常量
- P8 06 動態內存分配
- P9 07 內聯函數和重載函數
- P10 08 函數缺省參數
- P11 09 面向對象程序設計
- P12 10 面嚮對象語言發展歷程
- P13 11 從客觀事物抽象出類的例子
- P14 12 類成員的可訪問範圍
- P15 01 內聯成員函數和重載成員函數
- P16 02 構造函數(constructor)
- P17 03 複製構造函數
- P18 04 類型轉換構造函數
- P19 05 析構函數
- P20 06 靜態成員變量和靜態成員函數
- P21 07 成員對象和封閉類
- P22 08 友元(Friend)
- P23 09 this指針
- P24 10 常量對象,常量成員函數和常引用
- P25 01 運算符重載
- P26 02 賦值運算符重載(淺拷貝與深拷貝)
- P27 03 運算符重載爲友元函數
- P28 04 運算符重載實例:長度可變的整型數組類
- P29 05 流插入運算符和流提取運算符的重載
- P30 06 自增自減運算符的重載
- P31 01 繼承和派生
- P32 02 複合關係和繼承關係
- P33 03 基類派生類同名成員和`Protected`關鍵字
- P34 04 派生類的構造函數
- P35 05 public繼承的賦值兼容規則
- P36 01 多態和虛函數
- P37 02 使用多態的遊戲程序實例
- P38 03更多多態程序實例
- P39 04 多態實現原理
- P40 虛析構函數
- P41 純虛函數和抽象類
- P42 文件操作
- P43 函數模板
- P44 類模板
- P45 string類
- P46 輸入輸出
- P47 STL 標準模板庫
- P49 順序容器vector
- P50 List和Deque
- P51 函數對象
- P52 set 和 multiset
- P53 map和multimap
- P54 容器適配器
- P55
C++程序設計
P3 01 :函數指針
- 定義: 類型名( 指針變量名)(參數類型1, 參數類型2, …)*
例如:int (*pf)(int, char)
- 使用:函數指針名(實參表)
例如:pf = PrintMin; pf(x, y);
- 應用:C快速排序任意類型數組:
qsort
庫函數
void qsort(void *base, int nelem, unsigned int width, int(* pfCompare)(const void *, const void *)
函數需要以下四個參數:- 數組的起始地址:
base
- 數組元素個數:
nelem
- 數組元素大小:
width
- 排序前後規則:
int(* pfCompare)(a, b)
其中,返回值包含以下三種情況:- 負整數:a在前,b在後;
- 0:哪個參數在前都可以;
- 正整數:a在後, b在前。
- 數組的起始地址:
- 課件
- 如下:
P4 02 命令行參數
命令行程序示例:
> notepad sample.txt
> copy file1.txt file2.txt
實現方式:
int main(int argc, char * argv[])
{
...
}
其中,argc
:命令行參數的個數。由於可執行程序本身的文件名也計算在內,故argc
大於等於1。而argv[]
是一個指針數組,argv[0]
:可執行文件名;argv[1]
:第2個命令行參數;以此類推。
注:某一命令行參數如果有空格,可用雙引號括起。
- 課件
- 如下:
- 如下:
P5 03 位運算
位運算符包括:
&
:按位與|
:按位或^
:按位異或~
:按位非(取反)<<
:按位左移>>
:按位右移
&
:按位與運算
用途:
- 變量中的某位置清0,且同時保留其他位不變。
例如:int型變量n
,使其低8位置0,其他位不變:
n = n & 0xffffff00;
或者:n &= 0xffffff00;
若n
爲short
型(16位),則n &= 0xff00
- 獲取變量中的某一位。
例如:判斷n
第7位是否爲1(從右往左,從0開始):
判斷n & 0x80
是否等於0x80
,因爲0x80 == 1000 0000
。
|
:按位或運算
用途:
- 變量中的某位置置1,且同時保留其他位不變。
例如:int型變量n
,使其低8位置1,其他位不變:
n |= 0xff;
^
:按位異或運算
用途:
- 變量中的某位置取反,且同時保留其他位不變。
例如:int型變量n
,使其低8位取反,其他位不變:
n ^= 0xff;
特點:由a ^ b = c
可得:c ^ b = a
以及c ^ a = b
因此按位異或運算可用作簡單的加密和解密。 - 不通過臨時變量,交換兩個變量的值。
int a = 5, b = 7;
a = a ^ b;
b = b ^ a;
a = a ^ b;
~
:按位非運算
用途:pass.
<<
:按位左移運算
含義:a << b
:將a
按位左移b
位。
性質:高位丟棄,低位補0;a的值不會因此改變。
左移n
位,相當於乘以2n。但是左移運算比乘法運算快得多。
>>
:按位右移運算
含義:a >> b
:將a
按位右移b
位。
性質:低位丟棄,高位補0;a的值不會因此改變。
有符號數,如long
, int
, short
, char
類型,符號位(最高位)一起移動。大多數編譯器規定,如果原符號位爲1,則右移時高位就補充1;原符號位爲0,則右移時高位就補充0。
右移n
位,相當於除以2n,並將結果往小裏取整。例如
-25 >> 4 = -2
-2 >> 4 = -1
18 >> 4 = 1
注:負數轉化成二進制:以補碼形式表示,即先按正數轉換,然後取反加1。
- 課件
- 如下:
- 如下:
P6 04 引用
形式:類型名 & 引用名 = 某個變量名
例如:
int n = 4;
int & r = n; // r 引用了 n, r 類型是 int &
含義:某個變量的引用,等價於這個變量,相當該變量的一個別名。
- 課件
- 如下:
- 如下:
P7 05 const 關鍵字和常量
- 要點:
- 避免使用
define
, 因爲const
有類型方便檢查。
- 避免使用
- 課件
- 如下:
錯誤1:不可通過常量指針修改其指向的內容;錯誤2:strcpy()
的第一個參數類型爲char *
,而p
爲const char *
,不能將const char *
類型的指針賦給char *
類型。
- 如下:
P8 06 動態內存分配
- 要點:
- C語言:通過
malloc
分配內存。C++:new
關鍵字實現動態分配內存。 - 分配變量:
P = new T;
,其中T
爲任意類型名。 - 分配數組:
P = new T[N];
- C語言:通過
- 課件
- 如下:
- 如下:
P9 07 內聯函數和重載函數
內聯函數:不用棧調用,直接貼到主程序。
優點:加快程序運行。
缺點:導致可執行程序體積過大。
重載函數:名字相同,參數表不同。
- 課件
- 如下:
P10 08 函數缺省參數
- 課件
- 如下:
- 如下:
P11 09 面向對象程序設計
- 結構化程序設計的弊端
- 課件
- 如下:
- 面向對象程序設計
含義:類,相當於模板。
- 如下:
P12 10 面嚮對象語言發展歷程
- 課件
- 如下:
- 如下:
P13 11 從客觀事物抽象出類的例子
- 實例化對象
- 類成員的訪問:
- 對象名.成員名
- 指針->成員名
- 引用名.成員名
- 課件
- 如下:
實例化過程:
注:函數形參裏的&r
去掉不影響結果輸出,但效率低。
- 如下:
P14 12 類成員的可訪問範圍
-
要點:
- 缺省類型的成員爲私有成員,即未明確可訪問類型的變量。
- 類的公有成員函數內部的私有變量是可以訪問到的。
- 類的私有成員變量只能由類的成員函數訪問。原因之一:方便修改。
-
課件
- 如下:
- 如下:
P15 01 內聯成員函數和重載成員函數
-
要點:
- 內聯函數的設計目的:單獨一個函數語句少的情況下,反覆調用,開銷大。
- 參數缺省的目的:提高程序的可擴展性。添加新的函數時,不需要的參數可直接用默認值。
-
課件
- 如下:
- 如下:
P16 02 構造函數(constructor)
-
要點:
- 構造函數的目的:對象初始化
- 它不分配存儲空間,而是在已經分配空間之後做初始化的工作。例如:蓋房子,房子是一個對象,構造函數僅僅是在房子蓋好之後進行裝修。
- 對象生成時一定會調用構造函數。
- 對象一定要初始化在使用。
-
課件:
- 如下
注:pArray[3]
是 指針數組,不是對象數組,它的每一個元素都是指針,可以不初始化,因此,它不會引發對象生成。指針只表示指示作用,所以不會導致指示的對象生成構造函數。此時,對前兩個指針初始化,new
的返回值類型爲Test *
。
- 如下
P17 03 複製構造函數
-
要點:
- 把某一對象C1的成員變量複製到另一對象C2,即複製品。
- 複製構造函數被調用的三種方式:
- 一個對象去初始化另一個對象;
- 函數參數是類A的對象
- 函數返回值是類A的對象
-
課件
- 如下:
注:C++的標準不允許複製構造函數傳值參數,否則會編譯出錯。因爲是傳值參數,我們把形參複製到實參會調用複製構造函數。如果允許複製構造函數傳值,就會在複製構造函數內調用複製構造函數,就會形成永無休止的遞歸調用從而導致棧溢出。(來自:劍指offer)
注:複製構造函數的作用:複製一個對象的成員變量給另一對象。
void Func(Complex1 a1){} // 會調用複製構造函數,如同值傳遞 void Func(Complex1 & a1){} // 對象引用,不會調用複製構造函數,如同地址傳遞 void Func(const Complex1 a1){} // 會調用複製構造函數,不允許修改。 void Func(const Complex1 & a1){}// 常對象引用,不會調用複製構造函數,不允許修改,如同地址傳遞
博客C++構造函數詳解(複製構造函數)是這麼說的:複製構造函數參數爲類對象本身的引用,用於根據一個已存在的對象複製出一個新的該類的對象,一般在函數中會將已存在對象的數據成員的值複製一份到新創建的對象中。 注:(來自B站評論)不管是返回一個類對象還是傳入類對象作爲參數,這個類對象不再是類對象,而是調用了以這個類對象作爲參數的複製構造函數。即非引用類型的對象作爲形參或是函數返回值時,在調用函數過程都會初始化同類型的對象,因此都會調用複製構造函數。
- 如下:
P18 04 類型轉換構造函數
- 要點:
- 適用於解決兩邊類型不一致的情況。
- 課件
- 如下:
注:Complex c2 = 9;
中的等號=
代表初始化,並非賦值。在調用類型轉換構造函數時不會生成臨時對象。而c1 = 9;
爲賦值語句,等號兩端爲不同類型,相當於以9作爲實參調用類型轉換構造函數Complex (int i)
,生成一個臨時Complex對象賦值給c1
。
- 如下:
P19 05 析構函數
- 要點:
- 在對象銷燬時調用,與構造函數相對應。
- 課件
- 如下:
注:構造函數和析構函數的調用時機
d4 = 6;
:調用類型轉換構造函數,生成臨時對象。{}
:代表作用域,相當於生命週期。- 靜態變量在整個函數結束時消亡。
- 析構函數釋放順序:局部變量->靜態變量->全局變量
- 如下:
P20 06 靜態成員變量和靜態成員函數
- 要點:
- 靜態成員變量和函數,相當於該類的全局變量和函數。
- 靜態成員函數中不能包含非靜態變量和函數。
- 靜態成員,實際上不放在成員內部,而是放在所有成員的外面,被所有對象共享。即靜態成員只與該類相關,與其他變量和成員基本上沒有關係。
- 課件
- 如下:
注:複製構造函數,與所有構造函數享有同等權利。
- 如下:
P21 07 成員對象和封閉類
- 要點:
成員變量(形參)
,等價於成員變量=形參。括號()
相當於賦值操作。成員對象(形參1,形參2)
,等價於通過形參1和2來初始化該成員對象。具體看下面的例子。- 封閉類的構造函數後跟有冒號
:
和成員變量的參數表。
- 課件
- 如下:
注:封閉類構造和析構的調用順序:先構造的後析構;後構造的先析構。類似棧實現。構造函數:成員對象->封閉類。析構函數:封閉類->成員對象。
- 如下:
P22 08 友元(Friend)
- 要點:
- 友元函數和友元類
- 課件
- 如下:
- 如下:
P23 09 this指針
- 要點:
- this指針可以理解爲C++程序轉換爲C程序所用到的指針。
- 課件
- 如下:
注:C++轉換成C,利用C編譯器編譯。因此,C++的class
對應於C的結構體struct
,又由於C無成員函數,故C需要定義全局函數,其全局函數的參數相比較於C++成員函數要多一個形參,即this
指針,它指向C++中成員函數所作用的對象,在C中即爲結構體變量。
- 如下:
P24 10 常量對象,常量成員函數和常引用
- 要點:
const
:保證其限定的成分不被修改。- 常引用:對象的引用作爲函數參數,不是對象,因此不會引發複製構造函數。而聲明爲常引用,確保形參不會修改原始值,既保證效率,又保證數據安全。
- 課件
- 如下:
- 如下:
P25 01 運算符重載
- 要點:
- 注意重載成普通函數和成員函數的區別。
- 課件
- 如下:
- 如下:
P26 02 賦值運算符重載(淺拷貝與深拷貝)
- 要點:
- 賦值
=
重載解決的問題:c1 = c2;
等號兩端類型不匹配。 - 運算符重載可通過重載爲
成員函數
和普通函數
來實現。但賦值運算符只可重載爲成員函數。
- 賦值
- 課件
- 如下:
注:String s2 = "hello!"
非賦值語句,而是初始化,它不會調用s2.operator=()
成員函數,而是會調用String(char *)
構造函數。
注:S1 = S2;
兩者指向同一塊地址。S1
和S2
消亡時,內存先後釋放兩次,會造成內存錯誤或異常終止。
注:設計構造函數時,也要對其做與運算符重載同樣的處理。
- 如下:
P27 03 運算符重載爲友元函數
- 要點:
- 略。
- 課件
- 如下:
- 如下:
P28 04 運算符重載實例:長度可變的整型數組類
- 要點:
- 數組大小可變的實現方法有兩種:
- 動態申請內存:
new
和delete
。 - 長度可變:運算符重載。
- 動態申請內存:
- 數組大小可變的實現方法有兩種:
- 課件
- 如下:
注:動態分配空間需要指針成員變量。a2 = a;
:賦值運算符重載。a2[i]
:[]重載。a4(a)
:不能用缺省的複製構造函數,因爲存在深淺拷貝的問題。
注:非引用函數的返回值,不可以作爲左值引用!
注:初始化成員列表:CArray(int s) : size(s) { ... }
,將size
初始化爲s
。調用默認複製構造函數指向了同一片內存:CArray a2(a1);
,會將a1
的成員變量複製給a2
。
注:空間分配方案:32
->64
->128
->...
,以此類推。標準庫的vector
實現可變長度數組就是採用了這種方式。相比每次內存增1的方案要高效地多。
- 如下:
P29 05 流插入運算符和流提取運算符的重載
- 要點:
- 略。
- 課件
- 如下:
- 如下:
P30 06 自增自減運算符的重載
- 要點:
- 可通過全局函數和成員函數來實現。
- 全局函數若是想訪問私有成員,需要將其聲明爲友元。
- 全局函數:操作數的數目等於參數個數。比成員函數多一個參數。
- 成員函數與全局函數的區別(參考:c++:利用成員函數和全局函數實現對運算符的重載):
- 全局變量可以在整個程序裏使用;而局部變量只可以在一個函數或一段代碼中使用。
- 類成員函數是面向對象,全局函數是面向過程。
- 類成員函數 => 全局函數:增加一個參數,增加的這個參數代替this指針。
- 全局函數 => 類成員函數:減少一個參數,減少的這個參數由this指針隱藏。
- 課件
- 如下:
注:深淺複製是在成員變量是指針時所考慮的。
- 如下:
P31 01 繼承和派生
- 要點:
- 略。
- 課件
- 如下:
注:PrintInfo()
非重載,而是覆蓋!因爲重載:函數名相同,參數表不同。而覆蓋是同名同參數表。
- 如下:
P32 02 複合關係和繼承關係
- 要點:
- 類間關係包含以下三種:
- 沒關係
- 繼承關係:
是
- 複合關係:
有
- 類間關係包含以下三種:
- 課件
- 如下:
注:使Circle
方便操作center
的成員變量,最好聲明爲CPoint
的友元函數。
注:這樣的定義方式是:人中有狗,狗中有人。重複定義,CMaster
體積包含1個CDog
對象,而CDog
體積又包含CMaster
。
注:這種定義方式,如何維護不同的狗的相同的主任的信息一致性問題。修改一個狗主人信息,需要全部修改。
注:複合關係的定義:類的成員對象是另一個類的固有屬性或組成成分。而狗非主人的固有屬性。狗沒有了狗格“人格”:對狗操作,必須要對其主人進行操作,沒有自由,不能獨立活動。
注:一個類是另一個類的指針,稱兩個類是知道的關係。一個類的對象通過指針就能知道另一個類的對象。類似於雙向樹。
- 如下:
P33 03 基類派生類同名成員和Protected
關鍵字
- 要點:
- 基類和派生類的同名成員包括:同名成員變量、同名成員函數。不推薦!
- 同名條件下,訪問基類成員的方式:
- 函數訪問:基類
::
成員 - 對象訪問:對象
.
基類::
成員
- 函數訪問:基類
- 派生類不能訪問基類的私有成員變量。必須聲明爲
protected
類型纔行,但是也只能允許當前對象
訪問。
- 課件
- 如下:
- 如下:
P34 04 派生類的構造函數
- 要點:
- 略
- 課件
- 如下:
- 如下:
P35 05 public繼承的賦值兼容規則
- 要點:
1.派生類構造函數前會從最頂層基類開始依次執行各個基類的構造函數。 - 課件
- 如下:
- 如下:
P36 01 多態和虛函數
- 要點:
- 多態:提高程序可擴充性。
- 虛函數可參與多態,而普通成員函數不能。
- 課件
- 如下:
- 如下:
P37 02 使用多態的遊戲程序實例
- 要點:
- 新增怪物:使用
多態
可以使得代碼改動和增加量最小。 - 新增怪物可通過基類指針指向派生類對象來實現。
- 頂層基類:概括了所有怪物的共同特點。
- 新增怪物:使用
- 課件
- 如下:
注:基類指針p
是一個指向派生類對象的指針,保存了派生類對象的地址。利用了public繼承的賦值兼容規則3:派生類對象的地址可以賦值給基類指針。
- 如下:
P38 03更多多態程序實例
- 要點:
- 純虛函數:無函數體
- 問題1:爲什麼構造函數和析構函數中調用虛函數,不是多態?
理由:當一個派生類對象那個初始化時,會先執行基類對象的構造函數。在基類對象的構造函數執行期間,派生類對象它自己的那部分成員變量還未被初始化。如果在基類構造函數執行期間調用虛函數,還允許虛函數是多態的話,那麼在基類的構造函數執行期間就會調用派生類的虛函數,這是由於多態,這時派生類對象它自己的成員變量還未被初始化好,此時執行派生類對象的成員函數,有可能不正確。因此,不能在基類的構造函數執行派生類的虛函數。 - 課件
- 如下:
注:pShape[i]:基類指針;PrintInfo():基類和派生類的同名虛函數。因此爲多態:指向的對象爲哪個派生類,就調用哪個對象的虛函數。
注:*S1:void類型,佔用多少個字節未知,因此直接取內容會編譯出錯。
】注:this:基類指針;func2():虛函數。因此調用過程取決於this指針是哪種類型的對象。
- 如下:
P39 04 多態實現原理
- 要點:
- 每一個含有虛函數的類(基類或繼承含有虛函數的基類的派生類)都有一個虛函數表。虛函數表存放了所有虛函數的內存地址。
- 多態的代價:會有額外的時間和空間開銷。其中,時間開銷是查找虛函數表;空間開銷是每個對象都多了4個字節的內存空間,存放虛函數表。
- 課件
- 如下:
- 如下:
P40 虛析構函數
- 要點:
- 通過基類指針刪除派生類對象時(
delete p
),僅調用基類析構函數。導致派生類無法回收內存空間。 - 基類析構函數聲明爲
virtual
,使得delete p
先調用派生類析構函數,再調用基類析構函數。
- 通過基類指針刪除派生類對象時(
- 課件
- 如下:
- 如下:
P41 純虛函數和抽象類
- 要點:
- 包含純虛函數的類,稱爲抽象類。
- 抽象類的指針和引用,只能指向由抽象類派生而來的派生類的對象。
- 派生類實現抽象類的所有純虛函數,才能稱爲非抽象類。包含一對
{}
也相當於實現化。
- 課件
- 如下:
- 如下:
P42 文件操作
- 要點:
ifstream
:input。從文件中讀取數據,輸入到文件流對象。ofstream
:output。文件流對象輸出,寫入數據到文件中,fstream
:既可讀取數據,也能寫入數據。- 文件讀取必須遵循以下流程:打開文件 -> 讀/寫文件 -> 關閉文件。
- 課件
- 如下:
- 如下:
P43 函數模板
- 要點:
- C++突出特性:抽象、封裝、繼承、多態、模板。
- 函數模板 => 泛型程序設計。
- 函數模板簡單地實現 “重載”的功能。例如
Swap
交換int
、float
、double
等。 - 模板 + 重載 ,需要定義多個參數類型避免二義性。
- 課件
- 如下:
- 如下:
P44 類模板
- 要點:
- 類模板中的函數模板的類型參數不能使用同一個。
- 課件
- 如下:
- 如下:
P45 string類
- 要點:
- string,模板類實現
- 課件
- 如下:
- 如下:
P46 輸入輸出
- 要點:
- 略
- 課件
- 如下:
注:cin >> x
強制類型轉換運算符的重載。雖然返回值爲cin
,經過fstream
強制類型轉換爲布爾類型的值。
注:peek
讀入但不流失,可判斷輸入流的數據類型。EOF
=-1
- 如下:
P47 STL 標準模板庫
- 要點:
- 容器使用前,一定要了解各個容器的時間複雜度。針對具體情況,擇優選取。
- 順序容器:
vector
動態數組(32->64->128->256);deque
雙向隊列(兩端增刪,性能佳);list
雙向鏈表。 - 關聯容器:排好序的,查找速度快。
set
:集合;multiset
:允許有相同元素;map
:字典(key:value);multimap
:允許key
相同。 - 容器適配器:
stack
;queue
;prority-queue
:優先級隊列。 - 反向迭代器
++
,會指向前一個元素,與正向不同。 - 雙向迭代器
* p
返回的是引用。
- 課件
- 如下:
- 如下:
P49 順序容器vector
- 要點:
- 略
- 課件
- 如下:
- 如下:
P50 List和Deque
- 要點:
- 略
- 課件
- 如下:
- 如下:
P51 函數對象
- 要點:
- 略
- 課件
- 如下:
- 如下:
P52 set 和 multiset
- 要點:
- 略
- 課件
- 如下:
- 如下:
P53 map和multimap
- 要點:
- 略
- 課件
- 如下:
- 如下:
P54 容器適配器
- 要點:
- 略
- 課件
- 如下:
- 如下:
P55
- 要點:
- 略
- 課件
- 如下:
未完待續…