C++中的重难点看这一篇就够了
sizeof()是一個運算符,不是一個函數
看程序效率的快慢,可以直接看其匯編語言程序的多少
擴展名:
c語言:cc++:cppJava:先有類,再有方法
c++完全兼容c語法
getchar()等待鍵盤輸入(如果敲回車,就會讀取鍵盤輸入)
函數重載(overload)
c語言不支持函數重載
兩個函數的函數名完全相同,但是函數參數類型,個數,順序不同
返回值類型不同,不能構成重載
隱式轉換(小轉大)也可在函數參數類型中適用,但要注意隱式轉換有可能產生二義性。
本質:
?采用了name mangling 或者叫 name decoration技術
C++編譯器默認會對符號(比如函數名)進行改編、修飾? 重載時會生成多個不同的函數名,不同編譯器(MSVC、g++)有不同的生成規則IDA pro 逆向工程 軟件
Debug模式:很多調試信息Release模式:去除調試信息,生成的可執行文件比較精簡、高效,會默認對函數進行優化visual stdio 2019可選擇不對其進行優化
默認參數
C++允許函數設置默認參數,在調用時可以根據情況省略實參。規則如下:
? · 默認參數只能按照右到左的順序
? · 如果函數同時有聲明、實現,默認參數只能放在函數聲明中
? · 默認參數的值可以是常量、全局符號(全局變量、函數名)
void(*p)(int) = test; //此語句的作用就是更換test函數的名稱,其中void指test函數的返回類型,int指test函數中參數類型,p為將要命名的函數名稱。 p(10); #include <iostream> using namespace std;void func(int v1, void(*p)(int)=test) {p(v1); }void test(int a) {cout << "test(int)-" << a << endl; }int main() {func(30);func(10, test);return 0; }如果函數的實參經常是一個值,可以考慮使用默認參數
函數重載 、默認參數可能會產生沖突、二義性(建議優先選擇使用默認參數)
例:
默認參數的本質
自動補充參數extern “C”
被extern "C"修飾的代碼會按照c語言的方式編譯
#include <iostream> using namespace std;extern "C" void func() {}extern "C" void func(int v) {}int main() {return 0; }寫法
extern "C" {void func(){}void func(int v){} }extern "C" void func() {}注:以上代碼報錯,因為C語言中不支持函數重載
如果函數同時有聲明和實現,要讓函數聲明被extern “C”修飾,函數實現可以不修飾。
#include <iostream> using namespace std;extern "C" void func();int mian() {return 0; }extern "C" void func() {} **如果相同的函數以不同的語言編譯,所代表的函數不相同**#include <iostream> using namespace std;void func(); extern "C" void func(int v);//除去參數V報錯原因與重載無關,是二義性int main() {func();func(10);return 0; }void func() {cout << "func()" << endl; }void func(int v) {cout << "func(int v)-"<<v << endl; }externC2-C、C++混合開發
由于C、C++編譯規則的不同,再C、C++混合開發時,可能會經常出現一下操作
extern 無法在C語言中使用
每個C++文件之前都隱含著一個定義 #define __cplusplus
可根據這個特點來使頭文件在不同的語言文件中判斷是否添加extern ”C”
如果定義了 __cplusplus這個宏,則extern "C"參與編譯
math.h頭文件的定義
#ifdef __cplusplus extern "C"{ #endifint sum(int v1, int v2);int delta(int v2, int v3);int divide(int v1, int v2);#ifdef __cplusplus } #endifC++可以不導入頭文件,但必須要寫聲明,頭文件其實就是聲明
重復包含頭文件,浪費編譯器工作時間
為了防止重復包含頭文件**#ifndef…#endif**
#ifndef 1 //為了防止重復定義頭文件#ifdef __cplusplus extern "C" { #endifint sum(int v1, int v2);int delta(int v2, int v3);int divide(int v1, int v2);#ifdef __cplusplus } #endif#endif // !1注意:防止重復包含頭文件的宏都是唯一的,一般使用__文件名
pragma once
我們經常使用#ifndef、#define、#endif來防止頭文件的內容被重復包含
#pragma once可以防止整個文件的內容被重復包含
使用:放在頭文件中最前面
區別:
1.#ifndef、#define、#endif受C\C++標準的支持,不受編譯器的任何限制
2.有些編譯器不支持#pragma once(較老編譯器不支持,如GCC3.4版本之前),兼容性不夠好
3.#ifndef、#define、#endif可以針對一個文件中的部分代碼,而#pragma once只能針對整個文件
內聯函數(inline function)
使用inline修飾的函數的聲明或者實現,可以使其變為內聯函數**
將函數展開為函數體代碼
#include <iostream> using namespace std;inline int sum(int v1, int v2); //將函數調用展開為函數體代碼inline void func() {cout << "func()" << endl;cout << "func()1" << endl; }int main() {func();/*這里的func()相當于cout << "func()" <<endl;*/int c = sum(10, 20);/*這里相當于int c = 10 + 20;*/cout << c << endl;return 0; }inline int sum(int v1, int v2) {return v1 + v2; }意義:
函數調用需要開辟棧空間,回收棧空間,內聯函數則不存在棧空間的開辟,不需要回收內存
什么時候使用內聯函數
? 適用于函數體積小
? 經常調用的函數
特點
? 編譯器會將函數調用直接展開為函數體代碼
? 可以減少函數調用的開銷
? 會增大代碼體積
注意
? 盡量不要內聯超過10行代碼的函數
? 有些函數即使聲明為inline,也不一定會被編譯器內聯,比如遞歸函數
內聯函數-----宏
#define add(v1,v2) v1+v2int main() {cout << add(10, 20) << endl;return 0; }對比:
? 內聯函數和宏,都可以減少函數調用的開銷
? 對比宏,內聯函數多了語法檢測和函數特性
#define add(v) v+vinlinde int add1(int v) {return v+v; }int main() {int a=10;cout<<add(++a)<<endl;//結果為24,相當于cout << ++a + ++a <<endl; cout<< add1(++a)<<endl;//結果為22 return 0;}表達式
C++有些表達式可以被賦值
int a=1; int b=2; (a=b)=4; //這里a=4,b=2const
? const 是常量的意思,被其修飾的變量不可修改
? 如果修飾的是類、結構體(的指針),其成員也不可修改
#include <iostream> using namespace std;struct Data {int year;int month;int day; };int main() {const int age = 10;const Data d = { 1200, 23, 2 };d.year = 15;cout << d.year << endl;//輸出錯誤return 0; }以下5個指針是什么含義?
int age=10;const int *p1=&age; //*p1是常量,p1可以改變 int const *p2=&age; //與p1沒區別 int * const p3=&age; //p3是常量,不能改變,*p3可以改變 const int * const p4=&age; //與p5沒區別 p4,*p4都是常量 int const *const p5=&age;const修飾的是其右邊的內容
struct Student {int age;};Student stu1={10}; Student stu2={20};const Student *pStu1=&stu1; //修飾 *pStu1 *pStu1 = stu2; //報錯 直接訪問 (*pStu1).age=30; //報錯 間接訪問 pStu1->age=30; //報錯 間接訪問 pStu1 = &stu2; //正確Student * const pStu2 = &stu2; //修飾pStu2 *pStu2=stu1; //正確 (*pStu2).age=30; //正確 pStu2->age=30; //正確 pStu2=&stu1; //報錯引用(Reference)
在c語言中,使用指針間接修改某個變量的值。
int age = 10;int height = 20;//定義了一個age的引用int& ref = age;int& ref1=ref;int& ref2=ref1;ref = 20;age += 30;age = height;age = 11;cout << height << endl;注意
引用相當于是變量的別名(基本數據類型、枚舉、結構體、類、指針、數組等,都可以由引用)
對引用做計算,就是對引用所指向的變量做計算
在定義的時候就必須初始化,一旦指向了某個變量,就不可以再改變,“從一而終”
可以利用引用初始化另一個引用,相當于某個變量的多個別名
不存在【引用的引用、指向引用的指針、引用數組】
引用的價值
比指針更安全、函數返回值可以被賦值
void swap(int &v1, int &v2) {int tmp = v1;v1 = v2;v2 = tmp; }int main() {int a = 10;int b = 20;swap(a, b);cout << "a=" << a << ",b=" << b << endl;return 0; }參數的引用是棧里的
引用的本質:
? 引用的本質就是指針,只是編譯器削弱了它的功能,所及引用就是弱化的指針
?
int age=10;//*p就是age的別名 int *p=&age; *p=30;//ref就是age的別名 int &ref=age; ref=40;指針的大小和環境有關(X86,X64)
ASLR 讓應用的起始地址是隨機的
匯編
匯編語言的種類
? 8086匯編(16bit)
? X86匯編(32bit)
? X64匯編(64bit)
? ARM匯編(嵌入式、移動設備)
X64匯編根據編譯器的不同,有兩種書寫格式
? Intel
? AT&T
匯編語言不區分大小寫
學習匯編語言2大知識點:
1.匯編指令
2.寄存器
寄存器
? 通常,CPU會先將內存中的數據存儲到寄存器中,然后再對寄存器中的數據進行運算。
64bit:
通用寄存器:RAX \ RBX \ RCX \ RDX;
寄存器大小和環境有關,64位為8個字節
32bit:
通用寄存器:EAX \ EBX \ECX \EDX;
16bit:
通用寄存器:AX \ BX \ CX \DX
一般規律:
R開頭的寄存器是64bit的,占8個字節
**imm:**立即數
內聯匯編
#include <iostream> using namespace std;int main() {int a = 10;__asm{mov eax,a}return 0; }mov dest,src
將src的內容賦值給dest,類似于dest=src
[地址值]
? 1.中括號[]里面放的都是內存地址
2.word 是2字節,dword是4字節(double word),qword是8字節 (quard word)?
call 函數地址
add op1,op2
? 類似于op1=op1+op2
sub op1,op2
類似于op1=op1-op2inc op
? 自增,類似于op=op+1
dec op
? 自減,類似于op=op-1
jmp 內存地址
? 跳轉到某個內存地址去執行代碼
? j開頭的一般都是跳轉,大多數是帶條件的跳轉,一般跟test、cmp等指令配合使用
指針的匯編特征
lea eax,[ebp-0Ch] mov dword ptr [ebp-18h],eax引用的匯編特征
與指針相同
引用的補充
//結構體的引用 #include <iostream> using namespace std;struct Data {int year;int month;int day; };int main() {Data d = { 2021,07,20 };Data& ref = d;d.day = 2013;cout << d.day << endl;return 0; } //指針的引用int age = 10;int* p = &age;int* &ref = p;//引用的 數據類型是int**ref = 30;int height=30;ref=&height;//更改ref的地址數組名array其實是數組的地址,也是數組首元素的地址
數組名array可以看做是指向數組首元素的指針(int *)
//數組的引用int array[]={1,2,3}; //法一: int (&ref)[3]=array; ref[0]=10; //法二: int * const &ref = array;指針數組
數組里面可以存放3個int*? *int p;
? *int arr1[3]={p,p,p}
用于指向數組的指針
? 指向存放3個整數的數組
int (*arr2)[3];常引用(const Reference)
引用可以被const修飾,這樣就無法通過引用修改數據了,可以稱為常引用
const必須寫在&符號的左邊,才算是常引用
int age=10; const int &ref=age; ref=30; //這時就不能更改其值,意義:
? 只讀,阻止其他函數修改所引用的值
int func(const int &a); const int &ref=age;//與下面相同 int const &ref=age;//ref不可以修改指向,且不可以通過ref間接修改所指向的變量int &const ref=age;//ref不能修改指向,但是可以通過ref間接修改所指向的變量 **沒有意義** 相當于int * const ref=age;const引用的特點
? 可以指向臨時數據(常量、表達式、函數返回值等)
? 可以指向不同類型的數據
int age = 10;int a=1; int b=2;const int &ref1 = a + b; const int &ref = 30; const double *ref2 = age;? 作為函數參數時(此規則也適用于const指針)
? 可以接受const和非const實參(非const引用,只能接受非const實參)
? 可以跟非const引用構成重載
int sum(const int &v1,const int &v2) {return v1 + v2; }int sum(int &v1,int &v2) {return v1 + v2; }int main() {int a = 10;int b = 20;sum(a,b);const int c = 10;const int d = 20;sum(c,d);sum(10,20);//重要作用}當常引用指向了不同類型的數據時,就會產生臨時變量,即引用指向的并不是初始化時的那個變量
int age = 10; const int &rAge = age; age = 30;cout << " age is " << age <<endl; 30 cout << " rAge is " << rAge <<endl; 30 int age = 10; const double &rAge = age; age = 30;cout << " age is " << age <<endl; 30 cout << " rAge is " << rAge <<endl; 10面向對象
類和對象
類
? C++可以使用struct、class來定義一個類
使用struct定義類
#include <iostream> using namespace std;struct Person {int age; //成員變量void fun()//成員函數(方法){cout << "Person :: run()"<<age << endl;} };int main() {//利用類創建對象Person person; //這個對象的內存在棧空間person.age = 10;person.fun();return 0; }struct 和 class的區別
? struct的默認成員權限是public
? class的默認誠邀您權限是private
命名規范
1.全局變量:g_ 2.成員變量:m_ 3.靜態變量:s_ 4.常量:c_通過指針間接訪問person對象
#include <iostream> using namespace std;struct Person {int age; //成員變量void fun()//成員函數(方法){cout << "Person :: run()"<<age << endl;} };int main() {Person* p = &person;p->age = 20;p->fun();return 0; }上面代碼中的person 對象、p指針的內存都是在函數的棧空間,自動分配和回收的
64位環境下,person占用4個字節(Person類中只有一個int變量),p占用8個字節(64位環境下,地址占用8個字節)
實際開發中,使用class比較多
this this就是指針
? this指針存儲著函數調用者的地址
? this指向了函數的調用者
#include <iostream> using namespace std;struct Person {int m_age;void run(){cout << "Person::run()" << this->m_age << endl;//this是隱式參數} };int main() {Person person;person.m_age = 21;person.run();return 0; }作用:可以使不同的對象,調用同一個函數。
指針訪問的本質
lea eax,[ebp-14h] mov dword ptr [ebp-20h],eaxmov eax,dword ptr[ebp-20h] mov dword ptr [eax],0Ahmov eax,dword ptr [ebp-20h] mov dword ptr [eax],0Ah原理:如何利用指針間接訪問所指向對象的成員變量?
? 1.從指針中取出對象的地址
? 2.利用對象的地址 + 成員變量的偏移量計算出成員變量的地址
? 3.根據成員變量的地址訪問成員變量的存儲空間
思考:最后打印出來的 每個成員變量值是多少?將person.display()換成p->display()成員值會變成多少?
struct Person {int m_id;int m_age;int m_height;void display(){//eax == &person.m_age == &person+4//mov eax,dword ptr [this]//[eax],[eax + 4],[eax + 8]//[&person + 4],[&person +4 + 4],[&person + 4 + 8]cout<<" id= "<< m_id<<" ,age ="<<m_age<<",height = "<<m_height<<endl;} }Person person; person.m_id=10; person.m_age=20; person.m_height=30;Person *p=(Person *) &person.m_age; //eax == &person.m_age == &person + 4 //mov eax,dword ptr[p] //mov dword ptr [eax + 0],40 //mov dword ptr [&person + 4 + 0],40 p->m_id=40; //mov dword ptr [eax + 4],50 //mov dword ptr [&person + 4 + 4],50 p->m_age=50;//將person對象的地址傳遞給display函數的this person.display();//會將指針p里面存儲的地址傳遞給display函數的this //將&person.m_age傳遞給display函數的this p->display();答案:10,40,50;
? 40,50,xxx
0xcc
函數所在棧空間在使用之前存在垃圾數據,需要 用cc來填充
cc->對應 int3(中斷):起到斷點的作用
當其它語句不小心跳到函數棧空間時,就會在cc的作用下停止執行。
內存
封裝
成員變量私有化,提供公共的getter和setter給外界區訪問成員變量
#include <iostream> using namespace std;struct Person { private:int m_age; public:void setAge(int age){if (age <= 0) return;m_age = age;}int getAge(){return m_age;}};int main() {Person person;person.setAge(4);cout << person.getAge() << endl;return 0; }內存空間的布局
? 每個應用都有自己獨立的內存空間,其內存空間一般都有以下幾大部分
? 代碼段(代碼區)
? 用于存放代碼,只讀
? 數據段(全局區)
? 用于存放全局變量等
? 棧空間
? 每調用一個函數就會給它分配一段連續的棧空間,等函數調用 完畢后會自動回收這段棧空間
? 自動分配和回收
? 堆空間
? 需要主動申請和釋放
? 在程序運行過程中,為了能夠自由控制內存的生命周期、大小,會經常使用堆空間的內存。
堆空間的申請和釋放
malloc/free
#include <iostream> using namespace std;void test() {int* p = (int*)malloc(4);*p = 10;free(p); }void test2() {char *p=(char *)malloc(4);p[0]=1;p[1]=2;p[2]=3;p[3]=4;/**p=10;*(p+1)=11;*(p+2)=12;*(p+3)=13;*/free(p);//回收所開辟所有空間 }int main() {test();return 0; }在X86環境中
int *p =(int *)malloc(4); *p=10;指針p在棧空間中,其占四個字節,存儲的是堆空間所開辟的4個字節空間的首地址。
函數調用完畢后棧空間會自動消失,但是堆空間還在,需要free去釋放
new/delete
int* p = new int; *p = 10; delete p;new[]/delete[]
char* p = new char[4]; delete[] p;注意
? 申請堆空間成功后,會返回那一段內存空間的地址
? 申請和釋放必須是1對1的關系,不然可能會存在內存泄漏
現在很多高級編程語言不需要開發人員去管理內存(比如Java),屏蔽了很多內存細節,利弊同時存在
? 利:提高開發效率,避免內存使用不當或泄漏
? 弊:不利于開發人員了解本質,永遠停留在API調用和表層語法糖,對性能優化無從下手
**堆空間的初始化 **
memory set
int size = sizeof(int) * 10; int* p = (int*)malloc(size); memset(p, 0, 40); //從p地址開始,將40個字節中每一個字節設置為0; int *p0 = new int; //沒有初始化 int *p1 = new int(); //初始化為0 int *p2 = new int(5); //初始化為5 int *p3 = new int[3]; //數組元素未被初始化 int *p4 = new int[3](); //3個數組元素都被初始化為0 int *p5 = new int[3]{}; //3個數組元素都被初始化為0 int *p6 = new int[3]{5};//數組的首元素被初始化為5,其他為0memset函數是將較大的數據結構(比如對象、數組等)內存清零的比較快的方法
Person person; person.m_id=1; person.m_age=20; person.m_height=180; memset(&person,0,sizeof(person));Person persons[]={{1,20,180},{2,25,165},{3,27,170}}; memset(persons,0,sizeofz(persons));對象的內存
? 對象的內存可以存在于3種地方
全局區(數據段):全局變量
棧空間:函數里面的局部變量
堆空間:動態申請內存(malloc、new等)
//全局區 Person g_person;int main() {//棧空間Person person;//堆空間Person *p=new Person;return 0; }構造函數(Constructor)
構造函數(也叫構造器),在對象創建的時候自動調用,一般用于完成對象的初始化工作。
#include <iostream> using namespace std;struct Person {int m_age;Person(){m_age = 0;cout << "Person()" << endl;}Person(int age){m_age = age;cout << "Person(int age)" <<age<< endl;}};int main() {Person person;Person person2(20);Person person3(30);Person *p=(Person *)malloc(sizeof(Person));p->m_age=10;Person *p1 = new Person; return 0; }特點
? 1.函數名與類同名,無返回值(void都不能寫),可以有參數,可以重載,可以有多個構造函數
? 2.一旦自定義了構造函數,必須用其中一個自定義的構造函數來初始化對象
注意
? 棧空間和堆空間的創建對象,都會調用構造函數
? 通過malloc分配的對象,不會調用構造函數(malloc只會申請堆空間)
默認情況下,編譯器會為每一個類生成空的無參的構造函數 錯誤的
在某些特定的情況下,編譯器才會為類生成空的無參的構造函數
構造函數的調用
#include <iostream> using namespace std;struct Person {int m_age;int m_money;Person(){m_age = 0;cout << "Person()" << endl;}Person(int age){m_age = age;cout << "Person(int age)" <<age<< endl;}};Person g_person0; //Person()調用了無參 Person g_perosn1(); //函數聲明 Person g_person2(20); //有參int main() {Person person; //無參Person person1(); //函數聲明Person person2(20); //有參Person* p0 = new Person; //無參Person* p1 = new Person(); //無參Person* p2 = new Person(20); //有參/*4個無參,3個有參*/return 0; }成員變量的初始化
#include <iostream> using namespace std;struct Person {int m_age;}; //全局區:成員變量初始化為0 Person g_person;int main() {Person person;//棧空間:沒有初始化成員變量//堆空間:沒有初始化成員變量Person* p0 = new Person;//堆空間:成員變量初始化為0(有構造函數時不會初始化,編譯器認為構造函數會初始化變量)Person* p1 = new Person();Person *p4 = new Person[3]; //成員變量不會被初始化Person *p5 = new Person[3](); //3個Person對象的成員變量都初始化為0(沒有自定義構造函數)Person *p6 = new Person[3]{}; //3個Person對象的成員變量都初始化為0(沒有自定義構造函數)cout << g_person.m_age << endl;//cout << person.m_age << endl;cout << p0->m_age << endl;cout << p1->m_age << endl;return 0; }如果自定義了構造函數,除了全局區,其他內存空間的成員變量默認都不會被初始化,需要開發人員手動初始化
初始化方法
struct Person {int m_age;Person(){memset(this,0,sizeof(Person));} }析構函數(Destructor)
析構函數(也叫析構器),在對象銷毀的時候自動調用,一般用于完成對象的清理工作。
#include <iostream> using namespace std;struct Person {int m_age;//新的Person對象誕生的過程Person(){cout << "Person::Person()" << endl;}//一個Person銷毀的象征~Person(){cout << "~Person()" << endl;} };int main() {{//作用域Person person;}return 0; }特點
函數名以~開頭,與類同名,無返回值(void都不能寫),無參,不可以重載,有且只有一個
注意
通過malloc分配的對象free的時候不會調用析構函數
Person *p = (Person*)malloc(sizeof(Person)); free(p);//此時不會調用析構函數構造函數、析構函數要聲明為public,才能被外界正常使用
內存管理:
#include <iostream> using namespace std;struct Car {int m_price;Car(){m_price = 0;cout << "Car::Car()" << endl;}~Car(){cout << "Car::~Car()" << endl;} };struct Person {int m_age;Car* m_car;//用來做初始化工作Person(){m_age = 0;m_car = new Car();//在堆空間,不會自動回收cout << "Person::person()" << endl;}//用來做內存清理工作~Person(){delete m_car;//這個位置適合cout << "Person::~person()" << endl;} };int main() {//內存泄漏:該釋放的內存沒有釋放{Person person;//delete person.m_car;//釋放內存,但這樣寫太麻煩}return 0; }//當棧空間person被回收之后,堆空間中的car不會被回收,所以在person中的析構函數中回收car
對象內部申請的堆空間,由對象內部回收
聲明和實現分離
具體參見實驗
#include <iostream> using namespace std;class Person { private:int m_age; public:void setAge(int age);int getAge();Person();~Person(); };void Person::setAge(int age) {m_age = age; }int Person::getAge() {return m_age; }Person::Person() {m_age = 0; }Person::~Person() {}int main() {getchar();return 0; }命名空間
? 可以避免命名沖突
#include <iostream> using namespace std;namespace MJ {int g_age;//它是個全局變量class Person{int m_money;int m_age;}; }class Person {int m_height; };int main() {//法一MJ::g_age=10;MJ::Person person;cout << sizeof(person) << endl;//法二using namespace MJ;g_age=10;Person person;using MJ::g_age;//只使用某一個getchar();return 0; }命名空間不影響布局
思考:
namespace MJ {int g_age; }namespace FX {int g_age; }using namespace MJ; using namespace FX;//這句代碼能通過編譯么? g_age=20; //不能,具有二義性命名空間的嵌套
namespace MJ {namespace SS{int g_age;} }int main() {MJ::SS::g_age = 10;using namespace MJ::SS;g_age = 20;using namespace MJ::SS::g_age;g_age = 30;return 0; }有個默認的全局命名空間,我們創建的空間默認嵌套在它里面
void func() {cout<<"func()"<<endl; }namespace MJ {void func(){cout<<"MJ::func()"<<endl;} };int main() {using namespace MJ;MJ::func();::func();//使用全局命名空間return 0; }命名空間的合并
namespace MJ {int g_age; }namespace MJ {int g_no; }//等價于namespce MJ {int g_age;int g_no; }實驗 聲明實現和分離
繼承
可以讓子類擁有父類所有成員函數。
#include <iostream> using namespace std;struct Person {int m_age;void run(){} };struct Student:Person {int m_age;int m_score;void run() {}void study() {} };struct Worker:Person {int m_age;int m_salary;void run(){} }; int main() {return 0; }C++中沒有基類
繼承之后對象的內存布局
struct Person {int m_age; };struct Student:Person {int m_no; };struct GoodStudent:Student {int m_money; }int main() {Person person;//4個字節Strdent stu; //8個字節GoodStudent gs;//12個字節 }在內存中父類的成員變量會排布在前面
成員訪問權限:
成員訪問權限、繼承方式有3種:
? public:公共的,任何地方都可以訪問(struct默認)
? protected:子類內部、當前類內部可以訪問
? private:私有的,只有當前類內部可以訪問(class默認)
#include <iostream> using namespace std;struct Person { public:int m_age;void run(){} };struct Student :protected Person {void study(){m_age = 10;} };struct GoodStudent :public Person {};int main() {Person person;person.m_age = 10; }子類內部訪問父類成員的權限,是以下2項中權限最小的那個
? 成員本身的訪問權限
? 上一級父類的繼承方式
開發中用的最多的繼承方式是public,這樣能保留父類原來的成員訪問權限
訪問權限不影響對象的內存布局
初始化列表
#include <iostream> using namespace std;struct Person {int m_age;int m_height;/*Person(int age,int height){m_age=age;m_height=height;}*/ //等價于Person(int age,int height):m_age(age),m_height(height)//初始化列表{} };int main() {Person person(18,180);getchar();return 0; }特點
? 一種便捷的初始化成員變量的方式
? 只能用在構造函數中
? 初始化順序只跟成員變量的聲明順序有關
初始化列表和默認參數的配合使用
struct Person {int m_age;int m_height;Person(int age=0,int height=0):m_age(age),m_height(height){} };int main() {Person person1;Person person2(17);Person person(18,180); }如果函數聲明和實現是分離的,初始化列表只能寫在函數的實現中
struct Person {int m_age;int m_height;Person(int age=0,int height=0) };Person(int age=0,int height=0):m_age(age),m_height(height){}int main() {Person person1;Person person2(17);Person person(18,180); }構造函數的互相調用:
構造函數調用構造函數必須寫在初始化列表當中,普通函數則不需要
struct Person {int m_age;int m_height;Person():Person(10,20){}Person(int age,int height);{m_age = age;m_height = height;} }**注意:**下面的寫法是錯誤的,初始化的是一個臨時對象
struct Person {int m_age;int m_height;Person(){Person(0,0);}Person(int age,int height):m_age(age),m_height(height){} }父類的構造函數:
如果父類沒有構造函數,子類不調用,如果有,子類則必須調用
#include <iostream> using namespace std;struct Person {int m_age;Person(){cout<<"Person::person()"<<end;}Person(int age){cout<<"Person::person(int age)"<<endl;} };struct Student:Person {int m_no;Student(){cout<<"Student::Student()"<<endl;} };int main() {Student student;return 0; }子類的構造函數會默認調用父類無參的構造函數
如果子類的構造函數顯示的調用了父類的有參構造函數,就不會再去默認調用父類的無參構造函數
如果父類缺少無參構造函數,子類的構造函數必須顯式調用父類的有參構造函數
class Person {int m_age; public:Person(int age):m_age(age){} };class Student:Person {int m_no; public:Student(int age,int no):m_no(no),Person(age){} }int main() {Student student(18,34); }析構函數的調用
class Person {int m_age; public:Person(){cout<<"Person::Person()"<<endl;}~Person(){cout<<"Person::~Person()"<<endl;} };class Student:Person {int m_no; public:Student(){//call Person::Person調用父類構造函數cout<<"Student::Student()"<<endl;}~Student(){cout<<"Student::~student()"<<endl;//call Person::~Person() 調用父類析構函數} }int main() {{Student student;}return 0; }多態
父類指針、子類指針
? 父類指針可以指向子類對象、是安全的,開發中經常用到(繼承方式必須是public)
? 子類指針指向父類對象是不安全的
#include <iostream> using namespace std;struct Person {int m_age; };struct Student :Person {int m_score; };int main() {//父類子針指向子類對象,父類指針只能訪問父類定義的變量,所以它是安全的Person* p = new Student();//而子類指針指向父類對象是不安全的,子類中的變量在父類中是不包含的return 0; }? 默認情況下,編譯器只會根據指針類型調用對應的函數,不存在多態
? 多態是面向對象非常重要的一個特性
? 同一操作作用于不同的對象,可以有不同的解釋,產生不同的執行結果
? 在運行時,可以識別出真正的對象類型,調用對應子類中的函數
多態的要素
? 子類重寫父類的成員函數(override),成員函數必須是虛函數
? 父類指針指向子類對象
? 利用父類指針調用重寫的成員函數
struct Animal {void speak(){cout<<"Animal::speak()"<<endl;}void run(){cout<<"Animal::run()"<<endl;} }struct dog:Animal {//重寫(覆寫、覆蓋、orrivade):要求返回值,函數名,參數要與父類相同void speak(){cout<<"dog::speak()"<<endl;}void run(){cout<<"Animal::run()"<<endl;} }struct cat:Animal {void speak(){cout<<"cat::speak()"<<endl;}void run(){cout<<"cat::run()"<<endl;} }void liu(Animal *p) {p->speak();p->run(); }int main() {liu(new dog());//無法實現多態cat *p=(cat *) new Dog();p->speak();//call cat::speakp->run();//call cat::runreturn 0; }虛函數
C++中的多態通過虛函數(virtual function)來實現
虛函數:被virtual修飾的成員函數
只要在父類中聲明為虛函數,子類中重寫的函數也自動變成虛函數(也就是說子類中可以省略virtual)
struct Animal {virtual void speak(){cout<<"Animal::speak()"<<endl;}virtual void run(){cout<<"Animal::run()"<<endl;} }struct dog:Animal {//重寫(覆寫、覆蓋、orrivade):要求返回值,函數名,參數要與父類相同void speak(){cout<<"dog::speak()"<<endl;}void run(){cout<<"Animal::run()"<<endl;} }struct cat:Animal {void speak(){cout<<"cat::speak()"<<endl;}void run(){cout<<"cat::run()"<<endl;} }void liu(Animal *p) {p->speak();p->run(); }int main() {Animal *p=new cat();p->speak();p->run();liu(new cat());liu(new dog());return 0; }虛表
? 虛函數的實現原理是虛表,這個虛表里面存儲著最終要調用的虛函數地址,這個虛表也叫作虛函數表
class Animal { public:int m_age;virtual void speak(){cout<<"Animal::speak()"<<endl;}virtual void run(){cout<<"Animal::run()"<<endl;} };class Cat:public Animal() { public:int m_life;void speak(){cout<<"Cat::speak()"<<endl;}void run(){cout<<"Cat::run()"<<endl;} };int main() {Animal *cat = new Cat();cat->m_age=20;cat->speak();cat->run();return 0; }所有的Cat對象(不管在全局區、棧、堆)共用一份虛表
如果類中有虛函數,則定義該類對象時以右邊new的對象類型為標準,如沒有,則以左邊對象類型為標準
多態使用的場合
一個父類下面包含了好幾個子類,每個子類都重寫了 父類中的方法
調用父類的成員函數
stuct Animal {virtual void speak(){cout<<"Animal::Speak()"<<endl;}virtual void run(){cout<<"Animal::run()"<<endl;} }struct Cat::Animal {void speak(){Animal::speak();//調用父類的成員函數} }虛析構函數
? 如果存在父類指針指向子類對象的時候,應該將析構函數聲明為虛函數(虛析構函數)
? delete父類 指針時,才會調用子類的析構函數,保證 析構的完整性
stuct Animal {virtual void speak(){cout<<"Animal::Speak()"<<endl;}virtual void run(){cout<<"Animal::run()"<<endl;}virtual ~Animal(){cout<<"Animal::~Animal"<<endl;} }struct Cat::Animal {virtual void speak(){cout<<"Cat::Speak()"<<endl;}virtual void run(){cout<<"Cat::run()"<<endl;}~Cat(){cout<<"Cat::~Cat()"<<endl;} }int main() {Animal *cat0=new Cat();cat0->speak;delete cat0; }純虛函數
? 純虛函數:沒有函數體且初始化為0 的虛函數,用來定義接口規范(類似于Java中的抽象類接口)
struct Animal {//動物的這些方法有實現是不合理的,所以將其設為純虛函數virtual void speak()=0; virtual void run()=0; }struct dog:Animal {void speak(){cout<<"dog::speak()"<<endl;}void run(){cout<<"Animal::run()"<<endl;} }struct cat:Animal {void speak(){cout<<"cat::speak()"<<endl;}void run(){cout<<"cat::run()"<<endl;} }void liu(Animal *p) {p->speak();p->run(); }int main() {Animal *p=new cat();p->speak();p->run();liu(new cat());liu(new dog());return 0; }抽象類(Abstract class)
? 含有純虛函數的類,不可以實例化(不可以創建對象)
? 抽象類可以包含非純虛函數、成員變量
? 如果父類是抽象類,子類沒有完全重寫純虛函數,那么這個子類依然是抽象類
多繼承
? C++允許一個類 可以有多個父類(不建議使用,會增加程序設計復雜度)
class Student { public:int m_score;void study(){cout<<"Student::study()"<<endl;} };class Worker { public:int m_salary;void work(){cout<<"Worker::work()"<<endl;} };class Undergraduate:public Student,public Worker { public:int m_grade;void play(){cout<<"Undergraduate::play()"<<endl;} };多繼承體系下的構造函數調用
struct Student {int m_score;Student(int score):m_score(score){}void study(){cout<<"Study::study()-score="<<m_score<<endl;} };struct Worker {int m_salary;Worker(int salary):m_salary(salary){}void work(){cout<<"Work::work()-salary="<<m_salary<<endl;} };struct Undergraduate:Student,Worker {int m_grade;// Undergraduate(int score,int salary,int grade)// :m_grade(grade),m_salary(salary),m_score(score){}Undergraduate(int score,int salary,int grade)//多繼承體系下的構造函數調用:m_grade(grade),Student(score),Worker(salary){}void play(){cout<<"Undergraduate::play()"<<m_grade<<m_salary<<m_score<<endl;} }虛函數
? 如果子類繼承的多個父類都有虛函數,那么子類就會產生對應的多張虛表
struct Student {virtual void study(){cout<<"Student::study()"<<endl;} };struct Worker {virtual void work(){cout<<"Worker::work()"<<endl;} };struct Undergraduate:Student,Worker {void study(){cout<<"Undergraduate::study()"<<endl;}void work(){cout<<"Undergraduate::work()"<<endl;}void play(){cout<<"Undergraduate::play()"<<endl;} }同名函數
class Student { public:void eat(){cout<<"Student::eat()"<<endl;} };class Worker { public:void eat(){cout<<"Worker::eat()"<<endl;} };class Undergraduate:public Student,public Worker { public:void eat(){cout<<"Undergraduate::eat()"<<endl;} };int main() {Undergraduate ug;ug.eat();ug.Student::eat();//調用父類中的eat()}同名成員變量
class Student { public:int m_age; };class Worker { public:int m_age; };class Undergraduate:public Student,public Worker { public:int m_age; };int main() {Undergraduate ug;ug.m_age=10;ug.Student::m_age=12;return 0; }菱形繼承
#include <iostream> using namespace std;struct Person {int m_age; };struct Student:Person {int m_score; };struct Worker:Person {int m_salary; };struct Undergraduate:Student,Worker {int m_grade; };int main() {Undergraduate ug;cout<<sizeof(ug)<<endl;//結果為20return 0; }菱形繼承帶來的問題:
? 最底下子類從基類繼承的成員變量冗余、重復
? 最底下子類無法訪問基類的成員,有二義性
虛繼承
? 虛繼承可以解決菱形繼承帶來的問題
class Person //Person 被稱為虛基類 {int m_age=1; };class Student:virtual public Person {int m_score=2; };class Worker:virtual public Person {int m_salary =3; };class Undergraduate:public Studetn,public Worker {int m_grade=4; };int main() {Undergraduate ug;cout<<sizeof(ug)<<endl;return 0; }多繼承的應用
#include <iostream> using namespace std;class JobBaomu { public:virtual void clean()=0;virtual void cook()=0; };class JobTeacher { public:virtual void playFootball()=0;virtual void playBaseball()=0; };class Student:public JobBaomu,public JobTeacher {int m_score; public:void clean(){}void cook(){} };class Worker {int m_salary; };int main() {/*兼職中心,招聘兼職,崗位如下:1.保姆:掃地、做飯2.老師:踢足球、打棒球應聘的角色1.學生2.上班族*/}靜態成員(static)
靜態成員:被static修飾的成員變量、函數
可以通過對象(對象.靜態成員)、對象指針(對象指針->靜態成員)、類訪問(類名::靜態變量名)
靜態成員變量
? 存儲在數據段(全局區、類似于全局變量),整個程序運行過程中只有一份內存,不需要創建對象就存在
? 對比全局變量,它可以設定訪問權限(public、protected、private),達到局部共享的目的
? 必須初始化,必須在類外面初始化,初始化時 不能帶static,如果類的聲明和實現分離(在實現.cpp中初始化)
靜態成員函數
? 內部不能使用this指針(this指針只能用在非靜態成員函數內部)
? 不能是虛函數(虛函數只能是非靜態成員函數)PS:虛函數是用于多態,通過父類指針調用虛函數
? 內部不能訪問非靜態成員變量、函數,只能訪問靜態成員變量、函數。
? 非靜態成員函數內部可以訪問靜態成員變量、函數。
? 靜態成員函數之間可以相互訪問。
? 構造函數和析構函數不能是靜態的。
? 當聲明和實現分離時,實現部分不能帶static。
#include <iostream> using namespace std;class Car { private:static int m_price; public:static void run(){cout<<"run()"<<endl;} };int Car::m_price=0;//初始化int main() {Car car1;car1.m_price=100;Car car2;car2.m_price=200;Car::m_price=400;Car*p = new Car();p->m_price=500;//靜態成員函數訪問方式Car.run();Car->run();Car::run();return 0; }應用
#include <iostream> using namespace std;class Car { private:static int ms_count; //統計創造Car對象的多少 public:Car(){//嚴格來說,這里需要考慮多線程安全問題ms_count++;}static int getCount() //可以不創建對象,通過類名直接訪問,獲得ms_count的值{return ms_count;}~Car(){ms_count--;} };int Car::ms_count=0;int main() {Car car;Car *p = new Car();return 0; }靜態成員經典應用----單例模式
單例模式:設計模式的一種,保證某個類永遠只創建一個對象
? 第一步:構造函數、析構函數私有化
? 第二步:定義一個私有的static成員變量指向唯一的那個單例對象
? 第三步:提供一個公共的訪問單例對象的接口
#include <iostream> using namespace std;Class Rocket { private:Rocket(){};//創建對象必須調用構造函數,一旦私有化就不能調用構造函數~Rocket(){};static Rocket *ms_rocket;//設置為靜態是為了保證只有一個Rocket類 public:static Rocket *sharedRocket(){//要考慮多線程安全if(ms_rocket==NULL){ms_rocket=new Rocket();}return ms_rocket;}static void deleteRocket(){//要考慮多線程安全if(ms_rocket!=NULL){delete ms_rocket;//將堆空間回收,但ms_rocket依然有值,不然有可能產生野指針。ms_rocket=NULL;}}void run(){cout<<"run()"<<endl;}};int main() {Rocket *p1=Rocket::shareRocket();//通過調用靜態函數來創建類Rocket *p2=Rocket::shareRocket();Rocket *p3=Rocket::shareRocket();Rocket *p4=Rocket::shareRocket();Rocket::deleteRocket();cout<<p1<<endl;cout<<p2<<endl;cout<<p3<<endl;cout<<p4<<endl;return 0; }new、delete誤區
int *p = new int; *p = 10; delete p; //回收堆空間4個字節,里面的內容還會在,不會被清除回收堆空間內存:這塊堆空間內存可以重新被別人使用
const成員
const成員:被const修飾的成員變量、非靜態成員函數
const成員變量:
? 必須初始化(類內部初始化),可以在聲明的時候直接初始化賦值。
? 非static的const成員變量還可以在初始化列表中初始化
const成員函數(非靜態):
? const關鍵字寫在參數列表后面,函數的聲明和實現必須帶const
? 內部不能修改非靜態成員變量
? 內部只能調用const成員函數、static成員函數
? 非const成員函數可以調用const成員函數
? const成員函數和const成員函數構成重載
? 非const對象(指針)優先調用非const成員函數
? const對象(指針)只能調用const成員函數、static成員函數
#include <iostream> using namespce std;class Car { public:const int mc_price=0;//直接賦值Car():m_price(0){} //初始化列表中初始化void run() const{cout<<"run()"<<endl;} };int main() {return 0; }引用類型成員
? 引用類型成員變量必須初始化(不考慮static情況)
? 在聲明的時候直接初始化
? 通過初始化列表初始化
class Car {int age;int &m_price = age; public:Car(int &price):m_price(price){} };拷貝構造函數(Copy Constructor)
? 拷貝構造函數是構造函數的一種
? 當利用已存在的對象創建一個新對象時(類似于拷貝),就會調用新對象的拷貝構造函數進行初始化
? 拷貝構造函數的格式是固定的,接收一個const引用作為參數
#include <iostream> using namespace std;class Car() {int m_price;int m_length; public:Car(int price=0,int length=0):m_price(price),m_length(length){cout<<"Car(int price=0,int length=0)"<<endl;}//拷貝構造函數Car(const Car &car):m_price(car.m_price),m_length(car.m_length){cout<<"Car(const Car &car)"<<endl;}void display(){cout<<"price="<<m_price<<",length="<<m_length<<endl;} };int main() {Car car1;Car car2(100);Car car3(100,5);//利用已經存在的car3對象創建了一個car4新對象//car4初始化時會調用拷貝構造函數Car car4(car3);/*具體拷貝過程car4.m_price=car3.m_price;car4.m_length=car4.m_length;*/getchar();return 0; }? 如果沒有拷貝構造函數,在默認情況下,也會將原先對象中所有的值拷貝到新的對象中
調用父類的拷貝構造函數
class Person {int m_age; public:Person(int age):m_age(age){}Person(const Person &person):m_age(person.m_age){} };class Student:public Person {int m_score; public:Student(int age,int score):Person(age),m_score(score){}//調用父類的構造函數Student(const Student &student):Person(student),m_score(student,m_score){}//調用父類的拷貝構造函數 } int main() {Car car1(100,5);//調用 構造函數Car car2(car1);//調用 拷貝構造函數Car car3=car2;//等價于第二種寫法 調用拷貝構造函數Car car4;//調用 構造函數car4=car3;//這里并不會調用拷貝構造函數,僅僅是簡單的賦值操作 }如果子類沒有調用父類的拷貝構造函數會默認調用父類的構造函數,如果子類顯式的調用了父類的拷貝構造函數,則不會調用父類的構造函數
深拷貝、淺拷貝
? 淺拷貝(shallow copy)
? 指針類型的變量只會拷貝地址值,地址拷貝
? 編譯器默認的提供的拷貝是淺拷貝(shallow copy)
? 將一個對象中所有成員變量的值拷貝到另一個對象
? 如果某個成員變量是個指針,只會拷貝指針中存儲的地址值,并不會拷貝指針指向的內存空間
? 可能會導致堆空間多次free的問題
#include <iostream> using namespace std;class Car {int m_price;char *m_name; public:Car(int price=0,char *name=NULL):m_price(price),m_name(name){}void dispaly(){cout<<"price is"<<m_price<<",name is"<<m_name<<endl;} };int main() {const char *name="bmw";char name2[]={"b","m","w","\0"};//沒寫new就是在棧空間/*char name2[]={"b","m","w","\0"};// "\0"標志著字符串已經結束,必須要寫cout<<strlen(name)<<endl;//求字符串長度,"\0"并不被計算,但存儲時會將其一起存儲cout<<name2<<endl;*/Car *car=new Car(100,name2);//此時m_name存儲的是name2的地址,堆空間m_name指向棧空間name2/*堆空間指向棧空間是非常危險的,因為棧空間會被回收,那么堆空間所指向的位置就沒有內容,此時堆空間就成為了空指針*/return 0; }深拷貝(deep copy)
? 將指針指向的內容拷貝到新的存儲空間
? 在淺拷貝的基礎上,在堆空間繼續申請空間,賦值原先堆空間中的內容,內容拷貝
? 如果要實現深拷貝(deep copy),就需要自定義拷貝構造函數
? 將指針類型的成員變量所指向的內存空間,拷貝到新的內存空間
#include <iostream> using namespace std;class Car {int m_price;char *m_name;void copy(const char *name = NULL){if(name==NULL) return;//申請新的堆空間m_name = new char[strlen(name)+1]{};//大括號會將申請堆空間數據清零//拷貝字符串數據到新的堆空間strcpy(m_name;name);} public:Car(int price=0,const char *name=NULL):m_price(price){/*if(name==NULL) return;//申請新的堆空間m_name = new char[strlen(name)+1]{};//大括號會將申請堆空間數據清零//拷貝字符串數據到新的堆空間strcpy(m_name;name);*/copy(name);}Car(const Car &car):m_price(car.price){/*if(car.m_name==NULL) return;//申請新的堆空間m_name=new char[strlen(name)+1]{};//拷貝字符串數據到新的堆空間strcpy(m_name,car.m_name);*/copy(car.m_name);}void dispaly(){cout<<"price is"<<m_price<<",name is"<<m_name<<endl;}~Car(){if(m_name == NULL) return;delete[] m_name;m_name=NULL;} };int main() {Car car1(100,"bmw");Car car2=car1;//默認為淺拷貝car2.display();/*char name[]={'b','m','w','\0'};Car *car = new Car(100,name);car->display();*/return 0; }對象型參數和返回值
使用對象類型作為函數的參數或者返回值,可能會產生一些不必要的中間對象
class Car {int m_price; public:Car(){}Car(int price):m_price(price){}Car(const Car &car):m_price(car.m_price){} };void test1(Car car)//當傳入值時相當于 Car car = car1,相當于拷貝了一個對象,變為引用或指針即可 Car &car {}Car test2() {Car car(20);//此處構造一個對象return car;//相當于拷貝構造 };int main() {Car car1(10);test1(car1);Car car2=test2();//默認構造一個對象,此種調用了一次拷貝構造(編譯器做了優化,原來進行了兩次),一次普通構造Car car3(30);//構造一個函數,此種調用了一次拷貝構造,兩次普通構造car3 = test2();//并沒有創建一個新對象,只是簡單的復制 }匿名對象(臨時對象)
? 沒有變量名、沒有被指針指向的對象,用完后馬上調用析構
class Car {};int main() {Car();//匿名對象,創建結束后就會被回收 return 0; }隱式構造、explicit
?
隱式構造(轉換構造)
C++中存在隱式構造的現象:某些情況下,會隱式調用單參數的構造函數
可以通過explicit 禁止隱式構造
class Person {int m_age; public:Person(){cout<<"Person()-"<<this<<endl;}explicit Person(int age):m_age(age){cout<<"Person(int)-"<<this<<endl;}Person(const Person &person){cout<<"Person(const Person &person)-"<<this<<endl;}~Person(){cout<<"~Person()-"<<this<<endl;}void display(){cout<<"display()-age is"<<this->m_age<<endl;} };void test1(Person person=30) {}Person test2() {return 40; }int main() {/*Person p1;Person p2(10);Person p3=p2;*/Person p1=20;//調用單參數構造函數,Person(int age),等價于Person p1(20)test1(30);test2();Person p1;//先構造一個對象p1=40;//隱式構造一個對象return 0; }編譯器自動生成的構造函數
? C++的編譯器在某些特定的情況下,會給類自動生成無參的構造函數,比如
? 1.成員變量在聲明的同時進行了初始化
? 2.有定義虛函數
? 3.虛繼承其它類
? 4.包含了對象類型的成員,且這個成員有構造函數(編譯器生成或自定義)
? 5.父類有構造函數(編譯器生成或自定義)
總結:
? 對象創建時 ,需要做一些額外操作時(比如內存操作、函數調用),編譯器一般都會為其自動生成無參的構造函數
/* 很多教程都說:編譯器會為每一個類都生成空的無參的構造函數 錯誤的 */class Person { public: int m_price=5;//成員變量在聲明的同時進行了初始化//等價于/*int m_price;Person(int m_price=5){}*/virtual void run(){}}int main() {Person person;return 0; }友元
? 友元包括友元函數和友元類
? ???如果將函數A(非成員函數)聲明為類C的友元函數,那么在函數A內部就能直接訪問類C對象的所有成員
? 如果將類A聲明為類C的友元類,那么在類A的所有成員函數內部都能直接訪問類C對象的所有成員
class Point {friend Point add(Point,Point);//友元函數friend class Math;//友元類int m_x;int m_y; public:int getX(){return m_x};int getY(){return m_y};Point(int x,int y):m_x(x),m_y(y){}void display(){cout<<"("<<m_x<<","<<m_y<<")"<<endl;} };Point add(Point p1,Point p2) {//return Point(p1.getX()+p2.getX(),p1.getY()+p2.getY());//調用過于頻繁,浪費空間return Point(p1.m_x+p2.m_x,p1.m_y+p2.m_y);//定義為友元函數之后就可以被允許直接訪問私有成員變量 }class Math { public:void display(Point p1,Point p2){cout<<p1.m_x<<","<<p2.m_y<<endl;} }; int main() {Point p1(10,20);Point p2(20,30);return 0; }內部類
如果將類A定義在類C的內部,那么類A就是一個內部類(嵌套類)
內部類的特點:
? 支持public、protected、private權限
? 成員函數可以直接訪問其外部類對象的所有 成員(反過來則不行)
? 成員函數可以直接不帶類名、對象名訪問其外部類的static成員
? 不會影響外部類的內存布局
? 可以在外部類內部聲明,在外部類外面進行定義
#include <iostream> using namespace std;class Person {static int ms_price;int m_age; public: //protected只有類內部以及子類能夠使用,private只有類內部能夠使用class Car{int m_price;ms_price=0;void run(){cout<<m_age<<endl;}}; };int main() {Person::Car car1;//定義Car對象,return 0; }內部類-聲明和實現分離
class Point {class Math{void test();}; };void Poinnt::Math::test() {} class Point {class Math; };class Point::Math {voit test(){} }; class Point {class Math; };class Point::Math {voit test(); };voit Point::Math::test() {}局部類
在一個函數內部定義的類,稱為局部類
局部類的特點
? 作用域僅限于所在的函數內部
? 其所有成員必須定義在類內部,不允許定義static成員變量
? 成員函數不能直接訪問函數的局部變量(static變量除外)
局部變量加上static修飾,其作用范圍相當于全局變量
void test() {static int age=10;//局部類 class Car{void run(){}}; }int main() {return 0; }運算符重載(operator overload)
運算符重載(操作符重載):可以為運算符增加一些新的功能
全局函數、成員函數都支持運算符重載
class Point {friend Point add(Point,Point);friend void operator+(Point,Point);friend ostream &operator<<(ostream &,const const Point &);friend istream &operator>>(istream &,Point &);int m_x;int m_y; public:Point(int x,int y):m_x(x),m_y(y){}void display(){cout<<"("<<m_x<<","<<m_y<<")"<<endl;}Point(const Point &point){m_x=point.m_x;m_y=point.m_y;}const void operator+(const Point &point) const//第一個const是用來限制返回值是個常量對象,不能給它賦值,第二個const是用來限制此函數為const函數,保證返回值能夠再次調用此函數{return Point(this->m_x+point.m_x,this->m_y+point.m_y);}Point &operator+=(const Point &point){m_x+=point.m_x;m_y+=point.m_y;return *this;}bool operator==(const Point &point){return (m_x==point.m_x)&&(m_y==point.m_y);}const Point operator-() const{return Point(-m_x,-m_y);}Point &operator++()//前置{m_x++;m_y++;return *this;}Point operator++(int)//后置{Point old(m_x,m_y);m_x++;m_y++;return old;}};/*Point add(Point p1,Point p2) {return Point(p1.m_x+p2.m_x,p1.m_y+p2.m_y); }*//*void operator+(const Point &p1,const Point &p2) {return Point(p1.m_x+p2.m_x,p1.m_y+p2.m_y); }*///output stream -> ostream ostream &operator<<(ostream &cout,const Point &point) {cout<<"("<<point.m_x<<","<<point.m_y<<")";return cout; }//input stream -> istream istream &operator>>(istream &cin,Point &point) {cin>>point.m_x;cin>>point.m_y;return cin; }int main() {Point p1(10,30);Point p2(20,30);Point p3(30,30);//Point p3=add(p1,p2);//Point p3=p1+p2+p3;p3.display();p1+p2;//當重載寫到類中時,相當于p1.operator+(p2);p1+=p2;(p1+=p2)=Point(40,50);-p1;Point p3=-(-p1);cout<<p1;//等價于operator(cout,p1)cin>>p1;cin>>p1>>p2;return 0; }調用父類的運算符重載函數
#include <iostream> using namespace std;class Person { public:int m_age; Person &operator=(const Person &person){m_age=person.m_age;} };class Student:public Person { public:int m_score;Student &operator=(const Student &student){Person::operator=(student);//調用父類的運算符重載函數m_score=student.m_score;} }int main() {Student stu1;stu1.m_age=20;stu1.m_score=100;return 0; }仿函數
將一個對象當作一個函數一樣來使用
對比普通函數,它作為對象可以保存狀態;
#include <iostream> using namespace std;class Sum {int m_age; public:int operator()(int a,int b){return a+b;}void func(){}};int main() {Sum sum;cout<<sum(10,20)<<endl;return 0; }運算符重載注意點
有些運算符重載不可以被重載,比如
? 對象成員訪問運算符: .
? 域運算符:::
? 三目運算符:?:
? sizeof
有些運算符只能重載為成員函數,比如
? 賦值運算符:=
? 下標運算符:[]
? 函數運算符:()
? 指針訪問運算符:->
模板(template)
泛型,是一種將類型參數化以達到代碼復用的技術,C++中使用模板來實現泛型
模板的使用格式
? template <typename\class T>
? typename和class是等價的
模板沒有被使用時,是不會被實例化出來的
#include <iostream> using namespace std;class Point {friend ostream& operator<<(ostream& , const Point&);int m_x;int m_y; public:Point(int x, int y) :m_x(x), m_y(y) {}Point operator+(const Point& point){return Point(m_x + point.m_x, m_y + point.m_y);} };ostream& operator<<(ostream& cout, const Point& point) {return cout << "(" << point.m_x << "," << point.m_y << ")"; }//int add(int a, int b) //{ // return a + b; //} // //double add(double a, double b) //{ // return a + b; //} // //Point add(Point a, Point b) //{ // return a + b; //}template <typename T> T add(T a, T b)//泛型 {return a + b; }int main() {add<int>(10, 20);/*cout << add(10, 20) << endl;cout << add(1.5, 1.6) << endl;*/return 0; }模板的聲明和實現如果分離到.h和.cpp中,會導致鏈接錯誤
一般將模板的聲明和實現統一放到一個.hpp文件中
參數模板
template<class T> void swapValues(T &v1,T &v2) {T temp = v1;v1 = v2;v2 = temp; }int main() {int a=10;int b=20;swapValues<int>(a,b);swapValues(a,b); }多參數模板
template<class T1,class T2> void display(const T1 &v1,const T2 &v2) {cout<<v1<<endl;cout<<v2<<endl; }動態數組、類模板
具體內容請見 實驗 類模板
#include <iostream> using namespace std;class Point {int m_x;int m_y; public:Point(int x, int y) :m_x(x), m_y(y) {} };template<typename Element> class Array {//用于指向首元素Element* m_data;//元素個數int m_size;//容量int m_capacity;public:Array(int capacity=0){m_capacity = (capacity > 0) ? capacity : 10;//申請堆空間m_data = new Element[m_capacity];}~Array(){if (m_data == NULL) return;delete[] m_data;}void add(Element value){if (m_size == m_capacity){//擴容/** 1.申請一塊更大的存儲空間* 2.將就空間的數據拷貝到新空間* 3.釋放舊空間*/cout << "空間不夠" << endl;return;}m_data[m_size++] = value;}Element get(int index){if (index < 0 || index >= m_size){//報錯,拋出異常throw "數組下標越界";}return m_data[index];}int size(){return m_size;}Element operator[](int index){return get(index);} };int main() {Array<int> array(3);array.add(10);array.add(20);array.add(30);array.add(40);array.add(50);cout << array.get(0) << endl;cout << array[1] << endl;cout << array.size() << endl;Array<Point> array1(2);Point point(1, 2);array1.add(Point(1, 2));array1.add(Point(3, 4));return 0; }類型轉換
c語言風格的類型轉換符
? (type)expression
? type(expression)
int a=10; double d=(double) a; double d2=double(a);C++中有4個類型轉換符
? static_cast
? dynamic_cast
? reinterpret_cast
? const_cast
使用格式:XX_cast(expression)
int a = 10; double d = static_cast<double>(a);const_cast
一般用于去除const屬性,將const轉換成非const
const Person *p1 = new Person(); p1->m_age = 10;Person *p2 = const_cast<Person *>(p1); //將const轉換為非const,p1和p2的值相同 Person *p3=(Person *)p1; //與上面沒區別,這是c語言的寫法 p2->m_age = 20;dynamic_cast
一般用于多態類型的轉換,有運行時安全檢測,不安全時將指針賦值為空指針
class Person {virtual void run(){} };class Student:public Person{};class Car{};int main() {Person *p1 = new Person();Person *p2 = new Student();Student *stu1 = (Student *)p1;//不安全,子類指針指向父類,不會進行安全檢測Student *stu2 = dynamic_cast<Studnet *>(p2);//安全Car *c1 = (Car*)p1;//沒有檢測Car *c2 = dynamic_cast<Car *>(p2);//不安全,沒有賦值return 0; }static_cast
對比dynamic_cast,缺乏運行時安全檢測
不能交叉轉換(不是同一繼承體系的,無法轉換)
? 交叉轉換:沒有任何聯系的兩個類之間進行的轉換
常用于基本數據類型的轉換、非const轉成const
reinterpret_cast
屬于比較底層的強制轉換,沒有任何類型檢查和格式轉換,僅僅是簡單的二進制數據拷貝
Person *p1 = new Person(); Person *p2 = new Student(); Student *stu1 = reinterpret_cast<Student *>(p1); Student *stu2 = reinterpret_cast<Student *>(p2); Car *car = reinterpret_cast<Car *>(p1);int *p = reinterpret_cast<int *>(100); int num = reinterpret_cast<int>(p);int i = 10; double d1 = reinterpret_cast<double &>(i);//結果并不相等C++11新特性
auto
可以從初始化表達式中推斷出變量的類型,大大簡化編程工作
屬于編譯器特性,不影響最終的機器碼質量,不影響運行效率
auto i = 10;//int auto p = new Persondecltype
可以獲取變量類型
int a = 10; decltype(a) b = 20;//intnullptr
可以解決NULL二義性的問題
int *p1 = nullptr;//空指針快速遍歷
int array[]={1,2,3,4}; int array[]{1,2,3,4};//更簡潔的初始化數組方式 for(int item:array) {cout<<item<<endl; }Lambda表達式
有點類似于JavaScript中的閉包、iOS中的Block,本質就是函數
完整結構:
[capture list](params list)mutable exception->return type{function body}capture list:捕獲外部變量列表
params list:形參列表,不能使用默認 參數,不能省略參數名
mutable:用來使用是否可以修改捕獲的變量
exception:異常設定
return type:返回值類型
function body:函數體
有時可以省略部分結構
[capture list](params list)->return type{function body} [capture list](params list){function body} [capture list]{function body} int add(int v1,int v2) {return v1 + v2; }int sub(int v1,int v2) {return v1 - v2; }int multiple(int v1,int v2) {return v1 * v2; }int divide(int v1,int v2) {return v1 / v2; }int exec(int v1,int v2,int (*func)(int ,int )) {return func(v1,v2); }int main() {///cout<<exec(10,20,add)<<endl;cout<<exec(10,20,sub)<<endl;cout<<exec(10,20,multiple)<<endl;cout<<exec(10,20,divide)<<endl;//等價于cout<<exec(20,10,[](int v1,int v2){return v1+v2;})<<endl;cout<<exec(20,10,[](int v1,int v2){return v1-v2;})<<endl;cout<<exec(20,10,[](int v1,int v2){return v1*v2;})<<endl;cout<<exec(20,10,[](int v1,int v2){return v1/v2;})<<endl; ///([] //最簡單的lambda表達式{cout<<"func"<<endl;})(); //調用lambda表達式///void (*P)()=[]{cout<<"func"<<endl;} //存儲lambda表達式auto p=[]{cout<<"func"<<endl;//等價于上面}p();p();///auto p = [](int a,int b)->int//帶有返回值的lambda表達式{return a+b;}cout<<p(10,20)<<endl;///return 0; }變量捕獲
int main() {int a=10;//默認都是值捕獲auto func = [a] //變量捕獲{cout<<a<<endl;};//地址捕獲auto func = [&a] //變量捕獲{cout<<a<<endl;};//隱式捕獲(值捕獲)auto func = [=]{cout<<a<<endl;};//隱式捕獲(地址捕獲)auto func = [&]{cout<<a<<endl;}a=20;func();return 0; }mutable
int a=10; /*auto func = [&a] {a++; };*/ //等價于上面 auto func = [a]()mutable {a++;//11 };func() cout<<a<<endl;//10 return 0;C++14
泛型Lambda表達式
auto func = [](auto v1,auto v2){return v1+v1;}; cout<<func(10,20.5)<<endl;對捕獲的變量進行初始化
int a; auto func = [a = 10]() {cout<<a<<endl; }; func();cout<<a<<endl;C++17
可以進行初始化的if、switch語句
//變量a,b的作用域使它所在的if語句、以及其后面的if-else語句 if(int a=10;a>10) {a=1; } else if(int b=20;a>5&&b>10) {b=2;a=2; } else if(0) {b=3;a=3; } else {b=4;a=4; }//變量a的作用域是它所在的switch語句 switch(int a=10,a) {case 1:break;case 2:break;default:break; }異常
編程過程中的常見錯誤類型
? 語法錯誤
? 邏輯錯誤
? 異常
異常時一種在程序運行過程中可能會發生的錯誤(比如內存不夠)
異常沒有被處理,會導致程序終止
int main() {cout<<1<<endl;for(int i=0;i<9999;i++){//這句代碼可能會產生異常(拋出異常)、系統拋出異常try{int *p=new int[999999];}catch(){cout<<"產生異常:內存不夠用";break;}} }拋出異常
throw異常后,會在當前函數中查找匹配的catch,找不到就終止當前函數代碼,去上一層函數中查找。如果最終都找不到匹配的catch,整個程序就會終止。
int divide(int v1,int v2) {if(v2==0){throw "不能除以0"} }int main()c {int a=10;int b=0;try{cout<<divide(a,b)<<endl;}catch(const char * exception){cout<<"產生異常"<<exception<<endl;}catch(int exception){cout<<"產生異常"<<exception<<endl;} }異常的拋出聲明
為了增強可讀性和方便團隊協作,如果函數內部可能會拋出異常,建議函數聲明一下異常類型
//拋出任意可能的異常 void func1() {}//不拋出任何異常 void func2() throw() {}//只拋出int、double類型的異常 void func3() throw(int,double) {}自定義異常類型
//所有異常的基類 class Exception { public:virtual const char *what() const=0; };class DivideException:public Exception { public:const char *what() const{return "不能除以0";} };int divide(int v1,int v2) {if(v2==0){//拋出異常throw DivideException();}return v1/v2; }void test() {try{int a = 10;int b = 0;cout<<divide(a,b)<<endl;}catch(const DivideException &exception){cout<<"產生了異常(DivideException)"<<exceptioin.what()<<endl;} }攔截所有類型的異常
try {int a=10;int b=0;int c = dibide(a,b); } catch(...)//攔截所有類型的異常 {cout<<"出現異常"<<endl; }標準異常(std)
系統自帶的異常
智能指針(Smart Pointer)
傳統指針存在的問題
? 需要手動管理內存
? 容易發生內存泄漏(忘記釋放、出現異常等)
? 釋放之后產生野指針
智能指針就是為了解決傳統指針存在的問題
? auto_ptr:屬于C++98標準,在C++11中已經不推薦使用(有缺陷,不如不能用于數組)
? shared_ptr:屬于C++11標準
? unique_ptr:屬于C++11標準
#include <iostream> using namespace std;class Person { public:int m_age;Person(){cout << "Person()" << endl;}Person(int age):m_age(age){}~Person(){cout << "~Person()" << endl;}void run(){cout << "run()-" << m_age << endl;} };void test() {//Person* p = new Person(20);//可以理解為:智能指針p指向了堆空間的Person對象auto_ptr<Person> p(new Person(20));p->run(); }int main() {test();{//會報錯,auto_ptr不能指向數組auto_ptr<Person> p(new Person[10]);p->run()}{shared_ptr<Person[]> p(new Person[30]);//share_ptr 這樣使用cout<<p1.use_count()<<endl;//查看強引用計數shared_ptr<Person[]> p2=p;shared_ptr<Person[]> p3=p2;p->run(); }return 0; }智能指針的簡單自實現
template<class T> class SmartPointer {T *m_pointer; public:SmartPointer(T* pointer):m_pointer(pointer){}~SmartPointer(){if(m_pointer == nullptr) return;delete m_pointer;}T *operator->(){return m_pointer;} };? 智能指針就是對傳統指針的再度封裝
shared_ptr
shared_ptr的設計理念
? 多個shared_ptr可以指向同一個對象,當最后一個shared_ptr在作用域范圍內結束時,對象才會釋放
可以通過一個已存在的智能指針初始化一個新的智能指針
shared_ptr<Person> p1(new Person()); shared_ptr<Person> p2(p1);針對數組的用法
shared_ptr<Person> ptr1(new Person[5]{},[](Person *p){delete[] p;})原理
一個shared_ptr會對一個對象產生強引用(strong reference)
每個對象都有個與之對應的強引用計數,記錄著當前對象被多少個shared_ptr強引用著
當有一個新的shared_ptr指向對象時,對象的強引用計數就會+1
當有一個shared_ptr銷毀時(比如作用域結束),強引用計數就會-1
當一個對象的強引用計數為0時(沒有任何shared_ptr指向對象時),對象就會自動銷毀(析構函數)
shared_ptr的循環引用
只針對智能指針
? weak_ptr 會對一個對象產生弱引用
? weak_ptr 可以指向對象解決shared_ptr的循環引用問題
class Person;class Car { public:shared_ptr<Person> m_person = nullptr;//將其變為weak_ptr<Person> m_person = nullptr,不會使強引用加1Car(){cout<<"Car()"<<endl;}~Car(){cout<<"~Car()"<<endl;} };class Person { public:shared_ptr<Car> m_car = nullptr;Person(){cout<<"Person()"<<endl;}~Person(){cout<<"~Person()"<<endl;} };int main() {{//會導致內存泄漏shared_ptr<Person> Person(new Person());shared_ptr<Car> car(new Car());person->m_car = car;car->m_person = person;}return 0; }unique_ptr
unique_ptr也會對一個對象產生強引用,它可以確保統一時間只有一個指針指向對象
當unique_ptr銷毀時(作用域結束時),其指向的對象也就自動銷毀了
可以使用std::move函數轉移unique_ptr的所有權
class Person;class Car { public:shared_ptr<Person> m_person = nullptr;Car(){cout<<"Car()"<<endl;}~Car(){cout<<"~Car()"<<endl;} };class Person { public:shared_ptr<Car> m_car = nullptr;Person(){cout<<"Person()"<<endl;}~Person(){cout<<"~Person()"<<endl;} };int main() {//ptr1強引用著Person對象unique_ptr<Person> ptr1(new Person());//轉移之后,ptr2強引用著person對象unique_ptr<Person> ptr2 = std::move(ptr1);return 0; }總結
以上是生活随笔為你收集整理的C++中的重难点看这一篇就够了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 车险到期去哪里买 车保险到期了到哪里去买
- 下一篇: 2021年济民制药还能持有吗 2021年