本章總結了C++的基本知識點,下面分別從函數,const和volatile,C++調用C, const用法,左右和右值引用,new和delete等幾個方面來寫。

文章目錄

  • 2.1 函數
  • 2.1.1 函數調用過程
  • 函數的調用過程
  • 函數調用效率的問題
  • 函數聲明也可以直接給默認值
  • 2.1.2 inline內聯函數
  • 2.1.3詳解函數重載
  • 函數重載條件
  • 函數重載代碼實驗
  • 不能重載的問題
  • 2.2 volatile
  • 2.2.1 volatile作用
  • 2.2.2 語法
  • 2.2.3 volatile使用
  • 2.2.4 注意事項
  • 2.3 C++調用C語言
  • 2.4 const用法
  • 2.4.1 C++ const 與 普通變量區別?
  • 2.4.2 const和一級指針
  • 2.4.2.1 const修飾的量常出現的錯誤
  • 2.4.2.2 const和一級指針的結合:有4種情況
  • 2.4.3 const技巧
  • 2.4.4 const 與一級指針轉換公式
  • 2.4.5 const 與二級指針結合的3種轉換公式
  • 2.4.6 const二級指針的分析
  • 2.4.7 類型轉換面試題
  • 2.5左值引用和右值引用
  • 2.5.1 C++左值引用和指針的區別
  • 2.5.2 右值引用(可以引用立即數)
  • 2.5.3 const、指針、引用的結合使用
  • 2.5.4 指針和引用的轉換
  • 2.6 new和delete
  • 2.6.1 面試題:new delete 和 free malloc區別?
  • 2.6.2 面試題:C++有幾種new

2.1 函數

從三個方面介紹函數,分別是函數參數,inline內聯函數和函數重載。

2.1.1 函數調用過程

函數的調用過程

下面給出了Add的帶一個參數,帶兩個參數和不帶參數的調用過程.

int Add(int a = 10, int b = 20)  
{  
    return a + b;  
}  
int main()  
{  
    int a = 10;  
    int b = 20;  
    Add(a, b);  
    /*    實參入棧
    mov eax, dword ptr[ebp-8] 
    push eax 
 
    mov ecx, dword ptr[ebp-4] 
    push ecx 
 
    call Add    
    */  
  
    Add(a);  
    /* 
    mov ecx, dword ptr[ebp - 4] 
    push ecx 
    call Add 
    */  
  
    Add();  
    /* 
    push 14H 
    push 0Ah 
    call Add 
    */  
    return 0;  
  
}

從彙編層面可以看到調用參數時候,不帶參數比帶參數效率高。函數默認的參數的效率比帶參數時候,效率高。
注意:給默認值時候,從右向左給。

函數調用效率的問題

調用帶默認值的函數效率比調用不帶默認值的效率高,因為帶默認值參數的函數指令更少。

函數聲明也可以直接給默認值

聲明時候,也可以帶默認值,但是函數實現的時候,不需要帶默認值了。

int sum(int a = 10,int b = 20)

2.1.2 inline內聯函數

inline內聯函數和普通函數區別?
inline函數不生成相應的函數符號,也就沒有了普通函數的調用開銷。
比如調用int ret = Add(a, b);調用Add函數時,這是一個標準調用,需要參數壓棧,開闢Add函數棧幀,對棧幀初始化,執行函數中的指令,然後再回退到調用點,釋放棧幀;而inline內聯函數省去了函數調用開銷,在函數的調用點,直接把函數的代碼進行展開。

mov ebp,esp
sub esp,4ch
rep stos 0xCCCCCC  初始化


….


mov esp,ebp
mov

c++ 第二章知識梳理_#new和delete


注意:inline函數在release下才起作用,debug調試下不起作用。

2.1.3詳解函數重載

函數重載條件

C++代碼產生函數符號的時候,函數名+參數列表類型組成的!
C代碼產生函數符號的時候,函數名來決定!

函數重載代碼實驗

在同一個作用域內,一組函數,其中函數名相同(返回值不作為重載依據),參數列表的個數不同,這樣的一組函數稱為函數重載。

c++ 第二章知識梳理_#左右引用和右值引用_02

不能重載的問題

