C++期末復習
一、選擇題考察的知識點(40分)
C++在C的基礎上多了什么新特性
- 類和對象
- 繼承
- 多態、虛函數和RTT1(運行階段類型識別)
- 函數重載
- 引用變量
- 泛型(獨立於類型的)編程,這種技術是由模版和標准模版庫(STL)提供的
- 處理錯誤條件的異常機制
- 管理函數、類和變量名的名稱空間
編寫C++程序的步驟
1、用C++語言編寫程序
用高級語言編寫的程序稱為“源程序”(source program)。C++的源程序是以.cpp作為后綴的(cpp是c plus plus 的縮寫)。
2、對源程序進行編譯
為了使計算機能執行高級語言源程序,必須先用一種稱為“編譯器(complier)”的軟件(也稱編譯程序或編譯系統),把源程序翻譯成二進制形式的“目標程序(object program)”。
編譯是以源程序文件為單位分別編譯的。目標程序一般以.obj或.o作為后綴(object 的縮寫)。編譯的作用是對源程序進行詞法檢查和語法檢查。編譯時對文件中的全部內容進行檢查,編譯結束后會顯示出所有的編譯出錯信息。一般編譯系統給出的出錯信息分為兩種,一種是錯誤(error);一種是警告(warning) 。
3、將目標文件連接
在改正所有的錯誤並全部通過編譯后,得到一個或多個目標文件。此時要用系統提供的“連接程序(linker)”將一個程序的所有目標程序和系統的庫文件以及系統提供的其他信息連接起來,最終形成一個可執行的二進制文件,它的后綴是.exe,是可以直接執行的。
4、運行程序
運行最終形成的可執行的二進制文件(.exe文件),得到運行結果。
5、分析運行結果
如果運行結果不正確,應檢查程序或算法是否有問題。
C++中合法標識符的特征
第一個字符必須是字母(不分大小寫)或下划線(_);
后跟字母(不分大小寫)、下划線(_)或數字組成;
標識符中的大小寫字母有區別;
不能與c編譯系統已經預定義的、具有特殊用途的保留標識符(即關鍵字)同名。比如,不能將標識符命名為float,auto,break,case,this,try,for,while,int,char,short, unsigned,等等;
算符優先級(重點考察算術運算符、邏輯運算符和賦值運算符)
C++運算符優先級表,從上到下,從左到右,優先級依次減弱。 優先級 運算符 說明 結合性 1 :: 范圍解析 自左向右 2 ++ -- 后綴自增/后綴自減 () 括號 [] 數組下標 . 成員選擇(對象) −> 成員選擇(指針) 3 ++ -- 前綴自增/前綴自減 自右向左 + − 加/減 ! ~ 邏輯非/按位取反 (type) 強制類型轉換 * 取指針指向的值 & 某某的地址 sizeof 某某的大小 new,new[] 動態內存分配/動態數組內存分配 delete,delete[] 動態內存釋放/動態數組內存釋放 4 .* ->* 成員對象選擇/成員指針選擇 自左向右 5 * / % 乘法/除法/取余 6 + − 加號/減號 7 << >> 位左移/位右移 8 < <= 小於/小於等於 > >= 大於/大於等於 9 == != 等於/不等於 10 & 按位與 11 ^ 按位異或 12 | 按位或 13 && 與運算 14 || 或運算 15 ?: 三目運算符 自右向左 16 = 賦值 += −= 相加后賦值/相減后賦值 *= /= %= 相乘后賦值/相除后賦值/取余后賦值 <<= >>= 位左移賦值/位右移賦值 &= ^= |= 位與運算后賦值/位異或運算后賦值/位或運算后賦值 17 throw 拋出異常 18 , 逗號 自左向右
算術表達式、邏輯表達式
算術運算符
下表顯示了 C++ 支持的算術運算符。
假設變量 A 的值為 10,變量 B 的值為 20,則:
運算符 描述 實例 + 把兩個操作數相加 A + B 將得到 30 - 從第一個操作數中減去第二個操作數 A - B 將得到 -10 * 把兩個操作數相乘 A * B 將得到 200 / 分子除以分母 B / A 將得到 2 % 取模運算符,整除后的余數 B % A 將得到 0 ++ 自增運算符,整數值增加 1 A++ 將得到 11 -- 自減運算符,整數值減少 1 A-- 將得到 9 邏輯運算符
下表顯示了 C++ 支持的關系邏輯運算符。
假設變量 A 的值為 1,變量 B 的值為 0,則:
運算符 描述 實例 && 稱為邏輯與運算符。如果兩個操作數都非零,則條件為真。 (A && B) 為假。 || 稱為邏輯或運算符。如果兩個操作數中有任意一個非零,則條件為真。 (A || B) 為真。 ! 稱為邏輯非運算符。用來逆轉操作數的邏輯狀態。如果條件為真則邏輯非運算符將使其為假。 !(A && B) 為真。
條件判斷語句if的使用方法
循環語句(while, for)的使用方法
while循環語法
C++ 中 while 循環的語法:
while(condition) { statement(s); }在這里,statement(s) 可以是一個單獨的語句,也可以是幾個語句組成的代碼塊。condition 可以是任意的表達式,當為任意非零值時都為真。當條件為真時執行循環。
當條件為假時,程序流將繼續執行緊接着循環的下一條語句。
流程圖
在這里,while 循環的關鍵點是循環可能一次都不會執行。當條件被測試且結果為假時,會跳過循環主體,直接執行緊接着 while 循環的下一條語句。
for循環語法
C++ 中 for 循環的語法:
for ( init; condition; increment ) { statement(s); }下面是 for 循環的控制流:
- init 會首先被執行,且只會執行一次。這一步允許您聲明並初始化任何循環控制變量。您也可以不在這里寫任何語句,只要有一個分號出現即可。
- 接下來,會判斷 condition。如果為真,則執行循環主體。如果為假,則不執行循環主體,且控制流會跳轉到緊接着 for 循環的下一條語句。
- 在執行完 for 循環主體后,控制流會跳回上面的 increment 語句。該語句允許您更新循環控制變量。該語句可以留空,只要在條件后有一個分號出現即可。
- 條件再次被判斷。如果為真,則執行循環,這個過程會不斷重復(循環主體,然后增加步值,再然后重新判斷條件)。在條件變為假時,for 循環終止。
流程圖
函數默認參數的使用方法(注意默認參數和非默認參數的順序)
① 有函數聲明(原型)時,默認參數可以放在函數聲明或者定義中,但只能放在二者之一
double sqrt(double f = 1.0); //函數聲明 double sqrt(double f) //函數定義 { // .... }
② 沒有函數(原型)時,默認參數在函數定義時指定.
//沒有 函數聲明 double sqrt(double f = 1.0) //函數定義
③ 在具有多個參數的函數中指定默認值時,默認參數都必須出現在不默認參數的右邊,一旦某個參數開始指定默認值,它右邊的所有參數都必須指定默認值.
int f (int i1, int i2 = 2, int i3 = 3); // 正確 int g (int i1, int i2 = 2, int i3); // 錯誤, i3未指定默認值 int h (int i1 = 1, int i2, int i3 = 3); // 錯誤, i2未指定默認值
④ 在調用具有默認參數的函數時, 若某個實參默認,其右邊的所有實參都應該默認。
//例如, 一個函數聲明如下 int f(int i1 = 1, int i2 =2, int i3 = 3); //調用函數 f() f(); //正確, i1=1, i2=2, i3=3 f(3); //正確, i1=3, i2=2, i3=3 f(2, 3); //正確, i1=2, i2=3, i3=3 f(4, 5, 6); //正確, i1=4, i2=5, i3=6 f(, 2, 3); //錯誤, i1默認,其右邊的i2和i3沒有默認
重載函數的定義
一.重載函數的定義
函數的重載是一種特殊情況,C++允許在同一作用域中聲明幾個類似的同名函數,這些同名函數的形參列表
(參數個數,類型,順序)必須不同,常用來處理實現功能類似數據類型不同的問題。
在C++中不僅函數可以重載,運算符也可以重載。
運算符<<>>。既可以做移位運算符,也可以做輸出,輸入運算符。
注意:重載函數的參數個數,參數類型或參數順序三者中必須有一個不同。
函數重載的規則:
*函數名稱必須相同。
*參數列表必須不同(個數不同,類型不同,參數排列順序不同等)。
*函數的返回類型也可以相同也可以不相同。
*僅僅返回類型不同不足以成為函數的重載。
二.函數重載的作用
重載函數通常用來在同一作用域內 用同一個函數名 命名一組功能相似的函數,
這樣做減少了函數名的數量,避免了名字空間的污染,也大大方便了代碼的書寫,可讀性很強。
三.重載底層編譯的原理
因為函數名一樣,我們根據參數列表對函數進行重命名,
這樣的話底層編譯就可以識別不同功能的重載函數了。
例:
void Swap(int a , int b); Swap_int_int; void Swap(float a, float b); Swap_float_float; void Swap(float a, float b); Swap_float_float;
我們可以這樣去理解,先重命名函數,再去調用函數。
數組的定義,數組下標的范圍,數組元素的使用方法(下標引用,指針引用)
初始化字符數組的方法(注意字符串以’\0’結尾,多占一個字符)
發現了一個字符數組初始化的誤區,而這個往往能導致比較嚴重的性能問題,分析介紹如下:
往往我們在初始化一個字符 數組,大概有如下幾種寫法:char array1[1024] = ""; char array2[1024] = {0}; char array3[1024] = {'\0'}; char array4[1024]; array4[0] = '\0';
但這四種寫法,其實代表含義不同,看起來前三種寫法只是將array的第一個字符置為0,其實前三種在gcc編譯時,都是調用了memset來將整個array置為0,如果這個array很長,其實也會導致性能問題。我寫了一個簡單的小程序編譯生成test,objdump了一 下,執行“objdump -S test”可以看下面的代碼:
1 int main() { 2 400698: 55 push %rbp 3 400699: 48 89 e5 mov %rsp,%rbp 4 40069c: 48 81 ec 00 10 00 00 sub $0x1000,%rsp 5 char array1[1024] = ""; 6 4006a3: 0f b6 05 42 01 00 00 movzbl 322(%rip),%eax # 4007ec <_IO_stdin_used+0x4> 7 4006aa: 88 85 00 fc ff ff mov %al,0xfffffffffffffc00(%rbp) 8 4006b0: 48 8d bd 01 fc ff ff lea 0xfffffffffffffc01(%rbp),%rdi 9 4006b7: ba ff 03 00 00 mov $0x3ff,%edx 10 4006bc: be 00 00 00 00 mov $0x0,%esi 11 4006c1: e8 fa fe ff ff callq 4005c0 <memset@plt> //調用了memset 12 13 char array2[1024] = {0}; 14 4006c6: 48 8d bd 00 f8 ff ff lea 0xfffffffffffff800(%rbp),%rdi 15 4006cd: ba 00 04 00 00 mov $0x400,%edx 16 4006d2: be 00 00 00 00 mov $0x0,%esi 17 4006d7: e8 e4 fe ff ff callq 4005c0 <memset@plt> //調用了memset 18 19 char array3[1024] = {'\0'}; 20 4006dc: 48 8d bd 00 f4 ff ff lea 0xfffffffffffff400(%rbp),%rdi 21 4006e3: ba 00 04 00 00 mov $0x400,%edx 22 4006e8: be 00 00 00 00 mov $0x0,%esi 23 4006ed: e8 ce fe ff ff callq 4005c0 <memset@plt> //調用了memset 24 25 char array4[1024]; 26 array4[0] = '\0'; 27 4006f2: c6 85 00 f0 ff ff 00 movb $0x0,0xfffffffffffff000(%rbp) 28 29 return 0; 30 4006f9: b8 00 00 00 00 mov $0x0,%eax 31 }
所以,對這四種寫法,實際執行的代碼解釋如下:
char array1[1024] = ""; //第11行,調用memset將1023個字符置為0
char array2[1024] = {0}; //第17行,調用memset將1024個字符置為0
char array3[1024] = {'\0'}; //第23行,調用memset將1024個字符置為0
char array4[1024];
array4[0] = '\0'; //只是將第一個字符置為0而對於字符數組,往往只是作為一個字符串的臨時緩沖區使用,沒有必要將整個數組置為0,所以第四種寫法往往就能達到初始化的目的。建議使用第四種寫法來初始化一個字符數組,這樣能節約很多性能消耗。
引用的建立方法
1. 引用基本用法
引用是c++對c的重要擴充。在c/c++中指針的作用基本都是一樣的,但是c++增加了另外一種給函數傳遞地址的途徑,這就是按引用傳遞(pass-by-reference),它也存在於其他一些編程語言中,並不是c++的發明。
變量名實質上是一段連續內存空間的別名,是一個標號(門牌號)
程序中通過變量來申請並命名內存空間
通過變量的名字可以使用存儲空間
對一段連續的內存空間只能取一個別名嗎?
c++中新增了引用的概念,引用可以作為一個已定義變量的別名。
基本語法:
Type& ref = val;
注意事項:
&在此不是求地址運算,而是起標識作用。
類型標識符是指目標變量的類型
必須在聲明引用變量時進行初始化。
引用初始化之后不能改變。
不能有NULL引用。必須確保引用是和一塊合法的存儲單元關聯。
可以建立對數組的引用。
//1. 認識引用 void test01(){ int a = 10; //給變量a取一個別名b int& b = a; cout << "a:" << a << endl; cout << "b:" << b << endl; cout << "------------" << endl; //操作b就相當於操作a本身 b = 100; cout << "a:" << a << endl; cout << "b:" << b << endl; cout << "------------" << endl; //一個變量可以有n個別名 int& c = a; c = 200; cout << "a:" << a << endl; cout << "b:" << b << endl; cout << "c:" << c << endl; cout << "------------" << endl; //a,b,c的地址都是相同的 cout << "a:" << &a << endl; cout << "b:" << &b << endl; cout << "c:" << &c << endl; } //2. 使用引用注意事項 void test02(){ //1) 引用必須初始化 //int& ref; //報錯:必須初始化引用 //2) 引用一旦初始化,不能改變引用 int a = 10; int b = 20; int& ref = a; ref = b; //不能改變引用 //3) 不能對數組建立引用 int arr[10]; //int& ref3[10] = arr; } //1. 建立數組引用方法一 typedef int ArrRef[10]; int arr[10]; ArrRef& aRef = arr; for (int i = 0; i < 10;i ++){ aRef[i] = i+1; } for (int i = 0; i < 10;i++){ cout << arr[i] << " "; } cout << endl; //2. 建立數組引用方法二 int(&f)[10] = arr; for (int i = 0; i < 10; i++){ f[i] = i+10; } for (int i = 0; i < 10; i++){ cout << arr[i] << " "; } cout << endl;
2. 函數中的引用
最常見看見引用的地方是在函數參數和返回值中。當引用被用作函數參數的時,在函數內對任何引用的修改,將對還函數外的參數產生改變。當然,可以通過傳遞一個指針來做相同的事情,但引用具有更清晰的語法。
如果從函數中返回一個引用,必須像從函數中返回一個指針一樣對待。當函數返回值時,引用關聯的內存一定要存在。
//值傳遞 void ValueSwap(int m,int n){ int temp = m; m = n; n = temp; } //地址傳遞 void PointerSwap(int* m,int* n){ int temp = *m; *m = *n; *n = temp; } //引用傳遞 void ReferenceSwap(int& m,int& n){ int temp = m; m = n; n = temp; } void test(){ int a = 10; int b = 20; //值傳遞 ValueSwap(a, b); cout << "a:" << a << " b:" << b << endl; //地址傳遞 PointerSwap(&a, &b); cout << "a:" << a << " b:" << b << endl; //引用傳遞 ReferenceSwap(a, b); cout << "a:" << a << " b:" << b << endl; }
通過引用參數產生的效果同按地址傳遞是一樣的。引用的語法更清楚簡單:
1) 函數調用時傳遞的實參不必加“&”符
2) 在被調函數中不必在參數前加“*”符
引用作為其它變量的別名而存在,因此在一些場合可以代替指針。C++主張用引用傳遞取代地址傳遞的方式,因為引用語法容易且不易出錯。
不能返回局部變量的引用。
函數當左值,必須返回引用。
//返回局部變量引用 int& TestFun01(){ int a = 10; //局部變量 return a; } //返回靜態變量引用 int& TestFunc02(){ static int a = 20; cout << "static int a : " << a << endl; return a; } int main(){ //不能返回局部變量的引用 int& ret01 = TestFun01(); //如果函數做左值,那么必須返回引用 TestFunc02(); TestFunc02() = 100; TestFunc02(); return EXIT_SUCCESS; }
3. 引用的本質
引用的本質在c++內部實現是一個指針常量.
Type& ref = val; // Type* const ref = &val;
c++編譯器在編譯過程中使用常指針作為引用的內部實現,因此引用所占用的空間大小與指針相同,只是這個過程是編譯器內部實現,用戶不可見。
//發現是引用,轉換為 int* const ref = &a; void testFunc(int& ref){ ref = 100; // ref是引用,轉換為*ref = 100 } int main(){ int a = 10; int& aRef = a; //自動轉換為 int* const aRef = &a;這也能說明引用為什么必須初始化 aRef = 20; //內部發現aRef是引用,自動幫我們轉換為: *aRef = 20; cout << "a:" << a << endl; cout << "aRef:" << aRef << endl; testFunc(a); return EXIT_SUCCESS; }
4. 指針引用
在c語言中如果想改變一個指針的指向而不是它所指向的內容,函數聲明可能這樣:
void fun(int**);
給指針變量取一個別名。
Type* pointer = NULL;
Type*& = pointer;
Type* pointer = NULL; Type*& = pointer;
struct Teacher{ int mAge; }; //指針間接修改teacher的年齡 void AllocateAndInitByPointer(Teacher** teacher){ *teacher = (Teacher*)malloc(sizeof(Teacher)); (*teacher)->mAge = 200; } //引用修改teacher年齡 void AllocateAndInitByReference(Teacher*& teacher){ teacher->mAge = 300; } void test(){ //創建Teacher Teacher* teacher = NULL; //指針間接賦值 AllocateAndInitByPointer(&teacher); cout << "AllocateAndInitByPointer:" << teacher->mAge << endl; //引用賦值,將teacher本身傳到ChangeAgeByReference函數中 AllocateAndInitByReference(teacher); cout << "AllocateAndInitByReference:" << teacher->mAge << endl; free(teacher); }
對於c++中的定義那個,語法清晰多了。函數參數變成指針的引用,用不着取得指針的地址。
5. 常量引用
常量引用的定義格式:
const Type& ref = val;
常量引用注意:
1.字面量不能賦給引用,但是可以賦給const引用
2.const修飾的引用,不能修改。
void test01(){ int a = 100; const int& aRef = a; //此時aRef就是a //aRef = 200; 不能通過aRef的值 a = 100; //OK cout << "a:" << a << endl; cout << "aRef:" << aRef << endl; } void test02(){ //不能把一個字面量賦給引用 //int& ref = 100; //但是可以把一個字面量賦給常引用 const int& ref = 100; //int temp = 200; const int& ret = temp; }
[const引用使用場景]
常量引用主要用在函數的形參,尤其是類的拷貝/復制構造函數。
將函數的形參定義為常量引用的好處:
- 引用不產生新的變量,減少形參與實參傳遞時的開銷。
- 由於引用可能導致實參隨形參改變而改變,將其定義為常量引用可以消除這種副作用。
如果希望實參隨着形參的改變而改變,那么使用一般的引用,如果不希望實參隨着形參改變,那么使用常引用。
//const int& param防止函數中意外修改數據 void ShowVal(const int& param){ cout << "param:" << param << endl; }
面向對象中成員訪問標簽的使用方法:公有,私有,保護,注意三者的區別
C++中 public,protected, private 訪問標號小結
第一:private, public, protected 訪問標號的訪問范圍。private:只能由1.該類中的函數、2.其友元函數訪問。不能被任何其他訪問,該類的對象也不能訪問。
protected:可以被1.該類中的函數、2.子類的函數、以及3.其友元函數訪問。但不能被該類的對象訪問。
public:可以被1.該類中的函數、2.子類的函數、3.其友元函數訪問,也可以由4.該類的對象訪問。注:友元函數包括3種:設為友元的普通的非成員函數;設為友元的其他類的成員函數;設為友元類中的所有成員函數。
第二:類的繼承后方法屬性變化。private 屬性不能夠被繼承。
使用private繼承,父類的protected和public屬性在子類中變為private;
使用protected繼承,父類的protected和public屬性在子類中變為protected;
使用public繼承,父類中的protected和public屬性不發生改變;
靜態數據成員初始化方法(注意,如果定義時沒賦初值,對於靜態成員會初始化為0)
靜態成員的初始化:
與全局對象一樣對於靜態數據成員在程序中也只能提供一個定義,這意味着靜態數據成員的初始化不應該被放在頭文件中而應該放在含有類的非inline函數定義的文件中。
能在類中初始化的成員只有一種,那就是靜態常量成員。
class A { private: static const int count = 0; // 靜態常量成員可以在類內初始化 };
結論:
- 靜態常量數據成員可以在類內初始化(即類內聲明的同時初始化),也可以在類外,即類的實現文件中初始化,不能在構造函數中初始化,也不能在構造函數的初始化列表中初始化;
- 靜態非常量數據成員只能在類外,即類的實現文件中初始化,也不能在構造函數中初始化,不能在構造函數的初始化列表中初始化;
- 非靜態的常量數據成員不能在類內初始化,也不能在構造函數中初始化,而只能且必須在構造函數的初始化列表中初始化;
- 非靜態的非常量數據成員不能在類內初始化,可以在構造函數中初始化,也可以在構造函數的初始化列表中初始化;
構造函數、拷貝構造函數、析構函數的使用方法
菜鳥教程(構造&析構) https://www.runoob.com/cplusplus/cpp-constructor-destructor.html
虛函數的定義及使用方法(搞清楚虛函數的使用場景)
在某基類中聲明為 virtual 並在一個或多個派生類中被重新定 義的成員函數,用法格式為:virtual 函數返回類型 函數名(參數表) {函數體};實現多態性,通過指向派生類的基類指針或引用,訪問派生類中同名覆蓋成員函數。
虛函數定義:簡單地說,那些被virtual關鍵字修飾的成員函數,就是虛函數。虛函數的作用,用專業術語來解釋就是實現多態性(Polymorphism),多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異,而采用不同的策略。
虛函數的作用:用同一個調用形式,既能調用派生類又能調用基類的同名函數。
虛函數的使用方法是:
1. 在基類用virtual聲明成員函數為虛函數。
這樣就可以在派生類中重新定義此函數,為它賦予新的功能,並能方便地被調用。在類外定義虛函數時,不必再加virtual。
2. 在派生類中重新定義此函數,要求函數名、函數類型、函數參數個數和類型全部與基類的虛函數相同,並根據派生類的需要重新定義函數體。
C++規定,當一個成員函數被聲明為虛函數后,其派生類中的同名函數都自動成為虛函數。因此在派生類重新聲明該虛函數時,可以加virtual,也可以不加,但習慣上一般在每一層聲明該函數時都加virtual,使程序更加清晰。如果在派生類中沒有對基類的虛函數重新定義,則派生類簡單地繼承其直接基類的虛函數。
3. 定義一個指向基類對象的指針變量,並使它指向同一類族中需要調用該函數的對象。
4. 通過該指針變量調用此虛函數,此時調用的就是指針變量指向的對象的同名函數。
通過虛函數與指向基類對象的指針變量的配合使用,就能方便地調用同一類族中不同類的同名函數,只要先用基類指針指向即可。如果指針不斷地指向同一類族中不同類的對象,就能不斷地調用這些對象中的同名函數。這就如同前面說的,不斷地告訴出租車司機要去的目的地,然后司機把你送到你要去的地方。
虛基類的使用方法,為什么要使用虛基類
在類的繼承中,如果我們遇到這種情況:
“B和C同時繼承A,而B和C都被D繼承”
在此時,假如A中有一個函數fun()當然同時被B和C繼承,而D按理說繼承了B和C,同時也應該能調用fun()函數。這一調用就有問題了,到底是要調用B中的fun()函數還是調用C中的fun()函數呢?在C++中,有兩種方法實現調用:
(注意:這兩種方法效果是不同的)
- 使用作用域標識符來唯一表示它們比如:B::fun()
- 另一種方法是定義虛基類,使派生類中只保留一份拷貝。
繼承有哪幾種方式,每種方式的特點是什么?
如果在定義派生類時在基類前沒有指定訪問標簽,默認情況下是哪種繼承的方式?
private 私有繼承
輸入輸出流對象的使用方法
new, delete語句的使用方法
對於計算機程序設計而言,變量和對象在內存中的分配都是編譯器在編譯程序時安排好的,這帶來了極大的不便,如數組必須大開小用,指針必須指向一個已經存在的變量或對象。對於不能確定需要占用多少內存的情況,動態內存分配解決了這個問題。
new和delete運算符是用於動態分配和撤銷內存的運算符。
一、new用法
1.開辟單變量地址空間
使用new運算符時必須已知數據類型,new運算符會向系統堆區申請足夠的存儲空間,如果申請成功,就返回該內存塊的首地址,如果申請不成功,則返回零值。
new運算符返回的是一個指向所分配類型變量(對象)的指針。對所創建的變量或對象,都是通過該指針來間接操作的,而動態創建的對象本身沒有標識符名。
一般使用格式:
格式1:指針變量名=new 類型標識符;
格式2:指針變量名=new 類型標識符(初始值);
格式3:指針變量名=new 類型標識符 [內存單元個數];說明:格式1和格式2都是申請分配某一數據類型所占字節數的內存空間;但是格式2在內存分配成功后,同時將一初值存放到該內存單元中;而格式3可同時分配若干個內存單元,相當於形成一個動態數組。例如:
1)new int; //開辟一個存放整數的存儲空間,返回一個指向該存儲空間的地址。int *a = new int 即為將一個int類型的地址賦值給整型指針a
2)int *a = new int(5) 作用同上,但是同時將整數空間賦值為5
2.開辟數組空間
對於數組進行動態分配的格式為:
指針變量名=new 類型名[下標表達式];
delete [ ] 指向該數組的指針變量名;兩式中的方括號是非常重要的,兩者必須配對使用,如果delete語句中少了方括號,因編譯器認為該指針是指向數組第一個元素的指針,會產生回收不徹底的問題(只回收了第一個元素所占空間),加了方括號后就轉化為指向數組的指針,回收整個數組。
delete []的方括號中不需要填數組元素數,系統自知。即使寫了,編譯器也忽略。
請注意“下標表達式”不必是常量表達式,即它的值不必在編譯時確定,可以在運行時確定。
一維: int *a = new int[100]; //開辟一個大小為100的整型數組空間
二維: int **a = new int[5][6]
三維及其以上:依此類推.
一般用法: new 類型 (初值)
二、delete用法
1. 刪除單變量地址空間
int *a = new int;
delete a; //釋放單個int的空間
2. 刪除數組空間
int *a = new int[5];
delete []a; //釋放int數組空間
三、使用注意事項
1. new 和delete都是內建的操作符,語言本身所固定了,無法重新定制,想要定制new和delete的行為,徒勞無功的行為。
2. 動態分配失敗,則返回一個空指針(NULL),表示發生了異常,堆資源不足,分配失敗。
3. 指針刪除與堆空間釋放。刪除一個指針p(delete p;)實際意思是刪除了p所指的目標(變量或對象等),釋放了它所占的堆空間,而不是刪除p本身(指針p本身並沒有撤銷,它自己仍然存在,該指針所占內存空間並未釋放),釋放堆空間后,p成了空指針。
4. 內存泄漏(memory leak)和重復釋放。new與delete 是配對使用的, delete只能釋放堆空間。如果new返回的指針值丟失,則所分配的堆空間無法回收,稱內存泄漏,同一空間重復釋放也是危險的,因為該空間可能已另分配,所以必須妥善保存new返回的指針,以保證不發生內存泄漏,也必須保證不會重復釋放堆內存空間。
5. 動態分配的變量或對象的生命期。我們也稱堆空間為自由空間(free store),但必須記住釋放該對象所占堆空間,並只能釋放一次,在函數內建立,而在函數外釋放,往往會出錯。
6. 要訪問new所開辟的結構體空間,無法直接通過變量名進行,只能通過賦值的指針進行訪問。
用new和delete可以動態開辟和撤銷地址空間。在編程序時,若用完一個變量(一般是暫時存儲的數據),下次需要再用,但卻又想省去重新初始化的功夫,可以在每次開始使用時開辟一個空間,在用完后撤銷它。
什么函數會有this指針?友元函數是成員函數嗎?友元的作用是什么?
二、編程題(60分)
基本循環(1題)
編寫程序,計算下式的值 1-3+5-7….-99
// 編寫程序,計算下式的值 1-3+5-7….-99 #include<iostream> using namespace std; int main(){ int num,flag; num=flag=1; int sum=0; while(num<100){ sum+=num*flag; num+=2; flag=-flag; } cout<<sum<<endl; system("pause"); }
編寫程序,計算並輸出半徑r = 1到r = 20之間半徑為整數的圓形的面積,直到面積大於100為止
// 編寫程序,計算並輸出半徑r = 1到r = 20之間半徑為整數的圓形的面積,直到面積大於100為止 #include<iostream> #include<cmath> using namespace std; //利用反三角函數定義宏Pi #define Pi acos(-1) int main(){ for(int r=1;r++;r<20){ int s=Pi*pow(r,2); if(s>100){ break; } cout<<s<<endl; } system("pause"); }
水仙花數怎么計算,完數怎么計算等,具體看一下循環章節的練習題
// 水仙花數 #include<iostream> using namespace std; //定義宏 #define f(a) (a)*(a)*(a) int main(){ for(int i=100;i<1000;i++){ //判斷條件 if(f(i%10)+f(i/10%10)+f(i/100%10)==i){ cout<<i<<endl; } } system("pause"); return 0; }
// 完數 #include<iostream> using namespace std; int main() { int i,j,k,sum; cout<<" 1000以內的完數有:"<<endl; for(i=2;i<=1000;i++) { k=i/2; sum=0; for(j=1;j<=k;j++) if(i%j==0) sum=sum+j; if(sum==i) cout<<"sum="<<sum<<endl; } return 0; system("pause"); }
數組使用方法(1題)
有如下矩陣,編寫三個函數,分別計算每行的和、每列的和,矩陣中最大元素所在的位置
//矩陣 求各行各列的和 #include<iostream> using namespace std; int main(){ // 矩陣的定義 int arr[4][3]={{12,2,3},{4,5,6},{7,8,9},{10,11,12}}; // 二維數組行求和 for(int i=0;i<4;i++){ int rowSum = 0; for(int j=0;j<3;j++){ rowSum += arr[i][j]; } cout<<"第"<<i<<"行的和"<<rowSum<<endl; } // 二維數組列求和 for(int j=0;j<3;j++){ int columnSum = 0; for(int i=0;i<4;i++){ columnSum += arr[j][i]; } cout<<"第"<<j<<"列的和"<<columnSum<<endl; } // 矩陣最大元素 - 冒泡排序 int max = 0; for(int i=0;i<4;i++){ for(int j=0;j<3;j++){ if(arr[i][j]>max){ max = arr[i][j]; } } } cout<<"矩陣中的最大元素是:"<<max<<endl; system("pause"); }
編寫函數,求矩陣Y的值
題目有問題,考察矩陣乘法操作
字符串操作(1題)
給定一個字符串,寫函數判斷字符串中每個數字字符出現的頻率
#include<iostream> #include<string> using namespace std; int main() { string str; cout<<"input some text:"<<endl; //輸入一個字符串,傳給字符串變量str getline(cin,str); int frequency[256]={}; for(int i=0;i<str.size();i++){ frequency[(int)str[i]]++; // cnt[str[i]]++與str[i]相對應的字符個數增加一個 } for(int i=0;i<256;i++)//輸出字符出現次數 { if(frequency[i]!=0) // cnt[i]!=0遇到串結符也就是\0就等於0 cout<<(char)i<<':'<<frequency[i]<<"次"<<endl; } }
給定一個字符串,寫函數判斷字符串中是否包含另一個字符串
給定一個字符串,寫函數判斷該字符串是否為回文字串(正讀反讀均相同)
面向對象編程(1題) ---- 多態章節
多態的基本概念
多態是C++面向對象三大特性之一
多態分為兩類
- 靜態多態: 函數重載 和 運算符重載屬於靜態多態,復用函數名
- 動態多態: 派生類和虛函數實現運行時多態
靜態多態和動態多態區別:
- 靜態多態的函數地址早綁定 - 編譯階段確定函數地址
- 動態多態的函數地址晚綁定 - 運行階段確定函數地址
下面通過案例進行講解多態
class Animal { public: //Speak函數就是虛函數 //函數前面加上virtual關鍵字,變成虛函數,那么編譯器在編譯的時候就不能確定函數調用了。 virtual void speak() { cout << "動物在說話" << endl; } }; class Cat :public Animal { public: void speak() { cout << "小貓在說話" << endl; } }; class Dog :public Animal { public: void speak() { cout << "小狗在說話" << endl; } }; //我們希望傳入什么對象,那么就調用什么對象的函數 //如果函數地址在編譯階段就能確定,那么靜態聯編 //如果函數地址在運行階段才能確定,就是動態聯編 void DoSpeak(Animal & animal) { animal.speak(); } // //多態滿足條件: //1、有繼承關系 //2、子類重寫父類中的虛函數 //多態使用: //父類指針或引用指向子類對象 void test01() { Cat cat; DoSpeak(cat); Dog dog; DoSpeak(dog); } int main() { test01(); system("pause"); return 0; }
總結:
多態滿足條件
- 有繼承關系
- 子類重寫父類中的虛函數
多態使用條件
- 父類指針或引用指向子類對象
重寫:函數返回值類型 函數名 參數列表 完全一致稱為重寫
4.7.2 多態案例一-計算器類
案例描述:
分別利用普通寫法和多態技術,設計實現兩個操作數進行運算的計算器類
多態的優點:
- 代碼組織結構清晰
- 可讀性強
- 利於前期和后期的擴展以及維護
示例:
//普通實現 class Calculator { public: int getResult(string oper) { if (oper == "+") { return m_Num1 + m_Num2; } else if (oper == "-") { return m_Num1 - m_Num2; } else if (oper == "*") { return m_Num1 * m_Num2; } //如果要提供新的運算,需要修改源碼 } public: int m_Num1; int m_Num2; }; void test01() { //普通實現測試 Calculator c; c.m_Num1 = 10; c.m_Num2 = 10; cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl; cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl; cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl; }
//多態實現 //抽象計算器類 //多態優點:代碼組織結構清晰,可讀性強,利於前期和后期的擴展以及維護 class AbstractCalculator { public : virtual int getResult() { return 0; } int m_Num1; int m_Num2; }; //加法計算器 class AddCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 + m_Num2; } }; //減法計算器 class SubCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 - m_Num2; } }; //乘法計算器 class MulCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 * m_Num2; } }; void test02() { //創建加法計算器 AbstractCalculator *abc = new AddCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl; delete abc; //用完了記得銷毀 //創建減法計算器 abc = new SubCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl; delete abc; //創建乘法計算器 abc = new MulCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl; delete abc; } int main() { //test01(); test02(); system("pause"); return 0; }
總結:C++開發提倡利用多態設計程序架構,因為多態優點很多