日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++中的重难点看这一篇就够了

發布時間:2023/12/15 c/c++ 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++中的重难点看这一篇就够了 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

sizeof()是一個運算符,不是一個函數

看程序效率的快慢,可以直接看其匯編語言程序的多少

擴展名:

c語言:cc++:cpp

Java:先有類,再有方法

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; }

    如果函數的實參經常是一個值,可以考慮使用默認參數

    函數重載 、默認參數可能會產生沖突、二義性(建議優先選擇使用默認參數)
    例:

    void display(int a,int b=20) {cout<<"a is"<<a<<endl; }void display(int a) {cout<<"a is"<<a<<endl; }int main() {display(10);//該語句會產生二義性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 } #endif

    C++可以不導入頭文件,但必須要寫聲明,頭文件其實就是聲明

    重復包含頭文件,浪費編譯器工作時間

    為了防止重復包含頭文件**#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=2

    const

    ? 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-op2

    inc 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,其他為0

    memset函數是將較大的數據結構(比如對象、數組等)內存清零的比較快的方法

    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 Person

    decltype

    可以獲取變量類型

    int a = 10; decltype(a) b = 20;//int

    nullptr

    可以解決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++中的重难点看这一篇就够了的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。