一組函數,函數名稱相同,作用域相同,僅僅是返回值不同,不可以作為重載的條件。因為C++在生成符號時,按照函數名+參數的方式生成函數的符號;而C語言則是按照函數名的方式生成符號。
從生成函數符號的條件可知,函數符號不依賴於返回值,所以返回值不能作為函數重載的條件。

2.2 volatile

本節參考:
https://blog.csdn.net/weixin_59409001/article/details/146112651?ops_request_misc=&request_id=&biz_id=102&utm_term=C++%20%E4%B8%AD%20volatile%20&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-0-146112651.142v102pc_search_result_base3&spm=1018.2226.3001.4187

2.2.1 volatile作用

前景知識:通常情況下,編譯器為了提高性能,會對代碼進行各種優化,如內存序優化,將變量的值緩存到寄存器中;
避免每次從內存中讀取等。
volatile 告訴編譯器,不要對volatile修飾的變量優化。

2.2.2 語法

volatile 數據類型 變量名;
volatile 數據類型 * 變量名;
例如:
volatile int id;
volatile int *p = &sensorValue;

2.2.3 volatile使用

下面例子,sensorRegistorValue 聲明為 volatile,這個值可能隨時被改變,如果不使用volatile關鍵字,編譯器編譯時可能將 sensorRegistorValue 進行優化,例如將值緩存到寄存器中,每次讀取時每次直接從寄存器中讀取,而不是每次都從內存中讀取。

volatile int sensorRegistorValue = 1;

    void readRegistorValue()
    {
        cout << "sensor value = " << sensorRegistorValue << endl;
    }

    void test()
    {
        sensorRegistorValue = 2;
        readRegistorValue(); // 改變值
    }

在多線程中使用

// 在多線程中使用
    volatile bool flag = false;

    void workerThread() {
        std::this_thread::sleep_for(std::chrono::seconds(2));
        flag = true;  // 修改標誌位
        std::cout << "Flag is set to true." << std::endl;
    }

    void test() 
    {
        std::thread t(workerThread);

        while (!flag) 
        {
            // 等待標誌位被設置
        }

        std::cout << "Main thread detected flag change." << std::endl;

        t.join();
        /*
        在這個示例中,flag 被聲明為 volatile,
        確保主線程在每次循環中都會從內存中讀取 flag 的最新值,而不是使用寄存器中的緩存值,
        從而保證主線程能夠及時響應 workerThread 對 flag 的修改。 
        
        */
    }

2.2.4 注意事項

1 雖然,volatile 可以確保變量的每次讀寫都直接訪問內存,但它並不能保證線程安全。
在多線程環境中,如果多線線程訪問同一個 volatile 變量,對同一個變量進行讀寫,並不能保證線程安全。
2 volatile 會禁止編譯器對修飾的變量進行優化,可能會影響程序性能。
因此,只有在確實需要防止編譯器優化,且變量的值可能會被外部因素改變的情況下才使用 volatile。
3 volatile 可以和 const 一起使用,例如 const volatile int,表示該變量的值不能被程序修改,但可能會被外部因素改變。

2.3 C++調用C語言

想在cpp中,調用C函數,就要在C++中編譯時候,告訴C++編譯器按照C語言方式進行編譯,所以在.cpp中的接口中加上下面這句話。

c++ 第二章知識梳理_#C++函數_03


c++ 第二章知識梳理_#new和delete_04

2.4 const用法

2.4.1 C++ const 與 普通變量區別?

  1. C++中的const 必須被初始化,如果初始化為一個立即數,那麼被const修飾的變量就是一個常量,可以作為數組的下標,是真正的const,const應該被存放在了.text段,不能作為左值。
  2. C++中如果被const修飾的變量初始化為變量,比如,const int a = b; 那麼a就是一個常變量,與C語言中的const相同;
  3. 編譯方式不同,C++中用到const修飾的變量,會被替換為常量。
  4. 代碼如下:
int main()
{
	const int a = 20;
	int *p = (int *)&a;
	*p = 30;
	cout << a << "," << *p << "," << *(&a) << endl;
	system("pause");
	return 0;
}

2.4.2 const和一級指針

2.4.2.1 const修飾的量常出現的錯誤

