C++笔记汇总
C++基礎
本文是在學習B站黑馬C++課程時記錄。另有部分知識點為海賊班胡船長所教授。在此表達對他們的誠摯的感謝。
1、C++的左值右值:
首先了解引用的概念:引用是一個變量,它引用其他變量的 內存位置。
int x = 34 ; int &IRef = x;在代碼中,IRef 就是一個引用。在聲明中,引用是通 & 符號來修飾的。它出現在類型與變量的標識符之間,這種類型的引用稱為左值引用。
左值:可以看作是一個關聯了名稱的內存位置,允許程序的其他部分來訪問它。名稱:任何可用于訪問內存位置的表達式。“指向指定的內存,以變量形式存在” “相當于容器”
右值:右值是一個臨時值,它不能被程序的其他部分訪問?!安恢赶蛉魏蔚胤健?“相當于容器內的事物”
int x = 0; x = 5; x = sqrt(5);x是一個左值,這是因為x代表一個內存位置,它可以被程序的其他部分訪問。
表達式sqrt(5)時一個右值,因為它代表了一個由編譯器創建的臨時內存位置,以保存由函數返回的值。該內存位置僅被訪問一次。在此之后它就會 立即被刪除,再也不能被訪問。
C++11右值引用的概念:表示一個本應該沒有名稱的臨時對象。右值引用的聲明與左值引用類似,但是它使用的是2個&符號&&。臨時對象最多可以有一個左值引用指向它。
int && rRef = sqrt(5); cout << rRedf2、C++中常量的定義:
兩種形式:
使用宏定義
#define Day 7 // 注意不要加分號使用const修飾符
const int Day = 7; // 注意要加分號3、實數
小數默認是 雙精度double類型,因此在設置float類型時候,常在數字末尾添加f
float x = 3.14f;精度
| 字節 | 4 | 7 |
| 有效位數 | 8 | 15~16 |
科學計數法
float f = 3e34、sizeof()
可以查看占用內存空間
5、字符
字符要用單引號。字符占用1個字節
轉意字符: \a 警報 \t \n ? \"
6、字符串
C語言風格:注意變量名后面加中括號[],要用雙引號。
char 變量名[] = "...hellO"C++風格string:
#include <string> String 變量名 = "hello world"7、布爾類型
bool flag = true; //注意全小寫,與python不同 bool key = false;8、邏輯運算符
運算符:
非 ! , 與 && , 或 ||
9、 程序流程結構
順序結構、選擇結構、循環結構
if語句:注意與python的不同,條件表達式在括號內,且不加:。
三目運算符: 返回值的可以是變量,即左值
//條件表達式1?表達式2:表達式3 int a = 0; int b = 20; (a < b ? a: b) = 100; cout << a << b << endl;switch語句:注意在case后面使用break
switch(表達式) {case 結果1: // 注意類型,如果“結果1”是整型,傳入字符型的會導致出錯。cout<< "best" << endl; // 注意不加中括號break;case 結果2:cout<< "good" << endl;break;default:cout<< "bad" << endl;break; }優點:結構清晰,執行效率高
缺點:判斷時只能時整型或者字符型,不能判斷一個區間。
for語句:循環。注意C++中基于范圍的for循環如何使用。
while語句:循環
do while語句:先執行一次循環語句,然后再判斷循環條件
10、關于數字的個位數、十位數計算
案例描述:計算三位數字的個位、十位、百位數計算:
int x = rand()%1000 + 1 int a, b, c; a = x % 10; b = x /10 % 10; //先出發 后取模 c = x /100;11、跳轉語句
break, continue:跳出當前循環
goto語句:
FLAG: //標記定義 cout << "jump to here"<< endl; goto FLAG; //跳轉到FLAG位置12、數組
數組定義的三種形式
數據類型 數組名 [數組長度];
數據類型 數組名 [數組長度] = {值1, 值2 ...};
數據類型 數組名 [ ] = {值1, 值2 ...}
數組的輸出:使用for循環進行輸出。
sizeof(arr):可以統計整個數組在內存中的長度
數組名是一個常量不能進行復制操作。
13、 隨機數種子
srand(1); int x = rand() % 100 + 1;C++ 函數
1、函數的定義
函數返回值 函數名 (參數列表){//函數體語句return 表達式; }值傳遞: 就是函數調用時實參將 數值 傳入給形參。如果形參發生改變,并不回影響實參。
函數聲明:聲明可以有多次,定義只能有一次。
2、函數的分文件編寫
作用:讓代碼更清晰
頭文件 .h 中寫 函數 的聲明
頭文件中不能寫函數的定義,以防止重定義。
但是頭文件中可以寫 類 的定義。
在源文件 .cpp 或 .cc 中寫 函數 的定義
正確理解頭文件:#include "***.h"頭文件不進行編譯,而是在預編譯階段,將***.h的 內容 拷貝到引用位置,然后進行源文件的編譯。
3、內聯函數 inline
內聯函數在聲明時,使用關鍵字inline
內聯的意義:在編譯時,讓編譯器用該部分語句去替換函數表達式,節省了形參傳遞。
注意事項:
C++ 指針
1、定義和使用指針
// 數據類型 *指針變量名; int a = 10; int *p = &a; // 將a的地址復制給指針p cout <<"a的地址是:"<< &a << endl; cout <<"指針p是:" << p << endl; // 指針前加*, 代表解引用。 cout << "*p的值是:" << *p <<endl; *p = 1000; cout << "*p的值是:" << *p <<endl;2、指針的大小
32位系統:指針占4個字節空間大小。0x0000
64位系統:占8個字節空間大小
3、空指針和野指針
空指針nullptr:用來給指針變量進行初始化。并且空指針是不可以訪問的。
// 空指針的幾種形式 int *p = nullptr; int *p = 0; inr *p = NULL;野指針:指向沒有訪問權限的位置的指針。
int *p = (int*)0x1100;4、const修飾指針
const修飾指針的三種情況:
常量指針 — const int *指針名 又名 指向常量的指針
特點:指針的指向可以修改,但是指針指向的地址的值不可以修改。
// 常量指針 int a = 10, b = 20; const int *p = &a; // 常量指針的定義 *p = &b; // 正確,指針的指向可以修改 *p = 100; // 錯誤,指針指向的值不可以被修改指針常量 — int * const p
特點:指針的指向不可以修改,但是指針指向的值可以修改
// 指針常量 int * const p = &a;const即修飾指針,又修飾常量
const int* const p
特點:指針的指向和指針指向的值都不可以被修改
5、指針和函數
地址傳遞,可以修改實參。
C++結構體
結構體屬于用戶自定義的數據類型,允許用戶存儲不同的基礎數據類型。
建議:將結構體的定義放在頭文件中
1、結構體的定義和使用
語法: struct 結構體名 {結構體成員列表};
通過結構體創建變量的方式有三種:
struct 結構體名 變量名
struct 結構體名 變量名 = {成員1值, 成員2值}
定義結構體時順便創建變量
struct關鍵字在創建變量時可以省略。
#include<string> // 結構體定義 // 創建學生數據類型:學生(姓名, 年齡,分數) struct Student{// 成員列表string name;int age;int score; }s3; // 3 定義結構體時順便創建變量 //另外注意結構體后面要使用分號// 創建結構體類型變量 int main(){// 1 struct 結構體名 變量名struct Student s1; // struct關鍵字在創建變量時可以省略// 給s1屬性復制。通過點“.”操作符訪問結構體變量中的屬性s1.name = "東七七";s1.age = 21;s2.score = 77;// 2 struct 結構體名 變量名 = {成員1值, 成員2值}struct Student s2 = {"James", 34, 91} }2、結構體數組
作用: 將自定義的結構體放入到數組中方便維護
語法: struct 結構體名 數組名[元素大小] = { {},...,{} };
struct Student stuArray[10]={{"James", 34, 91},...}3、結構體指針
作用:通過指針訪問結構體中的成員
利用操作符 -> 可以通過結構體指針訪問結構體屬性
struct Student *p = &s1; p->name = "Alike"; p->age = 42;4、 嵌套結構體
作用: 結構體中的成員可以是另一個結構體
例如:每一個老師輔導一個學員, 一個老師的結構體中,記錄一個學生的結構體
// 學生結構體定義 struct Student{string name;int age;int score; } // 教師結構體定義 struct Teacher{int id;string name;int age;struct Student stu; }5、結構體做函數參數
作用:將結構體作為參數傳入
傳遞方式: 值傳遞,地址傳遞
// 值傳遞 void printStuInfo(Student stu){// 函數體 } // 地址傳遞 void printStuInfo_ptr(const Student *ptr){ //定義常量指針,使得ptr指向的值不可以被修改//函數體ptr->name; // 注意要用->訪問結構體成員 }6、結構體中const的使用
作用:用const修飾防止被錯誤修改
void printStuInfo_ptr(const Student *ptr){//函數體 ptr->name; // 注意要用->訪問結構體成員 }C++核心編程
1、內存分區模型
C++程序在執行時, 將內存大方向劃分為4個區域
代碼區:存放函數體的二進制代碼,由操作系統進行管理
全局區: 存放全局變量和靜態變量以及常量
棧區: 由編譯器自動分配釋放,存放函數的參數值,局部變量等
堆區:由程序員分配和釋放,若程序員不釋放,程序結束時由操作系統回收
內存四區的意義:
不同區域存放的數據,賦予不同的生命周期,給我們更大的靈活編程。
程序運行前:
在程序編譯后,生成了exe可執行程序,未執行該程序前分為兩個分區
代碼區:
? a. 存放CPU執行的機器指令
? b. 代碼區是 共享 的,目的是對于頻繁被執行的程序,只需要在內存中有一份代碼即可
? c. 代碼區是 只讀 的,目的是防止程序意外地修改了它的指令
全局區:
? a. 存放全局變量(注意是在main函數之外定義的)和靜態變量
? b. 全局區還包含了常量區,字符串常量和其他常量也存放在此
? c. 該區域的數據在程序結束后由操作系統釋放
? d. 局部變量、const修飾的局部變量(局部常量) 不在全局區,而在棧區
// 查看局部變量和全局變量的地址 // 全局變量 int g_a = 10; int g_b = 10;int main(){int a = 10;int b = 10:// 打印地址會發現, a、b的地址在一個區間內 g_a、g_b的地址在另一個區間內 }程序運行后:
棧區:
由編譯器自動分配釋放,存放函數的參數值、局部變量等。
注意事項:不要返回局部變量的地址,棧區開辟的數據由編譯器自動釋放。
int * func() //形參數據也會放到棧區 {int a = 10; // 棧區的數據在函數執行完自動釋放。return &a; // 有錯誤,但能通過編譯。返回了局部變量地址。 } int main(){int *p = func(); //接收func()的地址cout << *p << endl; // 第一次沒問題,因為編譯器做了保留cout << *p << endl; // 第二次出現亂碼 }堆區:
由程序員分配釋放,若程序員不釋放,程序結束時由操作系統回收。
在C++中主要利用new來開辟堆區內存,利用delete釋放
int *func(){// 利用new關鍵字 可以將數據開辟到堆區// 指針 本質也是局部變量, 放在棧上,指針保存的數據時放在堆區int *p = new int(10);return p; // 返回10在堆區地址, 且該地址由程序員使用delete才能釋放或者程序結束釋放 } int main(){int *p = func();cout << *p << endl; }語法: new 數據類型
返回值: new 返回時該數據類型的指針
new(10): 創建int類型的元素, 10表示值, 釋放用delete
new[10]: 在堆區開辟數組,10表示10個元素, 釋放用delete[]
// 1. new 的基本語法 int * func(){int *p = new int(10); // new返回的是int的指針,指向新分配元素的位置,位置的值為10;return p; } delete p; // 2. 在堆區利用new開辟數組 int * func2(){int* arr =new int[10]; // 10代表數組有10個元素return arr } delete[] arr; // 釋放數組,需要加[]2、引用
作用: 給變量起別名,注意數據要相同, 因為不會發生數據類型隱式轉換
語法: 數據類型 &別名 = 原名;
注意事項:
引用定義時必須進行初始化
引用一旦初始化綁定后,就不能改變綁定對象了。
引用做函數參數 :
作用: 函數傳參時,可以利用引用的技術讓形參修飾實參
優點: 可以簡化指針修改實參
// 交換函數 // 1、值傳遞 void swap(int a, int b) { int temp = a; a = b; b = temp; } // 2、地址傳遞 void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } // 3、引用傳遞 void swap(int &a, int &b) { int temp = a; a = b; b = temp; }引用做函數返回值 :
作用: 引用是可以作為函數的返回值存在
注意:不要返回局部變量的引用
用法:函數的調用可以作為左值
int& func(){static int a = 10; // 靜態變量,存放在全局區,全局區上的數據在程序結束后系統釋放return a; } int p = func(); // 整個是復制操作, p的值與a相同,但是地址不同 int &r = func(); // r與a的值和地址都相同 func() = 1000; // 函數的調用作為左值。 過程: 返回靜態常量a的引用, 然后通過該引用修改a的值為1000。引用的本質 : 是一個指針常量。指針操作都由編譯器操作了。
// 引用的本質 int &r = a; // 實際被自動轉換為 int* const ref = &a; 指針常量是不可更改的,這也說明了為什么引用不可更改 r = 30; // 內部發現r是引用,自動轉換為 *ref = 20; 引用的值不是常量,所以可以更愛常量引用 :
作用: 常量引用主要用來修飾形參,防止誤操作
在函數形參列表中,可以加 const 修飾形參, 防止形參改變實參
// 常量引用 int & ref = 10; // 錯誤, 引用必須引用一塊合法的內存空間 const int & ref = 10; // 正確 // 加入const之后,編譯器將代碼修改為 int temp = 10; const int & ref = temp; ref = 20; //錯誤, 加入const之后變為只讀,不可以修改// 打印數據的函數 void showValue(const int & value){cout << value << endl; }3、函數提高(默認、占位、重載)
函數的默認參數:
函數的形參列表中的形參是可以有默認值的。
語法: 返回值類型 函數名 (參數= 默認值) { 函數體 }
// 如果某個位置已經有了默認參數,那么從這個位置往后,從左到右都必須有默認值 int func(int a; int b = 1; int c =2){return 0; }注意:如果 函數聲明 有默認參數,函數定義 就 不能 有默認參數。因為會出現重定義。
函數的占位參數:
函數的形參列表里可以有占位參數,用來做占位,調用函數時必須補充該位置。
語法: 返回值類型 函數名 (數據類型) {函數體};
// 占位參數 void func(int a, int){ // 第二個參數 是占位參數 return ; } // 調用 func(1,2); // 調用函數時必須補充該位置 // 占位參數可以有默認參數 void func2(int a, int = 10){ return ;}函數重載:
作用: 函數名可以相同,提高復用性
函數重載滿足條件:
同一個作用域下
函數名稱相同
函數的 參數類型不同 或者 個數不同 或者 順序不同(需要是不同類型)。
注意:函數的返回值 不可以作為函數重載的條件
// 函數重載 void func(){// 函數體 } void func(int a){ // 函數體 } void func(int a, string b){// 函數體 } void func(string b, int a){// 函數體 }函數重載的注意事項:
C++ 類和對象
C++面向對象的特性:抽象、封裝、繼承、多態
1、封裝
作用:
語法:class 類名{ 訪問權限: 屬性 / 行為 };注意最后的分號,默認訪問權限是private。
術語:
三種訪問權限:
| public | 公共權限 | 😀 | 😀 | |
| protected | 保護權限 | 😀 | 😈 | 繼承時,兒子可以訪問父親中的保護內容 |
| private | 私有權限 | 😀 | 😈 | 繼承時,兒子不可以訪問父親中的私有內容 |
Struct 和 Class 的區別:
唯一區別:默認訪問權限不同
成員屬性設置為私有:
案例1:
// 設計立方體類 // 屬性:長 寬 高 // 行為:獲取立方體的面積 獲取立方體的體積 // 分別利用全局函數和成員函數判斷兩個立方體是否相等 #include<iostream> #include<string> using namespace std;class Cube { public:// 設置 長void setL(int l) { m_L = l; }// 獲取 長int getL() { return m_L; }// 設置 寬void setW(int w) { m_W = w; }// 獲取 寬int getW() { return m_W; }// 設置 高void setH(int h) { m_H = h; }// 獲取 高int getH() { return m_H; }// 獲取立方體的面積int getArea() {return 2 * (m_L * m_W + m_L * m_H + m_W * m_H);}// 獲取立方體的體積int getVolume() {return m_L * m_W * m_H;}// 判斷兩個立方體是否相等bool m_isSame(Cube& c2) {if (m_L == c2.getL() && m_W == c2.getW() && m_H == c2.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.getArea() << endl;cout << "c1的體積是:" << c1.getVolume() << endl;Cube c2;c2.setL(10);c2.setW(10);c2.setH(10);cout << "c1、c2是否相等:" << c1.m_isSame(c2) << endl;cout << "c1、c2是否相等:" << isSame(c1, c2) << endl;Cube c3;c3.setL(10);c3.setW(9);c3.setH(10);cout << "c1、c3是否相等:" << c1.m_isSame(c3) << endl;cout << "c1、c3是否相等:" << isSame(c1, c3) << endl;return 0; }案例2:
#include<iostream> #include<string> using namespace std;class Point { public:void setX(int x) { m_x = x; }void setY(int y) { m_y = y; }int getX() { return m_x; }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_point) { center = center_point; }Point getCenter() { return center; } private:int m_r;Point center; // 在圓的對象內部使用了點Point對象來構建圓心 }; int main() {Circle c1;Point center; // 圓心點center.setX(3);center.setY(4);c1.setR(9);c1.setCenter(center); }類的多文件結構:
// 頭文件 Point.h #program once #include<iostram> using namespace std;// 僅使用類的聲明就可 class Point { public:void setX(int x) ;void setY(int y) ;int getX() ;int getY() ; private:int m_x;int m_y; }; // 源文件 Point.cpp 要使用 作用域限定符"::"來限定是Point類的成員函數 #include "Point.h" void Point::setX(int x) { m_x = x; } void Point::setY(int y) { m_y = y; } int Point::getX() { return m_x; } int Point::getY() { return m_y; }2、對象的初始化和清理
作用:C++中采用構造函數和析構函數來初始化對象和銷毀對象。
2.1 構造函數和析構函數概念
這兩個函數會被編譯器 自動調用,完成對象的初始化和清理工作。對象的初始化和清理是編譯器強制要求我們做的事情,因此如果我們不提供構造和析構,編譯器會提供編譯器提供的構造函數和析構函數是空實現。
構造函數的語法:類名(){}
析構函數的語法:~類名(){}
2.2 構造函數的分類及調用
兩種分類方式:
1. 按參數分為: 有參構造和無參構造(默認構造) 2. 按類型分類: 普通構造和拷貝(復制)構造(拷貝構造也是一種構造函數,用戶沒有調用的話,編譯器會自動聲成)三種調用方式:
注意事項 :
2.3 拷貝構造函數的調用時機
C++拷貝構造函數調用實際通常有三種情況:
使用一個已經創建完畢的對象來初始化一個新對象
Person p2(p1);值傳遞方式給函數參數傳值
void func(Person p){// 函數體 }以值方式返回局部對象
Person func(){Person p1;return p1; }? 拷貝構造函數的參數為什么是常量引用? :
2.4 構造函數調用規則
默認情況下,C++編譯器至少給一個類添加3個函數
構造函數調用規則如下:
2.5 深拷貝與淺拷貝(面試內容)
淺拷貝:簡單的賦值拷貝操作。增加了一個指針,指向原來已經存在的內存。
深拷貝:在堆區重新申請空間,進行拷貝操作。增加了一個指針,并新開辟了一塊空間。
問題:淺拷貝帶來的問題是堆區的內存重復釋放。
解決方法**:自己實現拷貝構造函數**,新開辟堆區空間,來解決淺拷貝帶來的問題。 即深拷貝
如果屬性有在堆區開辟的,一定要自己提供拷貝構造函數,防止淺拷貝帶來的問題
(理解時可以寫程序逐語句F11調試一下)
深拷貝: 當被復制的對象數據成員是指針類型時,不是復制該指針成員本身,而是將指針所指對象進行復制。
2.6 初始化列表
作用:C++提供了初始化列表語法,用來初始化屬性
語法:構造函數(): 屬性1(值1), 屬性2(值2) ... {}
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){}// 注意冒號“:”的位置,在參數列表之后。int m_A;int m_B;int m_C; };2.7 類對象作為類成員
C++類中的成員可以是另一個類的對象,我們稱該成員為對象成員。
class A{} class B{A a; }B類中有對象A作為成員,A為對象成員
// 當有其他類對象作為本類成員,構造時先構造類對象,再構造自身; 析構順序是先進(自身)后出(其他類對象)2.8 靜態成員
靜態成員就是在成員變量和成員函數前加上關鍵字static,稱為靜態成員
靜態成員分為:
靜態成員變量
- 所有對象共享同一份數據
- 在編譯階段分配內存(提前了)
- 類內聲明,類外初始化。
- 可以通過類名直接訪問靜態成員變量
- 私有靜態成員不能被外部訪問
靜態成員函數
- 所有對象共享同一個函數
- 靜態成員函數 只能 訪問靜態成員變量
- 也是有訪問權限的
3、 C++ 對象模型和this指針
3.1 成員變量和成員函數分開存儲
在C++中,類內的成員變量和成員函數分開存儲。
只有非靜態成員變量才存儲在類的對象上
// 空對象 class Person {}; Person p; // 占用內存空間 sizeof(p) 為1 // C++ 編譯器會給每個空對象也分配一個字節空間,是為了區分空對象占內存的位置 // 每個空對象也應該有一個獨一無二的內存地址 // 非靜態成員 class Person{ int a; }; Person p; // 占用內存空間 sizeof(p) 為4 // 非靜態成員函數 存儲不在對象上 class Person{ void setA(int a) {};int a; }; Person p; // 占用內存空間 sizeof(p) 為4 // 靜態成員 存儲不在對象上 class Person{ int a; static int b;}; Person p; // 占用內存空間 sizeof(p) 為4,3.2 this指針
每一個 非靜態成員函數 只會誕生一份函數實例,也就是說多個類型的對象會共用一塊代碼
這一塊代碼是如何區分哪個對象調用自己?
C++ 提供 this 指針來區分,this指針指向被調用的成員函數所屬的對象。
this指針:
用途:
當形參和成員變量同名時,可以用this指針來區分
在類的非靜態成員函數中返回對象本身,可以用return *this。
3.3 空指針訪問成員函數
C++中空指針也是可以調用成員函數的,但是也要注意有沒有用到this指針
如果用到this指針,需要加以判斷保證代碼的健壯性
// 空指針調用成員函數 class Person { public:void showClassName() {cout << "Class name is Person!" << endl;}void showAge() {if (this == nullptr) { return; }cout << "Age is : " << m_age << endl; // m_age實際上是 this->m_age。 因此如果對象是空指針就需要加以判斷}int m_age = 20; }; // 調用 int main() {Person *p = nullptr;p->showClassName();p->showAge();return 0; }3.4 const修飾成員函數
常函數:
常對象:
4、 友元
在程序里,有些私有屬性也想讓類外特殊的一些函數或者類進行訪問,就需要用到友元技術
目的:讓一個函數或者類,可以訪問另一個類中的私有成員
關鍵字:friend
友元的三種實現:
- 全局函數做友元 , 可以將全局函數放在類內實現
- 類做友元
- 成員函數做友元
類做友元:
語法: friend class 類名;
類做友元會用到 前向引用聲明:(多類相互引用的情況,類先聲明后使用)
注意事項:
成員函數做友元
語法: friend 返回值類型 類名::函數名();
#include<iostream> #include<string> using namespace std;class Building; // 前向引用聲明。 class GoodGay { public:GoodGay();void visit(); // 訪問Building屬性中的私有成員void visit2(); // 不能訪問Building屬性中的私有成員Building* building; };class Building {friend void GoodGay::visit(); // 成員函數作為友元 public:Building();string m_SittingRoom;private:string m_BedRoom; };GoodGay::GoodGay() {building = new Building; }void GoodGay::visit() {cout << "GoodGay正在visit:" << building->m_SittingRoom << endl;cout << "GoodGay正在visit:" << building->m_BedRoom << endl; }void GoodGay::visit2() {cout << "GoodGay正在visit:" << building->m_SittingRoom << endl;// cout << "GoodGay正在visit:" << building->m_BedRoom << endl; 不能訪問 } Building::Building() { //構造函數在類外實現{m_SittingRoom = "客廳";m_BedRoom = "臥室";} }int main() {GoodGay g;g.visit();g.visit2();return 0; }5、 運算符重載
概念: 對已有運算符進行重新定義,賦予其另一種功能,以適應 不同的數據類型。
5.1 加號運算符重載
operater
運算符重載也可以發生函數重載
總結:
對于內置數據類型的運算符是不能重載的
不要濫用運算符重載
5.2 左移運算符重載
作用:重載左移運算符 配合友元 可以實現輸出自定義數據類型
// 左移運算符重載 不能通過成員函數實現,只能利用全局函數重載左移運算符 // 訪問私有類時,可以將全局函數聲明為友元函數。 class Person {friend ostream& operator<<(ostream& cout, Person& p); public:// 利用成員函數重載 左移運算符// 不會利用成員函數重載左移<<運算符,因為無法實現cout為左值Person(int a, int b) {this->m_A = a;this->m_B = b;} private:int m_A;int m_B; };// 只能利用全局函數重載左移運算符 // 不能用void 用 ostream類 ostream & operator<<(ostream& cout, Person p) {cout << "m_A = " << p.m_A << " m_B = " << p.m_B;return cout; } int main() {Person p(10,10);return 0; }5.3 遞增運算符重載
作用:通過重載遞增運算符,來自己實現的整型數據類型
class MyInteger { public:MyInteger(){m_Num = 0;}// 重載前置運算符遞增 返回引用是為了一直只對一個對象修改MyInteger& operator++(){m_Num++;return *this;}// 重載后置運算符遞增// void operator++(int) int表示占位參數,可以用于區分前置和后置遞增// 注意:這里只能用 intMyInteger operator++(int) { // 先記錄當前結果MyInteger temp = *this; // 局部非靜態變量 因此返回值不是引用。// 后遞增m_Num++;// 最后將記錄結果返回return temp; // }int m_Num = 0; };// 為什么使用后置遞增運算 用引用傳參&myint會報錯 ostream& operator<<(ostream& out, MyInteger myint) {out << myint.m_Num << endl;return out; }int main() {MyInteger myint;cout << ++myint << endl;cout << myint << endl;MyInteger myint2 = myint++;cout << myint2 << endl;cout << &(myint++) << endl;return 0; }為什么使用后置遞增運算 用引用傳參 MyInteger &myint會報錯?
因為后置遞增返回臨時對象,不能綁定到非常量引用。所以那個形參必須寫成const引用。打印函數不會更改對象,所以最好要寫成const形式。通常來說const形參適用面廣一些(常量,非常量都能傳進去)。第二,推薦ostream& operator<<形參別寫成cout,造成誤導。
加入const之后,編譯器將代碼修改為:
MyInteger temp = 后置遞增的值; const MyInteger & myint = temp;只有傳入的是臨時對象時,才發生復制構造。
5.4 賦值運算符重載
C++ 編譯器至少給一個類添加4個函數:
如果類中有屬性指向堆區,做賦值操作時也會出現深淺拷貝問題。
// 賦值運算符重載 #include<iostream> #include<string> using namespace std;class Person { public:Person(int age) {m_Age = new int(age);}~Person() {if (m_Age != nullptr) {delete m_Age;m_Age = nullptr;}}Person &operator=(Person& p) {// 編譯器是提供淺拷貝// m_Age = p.m_Age;// 應該先判斷是否有屬性在堆區,如果有先釋放干凈,然后再深拷貝if (m_Age != nullptr) {delete m_Age;m_Age = nullptr;}// 深拷貝m_Age = new int(*p.m_Age);//返回對象自身return *this;}int* m_Age; }; int main() {Person p1(18);cout << "p1的年齡是:" << *p1.m_Age << endl;Person p2(20);cout << "p2的年齡是:" << *p2.m_Age << endl;p2 = p1;cout << "p2的年齡是:" << *p2.m_Age << endl;Person p3(60);p3 = p2 = p1;cout << "p3的年齡是:" << *p3.m_Age << endl;return 0; }總結: 鏈式編程要注意返回值的類型,如重載遞增運算、賦值運算都需要注意。
5.5 關系運算符重載
作用:重載關系運算符,可以讓兩個自定義類型對象進行對比操作。
// 舉例 重載== bool operator==(Person &p){if(this->m_A == p.m_A && this->m_Age == p.m_Age){return true;}return false; }5.6 函數調用運算符重載()
- 函數調用運算符 () 也可以重載
- 由于重載后使用的方式非常像函數的調用,因此稱為仿函數
- 仿函數沒有固定寫法, 非常靈活
6、繼承
繼承是面向對象三大特性之一??梢詼p少重復代碼
6.1 繼承的基本語法
語法:class 子類 : 繼承方式 父類
術語: 子類又稱 派生類,父類又稱 基類
派生類中的成員包含兩大部分:
一類是從基類中繼承來的,另一類是它自身的成員
6.2 繼承方式
繼承有三種方式:
父類中的私有private內容,子類無論哪種方式都不能訪問。
- 公共繼承 :父類中public、protected在子類的訪問權限不變
- 保護繼承 :父類中public、protected在子類中訪問權限變為protected
- 私有繼承 :父類中public、protected在子類中訪問權限變為private
6.3 繼承中的對象模型
問題:從父類繼承過來的成員, 哪些屬于子類對象中?
6.4 繼承中的構造和析構
子類繼承父類后,當創建子類對象,也會調用父類的構造函數
問題: 父類和子類的構造和析構順序誰先誰后?
先父類構造,后子類構造;先析構子類,后析構父類,與構造相反
// 繼承中的構造和析構順序 class Base { public:Base() {cout << "Base的構造函數!" << endl;}~Base() {cout << "Base的析構函數!" << endl;} };class Son :public Base { public:Son() {cout << "Son的構造函數!" << endl;}~Son() {cout << "Son的析構函數!" << endl;} };int main() {Son s1;return 0; }6.5 繼承中同名成員處理方式
問題:當子類與父類出現同名的成員,如何通過子類對象,訪問到子類或父類中同名的數據呢?
- 訪問子類同名成員 直接訪問即可
- 訪問父類同名成員 需要加作用域
注意事項:
如果子類中出現和父類同名的成員函數,子類的同名成員會隱藏掉父類中所有同名成員函數(包含重載函數)。 如果想要訪問父類中被隱藏的同名成員函數,需要加 作用域。
6.6 繼承中同名靜態成員處理方式
問題:繼承中同名的靜態成員在子類對象上如何進行訪問?
靜態成員和非靜態成員出現同名,處理方式一致
- 訪問子類同名成員 直接訪問即可
- 訪問父類同名成員 需要加作用域
注意:靜態成員在類內聲明,類外初始化。
// 同名靜態成員 與 同名靜態成員函數 class Base { public:static int m_A;static void func() {cout <<" Base - static void func()" << endl;} }; int Base::m_A = 100;class Son :public Base { public:static int m_A;static void func() {cout << " Son - static void func()" << endl;} }; int Son::m_A = 200;int main() {Son s;// 1、通過對象訪問cout << "Son 下 m_A = " << s.m_A << endl;cout << "Base 下 m_A = " << s.Base::m_A << endl;// 2、通過類名訪問cout << "Son 下 m_A = " << Son::m_A << endl;cout << "Base 下 m_A = " << Son::Base::m_A << endl;// Son::Base::m_A 通過類名方式訪問Son中Base作用域下的m_A// 1、通過對象訪問s.func();s.Base::func();// 2、通過類名訪問Son::func();Son::Base::func();return 0; }6.7 多繼承語法
C++允許一個類繼承多個語法
語法: class 子類: 繼承方式 父類1, 繼承方式 父類2 ...
多繼承可能會引發父類中有同名成員出現,需要加作用域區分。
實際開發中不建議用多繼承。
// 多繼承 class A { public:A() {cout << "Create A !" << endl;}int m_N; };class B { public:B() {cout << "Create B !" << endl;}int m_N; };class C : public A, public B{ // 多繼承語法 public:C() {m_C = 100;m_D = 200;cout << "Create C !" << endl;}int m_C;int m_D; }; int main() {C c;cout << "This is A:" << c.A::m_N << endl;cout << "This is B:" << c.B::m_N << endl;return 0; }6.8 菱形繼承
菱形繼承的概念:
? 兩個派生類繼承同一個基類, 又有某個類同時繼承兩個派生類。這種繼承被稱為菱形繼承,或者鉆石繼承。
問題:
-
二義性
-
有的數據重復,只需要一項就可以
總結:利用 虛繼承 解決菱形繼承問題。術語理解:虛基類,虛繼承 關鍵字 virtual。
7、多態
多態是 C++ 面向對象的三大特性(封裝、繼承、多態)之一。
7.1 多態的基本概念
多態分為兩類:
- 靜態多態: 函數重載和運算符重載屬于靜態多態,復用函數名
- 動態多態:派生類和虛函數實現運行時多態
靜態多態和動態多態的區別:
- 靜態多態的函數地址早綁定 – 編譯階段確定函數地址
- 動態多態的函數地址晚綁定 – 運行階段確定函數地址
總結:
多態滿足條件:
- 有繼承關系
- 子類重寫父類中的虛函數
多態使用條件
- 父類的 指針或引用 指向 子類對象
重寫: 函數返回值類型 函數名 函數列表 完全一致稱為 重寫
7.2 虛函數(考點)
實現動態綁定的函數。
virtual 只是編譯器不要在編譯階段進行靜態綁定。
虛函數定義:
什么函數可以是虛函數:
-
一般成員函數
-
構造函數不能是
-
析構函數可以是
一般虛函數成員
virtual關鍵字:
派生類可以不顯式地用 virtual 關鍵字聲明虛函數,這時候系統會根據以下規則來判斷派生類地一個函數成員是不是虛函數:
7.3 多態的基本概念
虛函數指針 vfptr
虛函數表 vftable:
-
每一個多態類都有一個虛表
-
虛表中有當前類的各個虛函數的入口地址
-
每個對象隱含有一個指向當前類的虛表的指針
7.4 多態案例
多態案例一:計算器類
案例描述: 分別利用普通寫法和多態技術,設計實現兩個操作數進行運算的計算器類
多態的優點:
- 代碼組織結構清晰
- 可讀性強
- 利于前期和后期的擴展以及維護
7.5 純虛函數和抽象類
在多態中,通常父類中虛函數的實現是毫無意義的,主要都是調用子類重寫的內容
因此 可以將虛函數改為純虛函數
純虛函數語法: virtual 返回值類型 函數名 (參數列表) = 0;
當類中有了純虛函數,這個類也稱為抽象類。
抽象類的特點:
- 無法實例化對象
- 子類必須重寫抽象類中的純虛函數,否則也屬于抽象類
7.6 虛析構和純虛析構
多態使用時, 如果子類中有屬性開辟到堆區, 那么父類指針在釋放時無法調用到子類的析構代碼。
解決方式:將父類中的析構函數改為虛析構或者純虛析構
虛析構和純虛析構共性:
- 可以解決父類指針釋放子類對象
- 都需要具體函數實現
區別:
- 如果是純虛析構,該類屬于抽象類,無法實例化對象。
語法:
虛析構語法:virtual ~類名(){}
純虛析構語法: virtual ~類名 = 0; 類名::~類名(){} 純虛析構 需要有聲明 類外寫實現
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;this->m_Name = new string(name);}~Cat(){if (this->m_Name != nullptr) {cout << "Cat類析構函數調用" << endl;delete this->m_Name;m_Name = nullptr;}}virtual void speak() {cout << *this->m_Name <<"小貓在說話" << endl;}string* m_Name; };int main() {Animal* animal = new Cat("Tom");animal->speak();// 父類指針在析構時候 不會調用子類中的析構函數,導致子類如果有堆區屬性,// 會出現內存泄露情況delete animal;return 0; }總結:
7.7 override 和 final
override:C++ 11中引入的顯示覆蓋。使用override后,編譯器會檢查基類中是否存在一虛函數,與派生類中帶有聲明override的虛函數,有相同的虛函數簽名(函數名 參數列表 const一致),若不存在,則返回錯誤
final:不希望類或函數被修改,使用final聲明則該類(或函數)將不能被繼承(或覆蓋)。
class Base final{}; virtual void f() final;8、文件操作
程序運行時產生的數據都屬于臨時數據,程序一旦運行結束都會被釋放, 通過文件可以將 數據持久化
C++中對文件操作需要包含頭文件 #include<fstream>
文件類型分為兩種:
操作文件的三大類:
8.1 文本文件
寫文件的步驟:
文件打開方式:
| ios::in | 為讀文件而打開文件 |
| ios::out | 為寫文件而打開文件 |
| ios::ate | 初始位置:文件尾 |
| ios::app | 追加方式寫文件 |
| ios::trunc | 如果文件存在先刪除,再創建 |
| ios::binary | 二進制方式 |
注意:文件打開方式可以配合使用, 利用|操作符
例如:用二進制方式寫文件ios::binary | ios::out
#include<fstream> int main() {// 2、創建流對象ofstream ofs;// 3、制定打開方式ofs.open("test.txt", ios::out);// 4、寫內容ofs << "姓名:張三" << endl;ofs << "性別:男" << endl;ofs << "年齡:18" << endl;ofs.close();return 0; }讀文件的步驟
8.2 二進制文件
以二進制的方式對文件進行讀寫操作,打開方式要指定為 ios::binary 。
寫文件:
二進制方式寫文件主要利用流對象調用成員函數 write
函數原型:ostream& write(const char * buffer, int len);
參數解釋:字符指針buffer指向內存中一段存儲空間。len是讀寫的字節數。
// 二進制文件 寫文件 注意二進制寫文件盡量不要用string class Person { public:char m_Name[64]; // 姓名int m_Age; // 年齡 };int main() {// 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();return 0; }總結://創建流對象時候直接指定
讀文件:
二進制讀文件主要利用流對象調用成員函數read
函數原型:istream& read(char * buffer, int len);
參數解釋: buffer指向內存中一段存儲空間,len是讀寫的字節數。
// 二進制文件 讀文件 class Person { public:char m_Name[64]; // 姓名int m_Age; // 年齡 };int main() {// 2、 創建流對象ifstream ifs("person.txt", ios::out | ios::binary); //創建流對象時候直接指定// 3、 打開文件 判斷文件是否打開成功if (!ifs.is_open()) {cout << "打開成功" << endl;}// 4、 寫入文件Person p;ifs.read((char *)&p,sizeof(Person));cout << "姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;// 5、 關閉文件ifs.close();return 0; }C++泛型編程和STL庫
1、模板
1.1 模板的概念
模板就是建立通用的模具,大大提高復用性,例如ppt模板
缺點:
- 模板不能直接使用
- 模板并不是萬能的,不能包括所有的
1.2 函數模板
泛型編程:
- C++另一種編程思想稱為泛型編程, 主要利用的技術就是模板
- C++提供兩種模板機制: 函數模板 和 類模板
函數模板的語法:
函數模板的作用:建立一個通用參數,其函數返回值類型和形參類型可以不具體指定,用一個虛擬的類型來代表。
語法:
template<typename T> 函數聲明或定義解釋:
template — 聲明創建模板
typename — 表明其后面的符號是一種數據類型,可以用class代替
T — 通用的數據類型,名稱可以替換,通常為大寫字母
// 什么情況下用函數模板 // 下面是兩個普通函數,針對每一個數據類型都要單獨實現函數 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; }int main() {int a = 10;int b = 20;// 使用函數模板// 1、自動類型推導mySwap(a, b);cout << "a is " << a << " b is " << b << endl;// 2、顯示指定類型mySwap<int>(a, b);return 0; }總結:
- 函數模板利用關鍵字 template
- 使用函數模板有兩種方式: 自動類型推導、顯示指定類型
- 模板的目的是為了提高復用性,將類型參數化
1.3 函數模板注意事項
- 自動類型推導,必須推導出一致的數據類型T,才可以使用
- 模板必須要確定出T的類型,才可以使用
1.4 函數模板案例
案例描述:
- 利用函數模板封裝一個排序的函數,可以對不同數據類型數組進行排序
- 排序規則從大到小,排序算法為選擇排序
- 分別利用char 數組和int數組進行測試
總結:
函數模板如何聲明,如何在不同文件中使用?
1.5 普通函數與函數模板的區別
普通函數與函數模板的區別:
- 普通函數可以發生自動類型轉換(隱式類型轉換)
- 函數模板調用時,如果利用自動類型推導,不會發生隱式類型轉換
- 如果利用顯式指定類型的方式,可以發生隱式類型轉換
建議:使用顯式指定類型的方式調用函數模板,因為可以自己確定通用類型T。
1.6 普通函數與函數模板的調用規則
調用規則:
- 如果函數模板和普通函數都可以實現,優先調用普通函數。
- 可以通過 空模板參數列表, 強制調用函數模板
- 函數模板也可以重載
- 如果函數模板可以產生更好的匹配,優先調用函數模板。
總結:既然提供了函數模板,最好就不要再提供普通函數,否則容易出現二義性
1.7 模板的局限性
- 模板的通用性并不是萬能的
解決方案:C++提供了模板的重載,可以為特定類型提供具體化的模板
class Person { public:Person(const string name, const int age): m_Name(name), m_Age(age){cout << "constructor" << endl;}string m_Name;int m_Age; };template <class T> bool myCampare(T& a, T& b) {if (a == b) {return true;}else {return false;} }// Person 不能對比 // 解決方法有兩種 // 1、重載 "==" 運算符, 太過麻煩 // 2、利用具體化Person的版本實現, 具體化會優先調用 // 具體化實現 template<> bool myCampare(Person& a, Person& b) {if (a.m_Age == b.m_Age && a.m_Name == b.m_Name) {return true;}else {return false;} }int main() {int a = 10;int b = 20;bool ret = myCampare(a, b);cout << ret << endl;Person pa("Tom", 18);Person pb("Tom", 18);bool pret = myCampare(pa, pb);cout << pret << endl;return 0; }總結:
-
利用具體化的模板,可以解決自定義類型的 通用化
-
學習模板并不是為了寫模板,而是在STL庫中能夠運用系統提供的模板
1.8 類模板
類模板作用:
- 建立一個通用類,類中的成員 數據類型 可以不具體指定,用一個虛擬的類型來代表。
1.9 類模板與函數模板的區別
主要有兩點區別:
- 類模板沒有自動類型推導的使用方式,只有顯式指定類型方式
- 類模板在模板參數列表 <> 中可以有默認參數
1.10 類模板中成員函數創建時機
與普通類中成員函數創建時機是有區別的:
- 普通類中的成員函數一開始就可以創建
- 類模板中的成員函數并不是在一開始就創建,而是在調用時才創建
1.11 類模板對象做函數參數
類模板實例化出對象,向函數傳參的方式有三種:
1.12 類模板與繼承
當類模板遇到繼承時,需要注意以下幾點:
- 當子類繼承的父類是一個類模板時,子類在聲明的時候,需要指定出父類中T的類型
- 如果不指定,編譯器無法給子類分配內存
- 如果想靈活的指定出父類中T的類型,子類也需要變為類模板
1.12 類模板的成員函數的類外實現
類外實現:
template<class T1, class T2> class Person { public:Person(T1 name, T2 age);void showPerson();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; }int main() {Person<string, int>p1("Tom", 21);p1.showPerson();return 0; }1.13 類模板的分文件編寫
學習目標:
- 掌握類模板成員函數 分文件編寫 產生的問題以及解決方式
問題:
- 類模板中成員函數創建時機是在調用階段,導致分文件編寫時鏈接不到
解決:
-
解決方式1:直接包含.cpp源文件
-
解決方式2:將聲明和實現寫到同一個文件中,并更改后綴名為.hpp,hpp是約定的名稱,并不是強制
解決方式1
#include "Person.cpp"解決方式2
// Person.hpp #pragma once#include<iostream> #include <string> using namespace std;template<class T1, class T2> class Person { public:Person(T1 name, T2 age);void showPerson();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> #include <string> using namespace std; // 第一種解決方式(很少采用),直接包含 頭文件Person.h 改成源文件 Person.cpp。 // #include"Person.cpp" // 第二種解決方式, 將.h 和.cpp內容寫到一起,將后綴名改為.hpp文件; #include"Person.hpp"int main() {Person<string, int>p1("Tom", 21);p1.showPerson();return 0; }1.13 類模板與友元
學習目標:
- 掌握類模板配合友元函數的類內和類外實現
全局函數 類內 實現 – 直接在類內聲明友元即可
全局函數 類外 實現 – 需要提前讓編譯器知道全局函數的存在
template<class T1, class T2> // Person模板類聲明 class Person; // 讓編譯器看到下面函數中的Person類 // 全局函數 類外實現 實現部分 要放在前面 讓編譯器知道 template<class T1, class T2> void printPerson2(Person<T1, T2> p) { // 全局函數 不用加Person作用域cout << "全局函數 類外實現 /n 姓名: " << p.m_Name << " 年齡: " << p.m_Age << endl; }template<class T1, class T2> class Person { public:Person(T1 name, T2 age) {this->m_Name = name;this->m_Age = age;}// 全局函數 類內實現friend void printPerson(Person<T1, T2> p) {cout << "全局函數 類內實現/n 姓名: " << p.m_Name << " 年齡: " << p.m_Age << endl;}// 全局函數 類外實現 聲明部分// printPerson2后 加 空模板的參數列表// 如果全局函數 是類外實現,需要讓編譯器提前知道該全局函數的實現friend void printPerson2<>(Person<T1, T2> p); // 普通函數的聲明private:T1 m_Name;T2 m_Age; };int main() {// 1.全局函數在類內實現Person<string, int> p("Tom", 21);printPerson(p);printPerson2(p);return 0; }總結:建議全局函數做類內實現,用法簡單,而且編譯器可以直接識別。如果不將全局函數提前,可以使用全函數的聲明
1.13 案例實現一個通用的數組類
要求如下:
- 可以對內置數據類型以及自定義類型的數據進行存儲
- 將數組中的數據存儲到堆區
- 構造函數中可以傳入數組的容量
- 提供對應的拷貝構造函數以及**operator=**防止淺拷貝問題
- 提供尾插法和尾刪法對數組中的數據進行增加和刪除
- 可以通過下標的方式訪問數組中的元素
- 可以獲取數組中當前元素個數和數組的容量
2、STL 初識
- C++的面向對象和泛型編程思想,目的就是復用性的提升
- 為了建立數據結構和算法的一套標準,誕生了STL
2.1 STL基本概念
- STL(Standard Template Library, 標準模板庫 )
- STL從廣義上分為:容器(container) 算法(algorithm) 迭代器(iterator)
- 容器 和 算法 之間通過 迭代器 進行無縫連接
- STL幾乎所有代碼都采用了模板類或者模板函數
2.2 STL六大組件
STL大體分為六大組件,分別是 容器、算法、迭代器、 仿函數、適配器(配接器)、空間配置器
2.3 STL中容器、算法、迭代器
容器:置物之所也
STL容器就是將運用最廣泛的一些 數據結構 實現出來
常用的數據結構:數組,鏈表,樹,棧,隊列,集合,映射表等
這些容器分為序列容器和關聯式容器兩種:
- 序列式容器:強調 值 的排序,序列式容器中的每個元素均有固定的位置
- 關聯式容器:二叉樹結構,各元素之間沒有嚴格的物理上順序關系
算法:問題之解法也
有限的步驟,解決邏輯或數學上的問題,這一門學科我們叫做算法
算法分為:質變算法 和 非質變算法
質變算法:是指運算過程中會更改區間內元素的內容。例如拷貝、替換、刪除等等
非質變算法:是指運算過程中不會更改區間內的元素內容,例如查找、計數、遍歷、尋找極值等等
迭代器:容器和算法之間的膠合劑
提供一種方法,使之能夠依序訪問某個容器所含的各個元素,而又無需暴露該容器內部表示方式。
每個容器都有自己專屬迭代器。
迭代器使用非常類似與指針,初學階段我們可以先理解為指針
迭代器種類:
| 輸入迭代器 | 對數據的只讀訪問 | 只讀,支持++、==、!= |
| 輸出迭代器 | 對數據的只寫訪問 | 只寫,支持++ |
| 前向迭代器 | 讀寫操作,并能向前推進迭代器 | 讀寫,支持++、==、!= |
| 雙向迭代器 | 讀寫操作,并能向前向和后操作 | 讀寫,支持++、– |
| 隨機訪問迭代器 | 讀寫操作,可以以跳躍的方式訪問任意數據,功能最強的迭代器 | 讀寫,支持++、–、[n]、-n、<、<=、>、>= |
常用的容器中迭代器種類為雙向迭代器、隨機訪問迭代器。
2.4 容器算法迭代器初識
STL中最常用的容器為vector,可以理解為數組
vector存放內置數據類型:
容器: vector
算法:for_each
迭代器:vector<int>::iterator
#include<iostream> using namespace std; #include<vector> #include<algorithm>void myPrint(int val) { // 供第三種遍歷方法的for_each使用cout << val << endl; }int main() {// 創建vector容器vector<int> v;// 向容器中插入數據v.push_back(10);v.push_back(20);v.push_back(30);v.push_back(40);// 通過迭代器訪問容器中的數據// 起始迭代器 指向容器中的 第一個元素vector<int>::iterator itBegin = v.begin(); // 結束迭代器 指向容器中最后一個元素的下一個位置vector<int>::iterator itEnd = v.end(); // 第一種遍歷方式while (itBegin != itEnd) {cout << *itBegin << endl; // 使用*解引用itBegin++;}// 第二種遍歷方式 常用for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {cout << *it << endl;}// 第三種遍歷方式 使用Algorithm 中 for_each 算法for_each(v.begin(), v.end(), myPrint); }vector存放自定義數據類型:
class Person { public:Person(string name, int age) {this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age; };// 存放Person類型 void test1() {// 創建vector容器vector<Person> v;// Person類型Person p1("Tom", 16);Person p2("Tom1", 116);Person p3("Tom2", 216);Person p4("Tom3", 316);Person p5("Tom4", 416);// 向容器中插入數據v.push_back(p1);v.push_back(p2);v.push_back(p3);v.push_back(p4);v.push_back(p5);for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {// (*it) 是Person對象cout << "姓名:" << (*it).m_Name << " 年齡:" << (*it).m_Age << endl;cout << "姓名:" << it->m_Name << " 年齡:" << it->m_Age << endl;} }// 存放Person類型指針 void test2() {// 創建vector容器vector<Person *> v;// Person類型Person p1("Tom", 16);Person p2("Tom1", 116);Person p3("Tom2", 216);Person p4("Tom3", 316);Person p5("Tom4", 416);// 向容器中插入數據v.push_back(&p1);v.push_back(&p2);v.push_back(&p3);v.push_back(&p4);v.push_back(&p5);for (vector<Person *>::iterator it = v.begin(); it != v.end(); it++) {// *it 是指針 訪問Person中的屬性 要用 (*it)->m_Namecout << "姓名:" << (*it)->m_Name << " 年齡:" << (*it)->m_Age << endl;} } int main() {test1();test2();return 0; }vector容器嵌套容器:
容器中嵌套容器, 然后遍歷輸出
int main() {// 容器嵌套容器vector<vector<int>> v;// 創建小容器 v1;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) << "\t";}cout << endl;}return 0; }3、STL – 常用容器
常用容器有,string、vector、deque、stack、 queue、list、set/multiset、map/multimap。
3.1 String 容器
1. 基本概念:
本質:string 是 C++ 風格的字符串, 而string本質上是一個類
string和char*的 區別:
- char * 是一個指針
- string 是一個類,類內部封裝了char*,管理這個字符串,是一個 char * 型的的容器
特點:
string 類內部封裝了很多成員方法
例如: 查找find, 拷貝copy,刪除delete, 替換replace, 插入insert
string 管理 char*所分配的內存,不用擔心復制越界和取值越界,由類內部進行負責
2. string構造函數
構造函數原型:
- string(); // 創建一個空的字符串 例如: string str;
- string(const char * s) // 使字符串s初始化
- string(const string & str); // 使用一個string對象初始化另一個string對象
- string(int n, char c); // 使用n個字符c初始化
3. string 賦值操作
給string字符串進行賦值
賦值函數原型:
- string& operator=(const char *s); // char * 類型字符串 賦值 給當前字符串
- string& operator=(const string &s); // 把字符串s賦給當前的字符串
- string& operator=(char c); // 字符賦值給當前的字符串
- strin& 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賦給當前字符串
總結:string 賦值方法有很多,operator=更實用。
4. string字符串拼接
實現在字符串末尾拼接字符串
函數原型:
- string& operator+=(const char* str); // 重載+=運算符
- string& operator+=(const char* c);
- 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); // 與第三個相同
- string& append(const string &s, int pos, int n); // 把字符串s從pos開始的n個字符連接到字符串結尾。 pos是位置,從0開始
5.字符串查找替換
查找:查找指定字符串是否存在 find
替換:在指定的位置替換字符串 replace
函數原型:
- 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=npos開始查找,從右往左數npos個元素,查找是正向的
- int rfind(const char* s, int pos, int n) const;// 從pos位置查找s的前n個字符最后一次位置
- int rfind(const char c, int pos=0) const; // 查找字符c最后一次出現位置
- string& replace(int pos, int n, const string& str); // 替換從pos開始n個字符為字符串str
- string& replace(int pos, int n, const char*s);//替換從pos開始的n個字符為字符串s
6. 字符串比較
字符串之間的比較,主要用于判斷兩個字符串是否相等
- 字符串按字符的ASCII碼進行對比。ASCII碼大小,a<z
= 返回 0
> 返回 1
< 返回-1
函數原型:
- int compare(const string& s) const; //與字符串s比較
- int compare(const char* s) const; // 與字符串s比較
7. 字符存取
string中單字符存取方式有兩種
- char& operator[](int n); //通過[]方式取字符
- char& at(int n); // 通過at方法獲取字符
8. 字符串插入和刪除
對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個字符
9. 子串獲取
從字符串中獲取想要的子串
- 從字符串中獲取想要的子串
函數原型:
- string substr(int pos=0, int n = pos) const; // 返回由pos開始的n個字符組成的字符串
總結:可以跟其他查找成員函數配合,如find等來截取特定子串
3.2 vector容器
1. Vector基本概念
功能:
- vector數據結構和數組非常相似,也稱為單端數組
與普通數組的區別:
- 不同之處在于數組是靜態空間,而vector可以動態擴展
動態擴展:
- 并不是在原空間之后續接新空間,而是找更大的連續內存空間,然后將原數據拷貝新空間,釋放原空間
vector容器的迭代器是支持隨機訪問的迭代器。 begin()、end()、rbegin()、 rend()
函數原型:
- vector<T> v; //采用模板實現類實現,默認構造函數
- vector(v.begin(), v.end()); // 將v[begin(), end()) 左閉右開區間中的元素拷貝給本身,因為end()是指向最后一個元素的下一個位置
- vector(n, elem); // 構造函數將n個elem拷貝給本身
- vector(const vector &vec); // 拷貝構造函數
2.vector的賦值操作
函數原型:
- vector& operator=(const vector &vec); // 重載等號操作符
- assign(beg, end); // 將[beg, end)區間中的數據拷貝賦值給本身
- assign(n, elem); // 將n個elem拷貝賦值給本身
3.vector容量和大小
對vector容器的容量和大小的操作
函數原型:
- empty(); // 判斷容器是否為空
- capacity(); //容器的容量
- size(); // 返回容器中元素的個數
- resize(int num); // 重新指定容器的長度為num, 若容器邊長,則以默認值填充新位置, 如果容器變短,則末尾超出容器長度的元素被刪除
- resize(int num, elem); // 重新指定容器的長度為num, 若容器邊長, 則以elem值填充新位置,如果容器變短,則末尾超出容器長度的元素被刪除
4. vector插入與刪除
函數原型:
- push_back(elem) //尾部插入元素elem
- pop_back() // 刪除最后一個元素
- insert(const_interator pos, elem) //迭代器指向位置pos插入元素elem
- insert(const_interator pos, int count, elem) //迭代器指向位置pos插入count個元素elem
- erase(const_iterator pos) // 刪除迭代器指向的元素
- erase(const_iterator start, const_iterator end) // 刪除迭代器從start到end之間的元素
- clear();
5. vector數據存取
對vector中數據的存取操作
函數原型:
- at(int idx) //返回索引 idx 所指的數據
- operator[] // 返回索引 idx 所指的數據
- front() // 返回容器中第一個數據元素
- back() // 返回容器中最后一個數據元素
6. vector互換容器
實現兩個容器內元素進行互換
函數原型:
- swap(vec); // 將vec中元素與本身的元素互換
總結:可以利用swap收縮內存
7. 預留空間
功能:減少vector在動態擴展容量時的擴展次數
函數原型:reserve(int len); //容器預留len個元素長度,預留位置不初始化,元素不可訪問。
注意是reserve 預留 不是 reverse 反轉
vector<int> v1; v1.reserve(100000); int* p = nullptr; int num = 0; cout << v1.capacity() << endl; // 100000 for (int i = 0; i < 100000; i++) {v1.push_back(i);if (p != &v1[0]) {p = &v1[0];num++;} } cout << num << endl;3.3 deque容器
1. 基本概念
功能:
雙端數組,可以對 頭端和尾端 進行插入刪除操作
deque與vector區別:
- vector 對于頭部插入刪除效率低,數據量越大,效率越低
- deque相對而言,對頭部插入刪除速度會比vector快
- vector訪問元素時的速度會比deque快,這和兩者內部實現相關
deque內部工作原理:
deque內部有個中控器,維護每段緩沖區的內容,緩沖區存放真實數據。
中控器維護的是每個緩沖區的地址,使得使用deque時 像 一片連續的內存空間
(原理才想,鏈表,鏈接接多個成塊的內存空間)
- deque容器的迭代器也是支持隨機訪問
2. deque賦值操作
函數原型:
- deque& operator=(const deque & deq) //重載等號操作符
- assign(beg, end) // 將[beg, end)區間的數據拷貝賦值給本身
- assign(n, elem) // 將n個elem拷貝賦值給本身
3.deque大小操作
對deque容器的大小進行操作
函數原型:
- deque.empty()
- deque.size()
- deque.resize(num)
- deque.resize(num, elem)
注意: deque沒有容量capacity的成員
4. deque插入和刪除
函數原型:
兩端插入:
- push_back(elem)
- push_front(elem) // 從容器頭部插入一個數據 , 注意次序
- pop_back(elem)
- pop_front(elem)
指定位置操作:
- insert(pos, elem)
- insert(pos, n, elem)
- insert(pos, beg, end)
- clear()
- erase(beg, end)
- erase(pos)
5. deque數據存取
對deque中數據的存取操作
函數原型:
- at(int idx)
- operator[]
- front()
- back();
6. deque排序
算法:
- sort(iterator brg, iterator end);// 對beg到end區間內元素進行排序
- 對于支持隨機訪問的迭代器容器,都可以利用sort算法直接對其進行排序
3.4 STL評委打分案例
案例描述:
有五名選手,選手ABCDE,10個評委分別對每一名選手打分,去掉最高分和最低分,取平均分。
實現步驟:
3.5 stack容器 棧容器
1. 基本概念
stack 是一種先進后出(FILO)的數據結構,它只有一個出口。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JzSXhZgy-1599351751836)(C:\Users\ypw\AppData\Roaming\Typora\typora-user-images\image-20200904204744032.png)]
棧中只有頂端的元素才可以被外界使用,因此棧不允許有遍歷行為。
2. stack常用接口
構造函數:
- stack<T> stk
- stack(const stack &stk);
賦值操作:
- stack& operator=(const stack& stk)
數據存取:
- push(elem)
- pop() // 從棧頂移除第一個元素
- top() // 返回棧頂元素
大小操作:
- empty()
- size() // 返回棧的大小
3.6 queue 容器 隊列
Queue是一種先進先出的數據結構
隊列容器只有隊頭和隊尾才可以被外界使用,因此隊列不允許有遍歷行為。
常用接口:
構造函數:
- queue<T> q
- queue(const queue& q)
賦值操作:
- queue& operator=(const queue &q)
數據存取:
- push(elem)
- pop()
- back() 返回最后一個元素
- front()
大小操作:
- empty()
- size()
3.7 list容器 鏈表
1. 基本概念
功能:將數據進行鏈式存儲,可以在任意位置上進行快速插入或刪除元素
鏈表(list):是一種物理存儲單元上非連續的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接實現的
鏈表的組成: 鏈表由一系列的 結點 組成
結點: 一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域
STL中的鏈表是一個 雙向循環鏈表
優點:
- 采用動態存儲分配,不會造成內存浪費和溢出
- 鏈表執行插入和刪除操作十分方便,修改指針即可,不需要移動大量元素
缺點:
- 容器遍歷速度,沒有數組快
- 占用空間比數組大,因為不僅包含數據域還包含指針域 指針4位
性質: 插入操作和刪除操作都不會造成原有list迭代器失效, 這在vector是不成立的。
2. 構造函數
- list<T> l;
- list(beg, end)
- list(n, elem)
- list(const list &l)
3. list賦值和交換
函數原型:
- assign(beg, end)
- assign(n, elem)
- list& operator=(const list &l);
- l.swap(l2)
4. 大小操作
函數原型:
- size()
- empty()
- resize(num);
- resize(num, elem)
5. 插入和刪除操作
函數原型:
- push_back(elem)
- pop_back()
- push_front()
- pop_front()
- insert(iterator_pos, elem)
- insert(iterator_pos, n, elem)
- insert(iterator_pos, beg, end)
- clear()
- erase(beg, end)
- erase(iterator_pos)
- remove(elem) // 刪除容器中所有與elem值匹配的元素
6. 數據存取
函數原型:
- front()
- back()
list本質是鏈表,不是用連續線性空間存儲數據,迭代器也不支持隨機訪問
it++; it--; // 支持雙向 it = it + 1 // 錯誤, 不支持隨機訪問7. 反轉和排序
函數原型:
- reverse() // 反轉列表
- sort(); // 算法鏈表排序, 成員函數,而不是全局函數
8. 排序案例
案例描述:將Person自定義數據類型進行排序,Person中屬性有姓名、年齡、身高
排序規則: 按照年齡進行升序,如果年齡相同按照身高進行降序
void creatList(list<Person>& L) {Person p1("A", 12, 175);Person p2("B", 10, 175);Person p3("C", 17, 175);Person p4("D", 11, 175);Person p5("A", 12, 176);Person p6("A", 11, 185);L.push_back(p1);L.push_back(p2);L.push_back(p3);L.push_back(p4);L.push_back(p5);L.push_back(p6);for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {cout << "姓名:" << it->m_Name << "年齡: " << it->m_Age << " 身高:" << it->m_Height << endl;} } bool comparePerson(Person& p1, Person& p2) {if (p1.m_Name == p2.m_Name) {if (p1.m_Age == p2.m_Age) {return p1.m_Height > p2.m_Height;}else {return p1.m_Age > p2.m_Age;}}return p1.m_Name > p2.m_Name; } int main() {list<Person> L;creatList(L);L.sort(comparePerson); // 指定排序規則for (list<Person>::iterator it = L.begin(); it != L.end(); it++) {cout << "姓名:" << it->m_Name << "年齡: " << it->m_Age << " 身高:" << it->m_Height << endl;}return 0; }總結:使用自定義類型時,要給sort指定排序規則
3.8 set/multiset 容器 集合容器
1. set/multiset基本概念
簡介:所有元素都會在插入時 自動被排序
本質:
- set/multiset 屬于 關聯式容器, 底層結構是用 二叉樹 實現
set 和 multiset 的區別:
- set不允許容器中有重復的元素
- multiset允許容器中有重復的元素
2. 構造和賦值
構造:
- set<T> st;
- set(const set &st)
賦值:
- set& operator=(const set &st)
插入:
- insert(elem)
3. set 大小和交換
- size()
- empty()
- swap(st)
4. 插入和刪除
- insert(elem);
- clear();
- erase(pos)
- erase(beg, end)
- erase(elem)
5. 查找和統計
- 對set容器進行查找數據以及統計數據
函數原型:
- find(key) // 查找key,若存在返回該key的元素迭代器,若不存在,返回set.end()
- count(key) // 統計key的個數
6. set和multiset
都包含在#include<set>頭文件中
區別:
- set不可以插入重復數據, 而multiset可以
- set插入數據的同時會 返回插入結果,表示插入是否成功
- multiset不會檢測數據,因此可以插入重復數據
7. pair 對組創建
- 成對出現的數據,利用對組可以返回兩個數據
兩種創建方式:
- pair<type, type> p (value1, value2)
- pair<type, type> p = make_pair(value1, value3)
8. 改變set的排序規則
利用 仿函數,可以改變排序規則。
仿函數: 重載了函數調用運算符(), 本質上是一個類型
// 內置類型指定排序規則 class MyCompare { public:bool operator()(int v1, int v2) const { // 注意vs2019 重載()要加const return v1 > v2;} };int main() {// 指定排序規則 在插入數據之前set<int, MyCompare>s2;s2.insert(10);s2.insert(20);s2.insert(0);s2.insert(50);s2.insert(14);//printSet(s2);return 0; } // 自定義數據類型指定排序規則 class Person { public:Person(string name, int age) {this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age; }; class MyCompare { public:bool operator()(Person p1, Person p2) { // 注意vs2019 重載()要加const return p1.m_Age > p2.m_Age;} };int main() {// 自定義數據類型 利用仿函數都會指定排序規則set<Person, MyCompare> s1;// 創建Person對象Person p1("劉備", 42);Person p2("司馬", 54);Person p3("曹操", 48);Person p4("孫權", 21);s1.insert(p1);s1.insert(p2);s1.insert(p3);s1.insert(p4);return 0; } // 該代碼g++編譯沒有問題, vs2019報錯3.9 map/multimap 容器 高性能 高效率
1. map的基本概念
- map中所有元素都是pair
- pair中第一個元素為key(鍵值),起到索引作用, 第二個元素為value(實值)
- 所有元素都會根據 元素的鍵值 自動排序
本質:
- map/multimap屬于關聯式容器,底層結構是用二叉樹實現
優點:
- 可以根據key值快速找到value值
map和multimap區別:
- map不允許容器有重復key值元素
- multimap允許容器中有重復key值元素
2. map構造和賦值操作
函數原型:
構造:
- map<T1, T2> mp; // map默認構造函數
- map(const map &mp);
賦值:
- map& operator=(const map &mp)
3. map的容器大小 和交換
函數原型:
- size()
- empty()
- swap(mp)
4. map的插入和刪除
函數原型:
- insert(elem)
- clear()
- erase(pos)
- erase(pos, beg, end)
- erase(key)
5.map查找和統計
函數原型:
- find(key); // 查找key是否存在, 不存在返回map.end()迭代器
- count(key);
6. map排序
利用仿函數,可以改變排序規則
7. 案例
4、函數對象
4.1 概念:
- 重載函數調用操作符()的類,其對象常稱為函數對象
- 函數對象使用重載的() 時,行為類似函數調用,也叫 仿函數
本質:
函數對象(仿函數)是一個類,不是一個函數。
4.2 函數對象使用
特點:
- 函數對象在使用時,可以像普通函數那樣調用,可有參數,可有返回值
- 函數對象超出普通函數的概念,函數對象可以有自己的狀態
- 函數對象可以作為參數傳遞
4.3 謂詞 Pred
概念:
- 返回 bool類型 的仿函數稱為謂詞
- 如果operator() 接受 一個參數,那么叫做 一元謂詞
- 如果operator() 接受兩個參數,那么叫做 二元謂詞
4.4 內聯函數對象 functional
概念: STL內建了一些函數對象
分類:
- 算術仿函數
- 關系仿函數
- 邏輯仿函數
用法:
- 這些仿函數所產生的對象,用法和一般函數完全相同
- 使用內建函數對象,需要引入頭文件#include<functional>
算術仿函數 :
-
實現四則運算
-
其中negate是一元運算
仿函數原型:
- template<class T> T plus<T> // 加法仿函數 注意只需要一個模板類型參數,兩個會報錯
- template<class T> T minus<T> // 減法仿函數
- template<class T> T multiplies<T> // 乘法仿函數
- template<class T> T divides<T> // 除法仿函數
- template<class T> T modules<T> // 取模仿函數
- template<class T> T negate<T> // 取反仿函數, 一元運算
關系仿函數
仿函數原型:
- template<class T> bool equal_to<T>
- template<class T> bool not_equal_to<T>
- template<class T> bool greater<T>
- template<class T> bool greater_equal<T>
- template<class T> bool less<T>
- template<class T> bool less_euqal<T>
邏輯仿函數
仿函數原型:
- template<class T> bool logical_and<T>
- template<class T> bool logical_or<T>
- template<class T> bool logical_not<T>
5、常用算法
概述:
-
算法主要由頭文件<algorithm> <functional> <numeric>
-
<algorithm>是STL頭文件中最大的一個,范圍涉及到比較、交換、查找、遍歷操作、復制、修改等
-
<numeric> 體積很小,只包括幾個在序列上進行簡單數學運算的模板函數
-
<functional> 定義了一些模板類,用以聲明函數對象
5.1 常用的遍歷算法
算法簡介:
-
for_each() // 遍歷容器
-
transform // 搬運容器到另一個容器
函數原型:
for_each(iterator beg, iterator end, _func) 第四個參數是普通函數或者仿函數,提供了一種伴隨操作的方法
// for_each(beg, end, 普通函數或者仿函數) #include<algorithm>void printVector(const int &val) {cout << val << " "; }class PrintVector { public:void operator()(int val) {cout << val << " ";} };int main(){vector<int> v;for (int i = 0; i < 10; i++) {v.push_back(rand()%21+10);}for_each(v.begin(), v.end(), printVector); // 注意這里是調用函數 因此只寫函數名代表函數地址cout << endl;for_each(v.begin(), v.end(), PrintVector()); // 注意這里是仿函數cout << endl;return 0; }函數原型:
transform(iterator beg1, iterator end1, iterator beg2, _func) 第四個參數函數或者仿函數
// transform class Transform{ public: int operator()(int val) {return val;} };int main(){vector<int> v;for (int i = 0; i < 10; i++) {v.push_back(rand()%21+10);}vector<int> target;target.resize(v.size()); // 目標容器要提前開辟空間, 否則會報錯transform(v.begin(), v.end(), target.begin(), Transform());return 0; }5.2 常用的查找算法
- find // 查找元素
- find_if // 按條件查找元素
- adjacent_find // 查找相鄰的重復元素
- binary_search // 二分查找
- count // 統計元素個數
- count_if // 按條件統計元素個數
1. find
函數原型:
- find(iterator beg, iterator end, value) // 按條件查找元素,找到返回指定位置迭代器, 找不到返回結束迭代器位置
2. find_if
函數原型:
find_if(beg, end, _Pred)
3. adjacent_find
- 查找相鄰重復元素
函數原型:
- adjacent_find(iterator beg, iterator end); // 返回相鄰元素的第一個元素的迭代器
4. binary_search
- 查找指定元素是否存在,返回值是bool類型,不是迭代器。效率較高
- 必須在 有序 序列中使用,無序序列中不可用
函數原型:
-
bool binary_search(iterator beg, iterator end, value)
返回true或者false
5. count
統計元素個數
函數原型:
-
count(iterator beg, iterator end, value)
返回統計元素出現的次數
6. count_if
按條件統計元素個數
函數原型:
-
count(iterator beg, iterator end, _Pred)
返回統計元素出現的次數
5.3 常用的排序算法
算法簡介:
- sort() // 對容器內元素進行排序
- random_shuffle // 洗牌, 指定范圍內的元素隨機調整次序
- merge // 容器元素合并,并存儲到另一個容器中
- reverse // 反轉指定范圍的元素
1. sort
對容器內元素進行排序
函數原型:
- sort(iterator beg, iterator end, _Pred)
2. random_shuffle
洗牌 指定范圍內元素隨機排序
函數原型:
-
random_shuffle(iterator beg, iterator end)
可以使用隨機種子srand((unsigned int)time(nullptr));
3. merge
兩個有序序列合并成一個有序序列,并存儲到另一個容器,
函數原型:
- merge(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest_beg)
4. reverse
對容器內元素進行反轉
函數原型:
-
reverse(iterator beg, iterator end);
反轉指定范圍的元素
5.4 常用的拷貝和替換算法
算法簡介:
- copy //容器內指定范圍的元素拷貝到另一個容器
- replace ://容器內指定舊元素,替換為新元素
- replace_if//容器內指定范圍的舊元素,滿足條件的替換為新元素
- swap // 互換兩個容器的元素
1. copy 可以用賦值=來替代
函數原型:
-
copy(iterator beg, iterator end, iterator dest)
copy(v1.begin(), v1.end(), v2.begin()); // 目標容器要提前開辟空間
2. replace
函數原型:
- replace(iterator beg, iterator end, oldvalue, newvalue)
3. replace_if
按條件替換,區間內滿足條件的替換為新元素
函數原型:
- replace_if(iterator beg, iterator end, _Pred, newvalue)
利用仿函數可以靈活篩選滿足條件
4. swap
互換兩個相同類型容器中的元素
函數原型:
- swap(container c1, container c2);
5.5 常用的算術生成算法
頭文件#include <numeric>
算法簡介:
- acccumulate // 計算容器元素累計和
- fill // 向容器中添加元素
1. accumulate
計算區間內元素累計總和
函數原型:
-
accumulate(iterator beg, iterator end, value)
value : 起始的累加值
2. fill
向容器中填充指定的元素
函數原型:
-
fill(iterator beg, iterator end, value)
value 填充值
5.6 常用的集合算法
算法簡介:
- set_intersection // 求兩個容器的交集
- set_union // 求兩個容器的并集
- set_difference //求兩個容器的差集
1. set_intersection
求兩個容器的交集
-
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
交集放在目標容器中,目標容器需要提前開辟空間,因為是交集取最小的
注意,該算法會返回交集末尾的迭代器。
2. set_union
求兩個容器的并集, 要求兩個集合必須是有序序列
-
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
并集放在目標容器中,目標容器需要提前開辟空間,因為是并集,所以取兩個容器之和
同樣要注意,要利用返回的迭代器來作為并集末尾的迭代器
3. set_difference
求兩個集合的差集, 兩個集合必須是有序序列
理解: v1, v2 v1中 不屬于v1,v2 交集 的元素; v2中 不屬于v1,v2 交集 的元素; 因此注意前后順序
-
set_intersection(iterator beg1, iterator end1, iterator beg2, iterator end2, iterator dest)
交集放在目標容器中,目標容器需要提前開辟空間,取兩個中最大的容器大小(個人感覺取前面的容器大小就行)
同樣要注意,要利用返回的迭代器來作為差集末尾的迭代器
枚舉類
C++ 其他知識點
1、cin
注意類型匹配
int a; cin >> a; // 如果鍵盤輸入的是字母, 則a=0。2、隨機數
// 隨機數種子 #include <ctime> srand((unsigned int)time(NULL)); rand()3、system
system("pause"); // 請按任意鍵繼續 system("cls"); // 清屏操作4、彩色字體輸出
cout << "\033[1;33m" << "輸出內容" << "\033[0m" << endl;5、C++編譯
使用g++命令編譯c++文件而不是gcc
總結
- 上一篇: 1929年泡沫破裂: 又一次“剪羊毛”行
- 下一篇: linux mysql5.7版本升级,l