變量
-
變量的作用
方便我們管理內存空間 -
變量使用語法
數據類型 變量名 = 變量初始值; 示例: int a = 10; cout << "a = " << a << endl;釋義:創建變量 a ,輸出內容為變量 a 的值。
常量
-
常量的作用
用于記錄程序中不可更改的數據 -
define 宏常量使用語法
#define 常量名 常量值 宏常量通常定義在最上方釋義:#define 通常在文件上方定義,表示一個常量
-
const 修飾的常量使用語法
const 數據類型 常量名 = 常量值 修飾變量位置可在最上方也可在調用處上方釋義:const通常在變量定義前加關鍵字const,修飾該變量為常量,不可修改
-
示例:
#define Day 365 const int month = 12;
關鍵字
- 不要用關鍵字給變量或常量起名稱,否則會產生歧義。
| asm | auto | bool | break | case | catch | char |
|---|---|---|---|---|---|---|
| class | const | const_cast | continue | default | delete | do |
| double | dynamic_cast | else | enum | explicit | export | extern |
| false | float | for | friend | goto | if | inline |
| int | long | mutable | namespace | new | operator | private |
| protected | public | register | reinterpret_cast | return | short | signed |
| sizeof | static | static_cast | struct | switch | template | this |
| throw | true | try | typedef | typeid | typename | union |
| unsigned | using | virtual | void | volatile | wchar_t | while |
標識符命名規則
- 標識符作用:給變量、常量命名
- 標識符不能是關鍵字
- 標識符只能由字母、數字、下劃線組成
- 第一個字符必須為字母或下劃線
- 標識符中字母區分大小寫
數據類型
C++規定在創建一個變量或常量時,必須要指定出相應的數據類型,否則無法給變量分配內存。
數據類型-整型
- 整型變量表示的是整數類型的數據
- C++中能夠表示整型的類型有以下幾種方式,區別在于所占內存空間不同:
| 數據類型 | 占用空間 | 取值范圍 |
|---|---|---|
| short(短整數) | 2字節 | (-2^15 ~2^15-1) |
| int(整型) | 4字節 | (-2^31 ~2^31-1) |
| long(長整型) | Windows為4字節,Linux為4字節(32位),8字節(64位) | (-2^31 ~2^31-1) |
| long long (長長整型) | 8字節 | (-2^63 ~2^63-1) |
數據類型sizeof關鍵字
-
作用:利用sizeof關鍵字可以統計數據類型所占內存大小
-
語法
sizeof(數據類型 / 變量) -
示例:
int main() { short a = 10; int b =11; long c = 12; long long d = 13; cout << "short 類型所占內存空間為:" << sizeof(short) << endl; cout << "int 類型所占內存空間為:" << sizeof(int) << endl; cout << "long 類型所占內存空間為:" << sizeof(long) << endl; cout << "long long 類型所占內存空間為:" << sizeof(long long) << endl; system("pause"); system("cls"); //清屏操作 return 0; }
數據類型-實型
- 作用:用于表示小數
- 浮點型變量分為兩種:
- 單精度 float
- 雙精度 double
- 兩者區別在于表示的有效數字范圍不同
| 數據類型 | 占用空間 | 有效數字范圍 |
|---|---|---|
| float | 4字節 | 7位有效數字 |
| double | 8字節 | 15~16位有效數字 |
-
默認情況下,輸出一個小數,會顯示出6位有效數字
-
float 類型的變量后一般加一個 f ,否則float的數據類型默認識別為double雙精度。
-
示例:
int main() { float f1 = 3.1415926f; double f2 =3.1415926; cout << "f1 = " << f1 << endl; cout << "f2 = " << f2<< endl; //科學計數法 float f3 = 3e2; //3 * 10^2; float f4 = 3e-2; //3 * 0.1^2; // e 后面如果是正數,則代表乘以100^n; // e 后面如果是負數,則代表乘以0.1^n; system("pause"); return 0; }
數據類型-字符型
-
作用:字符型變量用于顯示單個字符
-
語法:
char ch = 'a';注意1:在顯示字符型變量時,用單引號將字符括起來,不要用雙引號
注意2:單引號內只能有一個字符,不可以用字符串 -
C和C++中字符型變量只占用一個字節
-
字符型變量并不是把字符本身放到內存中存儲,而是將對應的ASCII編碼放入到存儲單元
-
示例
int main() { char ch1 = 'a'; char ch2 = 'A'; cout << ch1 << endl; cout << (int)ch2 << endl; //釋義1 system("pause"); return 0; }釋義1:輸出 char 被強制轉化成 int 類型的語句。就是輸出 A 的ASCII編碼 ‘65’
-
ASCII碼大致由以下兩部分組成:
- ASCII非打印控制字符:ASCII表上的數字 0-31 分配給了控制字符,用于控制打印機等一些外圍設備
- ASCII打印字符:數字 32-126 分配給了能在鍵盤上找到的字符,當查看或打印文檔時就會出現。
數據類型-轉義字符
- 作用:用于表示一些不能顯示出來的ASCII字符,例如常用的有:\n \ \t
數據類型-字符串型
-
作用:用于表示一串字符
-
注意:字符串值需要用雙引號 "" 包含起來,與字符使用的單引號 '' 做區分。
-
C風格字符串:char 變量名[] = "字符串值";
示例:int main() { char str1[] = "hello-world"; //C風格字符串需要在變量名后面加中括號 [] cout << str1 << endl; system("pause"); return 0; } -
C++風格字符串:string 變量名 = "字符串值";
示例:#include<string> //用C++風格字符串時候,要引用這個頭文件 int main() { string str2 = "hello-world"; cout << str2 << endl; system("pause"); return 0; }
布爾類型bool
-
作用:布爾數據類型代表真或假的值
-
bool類型只有兩個值
- true --- 真(本質是1)
- false --- 假(本質是0)
-
bool類型占1個字節大小
-
示例:
int main() { bool flag = true; cout << flag << endl; flag = false; //重新賦值 cout << flag << endl; cout << "size of bool =" << sizeof(bool) << endl; system("pause"); return 0; }
數據的輸入
-
作用:用于從鍵盤獲取數據
-
關鍵字:cin
-
語法
cin >> 變量; -
示例:
int main() { int a = 0; cout << "請輸入整型變量:" << endl; cin >> a; cout << a << endl; double d =0; cout << "請輸入浮點型變量:" << endl; cin >> d; cout << d << endl; char ch =0; cout << "請輸入字符型變量:" << endl; cin >> ch; cout << ch << endl; system("pause"); return 0; }
運算符
作用:用于執行代碼的運算
| 運算符類型 | 作用 |
|---|---|
| 算數運算符 | 用于處理四則運算 |
| 賦值運算符 | 用于將表達式的賦值給變量 |
| 比較運算符 | 用于表達式的比較,并返回一個真值或假值 |
| 邏輯運算符 | 用于根據表達式的值返回真值或假值 |
算數運算符
作用:用于處理四則運算
| 運算符 | 術語 | 示例 | 結果 |
|---|---|---|---|
| + | 正號 | +3 | 3 |
| - | 負號 | -3 | -3 |
| + | 加 | 10+5 | 15 |
| - | 減 | 10-5 | 5 |
| * | 乘 | 10*5 | 50 |
| / | 除 | 10/5 | 2 |
| % | 取模(取余) | 10%3 | 1 |
| ++ | 前置遞增 | a=2;b=++a; | a=3;b=3 |
| ++ | 后置遞增 | a=2;b=a++; | a=3;b=2 |
| -- | 前置遞減 | a=2;b=--a; | a=1;b=1; |
| -- | 后置遞減 | a=2;b=a--; | a=1,b=2; |
-
示例:
int main() { //加減乘除 int a1 = 10; int b1 = 3; cout << a1 + b1 << endl; cout << a1 - b1 << endl; cout << a1 * b1 << endl; cout << a1 / b1 << endl; //兩個整數相除,結果依然是整數,將小數部分去除 int a3 = 10; int b3 = 20; cout << a3 / b3 << endl; //結果為0 int a2 = 10; int b2 = 0; cout << a2 / b2 << endl; //錯誤!兩個數相除,除數是不可以為0的 //兩個小數可以相除 double d1 = 0.5; double d2 = 0.25; cout << d1 / d2 << endl; //運算的結果也可以是小數 //取模運算本質就是求余數 int a4 = 10; int b4 = 3; cout << a4 % b4 << endl; //取余為1 int a5 = 10; int b5 = 20; cout << a5 % b5 << endl; //取余為10,當被除的數沒有除數大時,取余的值為被除的數值。取模運算的除數不可為0; //兩個小數時不可以做取模運算的 //前置與后置的區別 //前置遞增是先讓變量+1然后進行表達式運算 int a6 = 10; int b6 = ++a6 * 10; cout << a6 << endl; //輸出為11 cout << b6 << endl; //輸出為110 //后置遞增是先進行表達式運算后讓變量+1 int a7 = 10; int b7 = a7++ * 10; cout << a7 << endl; //輸出為11 cout << b7 << endl; //輸出為100 //前置遞減是先讓變量-1然后進行表達式運算 int a8 = 10; int b8 = --a8 * 10; cout << a8 << endl; //輸出為9 cout << b8 << endl; //輸出為90 //后置遞減是先進行表達式運算后讓變量-1 int a9 = 10; int b9 = a9-- * 10; cout << a9 << endl; //輸出為9 cout << b9 << endl; //輸出為100 system("pause"); return 0; }
賦值運算符
作用:用于將表達式的值賦給變量
| 運算符 | 術語 | 示例 | 結果 |
|---|---|---|---|
| = | 賦值 | a=2;b=3; | a=2;b=3; |
| += | 加等于 | a=0;a+=2; | a=2; |
| -= | 減等于 | a=5;a-=3; | a=2; |
| * = | 乘等于 | a=2;a*=2; | a=4; |
| /= | 除等于 | a=4;a/=2; | a=2; |
| %= | 模等于 | a=3;a%=2; | a=1; |
-
示例:
int main() { //加等于 += int a = 10; a += 2; //a = a + 2 cout << a << endl; //輸出為12 //減等于 -= a = 10; a -= 2; //a = a - 2 cout << a << endl; //輸出為8 //乘等于 *= a = 10; a *= 2; //a = a * 2 cout << a << endl; //輸出為20 //除等于 /= a = 10; a /= 2; //a = a / 2 cout << a << endl; //輸出為5 //模等于 %= a = 10; a %= 2; //a = a % 2 cout << a << endl; //輸出為0 system("pause"); return 0; }
比較運算符
作用:用于表達式的比較,并返回一個真值(1)或假值(0)
| 運算符 | 術語 | 示例 | 結果 |
|---|---|---|---|
| == | 相等于 | 4==3 | 0 |
| != | 不等于 | 4!=3 | 1 |
| < | 小于 | 4<3 | 0 |
| > | 大于 | 4>3 | 1 |
| <= | 小于等于 | 4<=3 | 0 |
| >= | 大于等于 | 4>=1 | 1 |
-
示例
int main() { //比較運算符 int a = 10; int b = 20; // == cout << (a == b) << endl; //輸出0,值為假 // != cout << (a != b) << endl; //輸出1,值為真 // > cout << (a > b) << endl; //輸出0,值為假 // < cout << (a < b) << endl; //輸出1,值為真 // <= cout << (a <= b) << endl; //輸出1,值為真 // >= cout << (a >= b) << endl; //輸出0,值為假 system("pause"); return 0; }
邏輯運算符
- 作用:用于根據表達式的值換貨真值或假值
- 運算符號
| 運算符 | 術語 | 示例 | 結果 |
|---|---|---|---|
| ! | 非 | !a | 如果a為假,則!a為真;如果a為真,則!a為假 |
| && | 與 | a&&b | 如果a和b都為真,則結果為真,否則為假 |
| || | 或 | a||b | 如果a和b有一個為真,則結果為真,二者都為假時,結果為假 |
-
示例
int main() { int a = 10; //在C++中值除了0都為真 //非運算,真變假,假變真 //邏輯運算符 非 ! cout << !a << endl; //輸出為0,值為假。非真即為假 cout << !!a << endl; //輸出為1,值為真。非假即為真 //同真為真,其余為假 //邏輯運算符 與 && int b = 10; cout << (a && b) << endl; //輸出為1,值為真。 int a = 0; cout << (a && b) << endl; //輸出為0.值為假。 int b = 0; cout << (a && b) << endl; //輸出為0,值為假,因為變量的值為0時都為假。 //同假為假,其余為真 //邏輯運算符 或 || int a = 10; int b = 10; cout << (a || b) << endl; //輸出為1,值為真。 int a = 0; cout << (a || b) << endl; //輸出為1,值為真。 int b = 0; cout << (a || b) << endl; //輸出為0,值為假。 system("pause"); return 0; }
程序流程結構
C/C++支持最基本的三種程序運行結構:順序結構、選擇結構、循環結構
- 順序結構:程序按順序執行,不發生跳轉
選擇結構
if 語句
作用:依據條件是否滿足,有選擇的執行相應功能,執行滿足條件的語句
if 語句的三種形式
-
單行格式 if 語句
語法:if (條件) {條件滿足執行的語句}
插入圖片
示例:int main() { //選擇結構單行 if 語句 //用戶輸入分數,如果分數大于600,視為考上一本大學,在屏幕上輸出 //用戶輸入分數 int score = 0; cout << "請輸入分數:" << endl; cin >> score; //把輸入的值賦值到 score 中 //打印用戶輸入的分數 cout << "您輸入的分數:" << score << endl; //判斷分數是否大于600,如果大于,那么輸出 // if 條件后面不要加分號 ; if (score > 600) { //判斷score值是否大于600 cout << "恭喜您考上了一本大學" << endl; } system("pause"); return 0; } -
多行格式 if 語句
語法: if (條件) {條件滿足執行的語句}else{條件不滿足執行的語句}
![單行格式if語法]()
示例:int main() { //選擇結構 多行 if 語句 //輸入考試分數,如果分數大于等于600,判斷考上一本,輸出考上一本 //如果輸入分數低于600,則輸出未考上一本 //輸入分數 int score = 0; cout << "請輸入考試分數:" << endl; cin >> score; //輸出用戶輸入的分數 cout << "您輸入的分數為:" << score << endl; //判斷是否滿足條件 if ( score >= 600 ) { //判斷score是否大于等于600 cout << "恭喜考上了一本大學" << endl; //當score大于等于600時,值為真,則輸入此語句 } else { cout << "未考上一本大學" << endl; //當score小于600時,值為假,則輸出此語句 } system("pause"); return 0; } -
多條件的 if 語句
語法:if (條件1) {條件1滿足執行的語句}else if (條件2) {條件2滿足執行的語句}.......else {條件都不滿足時執行的語句}
![多行if語句]()
示例:int main() { //選擇結構 多條件 if 語句 //輸入一個分數,如果大于600分,判斷考上一本,如果大于等于500分,判斷考上二本,如果大于等于400分,則判斷考上三本.....如果上述條件都不滿足,則判斷沒考上本科 //用戶輸入分數 int score = 0; cout << "請輸入分數:" << endl; cin >> score; //提示用戶輸入的分數 cout << "您輸入的分數為:" << score << endl; //判斷 if (score > 600) { //判斷score是否大于600 cout << "您考上了一本" << endl; //大于600時,值為真,則輸出此語句 } else if (score > 500) { //判斷score是否大于500 cout << "您考上了二本" << endl; //大于500時,值為真,則輸出此語句 } else if (score > 400) { //判斷score是否大于400 cout << "您考上了三本" << endl; //大于400時,值為真,則輸出此語句 } else { //條件都不滿足時 cout << "您沒考上本科" << endl; //輸出此語句 } system("pause"); return 0; } -
嵌套 if 語句
用處:在 if 語句中,可以嵌套使用 if 語句,達到更精確的條件判斷
![多條件if語句]()
示例要求:- 提示用戶輸入一個分數,根據分數做如下判斷
- 分數如果大于600分視為考上一本,大于500分考上二本,大于400分考上三本,其余視為沒考上本科
- 在一本分數中,如果大于700分視考入北大,大于650分視考上清華,大于600分考入人大
示例:
int main() { int score = 0; //提示輸入分數 cout << "請輸入考試分數" << endl; cin >> score; //顯示高考分數 cout << "您輸入的分數為:" << score << endl; //判斷 //大于600分 if (score > 600) { cout << "您考上了一本。" << endl; //大于700分 if (score > 700) { cout << "您考上北大。" << endl; } //大于650分 else if (score > 650) { cout << "您考上清華。" << endl; } //未滿足條件時 else { cout << "您考上人大。" << endl; } } //大于500分 else if (score > 500) { cout << "您考上了二本。" << endl; } //大于400分 else if (score > 400) { cout << "您考上了三本。" << endl; } //條件都不滿足時 else { cout << "您未考上本科。" << endl; } system("pause"); return 0; } -
游戲段位排名劃分
要求:- 按照300分為大師、260分為鉑金、220分為鉆石、180分為黃金劃分
- 超過320分為王者、超過360分為最強王者、超過400分宇宙韓國
示例:
int main() { int score = 0; cout << "請輸入您的排位得分:" << endl; if (score > 300) { cout << "您的段位已到大師級別" >> if (score > 320) { cout << "您的世界排名已到王者" << endl; } else if (score > 360){ cout << "您的世界排名已到最強王者" << endl; } else if (score > 400) { cout << "您的世界排名已到宇宙韓國" << endl; } else { cout << "抱歉,您的分數還沒到世界排名,僅僅是大師級別" << endl; } } else if (score > 260) { cout << "您的段位已到鉑金級別" << endl; } else if (score > 220) { cout << "您的段位已到鉆石級別" << endl; } else if (score > 180) { cout << "您的段位已到黃金級別" << endl; } else { cout << "抱歉,您的排位得分還未達到黃金段位" << endl; } system("pause"); return 0; }
三目運算符
作用:通過三目運算符實現簡單的判斷
語法:表達式1 ? 表達式2 : 表達式3
-
釋義:
如果表達式1的值為真,執行表達式2,并返回表達式2的結果; 如果表達式1的值為假,執行表達式3,并返回表達式3的結果;示例:
int main() { int a = 10; int b = 20; int c = 0; c = (a > b ? a : b); //a > b 的值為假,執行表達式 b ,并且為 c 賦值 cout << "c =" << c << endl; //在C++中三目運算符返回的是變量,可以繼續賦值 (a > b ? a : b) = 100; //a > b 的值為假,執行表達式b,并且為 b 賦值 cout << "a = " << a << endl; //輸出10 cout << "b = " << b << endl; //輸出100 (a < b ? a : b) = 100; //a < b 的值為真,執行表達式a,并且為 a 賦值 cout << "a = " << a << endl; //輸出100 cout << "b = " << b << endl; //輸出20 system("pause"); return 0; }
switch語句
作用:執行多條件分支語句;
-
語法:
switch(表達式) { case 結果1:執行語句; break; case 結果2:執行語句; break; ...... default:執行語句; break; }示例:
int main() { //給電影打分 //10~9 經典 //8~7 非常好 //6~5 一般 //5以下 爛片 //提示用戶給電影打分 cout << "請給電影進行打分" << endl; //用戶開始進行打分 int score = 0; cin >> score; cout << "您打的分數為:" << score << endl; //根據用戶輸入的分數來提示用戶最后的結果 switch (score) { case 10: cout << "您認為是經典電影" << endl; break; case 9: cout << "您認為是經典電影" << endl; break; case 8: cout << "您認為是非常好的電影" << endl; break; case 7: cout << "您認為是非常好的電影" << endl; break; case 6: cout << "您認為是一般的電影" << endl; break; case 5: cout << "您認為是一般的電影" << endl; break; default : cout << "您認為是爛片" << endl; break; } }if 語句與 switch 區別:
switch 缺點:判斷時候只能是整型或者字符型,不可以判斷一個區間(指定值) switch 優點:結構清晰,執行效率高 注意:case里如果沒有break,那么程序會一直向下執行
循環結構
while 循環語句
作用:滿足循環條件,執行循環語句
語法:while(循環條件){循環語句}
釋義:只要循環條件的結果為真,就執行循環語句
插入圖片
-
示例:
int main() { //從0開始循環輸出到9 //注意事項:在寫循環一定要避免死循環 //在執行循環語句的時候,程序必須提供跳出循環的出口,否則出現死循環 int num = 0; //while ()中填入循環條件 while (num < 10 ) { cout << num << endl; num++; } system("pause"); return 0; }案例:猜數字游戲
要求:系統隨機生成一個1-100之間的數字,玩家進行猜測,如果猜錯提示玩家數字過大或過小,如果猜對了提示玩家勝利,并且退出游戲。
插入圖片
代碼:int main() { //添加隨機數種子,利用當前時間系統生成隨機數,防止每次隨機數都一樣 srand ( (unsigned int)time(null) ); //系統生成隨機數 int num = rand()%100+1; //生成0~99的隨機數,+1是指代1-100; //玩家進行猜測 int val = 0; cout << "請玩家輸入猜測的數字:" << endl; while (1) { cin >> val; //判斷猜測 if ( val > num) { //猜錯提示過大,重新返回玩家進行猜測 cout << "猜測過大" << endl; } else if (val < num ) { //猜錯提示過小,重新返回玩家進行猜測 cout << "猜測過小" << endl; } else { //猜對 退出游戲 cout << "恭喜你猜對了" << endl; } } system("pause"); return 0; }
do...while 循環語句
作用:滿足循環條件,執行循環語句
語法: do{循環語句} while(循環條件);
注意:與while的區別在于do...while會先執行一次循環語句,再判斷循環條件
插入圖片
-
示例:
int main() { //在屏幕中輸出0-9這十個數字 int num = 0; do { //先輸出循環語句 cout << num << endl; num++; }while (num < 10); //判斷循環條件 system("pause"); return 0; }練習案例:水仙花數
案例描述:水仙花數是指一個3位數,他的每個位上的數字的3次冪之和等于它本身
例如:13+53+3^3=153
請利用 do...while語句,求出所有3位數中的水仙花數#include(math.h) int main() { int sum = 100; do { int a = 0; int b = 0; int c = 0; a = sum / 100; b = sum / 10 % 10; c = sum % 10; if (a*a*a + b*b*b + c*c*c == sum) { cout << num << endl; } num++; } while (sum < 1000); //別忘記加分號 ; system("pause"); return 0; }
for 循環
作用:滿足循環條件,執行循環語句
語法: for(起始表達式;條件表達式;末尾循環體){循環語句;}
-
示例:
int main(){ for (int i = 0; i < 10; i++){ cout << i << endl; } system("pause"); return 0; }詳解:
using namespace std; int main(){ //for 循環 //從數字0打印到數字9 for (int i = 0; i < 10; i++){ cout << i << endl; } system("pause"); return 0; }練習案例:敲桌子
要求:從1開始數到數字100,如果數字個位含有7或者數字十位含有7,再者該數字是7的倍數,我們打印敲桌子,其余數字直接打印輸出。using namespace std; int main(){ //先輸出1~100數字 for (int num = 1;num < 101; num++){ //從100個數字中找出特殊數字,打印敲桌子 //如果是7的倍數、個位有7、十位有7,視為特殊數字 if (num % 7 == 0 || num / 10 == 7 || num / 10 == 7 ) { cout << "特殊數字:" << num << " 敲桌子" << endl; } else { cout << num << endl; } } system("pause"); return 0; }
循環嵌套
作用:在循環體內再嵌套一層循環,解決矩陣式的實際問題
-
示例:
using namespace std; int main(){ for (int i = 0; i < 11; i++){ for (int j = 0; j < 11; j++){ cout << "* " ; } } system("pause"); return 0; }案例:乘法口訣表
示例:using namespace std; int main(){ for (int i = 1; i < 10; I++){ for (int j = 1; j <= i; j++){ cout << i << "*" << j << "=" << i*j << " "; } cout << endl; } system("pause"); return 0; }
跳轉語句
break 語句
作用:用于跳出選擇結構或者循環結構
break 使用的地方:
-
出現在switch條件語句中,作用是終止 case 并跳出 switch
-
出現在循環語句中,作用是跳出當前的循環語句
-
出現在嵌套循環中,跳出最近的內層循環語句
-
示例:
using namespace std; int main(){ //出現在 switch 語句中 cout << "請選擇副本難度" << endl; cout << "1、普通" << endl; cout << "2、中等" << endl; cout << "3、困難" << endl; int select = 0; //選擇難度結果的變量 cin >> select; //等待用戶輸入 switch (select){ case 1: cout << "您選擇的是普通難度" << endl; break; case 2: cout << "您選擇的是中等難度" << endl; break; case 3: cout << "您選擇的是困難模式" << endl; break; default : break; } //出現在循環語句中 //出現在嵌套循環語句中 system("pause"); return 0; }
continue 語句
作用:在循環語句中,跳過本次循環中余下尚未執行的語句,繼續執行下一次循環
-
示例:
using namespace std; int main(){ for (int i = 0; i <= 100; i++){ //如果是奇數輸出,偶數不輸出 if (i % 2 == 0){ //篩選條件,執行到此就不再向下執行,執行下一次循環 continue; //break會退出循環,而continue不會 } } system("pause"); return 0; }
goto 語句
作用:可以無條件跳轉語句
語法:goto 標記;
釋義:如果標記的名稱存在,執行到goto語句時,會跳轉到標記的位置
-
示例:
using namespace std; int main(){ cout << "1" << endl; cout << "2" << endl; goto FLAG; //跳轉到標記 cout << "3" << endl; cout << "4" << endl; FLAG: //標記 cout << "5" << endl; system("pause"); return 0; }
數組
概述:數組時一個集合,里面存放了相同類型的數據元素
特點:
- 數組中的每個數據元素都是相同的數據類型
- 數組是由連續的內存位置組成的
一維數組
一維數組定義方式
一維數組定義的三種方式:
-
數據類型 數組名[數組長度];
-
數據類型 數組名[數組長度] = {值1,值2,...};
-
數據類型 數組名[] = {值1,值2,...};
-
示例:
using namespace std; int main(){ //數據類型 數組名[數組長度]; int arr[5]; //給數組中的元素進行賦值 //數組元素的下標時從0開始索引的 arr[0] = 10; arr[0] = 20; arr[0] = 30; arr[0] = 40; arr[0] = 50; //訪問數據元素 cout << arr[0] << endl; //數據類型 數組名[數組長度] = {值1,值2,...}; //如果在初始化數據的時候,沒有全部填寫完,會用0填補剩余數據 int arr2[5] = {100,200,300}; //利用循環輸出數組中的元素 for (int i = 0; i < 5; i++){ cout << arr2[i] << endl; } //數據類型 數組名[] = {值1,值2,...}; //定義數組的時候,必須有初始的長度 int arr3[] = {90,80,70,60,50,40,30,20,10}; for (int j = 0; j < 9; j++){ cout << arr3[j] << endl; } system("pause"); return 0; }注意1:數組名的命名規范與變量名命名規范一致,不要和變量重名
注意2:數組中下標是從0開始索引
一維數組數組名
一維數組名稱的用途:
-
可以統計整個數組在內存中的長度
sizeof(arr) sizeof(arr[0]) -
可以獲取數組在內存中的首地址
cout << arr << endl; -
示例:
using namespace std; int main(){ //可以統計整個數組在內存中的長度 int arr[10] = {1,2,3,4,5,6,7,8,9,10}; cout << "整個數組占用內存空間為:" << sizeof(arr) << endl; cout << "每個元素占用內存空間:" << sizeof(arr[0]) << endl; cout << "數組中元素個數為:" << sizeof(arr) / sizeof(arr[0]) << endl; //可以獲取數組在內存中的首地址 cout << "數組首地址為:" << arr << endl; //輸出十六進制的內存地址 cout << "數組首地址為:" << (int)arr << endl; //輸出十進制的內存地址 cout << "數組中第一個元素地址為:" << (int)&arr[0] << endl; //數組名是常量,不可以進行賦值操作 system("pause"); return 0; }案例:五只小豬稱重
要求:在一個數組中記錄五只小豬的體重,找出并打印最重的小豬體重。using namespace std; int main(){ //創建五只小豬體重的數組 int arr[5] = {300,350,200,400,250}; //從數組中找到最大值 int max = 0; //先認定一個最大值為0 for (int i = 0; i < 5; i++){ //如果訪問的數組中的元素比 max 的值還要大,更新 max 值 if (arr[i] > max){ max = arr[i]; } } //打印最大值 cout << "最重的小豬體重為:" << max << endl; system("pause"); return 0; }案例2:數組元素逆置
要求:請聲明一個5元素的數組,并且將元素逆置using namespace std; int main(){ //實現數組元素逆置 //創建數組 int arr[5] = {1,3,2,5,4}; cout << "數組逆置前:" << endl; for (int i = 0; i <5; i++){ cout << arr[i] << endl; } //實現逆置 //記錄起始下標位置 int start = 0; //記錄結束下標位置 int end = sizeof(arr)/sizeof(arr[0]) - 1; //末尾元素下標 while (start < end) { //起始下標與結束下標的元素互換 int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; //起始位置++,結束位置--。下標更新 start++; end--; } //循環執行操作,直到起止位置 >= 結束位置 cout << "數組逆置后:" << endl; for (int i = 0; i <5; i++){ cout << arr[i] << endl; } system("pause"); return 0; }
冒泡排序
作用:最常用的排序算法,對數組內元素進行排序
-
比較相鄰的元素,如果第一個比第二個大,就交換他們兩個
-
對每一堆相鄰元素做同樣的工作,執行完畢后,找到第一個最大值
-
重復上述步驟,每次比較次數-1,直到不需要比較
-
示例:
using namespace std; int main(){ //利用冒泡排序實現升序序列 int arr[9] = {4,2,8,0,5,7,1,3,9}; cout << "排序前:" << endl; for (int i = 0; i < 9; i++){ cout << arr[i] << " " ; } cout << endl; //開始冒泡排序 //總共排序輪數為元素個數 -1 for (int i = 0; i < (sizeof(arr)/sizeof(arr[0]) - 1); i++){ //內層循環對比,次數 = 元素個數 - 當前輪數 - 1 for (int j = 0; j < ((sizeof(arr)/sizeof(arr[0])) - i - 1); j++){ //如果第一個數字比第二個數字大,交換兩個數字 if (arr[j] > arr[j + 1]){ //交換代碼 int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } //排序后結果 cout << "排序后:" << endl; for (int i = 0; i < 9; i++){ cout << arr[i] << " " ; } cout << endl; system("pause"); return 0; }
二維數組
二維數組就是在一維數組上多加一個維度
二維數組定義方式:
-
數據類型 數組名[行數][列數];
-
數據類型 數組名[行數][列數] = {{數據1,數據2} , {數據3,數據4}};
-
數據類型 數組名[行數][列數] = {數據1,數據2,數據3,數據4};
-
數據類型 數組名[ ][列數] = {數據1,數據2,數據3,數據4};
-
示例:
using namespace std; int main(){ //數據類型 數組名[行數][列數]; int arr[2][3]; arr[0][0] = 1; arr[0][1] = 2; arr[0][2] = 3; arr[1][0] = 4; arr[1][1] = 5; arr[1][2] = 6; //輸出二維數組元素值 for (int i = 0; i < (sizeof(arr)/sizeof(arr[0][0])/3); i++){ for (int j = 0; j < (sizeof(arr)/sizeof(arr[0][0])/2); j++){ cout << arr[i][j] << endl; } }//數據類型 數組名[行數][列數] = {{數據1,數據2} , {數據3,數據4}};
int arr2[2][3] = {
{1,2,3},
{4,5,6}
};
for (int i = 0; i < (sizeof(arr2)/sizeof(arr2[0][0])/3); i++){
for (int j = 0; j < (sizeof(arr2)/sizeof(arr2[0][0])/2); j++){
cout << arr2[i][j] << " " ;
}
cout << endl;
}//數據類型 數組名[行數][列數] = {數據1,數據2,數據3,數據4};
int arr3[2][3] = {1,2,3,4,5,6};
for (int i = 0; i < (sizeof(arr3)/sizeof(arr3[0][0])/3); i++){
for (int j = 0; j < (sizeof(arr3)/sizeof(arr3[0][0])/2); j++){
cout << arr3[i][j] << " " ;
}
cout << endl;
}//數據類型 數組名[ ][列數] = {數據1,數據2,數據3,數據4};
int arr4[][3] = {1,2,3,4,5,6};
for (int i = 0; i < (sizeof(arr4)/sizeof(arr4[0][0])/3); i++){
for (int j = 0; j < (sizeof(arr4)/sizeof(arr4[0][0])/2); j++){
cout << arr4[i][j] << " " ;
}
cout << endl;
}system("pause");
return 0;}
二維數組數組名
-
查看二維數組所占內存空間
-
獲取二維數組首地址
-
示例:
using namespace std; int main(){ //查看占用內存空間大小 int arr[2][3] = { {1,2,3}, {4,5,6} }; cout << "二維數組占用內存空間為:" << (sizeof(arr)) << endl; cout << "二維數組第一行占用內存空間為:" << sizeof(arr[0]) << endl; //arr[0]表示第一行 //查看二維數組首地址 cout << "二維數組首地址為:" << arr << endl; system("pause"); return 0; }案例:考試成績統計
要求:有三名同學(張三、李四、王五),考試成績如下表,請分別輸出三名同學的總成績
| 姓名 | 語文 | 數學 | 英語 |
|---|---|---|---|
| 張三 | 100 | 100 | 100 |
| 李四 | 90 | 50 | 100 |
| 王五 | 60 | 70 | 80 |
函數
- 作用:將一段經常使用的代碼封裝起來,減少重復代碼
- 一個較大的程序,一般分為若干程序塊,每個模塊實現特定的功能
函數的定義
-
返回值類型
-
函數名
-
參數列表
-
函數體語句
-
return表達式
-
語法
返回值類型 函數名 (參數列表){ 函數體語句 return表達式 }//示例:
int add(int num1,int num2){
int sum = num1 + num2;
return sum;
}
函數的調用
- 功能:使用定義好的函數
- 語法:函數名(參數)
- 示例
using namespace std; int add(int num1,int num2){ //定義中的num1,num2稱為形式參數,簡稱形參 int sum = num1 + num2; return sum; } int main(){ int a = 10; int b = 20; //調用add函數 int sum = add(a,b); //調用時的a,b成為實際參數,簡稱實參 cout << "sum = " << sum << endl; system("pause"); return 0; }
值傳遞
-
所謂值傳遞,就是函數調用時實參將參數數值傳入給形參
-
值傳遞時,如果形參發生改變,并不會影響實參
-
示例:
using namespace std; //定義函數,實現兩個數字進行交換函數 //如果函數不需要返回值,聲明的時候可以寫void void swap(int num1.int num2){ cout << "交換前:" << endl; cout << "num1:" << endl; cout << "num2" << endl; int temp = num1; num1 = num2; num2 = temp; cout << "交換后:" << endl; cout << "num1" << endl; cout << "num2" << endl; } int main(){ int a = 10; int b = 20; swap(a,b); system("pause"); return 0; }
函數的常見樣式
-
無參無返
-
有參無返
-
無參有返
-
有參有返
-
示例:
using namespace std; //函數常見樣式 //1、無參無返 void test01(){ //void a = 10; //無類型不可以創建變量,原因是因為無法分配內存 cout << "this is test01" << endl; //test01();函數調用 } //有參無返 void test02(int a){ cout << "this is test02 a = " << a << endl; } //無參有返 int test03(){ cout << "this is test03" << endl; return 1000; } //有參有返 int test04(int a){ cout << "this is test04 a = " << a << endl; return 1000; } int main(){ //無參無返的函數調用 test01(); //有參無返的函數調用 test02(100); //無參有返的函數調用 int num = test03(); cout << "num = " << num << endl; //有參有返的函數調用 int num2 = test04(10000); cout << "num2 = " << num2 << endl; system("pause"); return 0; }
函數的聲明
-
作用:告訴編譯器函數名稱及如何調用函數,函數的實際主體可以單獨定義。
-
函數的聲明可以多次,但是函數的定義只能有一次
-
示例
using namespace std; //比較函數,實現兩個整型數字進行比較,返回較大的值 //定義函數 int max(int a,int b); //函數的聲明 int max(int a,int b){ return a > b ? a : b; //函數的定義 } int main(){ int a = 10; int b = 20; cout << max(a,b) << endl; system("pause"); return 0; }
函數的分文件編寫
-
函數的分文件編寫
-
函數分文件編寫一般有四個步驟
- 創建后綴名為 .h 的頭文件
- 創建后綴名為 .cpp 的源文件
- 在頭文件中寫函數的聲明
- 在源文件中寫函數的定義
-
示例:
#include<iostream> using namespace std; //函數的分文件編寫 //實現兩個數字進行交換的函數 //函數的聲明 void swap(int a,int b); //函數的定義 void swap(int a,int b){ int temp = a; a = b; b = temp; cout << "a = " << a << endl; cout << "b = " << b << endl; } int main(){ int a = 10; int b = 20; swap(a,b); system("pause"); return 0; }//創建后綴名為 .h 的頭文件
//創建后綴名為 .cpp 的源文件
//在頭文件中寫函數的聲明
//在源文件中寫函數的定義include
using namespace std;
//實現兩個數字交換的函數聲明
void swap(int a,int b);include "swap.h"
指針
- 作用:可以通過指針間接訪問內存
- 內存編號是從0開始記錄的,一般用十六進制數字表示
- 可以利用指針變量保存地址
指針變量的定義和使用
- 示例:
using namespace std; int main(){ //定義指針 int a = 10; //指針定義的語法:數據類型 * 指針變量名 int * p; //讓指針記錄變量 a 的地址 p = &a; cout << "a的地址為:" << &a << endl; cout << "a的地址為:" << p << endl; //使用指針 //可以通過解引用的方式來找到指針指向的內存 //指針前加 * 代表解引用,找到指針指向的內存中的數據 *p = 1000; cout << "a = " << a << endl; cout << "*p = " << *p << endl; system("pause"); return 0; }
指針所占內存空間
-
指針也是一種數據類型。
-
示例:
using namespace std; int main(){ int a = 10; int * p; p = &a; //指針指向數據a的地址 //在32位操作系統下,占用4個字節空間 //在64位操作系統下,占用8個字節空間 cout << "sizeof(int *) = " << sizeof(p) << endl; cout << *p << endl; //解引用 cout << sizeof(p) << endl; cout << sizeof(char *) << endl; cout << sizeof(flot *) << endl; cout << sizeof(double *) << endl; system("pause"); return 0; }
空指針和野指針
-
空指針:指針變量指向內存中編號為0的空間
-
用途:初始化指針變量
-
注意:空指針指向的內存是不可以訪問的
-
示例:
using namespace std; int main(){ //空指針用于給指針變量進行初始化 int * p = NULL; //空指針是不可以進行訪問的 //0~255之間的內存編號是系統占用的,因此不可以訪問 *p = 1000; //報錯:寫入權限沖突 system("pause"); return 0; } -
野指針:指針變量指向非法的內存空間
-
示例:
using namespace std; int main(){ //野指針變量p指向內存地址編號為0x1100的空間 //在程序中,盡量避免出現野指針 int * p = (int *)0x1100; //訪問野指針報錯 cout << *p << endl; system("pause"); return 0; }
const修飾指針
-
const 修飾指針有三種情況:
- const修飾指針---常量指針
- const修飾常量---指針常量
- const即修飾指針,又修飾常量
-
示例:
using namespace std; int main(){ int a = 10; int b = 10; //const修飾的是指針,指針指向可以改,指針指向的值不可以更改 //常量指針 const int * p1 = &a; p1 = &b; //正確 *p1 = 100; //錯誤 //const修飾的是常量,指針指向不可以改,指針指向的值可以改 //指針常量 int * const p2 = &a; p2 = &b; //錯誤 *p2 = 20; //正確 //const即修飾指針又修飾常量,指針的指向和指針指向的值都不可以改 const int * const p3 = &a; *p3 = 20; //錯誤 p3 = &b; //錯誤 *p3 = 10; //正確system("pause");
return 0;}
指針和數組
- 作用:利用指針訪問數組中元素
- 示例:
using namespace std; int main(){ //指針和數組 //利用指針訪問數組中的元素 int arr[10] = {1,2,3,4,5,6,7,8,9,10}; cout << "第一個元素為:" << arr[0] << endl; int * p = arr; //arr就是數組的首地址 cout << "利用指針訪問第一個元素:" << *p << endl; p++; //讓指針向后偏移4個字節 cout << "利用指針訪問第二個元素:" << *p << endl; cout << "利用指針遍歷數組" << endl; int * p2 = arr; for (int i = 0; i < 10; i++){ cout << arr[i] << endl; cout << *p2 << endl; p2++; } system("pause"); return 0; }
指針和函數
-
作用:利用指針作函數參數,可以修改實參的值
-
示例:
using namespace std; //實現兩個數字進行交換 void swap01(int a,int b){ int temp = a; a = b; b = temp; cout << "swap01 a = " << a << endl; cout << "swap01 b = " << b << endl; } void swap02(int *p1,int *p2){ int temp = *p1; *p1 = *p2; *p2 = temp; } int main(){ //指針和函數 //值傳遞 int a = 10; int b = 20; swap01(a,b); //地址傳遞 //如果是地址傳遞,可以修飾實參 swap02(&a,&b); cout << "a = " << a << endl; cout << "b = " << b << endl; // system("pause"); return 0; }
指針、數組、函數
-
案例:封裝一個函數,利用冒泡排序,實現對整型數組的升序排序
-
例如:int arr[10] = {4,3,6,9,1,2,10,8,7,5};
-
示例:
//冒泡排序函數:參數1是數組的首地址,參數2是數組的長度 void bubbleSort(int * arr,int len) //int * arr 也可以寫作int arr[] { for (int i = 0; i < len; i++){ for (int j = 0; j < len - 1 -i; j++){ if (arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } //打印數組 void printArray(int * arr, int len) { for (int i = 0; i < len; i++){ cout << arr[i] << endl; } } int main(){ //創建一個數組 int arr[10] = {4,3,6,9,1,2,10,8,7,5}; //數組長度 int len = sizeof(arr) / sizeof(arr[0]); //創建函數,實現冒泡排序 bubbleSort(arr, len) //打印排序后的數組 printArray(arr, len); system("pause"); return 0; }
結構體
- 概念:結構體屬于用戶自定義的數據類型,允許用戶存儲不同的數據類型
結構體定義和使用
-
語法: struct 結構體名 {結構體成員列表};
-
通過結構體創建變量的方式有三種:
- struct 結構體名 變量名
- struct 結構體名 變量名 = {成員1值,成員2值...};
- 定義結構體時順便創建變量
-
注意:
- 定義結構體時的關鍵字時struct,不可省略
- 創建結構體變量時,關鍵字struct可以省略
- 結構體變量利用操作符" . "訪問成員
-
示例:
using namespace std; #include <string> //創建學生數據類型:學生包括(姓名、年齡、分數) //自定義數據類型:一些類型集合組成的一個類型 struct Student{ //成員列表 //姓名 string name; //年齡 int age; //分數 int score; }s3; //在定義結構體時順便創建結構體變量 int main(){ //通過學生類型創建具體學生 //struct Student s1; //struct 關鍵字可以省略 struct Student s1; //給s1屬性賦值,通過 . 訪問結構體變量中的屬性 s1.name = "張三"; s1.age = 18; s1.score = 100; cout << "姓名:" << s1.name << " 年齡:" << s1.age << " 分數:" << s1.score << endl; //struct Student S2 = {...}; struct Student s2 = {"李四", 19 ,80}; cout << "姓名:" << s2.name << " 年齡:" << s2.age << " 分數:" << s2.score << endl; //在定義結構體時順便創建結構體變量 s3.name = "王五"; s3.age = 19; s3.score = 90; cout << "姓名:" << s3.name << " 年齡:" << s3.age << " 分數:" << s3.score << endl; system("pause"); return 0; }
結構體數組
-
作用:將自定義的結構體放入到數組中方便維護
-
語法:struct 結構體名 數組名[元素個數] = {{},{},...{}};
-
示例:
#include <string> using namespace std; //結構體定義 struct student{ //成員列表 string name; //姓名 int age; //年齡 int score; //分數 }; int main(){ //結構體數組 struct student stuArray[3] = { {"張三",18,80}, {"李四",19,90}, {"王五",18,100} }; //給結構體數組中的元素賦值 stuArray[2].name = "趙六"; stuArray[2].age = 17; stuArray[2].score = 95; stuArray[2] = {"錢七",18,85}; //遍歷結構體數組 for (int i = 0; i < 3; i++){ cout << "姓名:" << stuArray[i].name << "年齡:" << stuArray[i].age << "分數:" << stuArray[i].score << endl; } system("pause"); return 0; }
結構體指針
-
作用:通過指針訪問結構體中的成員
- 利用操作符 -> 可以通過結構體指針訪問結構體屬性
-
示例:
#include <string> using namespace std; //結構體定義 struct student{ //成員列表 string name; //姓名 int age; //年齡 int score; //分數 }; int main(){ //創建學生結構體變量 struct student s = {"張三",18,100}; //通過指針指向結構體變量 struct student * p = &s; //通過指針訪問結構體變量中的數據 //通過結構體指針 訪問結構體中的屬性,需要利用" -> " cout << "姓名:" << p->name << "年齡:" << p->age << "分數:" << p->score << endl; system("pause"); return 0; }
結構體嵌套結構體
-
作用:結構體中的成員可以是另一個結構體
-
例如:每個老師輔導一個學員,一個老師的結構體中,記錄一個學生的結構體
-
示例:
#include <string> using namespace std; //學生結構體定義 struct student{ //成員列表 string name; //姓名 int age; //年齡 int score; //分數 }; //老師結構體定義 struct teacher{ //成員列表 int id; //職工編號 string name; //教師姓名 int age; //教師年齡 struct student stu; //子結構體 學生 }; int main(){ //結構體嵌套結構體 //創建老師 teacher t; t.id = 10000; t.name = "老王"; t.age = 50; t.stu.name = "小王"; t.stu.age = 20; t.stu.score = 60; cout << "老師編號:" << t.id << "老師姓名:" << t.name << "老師年齡:" << t.age << "老師輔導的學生姓名:" << t.stu.name << "學生年齡:" << t.stu.age << "學生分數:" << t.stu.score << endl; system("pause"); return 0; }
結構體做函數參數
-
作用:將結構體作為參數向函數中傳遞
-
傳遞方式有兩種:
- 值傳遞
- 地址傳遞
-
示例:
using namespace std; #include <string> //學生結構體定義 struct student{ //成員列表 string name; //姓名 int age; //年齡 int score; //分數 }; void printStudent(student stu){ stu.age =28; cout << "子函數中 姓名:" << stu.name << "年齡:" << stu.age << "分數:" << stu.score } //打印學生信息函數 //值傳遞 void printStudent1(struct student s){ cout << "子函數中打印 姓名:" << s.name << "年齡:" << s.age << "分數:" << s.score << endl; } //地址傳遞 void printStudent2(struct student * p){ cout << "子函數2中打印 姓名:" << p->name << "年齡:" << p->age << "分數:" << p->score << endl; } int main(){ //結構體做函數參數 //將學生傳入到一個參數中,打印學生身上的所有信息 //創建結構體變量 struct student s; s.name = "張三"; s.age = 20; s.score = 85; //值傳遞 printStudent1(s) //地址傳遞 printStudent2(&s); //cout << "main函數中打印 姓名:" << s.name << "年齡:" << s.age << "分數:" << s.score << endl; system("pause"); return 0; }
結構體中 const 使用場景
-
作用:用const來防止誤操作
-
示例:
#include <string> using namespace std; //學生結構體定義 struct student{ //成員列表 string name; //姓名 int age; //年齡 int score; //分數 }; //const使用場景 //將函數中的形參改成指針,可以減少內存空間,而且不會復制新的副本出來 void printStudent(const student * stu) //加const防止函數體中的誤操作 { //stu->age = 100; //操作失敗,因為加了const修飾 cout << "子姓名:" << stu->name << "年齡:" << stu->age << "分數:" << stu->score << endl; }int main(){
//創建結構體變量
struct student s = { "張三",15,70};//通過函數打印結構體變量信息
system("pause");
return 0;}
//算法技巧
void allocateSpace(struct Teacher tArray[] , int len){
string nameSeed = "ABCDE";
for (int i = 0; i < len; i++){
tArray[i].tName = "Teacher_";
tArray[i].tname += nameSeed[i];
}
}
C++核心編程
內存分區模型
- C++程序在執行時,將內存大方向劃分為4個區域
- 代碼區:存放函數體的二進制代碼,由操作系統進行管理的
- 全局區:存放全局變量和靜態變量以及常量
- 棧區:由編譯器自動分配釋放,存放函數的參數值,局部變量等
- 堆區:由程序員分配和釋放,若程序員不釋放,程序結束時由操作系統回收
- 內存四區意義:
不同區域存放的數據,賦予不同的生命周期,給我們更大的靈活編程
程序運行前
-
在程序編譯后,生成了exe可執行程序,未執行該程序前分為兩個區域
-
代碼區:
- 存放CPU執行的機器指令
- 代碼區是共享的,共享的目的是對于頻繁被執行的程序,只需要在內存中有一份代碼即可
- 代碼區是只讀的,使其只讀的原因是防止程序意外的修改了它的指令
-
全局區:
- 全局變量和靜態變量存放在此
- 全局區還包含了常量區,字符串常量和其他常量也存放在此
- 該區域的數據在程序結束后由操作系統釋放
- 示例:
using namespace std; //創建全局變量 int b = 10; //全局變量是指不在函數體內的變量 //const修飾的全局變量 const int c_g_a = 10; int main(){ //創建普通局部變量 int a = 10; //靜態變量 在普通變量前面加static,屬于靜態變量 static int s_a = 10; //常量 //字符串常量 cout << (int)&"hello world" << endl; //const修飾的變量 //const修飾的全局變量和const修飾的局部變量 const int c_l_a = 10 //const修飾的局部變量 system("pause"); return 0; }
-
總結:
- C++中在程序運行前分為全局區和代碼區
- 代碼區特點是共享和只讀
- 全局區中存放全局變量、靜態變量、常量
- 常量區中存放const修飾的全局變量和字符串常量
程序運行后
-
棧區
-
由編譯器自動分配釋放,存在函數的參數值,局部變量等
-
注意事項:不要返回局部變量的地址,棧區開辟的數據由編譯器自動釋放
-
示例:
using namespace std; //棧區數據注意事項 --- 不要返回局部變量的地址 //棧區的數據由編譯器管理開辟和釋放 int * func() //形參數據也會放在棧區 { int a = 10; //局部變量 存放在棧區,棧區的數據在函數執行完后自動釋放 return &a; //返回局部變量的地址 } int main(){ //接收func函數的返回值 int * p = func(); cout << *p << endl; //第一次可以打印正確的數字,是因為編譯器做了保留 cout << *p << endl; //第二次這個數據就不保留了 system("pause"); return 0; }
-
-
堆區
-
由程序員分配釋放,若程序員不釋放,程序結束后由操作系統回收
-
在C++中主要利用new在堆區開辟內存
-
示例:
using namespace std; int * func (){ //利用new關鍵字,可以將數據開辟到堆區 //指針本質也是局部變量,放在棧上,指針保存的數據是放在堆區 int * p = new int(10); return p; } int main(){ //在堆區開辟數據 int * p = func(); cout << *p << endl; system("pause"); return 0; }
-
new操作符
-
堆區開辟的數據,由程序員手動開辟,手動釋放,釋放利用操作符 delete
-
語法: new 數據類型
-
利用 new 創建的數據,會返回該數據對應的類型的指針
-
示例:
using namespace std; //new的基本語法 int * func(){ //在堆區創建整型數據 //new返回的是該數據類型的指針 int * p = new int(10); return p; } //在堆區利用new開辟數組 void test01(){ int * p = func(); cout << *p << endl; //堆區的數據,由程序員管理開辟,程序員管理釋放 //如果想釋放堆區的數據,利用關鍵字 delete delete p; cout << *p << endl; //錯誤:引發異常,讀取訪問出錯。因為內存已經被釋放,再次訪問就是非法操作,會報錯 } //在堆區利用new開辟數組 void test02(){ //創建10整型數組的數組,在堆區 int * arr = new int[10]; //10代表數組有10個元素 for (i = 0; i < 10; i++){ arr[i] = i + 100; //給10個元素賦值 100~109 } for (int i = 0; i < 10; i++){ cout << arr[i] << endl; } //釋放堆區數組 //釋放數組的時候,要加[]才可以 delete [] arr; } int main(){ //調用test02() test02(); system("pause"); return 0; }
引用
-
作用:給變量起別名
-
語法:數據類型 &別名 = 原名
-
示例:
using namespace std; int main(){ //引用的基本語法 //數據類型 &別名 = 原名 int a = 10; //創建引用 int &b = a; cout << "a = " << a << endl; cout << "b = " << b << endl; b = 100; cout << "a = " << a << endl; cout << "b = " << b << endl; system("pause"); return 0; }
引用注意事項
-
引用必須初始化
-
引用在初始化后,不可以改變
-
示例:
using namespace std; int main(){ int a = 10; int b = 10; //int &c; //錯誤,引用必須初始化 int &c = a; //一旦初始化后,就不可以更改 c = b; //這是賦值操作,不是更改引用 cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; system("pause"); return 0; }
引用做函數參數
-
作用:函數傳參時,可以利用引用的技術讓形參修飾實參
-
優點:可以簡化指針修改實參
-
示例:
using namespace std; //值傳遞 void mySwap01(int a, int b){ int temp = a; a = b; b = temp; cout << "mySwap01 a = " << a << endl; cout << "mySwap01 b = " << b << endl; } //地址傳遞 void mySwap02(int * a; int * b){ int temp = *a; *a = *b; *b = temp; cout << "mySwap02 a = " << a << endl; cout << "mySwap02 b = " << b << endl; } //引用傳遞 void mySwap03(int &a, int &b){ int temp = a; a = b; b = temp; } int main(){ int a = 10; int b = 20; mySwap01(a,b); //值傳遞,形參不會修飾實參 mySwap02(&a,&b); //地址傳遞,形參會修飾實參 mySwap03(a,b); //引用傳遞,形參會修飾實參 cout << "a = " << a << endl; cout << "b = " << b << endl; system("pause"); return 0; } -
總結:通過引用參數產生的效果同按地址傳遞是一樣的。引用的語法更清楚簡單
引用做函數返回值
-
作用:引用是可以作為函數的返回值存在的
-
注意:不要返回局部變量引用
-
用法:函數調用作為左值
-
示例:
using namespace std; //引用做函數的返回值 //不要返回局部變量的引用 int& test01(){ int a = 10; //局部變量,存放在四區中的棧區 return a; } //函數的調用可以作為左值 int& test02(){ static int a = 10; //靜態變量存放在全局區,全局區上的數據在程序結束后系統釋放 return a; } int main(){ int &ref = test01(); cout << "ref = " << ref << endl; //第一次結果準確,是因為編譯器做了保留 cout << "ref = " << ref << endl; //第二次結果錯誤,因為a的內存已經釋放 int &ref2 = test02(); cout << "ref2 = " << ref2 << endl; cout << "ref2 = " << ref2 << endl; test02() = 1000; //如果函數的返回值是引用,這個函數調用可以作為左值 cout << "ref2 = " << ref2 << endl; cout << "ref2 = " << ref2 << endl; system("pause"); return 0; }
引用的本質
-
本質:有用的本質再C++內部實現是一個指針常量
-
示例:
using namespace std; //發現是引用,轉換為int * const ref = &a; void func(int& ref){ ref = 100; //ref是引用,轉換為*ref = 100; } int main(){ int a = 10; //自動轉換為 int * const ref = &a;指針常量是指針指向不可改,也說明為什么引用不可更改 int& ref = a; ref = 20; //內部發現ref是引用,自動幫我們轉換為:*ref = 20; cout << "a : " << a << endl; cout << "ref: " << ref << endl; system("pause"); return 0; }
常量引用
-
作用:常量引用主要用來修飾形參,防止誤操作
-
再函數形參列表中,可以加 const 修飾形參,防止形參改變實參
-
示例:
using namespace std; //引用使用的場景,通常用來修飾形參 void showValue(const int& v){ //v += 10; cout << v << endl; } int main(){ //常量引用 //使用場景:用來修飾形參,防止誤操作 int a = 10; //int & ref = 10; 引用本身需要一個合法的內存空間,因此這行錯誤 const int & ref = 10; //加入const就可以了,編譯器優化代碼,int temp = 10; const int & ref = temp; //ref = 20; //加入const之后變為只讀,不可以修改。 showValue(a); cout << ref << endl; system("pause"); return 0; }
函數提高
函數默認參數
-
在C++中,函數的形參列表中的形參是可以有默認值的。
-
語法:返回值類型 函數名 (參數 = 默認值){}
-
示例:
using namespace std; //函數默認參數 //如果我們傳入數據,就用自己的數據,如果沒有,那么用默認值 int func(int a, int b = 10; int c = 10){ return a + b + c; } //1、如果某個位置參數有默認值,那么從這個位置往后,從左往右,必須都要有默認值 //2、如果函數聲明有默認值,函數實現的時候就不能有默認參數 int func2(int a = 10, int b = 10); int func2(int a, int b){ return a + b; } int main(){ system("pause"); return 0; }
函數占位參數
-
C++中函數的形參列表里可以有占位參數,用來做占位,調用函數時必須填補該位置
-
語法:返回值類型 函數名(數據類型){}
-
示例:
using namespace std; //函數占位參數,占位參數也可以有默認參數 void func(int a, int){ cout << "this is func" << endl; } int main(){ func(10,10);//占位參數必須填補 system("pause"); return 0; }
函數重載
函數重載概述
-
作用:函數名可以相同,提高復用性
-
函數重載滿足條件:
- 同一個作用域下
- 函數名稱相同
- 函數參數類型不同或者個數不同或者順序不同
-
注意:函數的返回值不可以作為函數重載的條件
-
示例:
using namespace std; //函數重載需要函數都在同一個作用域下 void func(){ cout << "func的調用" << endl; } void func(int a){ cout << "func(int a)的調用" << endl; } void func(double a){ cout << "func(double a)的調用" << endl; } int main(){ func(); func(10); func(3.14) system("pause"); return 0; }
函數重載注意事項
-
引用作為重載條件
-
函數重載碰到函數默認參數
-
示例:
using namespace std; //引用作為重載條件 void func(int &a){ //int &a = 10; 不合法 cout << "func (int &a)" << endl; } void func(const int &a){ //const int &a = 10; cout << "func (const int &a)" << endl; } //函數重載碰到函數默認參數 void func2(int a, int b = 10){ cout << "func2 (int a, int b)" << endl; } void func2(int a){ cout << "func2 (int a)" << endl; } int main(){ //int a = 10; //func(a); func(10); //func2(10); //當函數重載碰到默認參數,出現二義性,報錯,盡量避免這種情況 system("pause"); return 0; }
類和對象
-
C++面向對象的三大特性:封裝、繼承、多態
-
C++認為萬事萬物都皆為對象,對象上有其屬性和行為
-
例如:
人可以作為對象,屬性有姓名、年齡、身高、體重...,行為有走、跑、跳、吃飯、唱歌... 車也可以作為對象,屬性有輪胎、方向盤、車燈...,行為有載人、放音樂、開空調... 具有相同性質的對象,我們可以抽象稱為類,人屬于人類,車屬于車類
封裝
封裝的意義
-
封裝是C++面向對象三大特性之一
-
封裝的意義
- 將屬性和行為作為一個整體,表現生活中的事務
- 將屬性和行為加以權限控制
-
封裝意義一:
在設計類的時候,屬性和行為寫在一起,表現事物-
語法:class 類名{ 訪問權限: 屬性 / 行為};
-
示例1:設計一個圓類,求圓的周長
using namespace std; //圓周率 const double PI = 3.14 //設計一個圓類,求圓的周長 //class代表設計一個類,類后面緊跟著的就是類名稱 class Circle{ //訪問權限 //公共權限 public: //屬性 //半徑 int m_r; //行為 //獲取圓的周長 double calculateZC(){ return 2 * PI * m_r; } }; int main(){ //通過圓類 創建具體的圓(對象) //實例化 (通過一個類,創建一個對象的過程) Circle c1; //給圓對象的屬性進行賦值 c1.m_r = 10; cout << "圓的周長為:" << c1.calculateZC() << endl; system("pause"); return 0; } //封裝的意義 //將屬性和行為作為一個整體,用來表現生活中的事物 -
示例2:
using namespace std; #include <string>; //學生類 class Student{ //權限 public: //類中的屬性和行為,我們統一稱為成員 //屬性 成員屬性 成員變量 //行為 成員函數 成員方法 //屬性 string m_Name; int m_ID; //行為 void showStudent(){ cout << "姓名" << m_Name << "學號" << m_ID << endl; } //給姓名賦值 void setName(string name){ m_Name = name; } } int main(){ //創建一個具體學生 實例化對象 Student s1; //給s1對象進行屬性賦值操作 //s1.m_Name = "張三"; s1.setName("張三"); s1.m_ID = 1; //現實學生信息 s1.showStudent(); system("pause"); return 0; }
-
-
封裝意義二:
-
類在設計時,可以把屬性和行為放在不同的權限下,加以控制
-
訪問權限有三種
- public 公共權限
- protected 保護權限
- private 私有權限
-
示例:
using namespace std; //訪問權限 //公共權限 public 成員類內可以訪問 類外可以訪問 //保護權限 protected 成員類內可以訪問 類外不可以訪問 繼承父子關系可以訪問 //私有權限 private 成員類內可以訪問 類外不可以訪問 繼承父子關系不可以訪問 class Person{ public: //公共權限 string m_name; //姓名 protected: //保護權限 string m_Car; //汽車 private: //私有權限 int m_Password; //銀行卡密碼 public: void func(string a, string b. int c){ m_Name = a; m_Car = b; m_Password = c; } }; int main(){ //實例化一個具體對象 Person p1; p1.m_Name = "李四"; p1.m_Car = "奔馳" //報錯,保護權限內容,在類外訪問不到 p1.m_Password = 123; //報錯,私有權限內容,在類外訪問不到 p1.func("張三", "拖拉機", 123456); //可以訪問,因為兩個私有權限在public共有權限套用下,屬于類內 system("pause"); return 0; }
-
struct和class區別
-
在C++中struct和class唯一的區別就在于默認的訪問權限不同
-
區別:
- struct默認權限為共有
- class默認權限為私有
-
示例:
class c1{ int m_A; //默認是私有權限 private }; struct c2{ int m_A; //默認是公有權限 public };
成員屬性設置為私有
-
優點1:將所有成員屬性設置為私有,可以主機控制讀寫權限
-
優點2:對于寫權限,我們可以檢測數據的有效性
-
示例:
using namespace std; #include <string> //成員屬性設置為私有 //1、可以主機控制讀寫權限 //2、對于寫權限可以檢測數據的有效性 //設計人類 class Person{ public: //設置姓名 void setName(string name){ m_Name = name; } //獲取姓名 string getName(){ return m_Name; //不能直接反悔name,因為name只在setName里有生存周期; } //獲取年齡 只讀權限 int getAge(){ m_Age = 0; //初始化為0歲 return m_Age; } //設置年齡 可寫(范圍0-150) void setAge(int Age){ if (age < 0 || age > 150){ m_Age = 0; cout << "輸入年齡有誤!" << endl; return; } m_Age = Age; } //設置情人 只寫權限 void setLover(string lover){ m_Lover = lover; } private: //姓名 可讀可寫 string m_Name; //年齡 只讀 int m_Age; //情人 只寫 string m_Lover; }; int main(){ Person p; p.setName("張三"); //正確,可以寫 cout << "姓名為:" << p.getName << endl; //正確,可以讀 p.setAge(18); //正確,可以寫 cout << "年齡為:" << p.getAge << endl; //正確,可以讀 //設置情人 p.setLover("蒼井"); //正確,可以寫 cout << "情人為:" << p.getLover << endl; //錯誤,不可以讀 system("pause"); return 0; } -
案例1:設計立方體類
- 設計立方體類
- 求出立方體的面積和體積
- 分別用全局函數和成員函數判斷兩個立方體是否相等
- m_L:長、m_W:寬、m_H:高
-
示例:
using namespace std; //創建立方體類 class Cub{ public: //行為 //獲取立方體面積 int calculateS(){ return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H; } //獲取立方體體積 int calculateV(){ return m_L * m_W * m_H; } //設置獲取長寬高 //設置長 void setL(int L){ m_L = L; } //獲取長 int getL(){ return m_L; } //設置高 void setH(int H){ m_H = H; } //獲取高 int getLH(){ return m_H; } //設置寬 void setLW(int W){ m_W = W; } //獲取寬 int getW(){ return m_W; } //利用成員函數判斷兩個立方體是否相等 bool isSnameByClass(Cube &c){ if(m_L == c.getL() && m_W == c.getW() && m_H == c.getH()){ return true; } return false; } //屬性 設置為私有 private: int m_L; //長 int m_W; //寬 int m_H; //高 }; //利用全局函數判斷,兩個立方體是否相等 bool isSame(Cube &c1 , Cube &c2){ //利用 & 引用的方式去除數據緩存步驟 if(c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()){ return true; } return false; } int main(){ //創建立方體對象 Cube c1; c1.setL(10); c1.setW(10); c1.setH(10); cout << "c1的面積為:" << c1.calculateS() << endl; cout << "c1的體積為:" << c1.calculateV() << endl; //創建第二個立方體 Cube c2; c2.setL(10); c2.setW(10); c2.setH(10); cout << "c2的面積為:" << c2.calculateS() << endl; cout << "c2的體積為:" << c2.calculateV() << endl; //利用全局函數判斷 bool ret = isSame(c1,c2); if(ret){ cout << "c1和c2是相等的" << endl; }else{ cout << "c1和c2是不相等的" << endl; } //利用成員函數判斷 ret = c1.isSameByClass(c2); if(ret){ cout << "成員判斷:c1和c2是相等的" << endl; }else{ cout << "成員判斷c1和c2是不相等的" << endl; } system("pause"); return 0; } -
練習案例2:點和圓的關系
- 設計一個圓形類(Circle)和一個點類(Point),計算點和圓的關系
-
示例:
#include <iostream> using namespace std; //點和圓關系案例 //點類 class Point { public: //設置x void setX(int x) { m_X = x; } //獲取x int getX() { return m_X; } //設置y void setY(int y) { m_X = y; } //獲取y int getY() { return m_Y; } private: int m_X; int m_Y; }; //圓類 class Circle { public: //設置半徑 void setR(int r) { m_R = r; } //獲取半徑 int getR() { return m_R; } //設置圓心 void setCenter(Point center) { m_Center = center; } //獲取圓心 Point getCenter() { return m_Center; } private: int m_R; //半徑 Point m_Center; //圓心 }; //判斷點和圓關系 void inInCircle(Circle& c, Point& p) { //計算兩點之間的平方 int distance = (c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) + (c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY()); //計算半徑的平方 int rDistance = c.getR() * c.getR(); //判斷關系 if (distance == rDistance) { cout << "點在圓上" << endl; } else if (distance > rDistance) { cout << "點在圓外" << endl; } else { cout << " 點在圓內" << endl; } } int main() { //創建圓 Circle c; c.setR(10); Point center; center.setX(10); center.setY(0); c.setCenter(center); //創建點 Point p; p.setX(10); p.setY(10); //判斷關系 inInCircle(c, p); system("pause"); return 0; }
對象的初始化和清理
- 生活中我們買的電子產品都基本會有出廠設置,在某一天我們不用時候也會刪除一些自己信息數據保證安全
- 在C++中的面向對象來源于生活,每個對象也都會有初始設置以及對象銷毀前的清理數據的設置
構造函數和析構函數
-
對象的初始化和清理也是兩個非常重要的安全問題
- 一個對象或者變量沒有初始狀態,對其使用后果是未知
- 同樣的使用完一個對象或變量,沒有及時清理,也會造成一定的安全問題
-
C++利用了構造函數和析構函數解決上述問題,這兩個函數將會被編譯器自動調用,完成對象初始化和清理工作。
-
對象的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供
-
編譯器提供的構造函數和析構函數是空實現。
- 構造函數:主要作用在于創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無需手動調用。
- 析構函數:主要作用在于對象銷毀前系統自動調用,執行一些清理工作。
-
構造函數語法:類名(){}
- 構造函數沒有返回值也不寫void
- 函數名稱與類名相同
- 構造函數可以有參數,因此可以發生重載
- 程序在調用對象時候會自動調用構造,無需手動調用,而且只會調用一次
-
析構函數語法:~類名(){}
- 析構函數沒有返回值也不寫void
- 函數名稱與類名相同,在名稱前加上符號 ~
- 析構函數不可以有參數,因此不可以發生重載
- 程序在對象銷毀前會自動調用析構,無需手動調用,而且只會調用一次
-
示例:
#include <iostream> using namespace std; #include <string> //對象的初始化和清理 //1、構造函數 進行初始化操作 class Person { public: //1.1、構造函數 //沒有返回值 不用寫void //函數名 與類名相同 //構造函數可以有參數,可以發生重載 //創建對象的時候,構造函數會自動調用,而且只調用一次 Person() { cout << "Person 構造函數的調用" << endl; } //2、析構函數 進行清理的操作 //沒有返回值 不寫void //函數名和類名相同 在名稱前面加 ~ //析構函數不可以有參數的,不可以發生重載 //對象在銷毀前,會自動調用析構函數,而且只會調用一次 ~Person() { cout << "Person的析構函數調用" << endl; } }; //構造和析構都是必須有的實現,如果我們自己不提供,編譯器會提供一個空實現的構造和析構 void test01() { Person p; //在棧上的數據,test01執行完畢后,釋放這個對象 } int main() { test01(); //Person p; system("pause"); return 0; }
構造函數的分類及調用
-
兩種分類方式
- 按參數分為:有參構造和無參構造
- 按類型分為:普通構造和拷貝構造
-
三種調用方式
- 括號法
- 顯示法
- 隱式轉換法
-
示例:
#include <iostream> using namespace std; #include <string> //1、構造函數的分類及調用 //分類 //按照參數分類 無參構造函數(默認構造)和有參構造函數 //按照類型分類 普通構造函數和拷貝構造函數 class Person { public: //構造函數 Person() { //無參構造函數 cout << "Person的無參構造函數調用" << endl; } Person(int a) { //有參構造函數 age = a; cout << "Person的有參構造函數調用" << endl; } //拷貝構造函數 Person(const Person &p) { //將傳入的所有屬性,拷貝到參數身上 age = p.age; cout << "Person的拷貝構造函數調用" << endl; } ~Person() { //析構函數 cout << "Person的析構函數調用" << endl; } int age; }; //調用 void test01() { //1、括號法 //Person p1; //默認構造函數調用 //Person p2(10); //有參構造函數 //Person p3(p2); //拷貝構造函數 //注意事項 //調用默認構造函數的時候,不要加() //因為下面這行代碼,編譯器會認為式一個函數的聲明,不會認為在創建對象 //Person p1(); /*cout << "p2的年齡為:" << p2.age << endl; cout << "p3的年齡為:" << p3.age << endl;*/ //2、顯示法 //Person p1; //Person p2 = Person(10); //有參構造 //Person p3 = Person(p2); //拷貝構造 /*Person(10);*/ //匿名對象 特點:當前行執行結束后,系統會立即回收掉匿名對象 //cout << "aaaa" << endl; //注意事項2 //不要利用拷貝構造函數 初始化匿名對象,編譯器會認為Person(p3)等價于Person p3(對象聲明) /*Person(p3);*/ //3、隱式轉換法 Person p4 = 10; //相當于寫了 Person p4 = Person(10); 相當于有參構造 Person p5 = p4; //拷貝構造 } int main() { test01(); system("pause"); return 0; }
拷貝構造函數調用時機
-
C++種拷貝構造函數調用時機通常有三種情況
- 使用一個已經創建完畢的對象來初始化一個新對象
- 值傳遞的方式給函數參數傳值
- 以值方式返回局部對象
-
示例
#include <iostream> using namespace std; #include <string> //拷貝構造函數調用時機 class Person { public: Person() { //無參構造函數 cout << "Person的無參構造函數調用" << endl; } Person(int age) { //有參構造函數 m_Age = age; cout << "Person的有參構造函數調用" << endl; } //拷貝構造函數 Person(const Person & p) { //將傳入的所有屬性,拷貝到參數身上 m_Age = p.m_Age; cout << "Person的拷貝構造函數調用" << endl; } ~Person() { //析構函數 cout << "Person的析構函數調用" << endl; } int m_Age; }; //1、使用一個已經創建完畢的對象來初始化一個新對象 //void test01() { // Person p1(20); //有參構造函數 // Person p2(p1); //拷貝構造函數 // // cout << "p2的年齡為" << p2.m_Age << endl; //} //2、值傳遞的方式給函數參數傳值 //void doWork(Person p) { // //} // //void test02() { // Person p; // doWork(p); //} //3、值方式返回局部對象 Person doWork2() { Person p1; cout << (int*)&p1 << endl; return p1; } void test03() { Person p = doWork2(); cout << (int*)&p << endl; } int main() { //test01();system("pause");
return 0;}
構造函數調用規則
- 默認情況下,C++編譯器至少給一個類添加3個函數
- 默認構造函數(無參,函數體為空)
- 默認析構函數(無參,函數體為空)
- 默認拷貝構造函數,對屬性進行值拷貝
- 構造函數調用規則如下
- 如果用戶定義有參構造函數,C++不在提供默認無參構造,但是會提供默認拷貝構造
- 如果用戶定義拷貝構造函數,C++不會再提供其他構造函數
- 示例:
#include <iostream> using namespace std; #include <string> //構造函數的調用規則 //1、創建一個類,C++編譯器會給每個類都添加至少三個函數 //默認構造(空實現) //析構函數(空實現) //拷貝構造(空實現) //2、如果我們寫了有參構造函數,編譯器就不再提供默認構造,依然提供拷貝構造 //如果我們寫了拷貝構造函數,編譯器就不再提供其他普通構造函數 class Person { public: Person() { //無參構造函數 cout << "Person的無參構造函數調用" << endl; } Person(int age) { //有參構造函數 m_Age = age; cout << "Person的有參構造函數調用" << endl; } //拷貝構造函數 Person(const Person & p) { //將傳入的所有屬性,拷貝到參數身上 m_Age = p.m_Age; cout << "Person的拷貝構造函數調用" << endl; } ~Person() { //析構函數 cout << "Person的析構函數調用" << endl; } int m_Age; }; void test01() { Person p; p.m_Age = 18; Person p2(p); cout << "p2的年齡為" << p2.m_Age << endl; } void test02() { Person p; } int main() { //test01(); //示例1 test02(); system("pause"); return 0; }
深拷貝與淺拷貝
-
深淺拷貝是面試經典問題,也是常見的一個坑
-
淺拷貝:簡單的賦值拷貝操作
-
深拷貝:在堆區重新申請空間,進行拷貝操作
-
示例:
#include <iostream> using namespace std; #include <string> //深拷貝與淺拷貝 //淺拷貝帶來的問題就是堆區的內存重復釋放 //淺拷貝的問題要利用深拷貝進行解決 class Person { public: Person() { //無參構造函數 cout << "Person的無參構造函數調用" << endl; } Person(int age,int height) { //有參構造函數 m_Age = age; m_Height = new int(height); cout << "Person的有參構造函數調用" << endl; } //自己實現拷貝構造函數解決淺拷貝帶來的問題 Person(const Person &p) { cout << "Person拷貝構造函數調用" << endl; //將傳入的所有屬性,拷貝到參數身上 m_Age = p.m_Age; //m_Height = p.m_Height; 編譯器默認實現的就是這行代碼 //深拷貝操作 m_Height = new int(*p.m_Height); } //析構代碼,將堆區開辟數據做釋放操作 //析構時候,構造函數執行先進后出,誰先執行誰最后釋放 ~Person() { //析構函數 if (m_Height != NULL) { delete m_Height; //清空堆區內存 m_Height = NULL; //防止野指針出現 } cout << "Person的析構函數調用" << endl; } int m_Age; //年齡 int *m_Height; //身高 }; void test01() { Person p1(18 ,160); cout << "p1的年齡為" << p1.m_Age << "身高為:" << *p1.m_Height << endl; Person p2(p1); cout << "p1的年齡為" << p2.m_Age << "身高為:" << *p2.m_Height << endl; } void test02() { Person p; } int main() { test01(); //示例1 test02(); system("pause"); return 0; }
初始化列表
-
作用:C++提供了初始化列表語法,用來初始化屬性
-
語法:構造函數():屬性1(值1),屬性2(值2)...{}
-
示例:
#include <iostream> using namespace std; #include <string> //初始化列表 class Person { public: //傳統初始化操作 /*Person(int a, int b, int c) { m_A = a; m_B = b; m_C = c; }*/ //初始化列表初始化屬性 Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) { cout << "m_A = " << m_A << endl; cout << "m_B = " << m_B << endl; cout << "m_C = " << m_C << endl; } private: int m_A; int m_B; int m_C; }; void test01() { //Person p(10, 20, 30); Person p(30,20,10); } int main() { test01(); //示例1 system("pause"); return 0; }
類對象作為類成員
-
C++類中的成員可以是另一個類的對象,我們稱該成員為 對象成員
-
示例:
class A{} class B{ A a; } -
B類中有對象A作為成員,A為對象成員
-
A與B的構造和析構的順序示例:
#include <iostream> using namespace std; #include <string> //類對象作為類成員 //手機類 class Phone { public: Phone(string pName) { m_PName = pName; cout << "Phone的構造函數調用" << endl; } ~Phone() { cout << "Phone的析構函數調用" << endl; }//手機品牌名稱
string m_PName;};
//人類
class Person {
public://Phone m_Phone = pName 隱式轉換法
Person(string name, string pName) :m_Name(name), m_Phone(pName) {
cout << "Person的構造函數調用" << endl;
}~Person() {
cout << "Person的析構函數調用" << endl;
}//姓名
string m_Name;
//手機
Phone m_Phone;};
//當其他的類的對象作為本類成員,構造時候先構造類對象,再構造自身。
//析構的順序與構造的順序相反
void test01() {
Person p("張三", "蘋果");
cout << p.m_Name << "拿著" << p.m_Phone.m_PName << endl;
}int main()
{
test01(); //示例1system("pause");
return 0;}
靜態成員
-
靜態成員就是再成員變量和成員函數前加上關鍵字static,稱為靜態成員
-
靜態成員分成:
- 靜態成員變量
- 所有對象共享同一份數據
- 在編譯階段分配內存
- 類內聲明,類外初始化
- 靜態成員函數
- 所有對象共享同一個函數
- 靜態成員函數只能訪問靜態成員變量
- 靜態成員變量
-
示例1:靜態成員變量
#include <iostream> using namespace std; #include <string> //靜態成員變量 class Person { public: //1、所有對象都共享同一份數據 //2、編譯階段就分配內存 //3、類內聲明,類外初始化操作 static int m_A; //靜態成員變量也是有訪問權限的 private: static int m_B; }; int Person:: m_A = 100; //類外初始化 int Person::m_B = 300; void test01() { Person p; //打印出來100 cout << p.m_A << endl; Person p2; p2.m_A = 200; //打印出來200 cout << p2.m_A << endl; } void test02() { //靜態成員變量 不屬于某個對象上,所有對象都共享同一份數據 //因此靜態成員變量有兩種訪問方式 //1、通過對象進行訪問 Person p; cout << p.m_A << endl; //2、通過類名進行訪問 cout << Person::m_A << endl; //cout << Person::m_B << endl; 類外訪問不到私有靜態成員變量 } int main() { //test01(); //test01 test02(); //test02 system("pause"); return 0; } -
示例2:靜態成員函數
#include <iostream> using namespace std; #include <string> //靜態成員函數 //所有對象共享同一個函數 //靜態成員函數只能訪問靜態成員變量 class Person { public: //靜態成員函數 static void func() { m_A = 100; //m_B = 200; 靜態成員函數不可以訪問非靜態成員變量 cout << "static void func調用" << endl; } //靜態成員變量 static int m_A; //非靜態成員變量 int m_B; //靜態成員函數也是有訪問權限的 private: static void func2() { cout << "static void fun2調用" << endl; } }; int Person::m_A = 0; //有兩種訪問方式 void test01() { //1、通過對象訪問 Person p; p.func(); //2、通過類名訪問 Person::func(); //Person::func2(); 類外訪問不到私有靜態成員函數 } int main() { //test01(); //示例1 system("pause"); return 0; }
C++對象模型和this指針
成員變量和成員函數分開存儲
-
在C++中,類內的成員變量和成員函數分開存儲,只有非靜態成員變量才屬于類的對象上
-
示例:
#include <iostream> using namespace std; #include <string> //成員變量和成員函數是分開存儲的 class Person { int m_A; //非靜態成員變量 屬于類的對象上面 static int m_B; //靜態成員變量 不屬于類對象上 //非靜態成員函數 不屬于類的對象上 void func() { } //靜態成員函數 不屬于類的對象上 static void func2() { } }; int Person::m_B = 10; //void test01() { // Person p; // // //空對象占用內存空間為:1字節 // //C++編譯器會給每個空對象也分配一個字節空間,是為了區分空對象占內存的位置 // //每個空對象也應該有一個獨一無二的內存地址 // cout << "size of p = " << sizeof(p) << endl; //} void test02() { Person p; // cout << "size of p = " << sizeof(p) << endl; } int main() { //test01(); //示例1 system("pause"); return 0; }
this指針概念
-
在C++中成員變量和成員函數是分開存儲的
-
每一個非靜態成員函數只會生成一份函數實例,多個同類型的對象會共用一塊代碼
-
C++通過提供特殊的對象指針,this指針解決區分哪個對象調用自己的代碼。
-
this指針指向被調用的成員函數所屬的對象
-
this指針式隱含每一個非靜態成員函數內的一種指針
-
this指針不需要定義,直接使用
-
this指針的用途
- 當形參和成員變量同名時,可用this指針來區分
- 在類的非靜態成員函數中返回對象本身,可使用return *this
-
示例:
#include <iostream> using namespace std; #include <string> class Person { public: Person(int age) { //this指針指向被調用的成員函數所屬的對象 this->age = age; } Person& PersonAddAge(Person &p) { this->age += p.age; //this指向p2的指針,而*this指向的就是p2這個對象本體 return *this; } int age; }; //1、解決名稱沖突 void test01() { Person p1(18); cout << "p1的年齡為:" << p1.age << endl; } //2、返回對象本身用 *this void test02() { Person p1(10); Person p2(10); //鏈式編程思想 p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); cout << "p2的年齡為:" << p2.age << endl; } int main() { //test01(); //示例1 test02(); system("pause"); return 0; }
空指針訪問成員函數
-
C++中空指針也是可以調用成員函數的,但是也要注意有沒有用到this指針
-
如果用到this指針,需要加以判斷保證代碼的健壯性
-
示例:
#include <iostream> using namespace std; #include <string> //空指針訪問成員函數 class Person { public: void showClassName() { cout << "this is Person class" << endl; } void showPersonAge() { //報錯原因是因為傳入的指針是為NULL //用if判斷指針是否為空,防止崩潰 if (this == NULL) { return; } cout << "age = " << m_Age << endl; } int m_Age; }; void test01() { Person* p = NULL; p->showClassName(); p->showPersonAge(); //報錯原因是因為傳入的指針是為NULL } int main() { //test01(); //示例1 system("pause"); return 0; }
const修飾成員函數
-
常函數:
- 成員函數后加const后我們稱為這個函數為常函數
- 常函數內不可以修改成員屬性
- 成員屬性聲明時加關鍵字mutable后,在常函數中依然可以修改
-
常對象:
- 聲明對象前加const稱該對象為常對象
- 常對象只能調用常函數
-
示例:
#include <iostream> using namespace std; #include <string> //常函數 class Person { public: //this指針的本質,是指針常量,指針的指向是不可以修改的 //Person * const this; //在成員函數后面加const,修飾的是this指向,讓指針指向的值也不可以修改 void showPerson() const { this->m_B = 100; //this->m_A = 100; //this = NULL; this指針不可以修改指針的指向的 } void func() { } int m_A; mutable int m_B; //特殊變量,即使在常函數中,也可以修改這個值,加關鍵字mutable }; //常對象 void test() { const Person p; //在對象前加const,變為常對象 //p.m_A = 100; 錯誤 p.m_B = 100; //m_B是特殊值,在常對象下也可以修改 //常對象只能調用常函數 p.showPerson(); //p.func(); 錯誤,因為常對象不可以調用普通成員函數,因為普通成員函數可以修改屬性 } void test01() { Person p; p.showPerson(); } int main() { //test01(); //示例1 system("pause"); return 0; }
友元
- 在程序里,有些私有屬性也想讓類外特殊的一些函數或者類進行訪問,就需要用到友元的技術
- 友元的目的就是讓一個函數或者類訪問另一個類中私有成員
- 友元的關鍵字為 friend
- 友元的三種實現
- 全局函數做友元
- 類做友元
- 成員函數做友元
全局函數做友元
-
示例:
#include <iostream> using namespace std; #include <string> //建筑類 class Building { //goodDay全局函數是Building好朋友,可以訪問Building中私有成員 friend void goodGay(Building* building); public: Building() { m_SittingRoom = "客廳"; m_BedRoom = "臥室"; } public: string m_SittingRoom; //客廳 private: string m_BedRoom; //臥室 }; //全局函數 void goodGay(Building *building) { cout << "好朋友的全局函數 正在訪問:" << building->m_SittingRoom << endl; cout << "好朋友的全局函數 正在訪問:" << building->m_BedRoom << endl; } void test() { Building building; goodGay(&building); } int main() { //test01(); //示例1 system("pause"); return 0; }
類做友元
-
示例:
#include <iostream> using namespace std; #include <string> //類做友元 class Building; class GoodDay { public: GoodDay(); void visit(); //參觀函數 訪問Building中的屬性 Building* building; }; class Building { friend class GoodDay; //GoodDay類是本類的好友,可以訪問本類私有的成員 public: Building(); public: string m_SittingRoom; // 客廳 private: string m_BedRoom; //臥室 }; //類外寫成員函數 Building::Building() { m_BedRoom = "客廳"; m_SittingRoom = "臥室"; } GoodDay::GoodDay() { //創建一個建筑物對象 building = new Building; } void GoodDay::visit(){ cout << "好友類正在訪問:" << building->m_SittingRoom << endl; cout << "好友類正在訪問:" << building->m_BedRoom << endl; } void test01() { GoodDay gg; gg.visit(); } int main() { //test01(); //示例1 system("pause"); return 0; }
成員函數做友元
-
示例:
#include <iostream> using namespace std; #include <string> //成員函數做友元 class Building; class GoodDay { public: GoodDay(); void visit(); //讓visit函數可以訪問Building中私有成員 void visit2(); //讓visit2函數不可以訪問Building中私有成員 Building* building; }; class Building { //告訴編譯器 GoodDay類下的visit成員函數作為本類好友,可以訪問私有成員 friend void GoodDay::visit(); public: Building(); public: string m_SittingRoom; //客廳 private: string m_BedRoom; //臥室 }; //類外實現成員函數 GoodDay::GoodDay() { building = new Building; } Building::Building() { m_SittingRoom = "客廳"; m_BedRoom = "臥室"; } void GoodDay::visit() { cout << "visit函數正在訪問:" << building->m_SittingRoom << endl; cout << "visit函數正在訪問:" << building->m_BedRoom << endl; } void GoodDay::visit2() { cout << "visit2函數正在訪問:" << building->m_SittingRoom << endl; } void test01() { GoodDay gg; gg.visit(); gg.visit2(); } int main() { //test01(); //示例1 system("pause"); return 0; }
運算符重載
- 運算符重載概念:對已有的運算符重新進行定義,賦予其另一種功能,以適應不同的數據類型
加號運算符重載
-
作用:實現兩個自定義數據類型相加的運算
-
示例:
#include <iostream> using namespace std; #include <string> //加號運算符重載 class Person { public: //1、成員函數重載加號 + /*Person operator+(Person& p) { Person temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B = p.m_B; return temp; }*/ int m_A; int m_B; }; //2、全局函數重載加號 + Person operator+(Person& p1, Person& p2) { Person temp; temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; return temp; } //3、函數重載的版本 Person operator+(Person& p1, int num) { Person temp; temp.m_A = p1.m_A + num; temp.m_B = p1.m_B + num; return temp; } void test01() { Person p1; p1.m_A = 10; p1.m_B = 10; Person p2; p2.m_A = 10; p2.m_B = 10; //成員函數重載本質調用 /*Person p3 = p1.operator+(p2);*/ //全局函數重載本質調用 /*Person p3 = operator+(p1 , p2);*/ Person p3 = p1 + p2; //運算符重載也可以發生函數重載 Person p4 = p1 + 100; cout << "p3.m_A = " << p3.m_A << endl; cout << "p3.m_B = " << p3.m_B << endl; cout << "p4.m_A = " << p4.m_A << endl; cout << "p4.m_B = " << p4.m_B << endl; } int main() { test01(); //示例1 system("pause"); return 0; } -
總結:
- 1、對于內置的數據類型的表達式的運算符是不可能改變的
- 2、不要濫用運算符重載
左移運算符重載
-
可以輸出自定義數據類型
-
示例:
#include <iostream> using namespace std; #include <string> //左移運算符重載 class Person { //友元 friend ostream& operator<<(ostream& cout, Person& p); public: //構造函數提供接口賦值 Person(int a, int b) { m_A = a; m_B = b; } private: //利用成員函數重載 左移運算符 p.operator<<(cout) 簡化版本 p << cout //不會利用成員函數重載 << 運算符,因為無法實現 cout在左側 /*void operator<<(cout) { }*/ int m_A; int m_B; }; //只能利用全局函數重載左移運算符 //cout 屬于 ostream 輸出流 屬性屬于ostream 注意 operator前需要加引用符 & ostream & operator<<(ostream &cout , Person &p ) { //本質 operator<<(cout,p) 簡化 cout << p cout << "m_A = " << p.m_A << " m_B = " << p.m_B; return cout; } void test01() { Person p(10,10); cout << p << endl; } int main() { test01(); //示例1 system("pause"); return 0; }
遞增運算符重載
-
作用:通過重載遞增運算符,實現自己的整數數據
-
示例:
#include <iostream> using namespace std; #include <string> //遞增運算符重載 //自定義整形 class MyInteger { //友元 friend ostream& operator<<(ostream& cout, MyInteger myint); public: MyInteger() { m_Num = 0; } //重載前置++運算符 返回引用為了一直對一個數據進行遞增操作 MyInteger & operator++() { //先進行++運算 m_Num++; //再將自身作為返回 return *this; } //重載后置++運算符 // void operator++(int)里的int代表占位參數,可以用于區分前置和后置遞增 MyInteger operator++(int) { //先記錄當時結果 MyInteger temp = *this; //再遞增 m_Num++; //最后將記錄結果做返回 return temp; } private: int m_Num; }; //重載<<運算符 ostream& operator<<(ostream &cout, MyInteger myint) { cout << myint.m_Num; return cout; } void test01() { MyInteger myint; cout << ++myint << endl; } void test02() { MyInteger myint; cout << myint++ << endl; cout << myint << endl; } int main() { test01(); //示例1 test02(); system("pause"); return 0; }
賦值運算符重載
-
C++編譯器至少給一個類添加4個函數
- 默認構造函數(無參,函數體為空)
- 默認析構函數(無參,函數體為空)
- 默認拷貝構造函數,對屬性進行值拷貝
- 賦值運算符operator=,對屬性進行值拷貝
-
如果類中有屬性指向堆區,做賦值操作時也會出現深淺拷貝
-
示例:
#include <iostream> using namespace std; #include <string> //賦值運算符重載 class Person { public: Person(int age) { m_Age = new int(age); } ~Person() { if (m_Age != NULL) { delete m_Age; m_Age = NULL; } } //重載 賦值運算符 Person& operator=(Person &p) { //編譯器是提供的淺拷貝 //m_Age = p.m_Age; //應該先判斷時候有屬性在堆區,如果有先釋放干凈,然后再深拷貝 if (m_Age != NULL) { delete m_Age; m_Age = NULL; } //深拷貝操作 m_Age = new int(*p.m_Age); //返回對象本身 return *this; } int* m_Age; }; void test01() { Person p1(18); Person p2(20); p2 = p1; //賦值操作 cout << "p1的年齡為:" << p1.m_Age << endl; cout << "p2的年齡為:" << p2.m_Age << endl; } int main() { test01(); //示例1 test02(); system("pause"); return 0; }
關系運算符重載
-
作用:重載關系運算符,可以讓兩個自定義類型對象進行對比操作
-
示例:
#include <iostream> using namespace std; #include <string> //重載關系運算符 class Person { public: Person(string name, int age) { m_Name = name; m_Age = age; } //重載 == 號關系運算符 bool operator==(Person &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return true; } return false; } //重載 !=號關系運算符 bool operator!=(Person &p) { if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) { return false; } return true; } string m_Name; int m_Age; }; void test01() { Person p1("Tom", 18); Person p2("Tom", 18); if (p1 == p2) { cout << "p1和p2相同" << endl; } } int main() { test01(); //示例1 system("pause"); return 0; }
函數調用運算符重載
-
函數調用運算符()也可以重載
-
由于重載后使用的方式非常像函數的調用,因此稱為仿函數
-
仿函數沒有固定寫法,非常靈活
-
示例:
#include <iostream> using namespace std; #include <string> //函數調用運算符重載 class MyPrint { public: //重載函數調用運算符 void operator()(string text) { cout << text << endl; } }; void test01() { MyPrint myPrint; myPrint("hello world"); //由于使用起來非常類似于函數調用,因此稱為仿函數 } //仿函數非常靈活,沒有固定的寫法 //加法 class MyAdd { public: int operator()(int num1, int num2) { return num1 + num2; } }; void test02() { MyAdd myadd; int ret = myadd(100, 100); cout << "ret = " << ret << endl; //匿名函數對象 cout << MyAdd()(100, 100) << endl; } int main() { test01(); //示例1 test02(); system("pause"); return 0; }
繼承
- 繼承是面向對象三大特性之一
- 繼承的技術可以減少重復代碼
集成的基本語法
- 基本語法:class 子類 : 繼承方式 父類 {...};
- class A : public B {...};
- A:子類:稱為派生類
- B:父類:稱為基類
繼承方式
-
繼承方式一共有三種:
- 公共繼承
- 保護繼承
- 私有繼承
-
示例:
#include <iostream> using namespace std; #include <string> //繼承方式 class Basel { public: int m_A; protected: int m_B; private: int m_C; }; //公共繼承 class Son1 :public Basel { public: void func() { //父類中的公關權限成員 到子類中依然是公共權限 m_A = 10; //父類中的保護權限成員 到子類中依然是保護權限 m_B = 10; //父類中的私有權限成員 子類訪問不到 //m_C = 10; } }; //保護繼承 class Son2 :protected Basel { public: void func() { //父類中的公共成員,到子類中變為保護權限 m_A = 100; //父類中保護成員,到子類中變為保護權限 m_B = 100; //父類中的私有成員 子類訪問不到 //m_C = 100; } }; //私有繼承 class Son3 :private Basel { public: void func() { //父類中公共成員 到子類中變為私有成員 m_A = 100; //父類中保護乘員 到子類中變為私有成員 m_B = 100; //父類中私有的成員,子類訪問不到 //m_C = 100; } }; class GrandSon3 :public Son3 { public: void func() { //到了Son3中 m_A變為私有 此子類也訪問不到 //m_A = 1000; // 到了Son3中 m_B變為私有 此子類也訪問不到 //m_B = 1000; } }; void test01() { Son1 s1; s1.m_A = 100; //到Son1中 m_B是保護權限 類外訪問不到 //s1.m_B = 10; } void test02() { Son2 s2; //s1.m_A = 1000; 在Son2中 m_A變為保護權限,因此類外訪問不到 //是。m_B = 1000; 在Son2中 m_B保護權限 類外不可以訪問 } void test03() { Son3 s3; //s3.m_A = 100; 在Son3中 m_A變為私有成員 類外訪問不到 //s3.m_B = 100; 在Son3中 m_B變為私有成員 類外訪問不到 } int main() { test01(); //示例1 test02(); system("pause"); return 0; }
繼承中的對象模型
-
問題:從父類繼承過來的成員,哪些屬于子類對象中的
-
示例:
#include <iostream> using namespace std; #include <string> //繼承中的對象模型 class Base { public: int m_A; protected: int m_B; private: int m_C; }; class Son :public Base { public: int m_D; }; void test01() { //父類中所有非靜態成員屬性都會被子類繼承下去 //父類中私有成員屬性 是被編譯器給隱藏了 因此訪問不到 但是確實被繼承下去了 //可在vs 開發人員命令提示符里查看對象模型 cd到具體的文件路徑下 //命令 cl /d1 reportSingleClassLayout"類名" "文件命" “tab補全” (第一個是L第二個是數字1) cout << "size of Son = " << sizeof(Son) << endl; //16 } int main() { test01(); //示例1 system("pause"); return 0; }
繼承中構造和析構順序
-
子類繼承父類后,當創建子類對象,也會調用父類的構造函數
-
父類和子類的構造和析構順序
-
示例:
#include <iostream> using namespace std; #include <string> //繼承中的構造和析構的順序 class Base { public: Base() { cout << "Base構造函數" << endl; } ~Base() { cout << "Base析構函數" << endl; } }; class Son :public Base { public: Son() { cout << "Son構造函數" << endl; } ~Son() { cout << "Son析構函數" << endl; } }; void test01() { //Base b; //繼承中的構造和析構順序如下: //先構造父類,再構造子類 析構的順序與構造的順序相反 Son s; } int main() { test01(); //示例1 system("pause"); return 0; }
繼承同名成員處理方式
-
當子類與父類出現同名的成員,通過子類對象訪問子類與父類同名的數據方法:
- 訪問子類同名成員直接訪問即可
- 訪問父類同名成員需要加作用域
-
示例:
#include <iostream> using namespace std; #include <string> //繼承同名成員處理方式 class Base { public: Base() { m_A = 100; } void func() { cout << "Base - func()調用" << endl; } void func(int a) { cout << "Base - func(int a)調用" << endl; } int m_A; }; class Son :public Base { public: Son() { m_A = 200; } void func() { cout << "Son - func()調用" << endl; } int m_A; }; //同名成員屬性處理方式 void test01() { Son s; cout << "Son m_A = " << s.m_A << endl; //如果通過子類對象訪問父類中同名成員需要加作用域 cout << "Base m_A = " << s.Base::m_A << endl; } //同名成員函數處理方式 void test02() { Son s; s.func(); //直接調用 調用是子類中的同名成員 //調用父類中同名成員函數 s.Base::func(); //如果子類中出現和父類同名的成員函數,子類的同名成員會隱藏掉父類中所有同名成員函數 //如果想訪問到父類中被隱藏的同名成員函數,需要加作用域 //s.func(100); 報錯 s.Base::func(100); } int main() { test01(); //示例1 test02(); system("pause"); return 0; }
繼承同名靜態成員處理方式
-
繼承中同名的靜態成員在子類對象上與非靜態成員處理方式一致
- 訪問子類同名成員 直接訪問即可
- 訪問父類同名成員 需要加作用域
-
示例:
#include <iostream> using namespace std; #include <string> //繼承同名靜態成員處理方式 class Base { public: static void func() { cout << "Base - staitc void func()" << endl; } static void func(int a) { cout << "Base - staitc void func(int a)" << endl; } static int m_A; }; int Base::m_A = 100; class Son :public Base { public: static int m_A; static void func() { cout << "Son - staitc void func()" << endl; } static int m_A; }; int Son::m_A = 200; //同名靜態成員屬性處理方式 void test01() { //1、通過對象訪問 cout << "通過對象訪問:" << endl; Son s; cout << "Son m_A = " << s.m_A << endl; cout << "Base m_A = " << s.Base::m_A << endl; //2、通過類名訪問 cout << "通過類名訪問:" << endl; cout << "Son m_A = " << Son::m_A << endl; //第一個:: 代表通過類名方式訪問 第二個::代表訪問父類作用域下 cout << "Base m_A = " << Son::Base::m_A << endl; } //同名靜態成員函數處理方式 void test02() { //1、通過對象訪問 cout << "通過對象訪問:" << endl; Son s; s.func(); s.Base::func(); //通過類名訪問 cout << "通過類名訪問:" << endl; Son::func(); Son::Base::func(); //子類出現和父類同名靜態成員函數,也會隱藏父類中所有同名成員函數 //如果想訪問父類中被隱藏同名成員,需要加作用域 Son::Base::func(100); } int main() { test01(); //示例1 test02(); system("pause"); return 0; }
多繼承語法
-
C++允許一個類繼承多個類
-
語法:class 子類:繼承方式 父類1, 繼承方式 父類2...
-
多繼承可能會引發父類中有同名成員出現,需要加作用域區分
-
注意:C++實際開發中不建議用多繼承
-
示例:
#include <iostream> using namespace std; #include <string> //多繼承語法 class Base { public: Base() { m_A = 100; } int m_A; }; class Base2 { public: Base2() { m_A = 100; } int m_A; }; //子類 需要繼承Base和Base2 //語法:class 子類 :繼承方式 父類1, 繼承方式 父類2 ... class Son :public Base , public Base2 { public: Son() { m_C = 300; m_D = 400; } int m_C; int m_D; }; void test01() { Son s; cout << "sizeof Son = " << sizeof(s) << endl; //當父類中出現同名成員,需要加作用域區分 cout << "Base m_A = " << s.Base::m_A << endl; cout << "Base2 m_A = " << s.Base2::m_A << endl; } int main() { test01(); //示例1 system("pause"); return 0; }
菱形繼承
-
菱形繼承概念:
- 兩個派生類繼承同一個基類
- 又有某個類同時繼承這兩個派生類
- 菱形繼承也成為鉆石繼承
-
菱形繼承問題:
- 當兩個派生類同時繼承基類,繼承這兩個派生類的新類使用數據時,就會產生二義性
- 新類繼承基類的數據繼承了兩份,這份數據中只需要一份就可以
-
示例:
#include <iostream> using namespace std; #include <string> //動物類 class Animal { public: int m_Age; }; //利用虛繼承 解決菱形繼承的問題 // 繼承之前 加上關鍵字 virtual 變為虛繼承 // Animal類稱為 虛基類 //羊類 class Sheep :virtual public Animal { }; //駝類 class Tuo :virtual public Animal { }; //羊駝類 class SheeoTuo :public Sheep, public Tuo { }; void test01() { SheeoTuo st; st.Sheep::m_Age = 18; st.Tuo::m_Age = 28; //當出現菱形繼承,有兩份父類擁有相同的數據,需要加以作用域區分 cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl; cout << "st.m_Age = " << st.m_Age << endl; //這份數據知道只要有一份就可以了,菱形繼承導致了數據有兩份,資源浪費 } //虛繼承 //vbptr ---- vbtable 指向虛基類表 //v - virtual 虛 //b - base 基類 //ptr - pointer 指針 int main() { test01(); //示例1 system("pause"); return 0; }
多態
多態的基本概念
-
多態是C++面向對象三大特性之一
-
多態分為兩類
- 靜態多態:函數重載和運算符重載屬于靜態多態,復用函數名
- 動態多態:派生類和虛函數實現運行時多態
-
靜態多態和動態多態區別:
- 靜態多態的函數地址早綁定 - 編譯階段確定函數地址
- 動態多態的函數地址晚綁定 - 運行階段確定函數地址
-
示例:
#include <iostream> using namespace std; #include <string> //多態 //動物類 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; } }; //執行說話的函數 //地址早綁定 在編譯階段就確定函數地址 /如果想執行讓貓說話,那么這個函數地址就不能提前綁定,需要在執行階段進行綁定,地址晚綁定 //動態多態滿足條件 //1、有繼承關系 //2、子類要重寫父類的虛函數 //動態多態使用 //父類的指針或者引用 指向子類對象 //我們希望傳入什么對象,那么久調用什么對象的函數 //如果函數地址在編譯階段就能確定,就是靜態聯編 //如果函數地址在運行階段才能確定,就是動態聯編 void doSpeak(Animal& animal) { //Animal & animal = cat; animal.speak(); //動物在說話 } void test01() { Cat cat; doSpeak(cat); Dog dog; doSpeak(dog); } int main() { test01(); //示例1 system("pause"); return 0; }
多態案例---計算器類
-
多態的優點
- 代碼組織結構清晰
- 可讀性強
- 利于后期和前期的擴展以及維護
-
示例:
#include <iostream> using namespace std; #include <string> ////分別利用普通寫法和多態技術實現計算器 //普通寫法 class Calculator { public: int getRestlt(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; } else if (oper == "/") { return m_Num1 / m_Num2; } //如果想擴展新的功能,需要修改源碼 //在正式開發環境中,提倡開閉原則 //在開閉原則:對擴展進行開放,對修改進行關閉44 } int m_Num1; //操作數 1 int m_Num2; //操作數 2 }; void test01() { //創建計算器對象 Calculator c; c.m_Num1 = 10; c.m_Num2 = 10; cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getRestlt("+") << endl; } //利用多態實現計算器 //多態好處: // 1、組織結構清晰 // 2、可讀性強 // 3、對于前期和后期擴展以及維護性高 //實現計算器抽象類 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 = 100; abc->m_Num2 = 10; cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl; delete abc; } int main() { test01(); //示例1 test02(); //示例2 system("pause"); return 0; } -
總結:C++開發提倡利用多態設計程序架構,因為多態優點很多
純虛函數和抽象類
-
在多態中,通常父類中虛函數的實現是毫無意義的,主要都是調用子類重寫的內容,因此可將虛函數改為純虛函數
-
純虛函數語法:virtual 返回值類型 函數名 (參數列表) = 0;
-
當類中有了純虛函數,這個類也成為抽象類
-
抽象類特點:
- 無法實例化對象
- 子類必須重寫抽象類中的純虛函數,否則也屬于抽象類
-
示例:
#include <iostream> using namespace std; #include <string> //純虛函數和抽象類 class Base { public: //純虛函數 //只要有一個純虛函數,這個類稱為抽象類 //抽象類特點: //1、無法實例化對象 //2、抽象類的子類必須要重寫父類中的純虛函數,否則也屬于抽象類 virtual void func() = 0; }; class Son :public Base { public: virtual void func() { cout << "func函數調用" << endl; } }; void test01() { //Base b;//抽象類是無法實例化對象的 //new Base;抽象類是無法實例化對象的 //Son s; //子類必須重寫父類中的純虛函數,否則無法實例化對象 Base* base = new Son; base->func(); delete base; //記得銷毀 } void test02() { } int main() { test01(); //示例1 system("pause"); return 0; }
多態案例---制作飲品
-
案例描述:
- 流程:煮水、沖泡、倒入杯中-加入輔料
- 利用多態技術實現,提供抽象制作飲品基類,提供子類制作咖啡和茶葉
-
示例:
#include <iostream> using namespace std; #include <string> //多態案例2 制作飲品 class AbstractDrinking { public: //煮水 virtual void Boil() = 0; //沖泡 virtual void Brew() = 0; //倒入杯中 virtual void PourInCup() = 0; //加入輔料 virtual void PutSomething() = 0; //制作飲品 void makeDrink() { Boil(); Brew(); PourInCup(); PutSomething(); } }; //制作咖啡 class Coffee :public AbstractDrinking { public: //煮水 virtual void Boil() { cout << "煮農夫山泉" << endl; } //沖泡 virtual void Brew() { cout << "沖泡咖啡" << endl; } //倒入杯中 virtual void PourInCup() { cout << "倒入杯中" << endl; } //加入輔料 virtual void PutSomething() { cout << "加入糖和牛奶" << endl; } }; //制作茶水 class Tea :public AbstractDrinking { public: //煮水 virtual void Boil() { cout << "煮農夫山泉" << endl; } //沖泡 virtual void Brew() { cout << "沖泡茶葉" << endl; } //倒入杯中 virtual void PourInCup() { cout << "倒入杯中" << endl; } //加入輔料 virtual void PutSomething() { cout << "加入檸檬" << endl; } }; //制作的函數 void doWork(AbstractDrinking* abs) { //AbstractDrinking * abs = new Coffee abs->makeDrink(); delete abs; //手動釋放 } void test01() { //制作咖啡 doWork(new Coffee); cout << "--------------------------" << endl; //制作茶葉 doWork(new Tea); } void test02() { } int main() { test01(); //示例1 system("pause"); return 0; }
虛析構和純虛析構
-
多態使用時,如果子類中有屬性開辟到堆區,那么父類指針在釋放時無法調用到子類的析構代碼
-
解決方式:將父類中的析構函數改為虛析構或者純虛析構
-
虛析構額純虛析構共性:
- 可以解決父類指針釋放子類對象
- 都需要有具體的函數實現
-
虛析構和純虛析構區別
- 如果是純虛析構,該類屬于抽象類,無法實例化對象
-
虛析構語法
- virtual ~類名(){}
-
純虛析構語法
- virtual ~類名() = 0;
- 類名::~類名(){}
-
示例:
#include <iostream> using namespace std; #include <string> //虛析構和純虛析構 class Animal { public: Animal() { cout << "Animal構造函數調用" << endl; } //利用虛析構可以解決父類指針釋放子類對象時不干凈的問題 /*virtual ~Animal() { cout << "Animal虛析構函數調用" << endl; }*/ //純虛析構 需要聲明也需要實現 //有了純虛析構之后,這個類也屬于抽象類,無法實例化對象 virtual ~Animal() = 0; //純虛函數 virtual void speak() = 0; }; Animal::~Animal() { cout << "Animal純虛析構函數調用" << endl; } class Cat :public Animal { public: Cat(string name) { cout << "Cat構造函數調用" << endl; m_Name = new string(name); } virtual void speak() { cout << *m_Name << "小貓在說話" << endl; } ~Cat() { if (m_Name != NULL) { cout << "Cat析構函數調用" << endl; delete m_Name; m_Name = NULL; } } string* m_Name; }; void test01() { Animal* animal = new Cat("Tom"); animal->speak(); //父類指針在析構時候,不會調用子類中析構函數,導致子類如果有堆區屬性,出現內存泄露 delete animal; } void test02() { } int main() { test01(); //示例1 system("pause"); return 0; } -
總結:
- 1、虛析構或純虛析構就是用來解決通過父類指針釋放子類對象
- 2、如果子類中沒有堆區數據,可以不寫為虛析構或純虛析構
- 3、擁有純虛析構函數的類也屬于抽象類
文件操作
- 通過文件可以將數據持久化
- C++中對文件操作需要包含頭文件
- 文件類型分為兩種
- 文本文件:文件以文本的ASCII碼形式存儲在計算機中
- 二進制文件“文件以文本的二進制形式存儲在計算機中,用戶一般不能直接讀懂它們
- 操作文件的三大類
- ofstream:寫操作
- ifstream:讀操作
- fstream:讀寫操作
文本文件
寫文件
- 寫文件步驟如下:
- 包含頭文件
-
include
-
- 創建流對象
- ofstream ofs;
- 打開文件
- ofs.open("文件路徑",打開方式);
- 寫數據
- ofs <<"寫入數據";
- 關閉文件
- ofs.close();
- 包含頭文件
- 文件打開方式:
| 打開方式 | 釋義 |
|---|---|
| ios::in | 為讀文件而打開文件 |
| ios::out | 為寫文件而打開文件 |
| ios::ate | 初始位置:文件尾 |
| ios::app | 追加方式寫文件 |
| ios::trunc | 如果文件存在先刪除再創建 |
| ios::binary | 二進制方式 |
-
注意:文件打開方式可以配合使用,利用|操作符
-
例如:用二進制方式寫文件 iso::binary | ios::out 二進制方式寫文件
-
示例:
#include <iostream> using namespace std; #include <string> #include <fstream> //文本文件 寫文件 void test01() { //1、包含頭文件fstream //2、創建流對象 ofstream ofs; //3、指定打開方式 ofs.open("test.txt", ios::out); //4、寫內容 ofs << "姓名:張三" << endl; ofs << "性別:男" << endl; //5、關閉文件 ofs.close(); } void test02() { } int main() { test01(); //示例1 system("pause"); return 0; }
讀文件
-
讀文件步驟:
- 包含頭文件
-
include
-
- 創建流對象
- ifstream ifs;
- 打開文件并判斷文件是否打開成功
- ifs.open("文件路徑",打開方式);
- 讀數據
- 四種方式讀取
- 關閉文件
- ifs.close();
- 包含頭文件
-
示例:
#include <iostream> using namespace std; #include <string> #include <fstream> //文本文件 讀文件 void test01() { //1、包含頭文件fstream //2、創建流對象 ifstream ifs; //3、指定打開方式 ifs.open("test.txt", ios::in); if (!ifs.is_open()) { cout << "文件打開失敗" << endl; return; } //4、讀數據 //第一種 /*char buf[1024] = { 0 }; while (ifs >> buf) { cout << buf << endl; }*/ //第二種 /*char buf[1024] = { 0 }; while (ifs.getline(buf,sizeof(buf))) { cout << buf << endl; }*/ //第三種 string buf; while (getline(ifs,buf)) { cout << buf << endl; } //第四種 //char c; //while ((c=ifs.get())!=EOF) //EOF end of file //{ // cout << c; //} //5、關閉文件 ifs.close(); } void test02() { } int main() { test01(); //示例1 system("pause"); return 0; }
二進制
- 以二進制的方式對文件進行讀寫操作
- 打開方式要指定為 ios::binary
寫文件
-
二進制方式寫文件主要利用流對象調用成員數write
-
函數原型:ostream& write(const char * buffer,int len);
-
參數解釋:字符指針buffer指向內存種一段存儲空間。len是讀寫的字節數
-
示例:
#include <iostream> using namespace std; #include <string> #include <fstream> //二進制文件 寫文件 class Person { public: char m_Name[64]; //姓名 int m_Age; //年齡 }; void test01() { //1、包含頭文件fstream //2、創建流對象 ofstream ofs("Person.txt", ios::out | ios::binary); //3、指定打開方式 //ofs.open("Person.txt", ios::out | ios::binary); //4、寫數據 Person p = { "張三",18 }; ofs.write((const char *)&p,sizeof(Person)); //5、關閉文件 ofs.close(); } void test02() { } int main() { test01(); //示例1 system("pause"); return 0; }
讀文件
-
二進制方式讀文件主要利用流對象調用成員函數read
-
函數原型:istream& read(char * buffer,int len);
-
參數解釋:字符指向buffer指向內存中一段存儲空間。len是讀寫的字節數
-
示例:
#include <iostream> using namespace std; #include <string> #include <fstream> //二進制文件 讀文件 class Person { public: char m_Name[64]; //姓名 int m_Age; //年齡 }; void test01() { //1、包含頭文件fstream //2、創建流對象 ifstream ifs; //3、指定打開方式 ifs.open("Person.txt", ios::in | ios::binary); if (ifs.is_open()) { cout << "文件打開失敗" << endl; return; } //4、讀數據 Person p; ifs.read((char*)&p, sizeof(Person)); cout << "姓名:" << p.m_Name << " 年齡:" << p.m_Age << endl; //5、關閉文件 ifs.close(); } oid test02() { } int main() { test01(); //示例1 system("pause"); return 0; }
C++提高編程
- C++泛型編程和STL技術
模板
模板的概念
- 作用:建立通用的模具,大大提高復用性
- 特點:
- 模板不可以直接使用,它只是一個框架
- 模板的通用并不是萬能的
函數模板
- C++另一種編程思想稱為泛型編程,主要利用的技術就是模板
- C++提供兩種模板機制:【函數模板】和【類模板】
函數模板語法
-
函數模板作用:
- 建立一個通用函數,其函數返回值類型和形參類型可以不具體制定,用一個虛擬的類型來代表
-
語法:
template<typename T> //函數聲明或定義 -
釋義:
- template --- 聲明創建模板
- typename --- 表示其后面的符號是一種數據類型,可以用class代替
- T --- 通用的數據類型,名稱可以替換,通常為大寫字母
-
示例:
#include<iostream> using namespace std; //函數模板 //交換兩個整型函數 void swapInt(int& a, int& b) { int temp = a; a = b; b = temp; } //交換兩個浮點型函數 void swapDouble(double& a, double& b) { double temp = a; a = b; b = temp; } //函數模板 //聲明一個模板,告訴編譯器后面代碼中緊跟著的【T】不要報錯,【T】是一個通用數據類型 template<typename T> //【typename】可以替換成class //通用交換函數 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; }//測試
void test01()
{
int a = 10;
int b = 20;//swapInt(a, b);
//利用函數模板交換
//兩種方式使用函數模板//1、自動類型推到
//mySwap(a, b);//2、顯示指定類型
mySwap(a,b); cout << "a = " << a << endl;
cout << "b = " << b << endl;//double c = 1.1;
//double d = 2.2;
//swapDouble(c, d);
//cout << "c = " << c << endl;
//cout << "d = " << d << endl;}
int main()
{
//測試
test01();system("pause");
return 0;}
-
總結:
- 函數模板利用關鍵字【template】
- 使用函數模板有兩種方式:
- 自動類型推導
- 顯式指定類型
- 模板的目的是為了提高復用性,講類型參數化
函數模板注意事項
- 注意事項
- 自動類型推導,必須推導出一致的數據類型T才可以使用
- 模板必須要確定處T的數據類型,才可以使用
- 示例
#include<iostream> using namespace std; //函數模板 //聲明一個模板,告訴編譯器后面代碼中緊跟著的【T】不要報錯,【T】是一個通用數據類型 template<typename T> //【typename】可以替換成class //通用交換函數 void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } //函數模板注意事項 //1、自動類型推導,必須推導出一致的數據類型T才可以使用 void test01() { int a = 10; int b = 20; mySwap(a, b); //正確 char c = 'c'; //mySwap(a, c); // 錯誤!推到不出一致的T類型 cout << "a = " << a << endl; cout << "b = " << b << endl; } //2、模板必須要確定處T的數據類型,才可以使用 template<typename T> void func() { cout << "func 調用"; } void test02() { //func(); //錯誤,模板不能獨立使用,必須確定出【T】的類型 func<int>(); //利用顯示指定類型的方式,給T一個類型,才可以使用該模板 } int main() { //測試 test01(); system("pause"); return 0; }
案例
-
案例描述
- 利用函數模板封裝一個排序的函數,可以對不同數據類型數組進行排序
- 排序規則從小到大,排序算法為選擇排序
- 分別利用char數組和int數組進行測試
-
示例:
#include<iostream> using namespace std; //實現通用 對數組進行排序的函數 //規則 從大到小 //算法 選擇 //測試 char數組、int數組 //交換函數模板 template<typename T> void mySwap(T& a, T& b) { T temp = a; a = b; b = temp; } //排序算法 template<typename T> void mySort(T arr[],int len) { for (int i = 0; i < len; i++) { int max = i; //認定最大值的下標 for (int j = i + 1; j < len; j++) { //認定的最大值比遍歷出的數值要小,說明【j】下標的元素才是真正的最大值 if (arr[max] < arr[j]) { max = j;//更新最大值下標 } } if (max != i) { //交換【max】和【i】下標元素 mySwap(arr[i], arr[max]); } } } //提供打印數組模板 template<typename T> void printArray(T arr[], int len) { for (int i = 0; i < len; i++) { cout << arr[i] << " "; } cout << endl; } //測試 void test01() { //測試char數組 char charArr[] = "badcfe"; //計算數組長度 int num = sizeof(charArr) / (sizeof(char)); //傳入排序函數模板參數 mySort(charArr, num); //傳入打印函數模板參數 printArray(charArr,num); } void test02() { //測試int數組 int intArr[] = { 7,5,1,3,9,2,4,6,8 }; //計算數組長度 int num = sizeof(intArr) / (sizeof(int)); //傳入排序函數模板參數 mySort(intArr, num); //傳入打印函數模板參數 printArray(intArr, num); } int main() { //測試 test01(); test02(); system("pause"); return 0; }
普通函數與函數模板的區別
-
區別:
- 普通函數調用時可以發生自動類型轉換(隱式類型轉換)
- 函數模板調用時,如果利用自動類型推導,不會發生隱式類型轉換
- 如果利用顯示指定類型的方式,可以發生隱式類型轉換
-
示例:
#include<iostream> using namespace std; //普通函數與函數模板區別 //1、普通函數調用可以發生隱式類型轉換 //2、函數模板 用自動類型推導,不可以發生隱式類型轉換 //3、函數模板 用顯示指定類型,可以發生隱式類型轉換 //普通函數 int myAdd01(int a, int b) { return a + b; } //函數模板 template<typename T> T myAdd02(T a, T b) { return a + b; } //測試 void test01() { int a = 10; int b = 20; char c = 'c'; //區別 1 cout << myAdd01(a, c) << endl; //自動類型推導 不可以發生隱式類型轉換 //cout << myAdd02(a, c) << endl;; //顯示指定類型 可以發生隱式類型轉換 cout << myAdd02<int>(a, c) << endl; } int main() { //測試 test01(); system("pause"); return 0; }
普通函數與函數模板的調用規則
-
調用規則如下:
- 如果函數模板和普通函數都可以實現,優先調用普通函數
- 可以通過空模板參數列表來強制調用函數模板
- 函數模板也可以發生重載
- 如果函數模板可以產生更好的匹配,優先調用函數模板
-
示例:
#include<iostream> using namespace std; //普通函數與函數模板調用規則 //1、如果函數模板和普通函數都可以實現,優先調用普通函數 //2、可以通過空模板參數列表來強制調用函數模板 //3、函數模板也可以發生重載 //4、如果函數模板可以產生更好的匹配,優先調用函數模板 void myPrint(int a, int b) { cout << "調用的普通函數" << endl; } template<typename T> void myPrint(T a, T b) { cout << "調用的模板函數" << endl; } template<typename T> void myPrint(T a, T b, T c) { cout << "調用的模板函數" << endl; } //測試 void test01() { int a = 10; int b = 20; int c = 30; //1、如果函數模板和普通函數都可以實現,優先調用普通函數 //myPrint(a, b); //2、通過空模板參數列表來強制調用函數模板 //myPrint<>(a, b); //3、函數模板也可以發生重載 //myPrint(a, b, c); //4、如果函數模板可以產生更好的匹配,優先調用函數模板 char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2); } int main() { //測試 test01(); system("pause"); return 0; }
模板的局限性
-
局限性
- 模板的通用性并不是萬能的
-
例如:
template<typename T> void f(T a,T b) { a = b; } -
上述代碼中提供的賦值操作,如果傳入的a和b是一個數組,就無法實現
-
例如:
template<typename T> void f(T a,T b) { if(a > b) { ... } } -
如果T的數據類型傳入的是像Person這樣的自定義數據類型,也無法正常運行
-
C++為了解決這種問題,提供模板的重載,可以為這些特定的類型提供具體化的模板
-
示例:
#include<iostream> using namespace std; #include<string> //模板局限性 //模板并不是萬能的,有些特定數據類型,需要用具體化方式做特殊實現 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } //姓名 string m_Name; //年齡 string m_Age; }; //對比兩個數據是否相等函數 template<typename T> bool myCompare(T& a, T& b) { if (a == b) { return true; } else { return false; } } //利用具體化Person的版本來實現代碼,具體化優先調用 template<> bool myCompare(Person& p1, Person& p2) { if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true; } else { return false; } } //測試 void test01() { int a = 10; int b = 20; bool ret = myCompare(a, b); if (ret) { cout << "a == b" << endl; } else { cout << "a != b" << endl; } } //自定義類型對比 void test02() { Person p1("Tom", 10); Person p2("Tom", 10); bool ret = myCompare(p1, p2); if (ret) { cout << "p1 == p2" << endl; } else { cout << "p1 != p2" << endl; } } int main() { //測試 test01(); test02(); system("pause"); return 0; } -
總結
- 利用具體化的模板,可以解決自定義類型的通用化
- 學習模板并不是為了寫模板,而是在STL能夠運用系統提供的模板
類模板
- 作用:
- 建立一個通用類,類中的成員數據類型可以不具體制訂,用一個虛擬的類型來代表
類模板語法
-
語法:
template<typename T> 類 -
解釋:
- template --- 聲明創建模板
- typename --- 表明其后面的符號是一種數據類型,可以用class代替
- T --- 通用的數據類型,名稱可以替換,通常為大寫字母
-
示例:
#include<iostream> using namespace std; #include<string> //類模板 template<class NameType,class AgeType> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; } string m_Name; int m_Age; }; //測試 void test01() { Person<string, int> p1("孫悟空", 999); p1.showPerson(); } int main() { //測試 test01(); system("pause"); return 0; } -
總結:類模板和函數模板語法相似,在聲明模板template后面加類,此類稱為類模板
類模板與函數模板區別
-
類模板與函數模板區別主要有兩點
- 類模板沒有自動類型推導的使用方式
- 類模板在模板參數列表中可以有默認參數
-
示例:
#include<iostream> using namespace std; #include<string> //類模板與函數模板的區別 template<class NameType,class AgeType = int> //<默認參數列表> class Person { public: Person(NameType name, AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "name: " << this->m_Name << " age: " << this->m_Age << endl; } string m_Name; int m_Age; }; //1、類模板沒有自動類型推導使用方法 void test01() { //Person p1("孫悟空", 999);錯誤 無法用自動類型推導 Person<string, int> p1("孫悟空", 999); //正確 只能用顯式指定類型 p1.showPerson(); } //2、類模板在模板參數列表中可以有默認參數 void test02() { Person<string> p2("豬八戒", 1000); //類模板中的模板參數列表 可以指定默認參數 p2.showPerson(); } int main() { //測試 test01(); test02(); system("pause"); return 0; } -
總結:
- 類模板使用只能用顯示指定類型方式
- 模板中的模板參數列表可以有默認參數
類模板中成員函數創建時機
-
類模板中成員函數和普通類中成員函數創建時機是有區別的:
- 普通類中的成員函數一開始就可以創建
- 類模板中的成員函數在調用時才創建
-
示例:
#include<iostream> using namespace std; #include<string> //類模板中成員函數創建時機 //類模板中成員函數在調用時采取創建 class Person1 { public: void showPerson1() { cout << "Person1 show" << endl; } }; class Person2 { public: void showPerson2() { cout << "Person2 show" << endl; } }; template<class T> class MyClass { public: T obj; //類模板中的成員函數并不是一開始就創建的,而是在模板調用時再生成 void func1() { obj.showPerson1(); } void func2() { obj.showPerson2(); } }; void test01() { MyClass<Person1>m; m.func1(); //m.func2(); //編譯會出錯,說明函數調用才會去創建成員函數 } int main() { //測試 test01(); system("pause"); return 0; }
類模板對象做函數參數
-
類模板實例化出的對象,向函數傳參的方式
-
一共有三種傳入方式:
- 指定傳入的類型 --- 直接顯示對象的數據類型
- 參數模板化 --- 將對象中的參數變為模板進行傳遞
- 整個類模板化 --- 將這個對象類型模板化進行傳遞
-
示例:
#include<iostream> using namespace std; #include<string> //類模板的對象做函數參數 template<class T1,class T2> class Person { public: Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl; } T1 m_Name; T2 m_Age; }; //1、指定傳入類型 void printPerson01(Person<string, int>&p) { p.showPerson(); } void test01() { Person<string, int> p("孫悟空", 999); printPerson01(p); } //2、參數模板化 template<class T1,class T2> void printPerson02(Person<T1, T2>&p2) { p2.showPerson(); cout << "T1 的類型為:" << typeid(T1).name() << endl; cout << "T2 的類型為:" << typeid(T2).name() << endl; } void test02() { Person<string, int> p2("豬八戒", 1000); printPerson02(p2); } //3、整個類模板化 template<class T> void printPerson03(T &p) { p.showPerson(); cout << "T 的類型為:" << typeid(T).name() << endl; } void test03() { Person<string, int> p3("唐僧", 30); printPerson03(p3); }int main()
{
//測試
test01();test02(); test03(); system("pause"); return 0;}
-
總結:
- 通過類模板創建的對象,可以有三種方式向函數中進行傳參
- 使用比較廣泛是第一種:指定傳入的類型
類模板與繼承
- 當類模板碰到繼承時,需要注意以下幾點:
- 當子類繼承的父類是一個類模板時,子類在聲明的時候,要指定出父類中T的類型
- 如果不指定,編譯器無法給子類分配內存
- 如果想靈活指定出父類中T的類型,子類也需變為類模板
- 示例:
#include<iostream> using namespace std; #include<string> //類模板與繼承 template<class T> class Base { T m; }; //class Son :public Base //錯誤,必須要知道父類中的T類型,才能繼承給子類 class Son:public Base<int> { }; void test01() { Son s1; } //如果想靈活指定父類中T類型,子類也需要變類模板 template<class T1,class T2> class Son2 :public Base<T2> { public: Son2() { cout << "T1的類型為:" << typeid(T1).name() << endl; cout << "T2的類型為:" << typeid(T2).name() << endl; } T1 obj; }; void test02() { Son2<int, char>S2; } int main() { //測試 test01(); test02(); system("pause"); return 0; }
類模板成員函數類外實現
-
示例:
#include<iostream> using namespace std; #include<string> //類模板與繼承 template<class T1,class T2> class Person { public: //成員函數類內聲明 Person(T1 name, T2 age); /*{ this->m_Name = name; this->m_Age = age; }*/ void showPerson() /*{ cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl; }*/ T1 m_Name; T2 m_Age; }; //構造函數 類外實現 template<class T1,class T2> Person<T1,T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } //成員函數 類外實現 template<class T1, class T2> void Person<T1,T2>::showPerson() { cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl; } void test01() { Person<string, int> p("tom", 20); p.showPerson(); } int main() { //測試 test01(); system("pause"); return 0; } -
總結:類模板中成員函數類外實現時,需要加上模板參數列表
類模板份文件編寫
-
類模板成員函數分文件編寫產生的問題以及解決方式
-
問題:
- 類模板中成員函數創建時機是在調用階段,導致分文件編寫時鏈接不到
-
解決:
- 解決方式1:直接包含.cpp源文件
- 解決方式2:將聲明和實現寫到同一個文件中,并更改后綴名為.hpp,hpp是約定的名稱,并不是強制
-
示例:
person.hpp代碼:#pragma once #include<iostream> using namespace std; #include<string> //類模板分文件編寫問題以及解決 template<class T1, class T2> class Person { public: Person(T1 name, T2 age); /*{ this->m_Name = name; this->m_Age = age; }*/ void showPerson(); /*{ cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl; }*/ T1 m_Name; T2 m_Age; }; //構造函數類外實現 template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } //成員函數類外實現 template<class T1, class T2> void Person<T1, T2>::showPerson() { cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl; }main主函數入口.cpp
#include<iostream> using namespace std; #include<string> //第一種解決方式,直接包含 源文件 #include"person.cpp" //第二種解決方式,將.h和.cpp種的內容寫到一起,將后綴名改為.hpp文件 #include"person.hpp" //#include<string> // ////類模板分文件編寫問題以及解決 //template<class T1,class T2> //class Person //{ //public: // // Person(T1 name, T2 age); // /*{ // this->m_Name = name; // this->m_Age = age; // }*/ // // void showPerson(); // /*{ // cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl; // }*/ // // T1 m_Name; // T2 m_Age; //}; ////構造函數類外實現 //template<class T1,class T2> //Person<T1,T2>::Person(T1 name, T2 age) //{ // this->m_Name = name; // this->m_Age = age; //} // ////成員函數類外實現 //template<class T1, class T2> //void Person<T1,T2>::showPerson() //{ // cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl; //} void test01() { Person<string, int> p("tom", 20); p.showPerson(); } int main() { //測試 test01(); system("pause"); return 0; } -
總結:主流的解決方式是第二種,將類模板成員函數寫到一起,并將后綴名改為.hpp
類模板與友元
-
類模板配合友元函數的類內和類外實現
-
全局函數雷內實現 - 直接在類內聲明友元即可
-
全局函數類外實現 - 需要提前讓編譯器知道全局函數的存在
-
示例:
#include<iostream> using namespace std; #include<string> //通過全局函數配合友元 打印Person信息 //提前讓編譯器知道Person類存在 //先做函數模板聲明,下方再做函數模板定義,再做友元 template<class T1,class T2> class Person; //全局函數 類外實現 可以先聲明函數模板,讓編譯器提前知道這個函數的存在 template<typename T1, typename T2> void PrintPerson2(Person<T1, T2>p); template<typename T1,typename T2> class Person { //全局函數 類內實現 friend void PrintPerson(Person<T1, T2>p) { cout << "姓名:" << p.m_Name << " 年齡:" << p.m_Age << endl; } //全局函數 類外實現 //加空模板參數列表 【 < > 】 //如果全局函數 是類外實現,需要讓編譯器提前知道這個函數的存在 friend void PrintPerson2<>(Person<T1,T2>p); public: Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; //全局函數 類外實現 template<typename T1, typename T2> void PrintPerson2(Person<T1, T2>p) { cout << "類外實現---姓名:" << p.m_Name << " 年齡:" << p.m_Age << endl; } //1、全局函數在類內實現 void test01() { Person<string, int>p("Tom", 20); PrintPerson(p); } //2、全局函數在類外實現 void test02() { Person<string, int>p1("Cat", 5); PrintPerson2(p1); } int main() { //測試 test01(); test02(); system("pause"); return 0; } -
總結:建議全局函數做類內實現,用法簡單,而且編譯器可以直接識別
類模板案例
-
案例描述:實現一個通用的數組類,要求如下
- 可以對內置數據類型以及自定義數據類型的數據進行存儲
- 將數組中的數據存儲到堆區
- 構造函數中可以傳入數組的容量
- 提供對應的拷貝構造函數以及operator=防止淺拷貝問題
- 提供尾插法和尾刪法對數組中的數據進行增加和刪除
- 可以通過下標的方式訪問數組中的元素
- 可以獲取數組中當前元素個數和數組的容量
-
示例:
MyArray.hpp//自己通用的數組類 #pragma once #include<iostream> using namespace std; template<class T> class MyArray { public: //有參構造 參數 容量 MyArray(int capacity) { cout << "MyArray 有參構造調用" << endl; this->m_Capacity = capacity; this->m_Size = 0; this->pAddress = new T[capacity]; } //拷貝構造 MyArray(const MyArray& arr) { cout << "MyArray 拷貝構造調用" << endl; this->m_Capacity = arr.m_Capacity; this->m_Size = arr.m_Size; //淺拷貝的問題會導致堆區數據重復釋放 //this->pAddress = arr.pAddress; //深拷貝 this->pAddress = new T[arr.m_Capacity]; //將arr中的數據都拷貝過來 for (int i = 0; i < this->m_Size; i++) { //如果T為對象,而且還包含指針,必須重載【=】操作符,因為這個等號不是構造而是賦值 //普通類型可以直接【=】,但是指針類型需要深拷貝 this->pAddress[i] = arr.pAddress[i]; } } //operator= 防止淺拷貝問題 MyArray& operator=(const MyArray &arr) { cout << "MyArray 的operator=調用" << endl; //先判斷原來堆區是否有數據,如果有先釋放 if (this->pAddress != NULL) { delete[]this->pAddress; this->pAddress = NULL; this->m_Capacity = 0; this->m_Size = 0; } //深拷貝 this->m_Capacity = arr.m_Capacity; this->m_Size = arr.m_Size; this->pAddress = new T[arr.m_Capacity]; for (int i = 0; i < this->m_Size; i++) { this->pAddress[i] = arr.pAddress[i]; } return *this; } //尾插法 void Push_Back(const T& val) { //先判斷容量時候等于大小 if (this->m_Capacity == this->m_Size) { return; } this->pAddress[this->m_Size] = val; //再數組末尾插入數據 this->m_Size++;//更新數組長度 } //尾刪法 void Pop_Back() { //讓用戶訪問不到最后一個元素,即為尾刪,邏輯刪除 if (this->m_Size == 0) //判斷數組長度是否為0,如果為0,代表數組無數據 { return; } this->m_Size--; //更新數組長度 } //通過下標方式訪問數組中的元素 函數調用返回一個左值,需要返回一個引用【&】 //重載【 [] 】操作符 arr[0] T& operator[](int index) { return this->pAddress[index];//不考慮越界,用戶自己去處理 } // 返回數組容量 int getCapacity() { return this->m_Capacity; } //返回數組長度 int getSize() { return this->m_Size; } //析構函數 ~MyArray() { cout << "MyArray 析構調用" << endl; if (this->pAddress != NULL) { delete[]this->pAddress; this->pAddress = NULL; } } private: T* pAddress;//指針指向堆區開辟的真實數組 int m_Capacity;//數組容量 int m_Size;//數組長度 };主函數.cpp
#include<iostream> using namespace std; #include<string> #include"MyArray.hpp" //打印函數 引用傳遞方式 void printInArray(MyArray<int>& arr) { //i < MyArray里面的getm_Size的int類型返回值 for (int i = 0; i < arr.getSize(); i++) { cout << arr[i] << endl; } }//1、全局函數在類內實現
void test01()
{
//創建一個MyArry類型數組 傳入變量類型為int 數組容量為5
MyArrayarr1(5);
for (int i = 0; i < 5; i++)
{
//利用尾插法向數組中插入數據
//傳入i的值
arr1.Push_Back(i);
}
cout << "arr1的打印輸出:" << endl;
//向打印函數中傳數組
printInArray(arr1);
//查看數組容量與長度
cout << "arr1的容量為:" << arr1.getCapacity() << endl;
cout << "arr1的長度為:" << arr1.getSize() << endl;//創建一個MyArry類型數組 傳入變量類型為int arr2數組值等于arr1數組的值 深拷貝 new一個新空間 MyArray<int>arr2(arr1); //尾刪 arr2.Pop_Back(); printInArray(arr2); cout << "arr2尾刪后:" << endl; cout << "arr2的容量為:" << arr2.getCapacity() << endl; cout << "arr2的長度為:" << arr2.getSize() << endl; //創建一個MyArry類型數組 傳入變量類型為int 數組容量為100 MyArray<int>arr3(100); ////創建一個MyArry類型數組 傳入變量類型為int arr3數組值等于arr1數組的值 深拷貝 new一個新空間 operator= 防止淺拷貝問題 arr3 = arr1;}
//測試自定義數據類型
class Person
{
public:Person() {}; Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age;};
//打印數組
void printPersonArray(MyArray&arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << "姓名:" << arr[i].m_Name << " 年齡:" << arr[i].m_Age << endl;
}
}void test02()
{
MyArrayarr(10);
Person p1("孫悟空", 999);
Person p2("韓信", 30);
Person p3("妲己", 20);
Person p4("趙云", 25);
Person p5("安其拉", 27);//將數據插入到數組中 arr.Push_Back(p1); arr.Push_Back(p2); arr.Push_Back(p3); arr.Push_Back(p4); arr.Push_Back(p5); //打印函數調用 printPersonArray(arr); //輸出容量 cout << "arr容量為:" << arr.getCapacity() << endl; //輸出長度 cout << "arr長度為:" << arr.getSize() << endl;}
int main()
{
//測試
test01();
test02();system("pause"); return 0;}
打印結果: ```cpp 0 1 2 3 4 arr1的容量為:5 arr1的長度為:5 MyArray 拷貝構造調用 0 1 2 3 arr2尾刪后: arr2的容量為:5 arr2的長度為:4 MyArray 有參構造調用 MyArray 的operator=調用 MyArray 析構調用 MyArray 析構調用 MyArray 析構調用 MyArray 有參構造調用 姓名:孫悟空 年齡:999 姓名:韓信 年齡:30 姓名:妲己 年齡:20 姓名:趙云 年齡:25 姓名:安其拉 年齡:27 arr容量為:10 arr長度為:5 MyArray 析構調用 -
總結:能夠利用所學知識點實現通用的數組
STL初識
STL的誕生
- 軟件界一直希望建立一種可重復利用的東西
- C++的面向對象和泛型編程思想,目的就是復用性的提升
- 數據結構和算法都未能有一套標準,導致被迫從事大量重復工作
- 建立數據結構和算法的一套標準,誕生了STL
STL基本概念
- STL(Standard Template Library,標準模板庫)
- STL從廣義上分為:容器(container)算法(algorithm)迭代器(iterator)
- 容器和算法之間通過迭代器進行無縫連接
- STL幾乎所有的代碼都采用了模板類或者模板函數
STL六大組件
- STL答題分為六大組件,分別是:容器、算法、迭代器、仿函數、適配器(配接器)、空間配置器
- 容器:各種數據結構,如vector、list、deque、set、map等,用來存放數據
- 算法:各種常用的算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器與算法之間的膠合劑
- 仿函數:行為類似函數,可作為算法的某種策略
- 適配器:一種用來修飾容器或者仿函數或迭代器接口的東西
- 空間配置器:負責空間的配置與管理
STL中容器、算法、迭代器
- 容器:置物之所也
- STL容器就是將運用最廣泛的一些數據結構實現出來
- 常用的數據結構:數組、鏈表、樹、棧、隊列、集合、映射表等
- 這些容器分為序列式容器和關聯式容器兩種
- 序列式容器:強調值的排序,序列式容器中的每個元素均有固定的位置
- 關聯式容器:二叉樹結構,各元素之間沒有嚴格的物理上的順序關系
- 算法:問題之解法也
- 有限的步驟,解決邏輯或數學上的問題,這一門學科我們叫做算法(Algorithms)
- 算法分為質變算法和非質變算法
- 質變算法:是指運算過程中會更改區間內的元素的內容。例如拷貝、替換、刪除等
- 非質變算法:是指運算過程中不會更改區間內的元素內容,例如查找、計數、遍歷、尋找極值等等
- 迭代器:容器和算法之間粘合劑
- 提供一種方法,使之能夠依序尋訪某個容器所含的各個元素,而又無需暴露該容器的內部表示方式
- 每個容器都有自己專屬的迭代器
- 迭代器使用非常類似于指針,初學階段我們可以先理解迭代器為指針
- 迭代器種類
| 種類 | 功能 | 支持運算 |
|---|---|---|
| 輸入迭代器 | 對數據的只讀訪問 | 只讀,支持++、==、!= |
| 輸出迭代器 | 對數據的只寫訪問 | 只寫、支持++ |
| 前向迭代器 | 讀寫操作,并能向前推進迭代器 | 讀寫、支持++、==、!= |
| 雙向迭代器 | 讀寫操作,并能向前和向后操作 | 讀寫,支持++、-- |
| 隨機訪問迭代器 | 讀寫操作,可以以跳躍的方式訪問任意數據,功能最強的迭代器 | 讀寫,支持++、--、[n]、-n、<、<=、>、>= |
- 常用的容器種迭代器種類為雙向迭代器,和隨機訪問迭代器
容器算法迭代器初識
- STL中最常用的容器為vector,可以理解為數組,下面我們將學習如果向這個容器中插入數據,并遍歷這個容器
vector存放內置數據類型
-
容器:vector
-
算法:for_each
-
迭代器:vector
::iterator -
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //標準算法頭文件 //vector容器存放內置數據類型 void myPrint(int val) { cout << val << endl; } void test01() { //創建一個vector容器,數組 需要包含#include<vector> vector的頭文件 vector<int> v; //向容器中插入數據 v.push_back(10); v.push_back(20); v.push_back(30); v.push_back(40); v.push_back(50); //通過迭代器訪問容器中的數據 //vector<int>::iterator 拿到vector<int>這種容器的迭代器類型 vector<int>::iterator itBegin = v.begin(); //起始迭代器 指向容器中第一個元素 vector<int>::iterator itEnd = v.end(); //結束迭代器,指向容器中最后一個元素的下一個位置 //第一種遍歷方式 while (itBegin != itEnd) { cout << *itBegin << endl; itBegin++; } cout << endl; //第二種遍歷方式 for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } cout << endl; //第三章遍歷方式 利用STL提供遍歷算法 for_each(v.begin(), v.end(), myPrint); } int main() { //測試 test01(); system("pause"); return 0; }運行結果
10 20 30 40 50 10 20 30 40 50 10 20 30 40 50
vector存放自定義數據類型
-
學習目標:vector中存放自定義數據類型,并打印輸出
-
示例
#include<iostream> using namespace std; #include<vector> #include<algorithm> //標準算法頭文件 #include<string> //vector容器存放自定義數據類型 class Person { public: Person(string name, int age) { this->m_Name = name; this->m_Age = age; } string m_Name; int m_Age; }; void test01() { vector<Person> v; Person p1("aaa", 10); Person p2("bbb", 15); Person p3("ccc", 20); Person p4("ddd", 25); Person p5("eee", 30); //向容器中添加數據 v.push_back(p1); v.push_back(p2); v.push_back(p3); v.push_back(p4); v.push_back(p5); //遍歷容器中的數據 cout << "自定義類型存數據" << endl; for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "*解引用 姓名:" << (*it).m_Name << " 年齡:" << (*it).m_Age << endl; cout << "this指針 姓名:" << it->m_Name << " 年齡:" << it->m_Age << endl; cout << endl; } } //存放指定值數據類型 指針 void test02() { vector<Person*> v; Person p1("aaa", 10); Person p2("bbb", 15); Person p3("ccc", 20); Person p4("ddd", 25); Person p5("eee", 30); //向容器中添加數據 傳址 v.push_back(&p1); v.push_back(&p2); v.push_back(&p3); v.push_back(&p4); v.push_back(&p5); //遍歷容器 cout << endl; cout << "自定義類型存地址" << endl; for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) { cout << "this指針 姓名:" << (*it)->m_Name << " 年齡:" << (*it)->m_Age << endl; cout << "*解引用 姓名:" << (*(*it)).m_Name << " 年齡:" << (*(*it)).m_Age << endl; cout << endl; } } int main() { //測試 test01(); test02(); system("pause"); return 0; }運行結果
*解引用 姓名:bbb 年齡:15 this指針 姓名:bbb 年齡:15 *解引用 姓名:ccc 年齡:20 this指針 姓名:ccc 年齡:20 *解引用 姓名:ddd 年齡:25 this指針 姓名:ddd 年齡:25 *解引用 姓名:eee 年齡:30 this指針 姓名:eee 年齡:30自定義類型存地址
this指針 姓名:aaa 年齡:10
*解引用 姓名:aaa 年齡:10this指針 姓名:bbb 年齡:15
*解引用 姓名:bbb 年齡:15this指針 姓名:ccc 年齡:20
*解引用 姓名:ccc 年齡:20this指針 姓名:ddd 年齡:25
*解引用 姓名:ddd 年齡:25this指針 姓名:eee 年齡:30
*解引用 姓名:eee 年齡:30
vector容器嵌套容器
-
容器中嵌套容器,將所有數據進行遍歷輸出
-
示例:
#include<iostream> using namespace std; #include<vector> #include<algorithm> //標準算法頭文件 #include<string> //vector容器嵌套容器 void test01() { vector<vector<int>> v; //創建小容器 vector<int> v1; vector<int> v2; vector<int> v3; vector<int> v4; //向小容器中添加數據 for (int i = 0; i < 4; i++) { v1.push_back(i + 1); v2.push_back(i + 2); v3.push_back(i + 3); v4.push_back(i + 4); } //將小容器插入到大容器中 v.push_back(v1); v.push_back(v2); v.push_back(v3); v.push_back(v4); //通過大容器,把所有數據遍歷 for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) { // (*it) --- 指的是容器vector<int> for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) { cout << *vit << " "; } cout << endl; } } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
1 2 3 4 2 3 4 5 3 4 5 6 4 5 6 7
STL-常用容器
string容器
string基本概念
- 本質:
- string是C++風格的字符串,而string本質上是一個類
- string和char * 區別:
- char *是一個指針
- string是一個類,類內部封裝了char,管理這個字符串,是一個char型容器
- 特點:
- string類內部封裝了很多成員方法
- 例如:查找find、拷貝copy、刪除delete、替換replace、插入insert
- string管理char*所分配的內存,不用擔心復制越界和取值越界等,由類內部進行負責
string構造函數
-
string(); //創建一個空的字符串 例如:string str;
-
string(const char * s); //使用字符串s初始化
-
string(const string & str); //使用一個string對象初始化另一個string對象
-
string(int n,char c); //使用n個字符c初始化
-
示例:
#include<iostream> using namespace std; #include<string> //string構造函數 void test01() { //默認構造 string s1; //初始化 const char* str = "hello world"; string s2(str); cout << "s2 = " << s2 << endl; //拷貝構造 string s3(s2); cout << "s3 = " << s3 << endl; ////使用n個字符初始化 string s4(10, 'a'); cout << "s4 = " << s4 << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
s2 = hello world s3 = hello world s4 = aaaaaaaaaa -
總結:string的多種構造方式沒有可比性,靈活使用即可
string賦值操作
-
功能描述:給string字符串進行賦值
-
賦值的函數原型
- string& openator=(const char* s); //char*類型字符串 賦值給當前的字符串
- string& openator=(const string &s) //把字符串s賦值給當前的字符串
- string& openator=(char c); //字符賦值給當前的字符串
- string& assign(const char *s); //把字符串s賦給當前的字符串
- string& assign(const char *s,int n);//把字符串s的前n個字符賦給當前的字符串
- string& assign(const string &s); //把字符串s賦給當前字符串
- string& assign(int n,char c); //把n個字符c賦給當前字符串
-
示例:
#include<iostream> using namespace std; #include<string> //string賦值操作 void test01() { //第一種方法 string str1; str1 = "hello world"; cout << "第一種方法 str1 = " << str1 << endl; //第二種方法 string str2; str2 = str1; cout << "第二種方法 str2 = " << str2 << endl; //第三種方法 string str3; str3 = 'a'; cout << "第三種方法 str3 = " << str3 << endl; //第四種方法 string str4; str4.assign("hello C++"); cout << "第四種方法 str4 = " << str4 << endl; //第五種方法 string str5; str5.assign("hello C++", 5); cout << "第五種方法 str5 = " << str5 << endl; //第六種方法 string str6; str6.assign(str5); cout << "第六種方法 str6 = " << str6 << endl; //第七種方法 string str7; str7.assign(10, 'w'); cout << "第七種方法 str7 = " << str7 << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
第一種方法 str1 = hello world 第二種方法 str2 = hello world 第三種方法 str3 = a 第四種方法 str4 = hello C++ 第五種方法 str5 = hello 第六種方法 str6 = hello 第七種方法 str7 = wwwwwwwwww -
總結:string的賦值方式很多,一般 operator= 的方式比較實用
字符串拼接
-
實現在字符串末尾憑借字符串
-
函數原型
- string& operator+=(const char* str); //重載+=操作符
- string& operator+=(const char str); //重載+=操作符
- string& operator+=(const string* str); //重載+=操作符
- string& append(const char* s); //把字符串s連接到當前字符串結尾
- string& append(const char* s,int n); //把字符串s的前n個字符連接到當前字符串結尾
- string& append(const string& s); //同operator+=(const String& str);
- string& append(const string& s,int pos,int n);//把字符串s中從pos開始的n個字符連接到字符串結尾
-
示例:
#include<iostream> using namespace std; #include<string> //string字符串拼接 void test01() { string str1 = "我"; //第一種用法 str1 += "愛玩游戲"; cout << "第一種方法 str1 = " << str1 << endl; //第二種方法 str1 += ":"; cout << "第二種方法 str1 = " << str1 << endl; //第三種方法 string str2 = "LOL DNF"; str1 += str2; cout << "第三種方法 str1 = " << str1 << endl; //第四種方法 string str3 = "I"; str3.append(" Love "); cout << "第四種方法 str1 = " << str3 << endl; //第五種方法 str3.append("game: abcde", 5); cout << "第五種方法 str1 = " << str3 << endl; //第六種方法 str3.append(str2); cout << "第六種方法 str1 = " << str3 << endl; //第七種方法 string str4 = " CF DATA2"; str3.append(str4, 0, 4); //只截取string字符串的元素下標0-4的字符 cout << "第七種方法 str1 = " << str3 << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
第一種方法 str1 = 我愛玩游戲 第二種方法 str1 = 我愛玩游戲: 第三種方法 str1 = 我愛玩游戲:LOL DNF 第四種方法 str1 = I Love 第五種方法 str1 = I Love game: 第六種方法 str1 = I Love game:LOL DNF 第七種方法 str1 = I Love game:LOL DNF CF
string查找和替換
-
功能描述
- 查找:查找指定字符串是否存在
- 替換:在指定的位置替換字符串
-
函數原型:
- int find(const string& str,int pos = 0) const; //查找str第一次出現位置,從pos開始查找
- int find(const char* s,int pos = 0) const; //查找s第一次出現位置,從pos開始查找
- int find(const char* s,int pos,int n) const; //從pos位置查找s的前n個字符第一次位置
- int find(const char c,int pos = 0) const; //查找字符c第一次出現位置
- int rfind(const string& str,int pos = npos) const;//查找str最后一次位置,從pos開始查找
- int rfind(const char* s,int pos = npos) const; //查找s最后一次出現位置,從pos開始查找
- int rfind(const char* s,int pos,int n) const; //從pos查找s的前n個字符最后一次位置
- int rfind(const char s,int pos = 0) const; //查找字符c最后一次出現位置
- string& replace(int pos,int a,const string& str); //替換從pos開始n個字符為字符串str
- string& replace(int pos,int a,const char* s); //替換從pos開始的n個字符為字符串s
-
示例:
#include<iostream> using namespace std; #include<string> //string字符串查找和替換 //1、查找 void test01() { string str1 = "abcdefgde"; //第一種方法 從頭開始查找字符c在當前字符串的位置 int pos = str1.find("de"); //通常要判斷一下 if (pos == -1) //未找到返回值為-1 pos值如果為-1 代表未找到 { cout << "未找到字符串" << endl; } else { cout << "第一種方法 pos = " << pos << endl; //返回第一次出現的下標位置 } //第一種方法find與第五種方法rfind //區別:rfind從右往左查找 find從左往右查找 pos = str1.rfind("de"); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第五種方法 pos = " << pos << endl; } //第二種方法 從pos開始查找字符串s在當前串中的位置 pos = str1.find("f", 0); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第二種方法 pos = " << pos << endl; } //第二種方法 從pos開始查找字符串s在當前串中的位置 pos = str1.find("f", 2); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第二種方法 pos = " << pos << endl; } //第三種方法 //從str1元素下標0的位置開始查找字符串"fgsk"前2位字符在當前串中的位置 pos = str1.find("fgsk", 0, 2); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第三種方法 pos = " << pos << endl; } //第四種方法 從pos開始查找字符c在當前串中第一次出現的位置 pos = str1.find('d'); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第四種方法 pos = " << pos << endl; } //第六種方法 元素下標【5】開始從右往左查找最后一次的位置 pos = str1.rfind("d", 5); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第六種方法 pos = " << pos << endl; } //第六種方法 第二次查找 從右往左查找第一個字符【d】 pos = str1.rfind("d"); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第六種方法 pos = " << pos << endl; } //第七種方法 查找字符串的前兩個字符 從最后一次位置開始 str1.size()表示最后一次位置 pos = str1.rfind("defg", str1.size(), 2); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第七種方法 pos = " << pos << endl; } //第八種方法 從右往左查找字符第一次出現的位置 str1.size()表示最后一個元素下標位置 pos = str1.rfind('d',str1.size()); if (pos == -1) { cout << "未找到字符串" << endl; } else { cout << "第八種方法 pos = " << pos << endl; } } //2、替換 void test02() { //第九種方法 替換 string str1 = "abcdefgde"; //從元素下標【1】開始,往后3個字符替換為“1234” str1.replace(1, 3, "1234"); if (str1.size()>1) { cout << "第九種方法 替換 str1 = " << str1 << endl; } else { cout << "字符串元素下標小于1,無法完成替換" << endl; } //第十種方法 替換 str1.replace(2, 4, "567890"); if (str1.size() > 1) { cout << "第九種方法 替換 str1 = " << str1 << endl; } else { cout << "字符串元素下標小于1,無法完成替換" << endl; } } int main() { //測試 test01(); test02(); system("pause"); return 0; }運行結果
第一種方法 pos = 3 第五種方法 pos = 7 第二種方法 pos = 5 第二種方法 pos = 5 第三種方法 pos = 5 第四種方法 pos = 3 第六種方法 pos = 3 第六種方法 pos = 7 第七種方法 pos = 7 第八種方法 pos = 7 第九種方法 替換 str1 = a1234efgde 第九種方法 替換 str1 = a1567890fgde -
總結:
- find查找是從左往右,rfind是從右往左
- find找到字符串后返回查找的第一個字符位置,找不到返回-1
- replace在替換時,要指定從哪個位置起,多少個字符,替換成什么樣的字符串
string字符串比較
-
字符串之間的比較
-
比較方式:
- 字符串比較是按照字符的ASCII碼進行對比
- = 返回 0
- > 返回 1
- < 返回 -1
-
函數原型:
- int compare(const string &s) const; //與字符串s比較
- int compare(const char *s) const; //與字符串s比較
-
示例:
#include<iostream> using namespace std; #include<string> //string字符串比較 void test01() { string str1 = "hello"; string str2 = "xello"; if (str1.compare(str2) == 0) { cout << "str1 = str2" << endl; } else if (str1.compare(str2) > 0) { cout << "strl > str2" << endl; } else { cout << "str1 < str2" << endl; } } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
str1 < str2 -
總結:字符串對比主要是用于比較兩個字符串是否相等,判斷誰大誰小的意義并不是很大
string字符存取
-
string中單個字符存取方式有兩種
- char& operator[](int n); //通過[]方式獲取字符
- char& at(int n); //通過at方法獲取字符
-
示例:
#include<iostream> using namespace std; #include<string> //string字符存取 void test01() { string str = "hello"; cout << "str = " << str << endl << endl; //1、通過 [] 訪問單個字符 for (int i = 0; i < str.size(); i++) { cout << str[i] << " "; } cout << endl << endl; //2、通過at方式訪問單個字符 for (int i = 0; i < str.size(); i++) { cout << str.at(i) << " "; } cout << endl << endl; //修改單個字符 str[0] = 'x'; cout << "str = " << str << endl << endl; str.at(1) = 'x'; cout << "str = " << str << endl << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
str = hello h e l l o h e l l o str = xello str = xxllo -
總結:string字符串中單個字符存取有兩種方式,利用[]或者at
string插入和刪除
-
作用:對string字符串進行插入和刪除字符操作
-
函數原型:
- string& insert(int pos,const char* s); //插入字符串
- string& insert(int pos,const string* str); //插入字符串
- string& insert(int pos,int n, char c); //在指定位置插入n個字符c
- string& erase(int pos,int n = npos); //刪除從pos開始的n個字符
-
示例:
#include<iostream> using namespace std; #include<string> //string字符串插入和刪除 void test01() { string str = "hello"; //插入 str.insert(1, "222"); cout << str << endl; str.insert(4, 3, '6'); cout << str << endl; //刪除 str.erase(1, 6); cout << str << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
h222ello h222666ello hello -
總結:插入和刪除的起始下標都是從0開始
string子串
-
作用:從字符串中獲取想要的子串
-
函數原型:
- string substr(int pos = 0;int n = npos) const; //返回由pos開始的n個字符組成的字符串
-
示例:
#include<iostream> using namespace std; #include<string> //string字符串子串 void test01() { string str = "abcdef"; string subStr = str.substr(1, 3); cout << "subStr = " << subStr << endl; } //實用操作 void test02() { string email = "zhangsan@sina.com"; //從郵件地址中 獲取 用戶名信息 //email_name = email從0開始截取到email.rfind('@')-1的字符串 email.rfind('@')是指從右往左查找 @ 字符并返回下標 string email_name = email.substr(0, email.rfind('@')); cout << "email_name = " << email_name << endl; } int main() { //測試 test01(); test02(); system("pause"); return 0; }運行結果:
subStr = bcd email_name = zhangsan
vector容器
vector基本概念
- 作用:vector數據結構和數組非常相似,也成為單端數組
- vector與普通數組區別:
- 不同之處在于數組是靜態空間,而vector可以動態擴展
- 動態擴展:
- 并不是在原空間之后續接新空間,而是找更大的內存空間,然后將原數據拷貝新空間,釋放原空間
- vector容器的迭代器是支持隨機訪問的迭代器
vector構造函數
-
作用:創建vector容器
-
函數原型:
- vector
v; //采用模板實現類實現,默認構造函數 - vector(v.begin(),v.end()); //將v[begin(),end()]區間中的元素拷貝給本身
- vector(n,elem); //溝站函數將n個elem拷貝給本身
- vector(const vector &vec); //拷貝構造函數
- vector
-
示例:
#include<iostream> using namespace std; #include<string> #include<vector> //vector容器構造 //打印函數 void printVevtor(vector<int> &v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test01() { vector<int> v1;//默認構造 無參構造 for (int i = 0; i < 10; i++) { v1.push_back(i); } printVevtor(v1); //通過區間方式進行構造 vector<int>v2(v1.begin(), v1.end()); printVevtor(v2); //n個elem方式構造 創建10個100的初始化操作 vector<int>v3(10, 100); printVevtor(v3); //拷貝構造 vector<int>v4(v3); printVevtor(v4); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100
vector賦值操作
-
作用:給vector容器進行賦值
-
函數模型
- vector& operator=(const vector&vec); //重載等號操作符
- assign(beg,end); //將[beg,end]區間中的數據拷貝賦值給本身
- assign(n,elem); //將n個elem拷貝賦值給本身
-
示例:
#include<iostream> using namespace std; #include<string> #include<vector> //vector賦值操作 //打印函數 void printVevtor(vector<int> &v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test01() { vector<int> v1;//默認構造 無參構造 for (int i = 0; i < 10; i++) { v1.push_back(i); } printVevtor(v1); //賦值 operator= vector <int>v2; v2 = v1; printVevtor(v2); //assing vector<int>v3; v3.assign(v1.begin(), v1.end()); printVevtor(v3); //n個elem 方式賦值 vector<int>v4; v4.assign(10, 100); printVevtor(v4); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 100 100 100 100 100 100 100 100 100 100 -
總結:vector賦值方式比較簡單,使用operator=或者assign都可以
vector容量和大小
-
作用:對vector容器的容量和大小操作
-
函數原型:
- empty(); //判斷容器是否為空
- capacity(); //容器的容量
- size(); //返回容器中元素的個數
- resize(int num); //重新指定容器的長度為num,若容器變長,則以默認值填充新位置。
//如果容器變短,則末尾超出容器長度的元素被刪除 - resize(int num,elem); //重新指定容器的長度為num,若容器變長,則以elem值填充新位置。
//如果容器變短,則末尾超出容器長度的元素被刪除
-
示例:
#include<iostream> using namespace std; #include<string> #include<vector> //vector容器的容量和大小操作 //打印函數 void printVevtor(vector<int> &v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test01() { vector<int> v1;//默認構造 無參構造 for (int i = 0; i < 10; i++) { v1.push_back(i); } printVevtor(v1); //判斷容器是否為空 if (v1.empty()) //為真, 代表容器為空 { cout << "v1為空" << endl; } else { cout << "v1不為空" << endl; cout << "v1的容量為:" << v1.capacity() << endl; cout << "v1的長度為:" << v1.size() << endl; } //重新指定大小 v1.resize(15,20); printVevtor(v1); //重新指定大小 v1.resize(5); printVevtor(v1); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 5 6 7 8 9 v1不為空 v1的容量為:13 v1的長度為:10 0 1 2 3 4 5 6 7 8 9 20 20 20 20 20 0 1 2 3 4 -
總結:
- 判斷是否為空 --- empty
- 返回元素個數 --- size
- 返回容器容量 --- capacity
- 重新指定大小 --- resize
vector插入和刪除
-
作用:對vector容器進行插入、刪除操作
-
函數原型:
- push_back(ele); //尾部插入元素ele
- pop_back(); //刪除最后一個元素
- insert(const_iterator pos,ele); //迭代器指向位置pos插入元素ele
- insert(const_iterator pos,int count,ele); //迭代器指向位置pos插入count個元素ele
- erase(const_iterator pos); //刪除迭代器指向的元素
- erase(const_iterator start,const_iterator end); //刪除迭代器從start到end之間的元素
- clear(); //刪除容器中所有元素
-
示例:
#include<iostream> using namespace std; #include<string> #include<vector> //vector容器的容量和大小操作 //打印函數 void printVevtor(vector<int> &v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test01() { vector<int> v1;//默認構造 無參構造 //尾插法 v1.push_back(10); v1.push_back(20); v1.push_back(30); v1.push_back(40); v1.push_back(50); //遍歷 printVevtor(v1); //尾刪法 v1.pop_back(); printVevtor(v1); //插入 第一個參數是迭代器 begin() v1.insert(v1.begin(), 100); printVevtor(v1); //插入位置 重載版本 v1.insert(v1.begin(), 2, 1000); printVevtor(v1); //刪除 第一個參數是迭代器 v1.erase(v1.begin()); printVevtor(v1); //刪除位置 重載版本 清空 == v1.clear(); v1.erase(v1.begin(), v1.end()); printVevtor(v1); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
10 20 30 40 50 10 20 30 40 100 10 20 30 40 1000 1000 100 10 20 30 40 1000 100 10 20 30 40 [endl] //因為最后一次輸出清楚了vector容器中元素,所以只產生了endl換行 -
總結:
尾插 --- push_back
尾刪 --- pop_back
插入 --- insert (位置迭代器)
刪除 --- erase (位置迭代器)
清空 --- clear
vector數據存取
-
作用:對vector中的數據的存取操作
-
函數原型:
- at(int idx); //返回索引|idx所指的數據
- operator[]; //返回索引|idx所指的數據
- front(); //返回容器中第一個數據元素
- back(); //返回容器中最后一個數據元素
-
示例:
#include<iostream> using namespace std; #include<string> #include<vector> //vector容器 數據存取 //打印函數 void printVevtor(vector<int> &v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } void test01() { vector<int> v1;//默認構造 無參構造 //插入數據 for (int i = 0; i < 10; i++) { v1.push_back(i); } //利用函數訪問數組中元素 printVevtor(v1); //利用[ ]中括號方式訪問數組中元素 for (int i = 0; i < v1.size(); i++) { cout << v1[i] << " "; } cout << endl; //利用at方式訪問元素 for (int i = 0; i < v1.size(); i++) { cout << v1.at(i) << " "; } cout << endl; //獲取第一個元素 cout << "第一個元素為:" << v1.front() << endl; //獲取最后一個元素 cout << "最后一個元素為:" << v1.back() << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 第一個元素為:0 最后一個元素為:9 -
總結:
- 除了用迭代器獲取vector容器中元素,[]中括號和at也可以
- frony返回容器第一個元素
- back返回容器最后一個元素
vector互換容器
-
作用:
- 實現兩個容器內元素進行互換
-
函數原型
- swap(vec); //將vec與本身的元素互換
-
示例:
#include<iostream> using namespace std; #include<string> #include<vector> //vector容器互換 //打印函數 void printVevtor(vector<int> &v) { for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; } //基本使用 void test01() { vector<int> v1;//默認構造 無參構造 //插入數據 for循環 for (int i = 0; i < 10; i++) { v1.push_back(i); } printVevtor(v1); vector<int>v2; //插入數據 for循環 for (int i = 10; i > 0; i--) { v2.push_back(i); } printVevtor(v2); cout << "交換后:" << endl; //交換數據 v1.swap(v2); printVevtor(v1); printVevtor(v2); } //實際使用 //巧用swap可以收縮內存空間 void test02() { vector<int>v; for (int i = 0; i < 100000; i++) { v.push_back(i); } cout << endl; cout << "v的容量為:" << v.capacity() << endl; cout << "v的元素長度為:" << v.size() << endl; cout << endl; v.resize(3); //重新指定元素長度 cout << "v的容量為:" << v.capacity() << endl; cout << "v的元素長度為:" << v.size() << endl; cout << endl; //巧用swap收縮內存 //vector<int>(v)為匿名對象 創建一個新的容器 //利用swap做容器交換 vector<int>(v).swap(v); cout << "v的容量為:" << v.capacity() << endl; cout << "v的元素長度為:" << v.size() << endl; cout << endl; } int main() { //測試 test01(); test02(); system("pause"); return 0; }運行結果:
0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1 交換后: 10 9 8 7 6 5 4 3 2 1 0 1 2 3 4 5 6 7 8 9 v的容量為:138255 v的元素長度為:100000 v的容量為:138255 v的元素長度為:3 v的容量為:3 v的元素長度為:3 -
總結:swap可以使兩個容器互換,可以達到實用的收縮內存效果
vector預留空間
-
作用:減少vector在動態擴展容量時的擴展次數
-
函數原型:
- reserve(int len); //容器預留len個元素長度,預留位置不初始化,元素不可訪問
-
示例:
#include<iostream> using namespace std; #include<string> #include<vector> //vector容器 預留空間 void test01() { vector<int> v1;//默認構造 無參構造 //利用reserve預留空間 v1.reserve(100000); //統計開辟次數 int num = 0; //指針 int* p = NULL; //插入數據 for循環 for (int i = 0; i < 100000; i++) { v1.push_back(i); //如果p指向不是v1[0]的地址,則p指向v1[0]的地址,開辟次數+1 //因為開辟的空間不夠時,vector會重新開辟一塊更大的空間去存儲數據 if (p != &v1[0]) { //p指向v1[0]的地址 p = &v1[0]; num++; } } cout << "開辟次數 num = " << num << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
開辟次數 num = 1 -
總結:如果數據量較大,可以一開始利用reserve預留空間
deque容器
- 功能:雙端數組,可以對頭端進行插入刪除操作
- deque與vector區別:
- vector對于頭部的插入刪除效率低,數據量越大,效率越低
- deque相對而言,對頭部的插入刪除速度會比vector快
- vector訪問元素時的速度會比deque快,這和兩者內部實現有關
- deque內部工作原理:
- deque內部有個中控器,維護每段緩沖區中的內容,緩沖區中存放真實數據
- 中控器維護的是每個緩沖區的地址,使得使用deque時像一片連續的內存空間
- deque容器的迭代器也是支持隨機訪問的
deque構造函數
-
函數原型:
- deque
deqT; //默認構造形式 - deque(beg,end); //構造函數將[beg,end]區間中的元素拷貝給本身
- deque(n,elem); //構造函數將n個elem拷貝給自身
- deque(const deque &deq); //拷貝構造函數
- deque
-
示例:
#include<iostream> using namespace std; #include<string> #include<deque> //deque 構造函數 //打印函數 void printDeque(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //容器中的數據不可以修改了 //*it = 100; //傳參處加const并且 迭代器前也加const 作用只讀不可寫 cout << *it << " "; } cout << endl; } void test01() { deque<int>d1; //插入數據 for (int i = 0; i < 10; i++) { d1.push_back(i); } //遍歷容器 printDeque(d1); //區間方式賦值 deque<int>d2(d1.begin(), d1.end()); printDeque(d2); //n個elem方式構造 創建10個100的初始化操作 deque<int>d3(10, 100); printDeque(d3); //拷貝構造 deque<int>d4(d3); printDeque(d4); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 -
總結:deque容器和vector容器的構造方式幾乎一致,靈活使用即可
deque賦值操作
-
作用:給deque容器進行賦值
-
函數原型:
- deque& operator=(const deque &deq); //重載等號操作符
- assign(beg,end); //將[beg,end]區間中的數據拷貝賦值給本身
- assing(n,elem); //將n個elem拷貝賦值給本身
-
示例:
#include<iostream> using namespace std; #include<string> #include<deque> //deque容器賦值操作 //打印函數 void printDeque(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //容器中的數據不可以修改了 //*it = 100; //傳參處加const并且 迭代器前也加const 作用只讀不可寫 cout << *it << " "; } cout << endl; } void test01() { deque<int>d1; //插入數據 for (int i = 0; i < 10; i++) { d1.push_back(i); } // operator= 賦值 deque<int>d2; d2 = d1; printDeque(d2); //assign 賦值 deque<int>d3; d3.assign(d1.begin(), d1.end()); printDeque(d3); ////將n個elem拷貝賦值給本身 deque<int>d4; d4.assign(10, 100); printDeque(d4); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 100 100 100 100 100 100 100 100 100 100 -
deque賦值操作與vector相同
deque大小操作
-
作用:對deque容器的大小進行操作
-
函數原型:
- deque.empty(); //判斷容器是否為空
- deque.size(); //返回容器中元素的個數
- deque.resize(num); //重新指定容器的長度為num,若容器變長,則以默認值填充新位置。
//如果容器變短,則末尾超出容器長度的元素被刪除 - deque.resize(num,elem); //重新指定容器的長度為num,若容器變長,則以elem值填充新位置。
//如果容器變短,則末尾超出容器長度的元素被刪除
-
示例:
#include<iostream> using namespace std; #include<string> #include<deque> //deque容器 大小操作 //打印函數 void printDeque(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //容器中的數據不可以修改了 //*it = 100; //傳參處加const并且 迭代器前也加const 作用只讀不可寫 cout << *it << " "; } cout << endl; } void test01() { deque<int>d1; //插入數據 for (int i = 0; i < 10; i++) { d1.push_back(i); } //判斷deque容器是否為空 if (d1.empty()) { cout << "d1為空" << endl; } else { cout << "d1不為空" << endl; cout << "d1的大小為:" << d1.size() << endl; //deque容器沒有容量概念 } //重新指定大小 //d1.resize(15); //重載版本 指定填充數據 d1.resize(15, 1); printDeque(d1); d1.resize(5); printDeque(d1); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
d1不為空 d1的大小為:10 0 1 2 3 4 5 6 7 8 9 1 1 1 1 1 0 1 2 3 4 -
總結:
- deque沒有容量的概念
- 判斷是否為空 --- empty
- 返回元素個數 --- size
- 重新指定個數 --- resize
deque插入和刪除
-
作用:向deque容器中插入和刪除數據
-
函數原型:
兩端插入操作:- push_back(elem); //在容器尾部添加一個數據
- push_front(elem); //在容器頭部插入一個數據
- pop_back(); //刪除容器最后一個數據
- pop_front(); //刪除容器第一個數據
指定位置操作: - insert(pos,elem); //在pos位置插入一個elem元素的拷貝,返回新數據的位置
- insert(pos,n,elem); //在pos位置插入n個elem數據,無返回值
- insert(poe,beg,end); //在pos位置插入[beg,end]區間數據,無返回值
- clear(); //清空容器所有數據
- erase(beg,end); //刪除[beg,end]區間數據,返回下一個數據的位置
- erase(pos); //刪除pos位置的數據,返回下一個數據的位置
-
示例:
#include<iostream> using namespace std; #include<string> #include<deque> //deque容器 大小操作 //打印函數 void printDeque(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //容器中的數據不可以修改了 //*it = 100; //傳參處加const并且 迭代器前也加const 作用只讀不可寫 cout << *it << " "; } cout << endl; } void test01() { deque<int>d1; //尾部插入數據 for (int i = 0; i < 5; i++) { d1.push_back(i); } printDeque(d1); //頭部插入數據 for (int i = 0; i < 5; i++) { d1.push_front(i+10); } printDeque(d1); //尾刪 d1.pop_back(); printDeque(d1); //頭刪 d1.pop_front(); printDeque(d1); cout << endl; } void test02() { deque<int>d1; d1.push_back(10); d1.push_back(20); d1.push_front(100); d1.push_front(200); printDeque(d1); //insert插入 d1.insert(d1.begin(), 1000); //頭部插入一個1000 printDeque(d1); //insert重載 d1.insert(d1.begin(), 2, 100); //頭部插入兩個100 printDeque(d1); //按照區間方式插入 deque<int>d2; d2.push_back(1); d2.push_back(2); d2.push_back(3); //1 2 3 100 100 1000 200 100 10 20 d1.insert(d1.begin(), d2.begin(), d2.end()); printDeque(d1); cout << endl; } void test03() { deque<int>d1; d1.push_back(10); d1.push_back(20); d1.push_front(100); d1.push_front(200); //刪除 deque<int>::iterator it = d1.begin(); it++; //頭部向右偏移刪除 d1.erase(it); printDeque(d1); //按照區間方式刪除 //clear與區間頭尾刪除方式一樣 都是清空數據。 //數據清空 最后打印出來一個換行 d1.clear(); d1.erase(d1.begin(), d1.end()); printDeque(d1); } int main() { //測試 test01(); test02(); test03(); system("pause"); return 0; }運行結果:
0 1 2 3 4 14 13 12 11 10 0 1 2 3 4 14 13 12 11 10 0 1 2 3 13 12 11 10 0 1 2 3 200 100 10 20 1000 200 100 10 20 100 100 1000 200 100 10 20 1 2 3 100 100 1000 200 100 10 20 200 10 20 //最后因為清空了數據 只打印出來一個換行 -
總結:
- 插入和刪除提供的位置是迭代器!
- 尾插 --- push_back
- 尾刪 --- pop_back
- 頭插 --- push_front
- 頭刪 --- pop_front
deque數據存取
-
作用:對deque中的數據的存儲操作
-
函數原型:
- at(int idx); //返回索引|idx所指的數據
- operator[]; //返回索引|idx所指的數據
- front(); //返回容器中第一個數據元素
- back(); //返回容器中最后一個數據元素
-
示例:
#include<iostream> using namespace std; #include<string> #include<deque> //deque容器 大小操作 //打印函數 void printDeque(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //容器中的數據不可以修改了 //*it = 100; //傳參處加const并且 迭代器前也加const 作用只讀不可寫 cout << *it << " "; } cout << endl; } void test01() { deque<int>d1; //尾部插入數據 for (int i = 0; i < 5; i++) { d1.push_back(i); } printDeque(d1); //通過[]中括號方式訪問元素 for (int i = 0; i < d1.size(); i++) { cout << d1[i] << " "; } cout << endl; //通過at方式訪問元素 for (int i = 0; i < d1.size(); i++) { cout << d1.at(i) << " "; } cout << endl; //訪問頭尾元素 cout << "第一個元素為;" << d1.front() << endl; cout << "最后一個元素:" << d1.back() << endl; } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 第一個元素為;0 最后一個元素:4 -
總結:
- 除了用迭代器獲取deque容器中元素,[]和at也可以
- front返回容器第一個元素
- back返回容器最后一個元素
deque排序
-
作用:利用算法實現對deque容器進行排序
-
算法:
- sort(iterator beg,iterator end) //對beg和end區間內元素進行排序
-
示例:
#include<iostream> using namespace std; #include<string> #include<deque> #include<algorithm> //標準算法頭文件 //deque容器 大小操作 //打印函數 void printDeque(const deque<int>& d) { for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) { //容器中的數據不可以修改了 //*it = 100; //傳參處加const并且 迭代器前也加const 作用只讀不可寫 cout << *it << " "; } cout << endl; } void test01() { deque<int>d1; //尾部插入數據 for (int i = 0; i < 5; i++) { d1.push_back(i); } printDeque(d1); //頭部插入數據 for (int i = 0; i < 5; i++) { d1.push_front(i); } printDeque(d1); //排序 默認排序規則 從小到達 升序 //對于支持隨機訪問的迭代器的容器,都可以利用 sort 算法直接對其進行排序 //vector容器也可以利用 sort 進行排序 sort(d1.begin(), d1.end()); cout << "排序后:" << endl; printDeque(d1); } int main() { //測試 test01(); system("pause"); return 0; }運行結果:
0 1 2 3 4 4 3 2 1 0 0 1 2 3 4 排序后: 0 0 1 1 2 2 3 3 4 4 -
總結:sort算法非常實用,使用時包含頭文件 algorithm 即可
案例-評委打分
-
案例描述:
- 有5名選手:選手ABCDE,10個評委分別對每一個選手打分,去除最高分,去除最低分,取平均分
-
實現步驟:
- 創建五名選手,放到vector中
- 遍歷vector容器,取出來每一個選手,執行for循環,可以把10個評分打分存到deque容器中
- sort算法對deque容器中分數排序,去除最高喝最低分
- deque容器遍歷一遍,累加總分
- 獲取平均分
-
示例:
#include<iostream> using namespace std; #include<string> #include<deque> #include<algorithm> //標準算法頭文件 #include<vector> #include<ctime> //案例 評委打分 //選手類 class Person { public: Person(string name, int score) { this->m_Name = name; this->m_Score = score; } string m_Name; //姓名 int m_Score; //平均分 }; void createPerson(vector<Person>&v) { for (int i = 0; i < 5; i++) { string nameSeed = "ABCDE"; string name = "選手"; name += nameSeed[i]; int score = 0; Person p(name, score); //將創建的Person對象 放入到容器中 v.push_back(p); } } //打分 void setScore(vector<Person>& v) { for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { //將評委的分數 放入到deque容器中 deque<int>d; for (int i = 0; i < 10; i++) { int score = rand() % 41 + 60; d.push_back(score); } //排序 sort(d.begin(),d.end()); //去除最高和最低分 d.pop_back(); d.pop_front(); //取平均分 int sum = 0; for (deque<int>::iterator it = d.begin(); it != d.end(); it++) { sum += *it; //累加每個評委的分數 } //求平均分 int avg = sum / d.size(); //將平均分 賦值給選手身上 it->m_Score = avg; } } //遍歷顯示 void showScore(vector<Person>&v) { for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) { cout << "選手:" << it->m_Name << "平均分:" << it->m_Score << endl; } } int main() { //隨機數種子 srand((unsigned int)time(NULL)); //1、創建5名選手 //存放選手的容器 vector<Person>v; createPerson(v); //2、給5名選手打分 setScore(v); //3、顯示最后得分 showScore(v); system("pause"); return 0; }運行結果:
//因為加入了隨機數 所以分數隨機 每次都不一樣 選手:選手A平均分:81 選手:選手B平均分:85 選手:選手C平均分:72 選手:選手D平均分:82 選手:選手E平均分:88



浙公網安備 33010602011771號