1.常量不能再作為左值
2.不能把常量的地址泄露給一個普通的指針 或者 普通的引用變量。

const int a = 10;
int *p = &a;   // 這種情況不被允許;

2.4.2.2 const和一級指針的結合:有4種情況

我們關注的是,const 與最近修飾的表達式,不關注int,double等類型。

(1) const int p = &a;
可以任意指向不同的int類型的內存,但是不能通過指針間接修改指向的內存的值;
(2) int const
p;

const 修飾的最近的表達式是 *p ,所以 (1)和 (2) 同;

(3) int *const p = &a; => p = &b 修改內存中的值:*p = 20;

const修飾的是p本身,這個指針p現在是常量,不能再指向其它變量,但是可以通過指針解引用修改指向的內存的值;

(4) const int const p = &a;
const如果右邊沒有指針
的話,const是不參與類型的;

這句話理解:第二個const指向了p,説明p不能再指向其他變量了;

第一個const類型為 const int * 表示指向的內容不能被修改;整體這句話相當於雙重修飾,更嚴格的限定。

c++ 第二章知識梳理_#c++_05


錯誤原因:const int ** 《====== int **

【深入理解一下int **, 內存模型如下:説明int a不能被修改,而實際情況是int a不是一個常整型】

c++ 第二章知識梳理_#new和delete_06

2.4.3 const技巧

記住:如果const右邊沒有指針,const 是不參與類型的

int a = 10;
int *p1 = &a;
const int *p2 = &a;  // const int *  <===  int  可以
int *const p3 = &a;  //  *p = &a;   可以 
int *const p3 = &a;  // int * const -> 等價於 int * -===>int *
int *p4 = p3 ;        // 同理,也是 int * 轉為 int *

2.4.4 const 與一級指針轉換公式

1 int* <= const int* 是錯誤的, 因為將常量地址暴露給普通指針,不能被允許;
2 const int * <= int* 是可以的!

2.4.5 const 與二級指針結合的3種轉換公式

int a = 10;
int *p = &a;
const int **q = &q;
const 與 二級指針的 3種表達式

const int **q;    //  const **q 不能被賦值,*q 和 q 都可以被賦值
int * const *q;	//  const 修飾 *q ,所以 *q不能被賦值,q 和 **q 可以被賦值
int **const q;    	//  const q 不能被賦值,**q *q都可以賦值

(1)int** <= const int** 是錯誤的!
(2) const int** <= int是錯誤的!
// Andy: const 二級指針結合後的轉換必須兩邊都有const
(3)int
<= intconst是錯誤的!
這是const和一級指針結合,相當於 int * 和 const * 這種方式;
【int * const *p, 主要看const後邊的表達式,p】
(4)int * const * <= int**是可以的!
const後面只有
就相當於,const * 和 int *結合,是可以的;
【相當於從int *  const int *】
(5)int * const * 《==== const int ** 不可以
【int * const *這個表達式相當於第一級指針是const int *類型的,第二級指針是int **;而const int **p的第一級指針是int *,第二級指針是const **; 】
【錯誤原因: const ** 不能轉為 int ** 】

2.4.6 const二級指針的分析

int a = 10;
int *p = &a;
const int **q = &p;  // 這句話錯誤,把常量的地址暴露給了變量。

*q <—> p,相當於同時指向了同一塊內存,這塊內存存放一級指針
const int * q = &p ;
如果上面第三句成立的話,相當於p可以修改
q的值,相當於把常量的地址暴露給了普通變量。
上面代碼有兩種修改方式:

int a = 10;
const int *p = &a;
const int **q = &p;

或者:

int a = 10;
int *p = &a;
const int * const*q = &p;  // 不暴露自己的常量地址,

2.4.7 類型轉換面試題

(1)

int a = 10;
const int p = &a; // const int <= int* 可以
int q = p; // int <= const int* ,這是錯誤的,*q = 20;

(2)

int a = 10;
int const p = &a; // const p 相當於 int <= int可以
int q = p; // int <= int
可以

(3)考const是否參與類型

int a = 10;
int *const p = &a; // int * — int * 可以
int *const q = p; // int * 《---- int 可以

(4)

int a = 10;
int const p = &a; // int <= int*
const int q = p; // const int <= int* 可以

(5)const 內存暴露給了普通變量,有兩種修改方式,見上面的筆記

int a = 10;
int *p = &a;
const int **q = &p;
const int * const *q2 = &p; // 修改方式1

(6)

int a = 10;
int p = &a;
int * const
q = &p; // int const* <= int * 可以

(7)

int a = 10;
int p = &a;
int const q = &p; // int <= int
* 可以

(8)

int a = 10;
int * const p = &a; // int* <= int* 可以
int **q = &p; // <= const int ** <===== int * const * 不行

&p 相當於 int * const * p,相當於 const * 轉為 int *
(9)

int a = 10;
const int p = &a; // Andy: 這個合法
int * const
q = &p; // int * const * <== const int * *

&p相當於,const int **p,
int * const *q 相當於 const *p 一級指針,二級指針是int **;
const int **p 一級指針是int *, 二級指針 const int **;
錯誤原因:相當於 const ** 轉為 int **

2.5左值引用和右值引用

2.5.1 C++左值引用和指針的區別

  1. 引用是一種更安全的指針。因為引用必須被初始化,指向一塊內存空間,是這塊內存空間的別名;而指針初始可以為空,所以引用更安全一些;
  2. 引用只有一級引用;指針可以有多級指針,而引用只有一級引用。
  3. 定義一個引用變量和定義一個指針變量,其彙編指令一模一樣,如下圖所示。
Int a = 10;
Int *p = &a;
Int &b = a;

c++ 第二章知識梳理_#const_07


彙編指令過程:將a內存地址放入eax中;再將eax 放入b指向的內存中;

c++ 第二章知識梳理_#new和delete_08

2.5.2 右值引用(可以引用立即數)

1 int &&a = 10; int const &d = 10; 這兩句代碼都可專門用來引用立即數,指令的實現原理是:編譯器先為10開闢一個臨時變量,存放10,a在內存中存放的是10在內存中的臨時變量的地址。

c++ 第二章知識梳理_#左右引用和右值引用_09


2. 右值引用變量本身也是一個左值(因為為10開闢了內存空間,然後右值引用指向了該內存空間,所以右值引用相當於這塊沒有名字的內存空間的別名,所以右值引用可以作為左值使用)。

3. 不能用右值引用變量,引用左值。【因為右值引用相當於生成了一個臨時變量,而左值已經是個變量,不必生成臨時變量,所以右值引用不能引用左值】

2.5.3 const、指針、引用的結合使用

判斷下面指針和引用的轉換:
1

int a = 10;
int *p = &a;
const int *&q = p;   將引用轉為指針:int **q = &p; // *q = 
const int **q = &p;   //const int** <= int**  不可以

2

int a = 10;
const int *p = &a;
int *&q = p;      	//int** <= const int**   不可以

3

int a = 10;
int *const p = &a;						   
int *&q = p;  				    轉為
// int **q = &p;  -> int ** 《====== int * const *   又等於  int * 《--- const *

4 在內存0x0018ffff 寫入四字節的10

有三種方式:

c++ 第二章知識梳理_#C++函數_10

2.5.4 指針和引用的轉換

int a = 10;
	int *p = &a;
	int *&q = p;   // 將這句話轉為如下:
// int **q = &p  <----》 int * &q = p;

2.6 new和delete

2.6.1 面試題:new delete 和 free malloc區別?

1 malloc 和 free 稱為C的庫函數;new和delete稱為C++的運算符;
2 new不僅可以做內存開闢,還可以做內存初始化;下邊代碼,通過new為p1分配內存,並做初始化為20;

int *p1 = new int(20);
	delete p1;

delete時,調用析構函數。
3 malloc開闢內存失敗,通過返回值與NULL比較;而new 開闢內存失敗,會拋出bad_alloc異常;通過該類型做判斷.

2.6.2 面試題:C++有幾種new

1 初始化new

int *p1 = new int(20);  // 初始化20操作

2 不拋出異常,返回值為nulptr

int *p2 = new (nothrow) int;

3 常量new

const int *p3 = new const int(40);

4 定位new : 在指定的內存上開闢int空間,初始化50

int data = 0;
	int *p4 = new (&data) int(50);
	cout << "data:" << data << endl;