文章目錄
- 函數重載
- 為什么C++支持重載,C語言不支持呢?
- extern “C”
- 引用再探
- 引用的特性
- 引用的使用場景
- 引用和指針
- 引用和指針的不同點:
- 內聯函數
- 什么是內聯函數?
- 內聯函數的特性
- 內聯函數的好處
- 類的內聯成員函數的聲明
- 內聯函數的使用
- constexpr函數
- 概念
- 特征
- 內聯函數和constexpr函數放在頭文件內
函數重載
在同一個作用域下,對于相同的函數名,函數的參數類型不同,參數順序不同,參數的個數不同, 都可以形成函數的重載(參數名不同,返回值不同不形成重載)
函數的重載主要用于處理功能相同,形參類型不同的數據。
void test(int i, int j)
{cout << "test" << endl;
}void test(double i, int j) // 類型不同
{cout << "test" << endl;
}void test(int j, double i) // 順序不同
{cout << "test" << endl;
}void test(double i, int j, int k) // 個數不同
{cout << "test" << endl;
}
為什么C++支持重載,C語言不支持呢?
因為windows對函數重載的處理更加復雜,所以這里用linux下的gcc和g++來看更加直觀。
首先我們要知道,鏈接器看到有函數被調用的時候,就會到符號表中去查找對應的函數名,來獲取函數的地址,再鏈接到一起
先看C語言是怎么處理的
通過反匯編我們可以看到,C語言并沒有對函數名進行處理,也就是說無論我們參數的個數,參數的類型,參數的順序怎么修改,它只認函數名,如果出現了第二個相同函數名的,就算重定義。
下面再看C++的:
這里可以看到,C++對函數名進行了處理,函數以_Z4開頭,接著是函數名,最后是所有參數的縮寫。
_Z是所有函數的前綴,4是函數名的字符個數,例如第一個_Z4testii則代表函數名為test,具有四個字符,參數類型縮寫分別是ii。
這也就是為什么返回值不同和參數名不構成重載的原因,它們不被作為對函數特征的處理。C++正是通過這種函數名修飾規則來實現函數的重載。
extern “C”
有時候我們在使用C++的時候,對于某些函數,想讓它按照C的風格來編譯,那么就在函數前加extern “C”,意思是告訴編譯器,將該函數按照C語言規則來編譯。
引用再探
引用的特性
- 引用在定義的時候必須初始化(因為引用是某個對象的別名,所以必須初始化)
- 一個對象可以有多個引用
- 一旦引用一個實體,就不能再引用別的實體(有點類似指針的頂層const)
引用的使用場景
- 作為參數
struct A
{int arr[1000000];
};void test(A& s1)
{}
假設我們存在一個超級大的結構體,如果我們直接將結構體傳過去的話,會產生一個臨時變量來將這個結構體拷貝到形參中,這是極大的開銷,但如果我們使用引用的話,傳的只是一個別名而已,所有的操作還是在結構體本身上進行的,但是需要注意的和上面一樣,如果我們要傳遞一個常量,就必須要在引用前加上const。
struct A
{int arr[1000000];
};void test (const A& s1)
{}
int main(int argc, char const *argv[])
{const A a = {10,324,32};test(a);return 0;
}
- 作為返回值
int& Add(int a, int b)
{int c = a + b;return c;
}int main()
{int& ret = Add(1, 2);Add(3, 4);cout << ret << endl;return 0;
}
對于這樣一個代碼,我們可能第一眼覺得ret會是3。
但是其實是7。
因為我們返回的是c的一個引用,但是c只存在于調用時的那個棧幀,調用結束后那個棧幀就會被銷毀,雖然銷毀后數據不會被清空,但是那片區域的訪問權限就會被放開,有可能會被下次調用的函數使用,也有可能會被其他的一個操作給使用,所以這是一種極為不安全的行為。
上面的7是第二次調用后修改了c的值。
所以,如果需要引用作為返回值,就必須保證出了函數作用域,返回的對象沒有歸還給系統,仍然存在。
以值作為參數或者返回值時,在傳參和返回的時候,都會傳遞或返回原變量的一個臨時的拷貝,這樣的效率是非常低下的,尤其是數據特別大的時候,但如果使用引用作為參數的話,就不會有這樣的問題。
引用和指針
語法概念上:引用是對象的一個別名,沒有獨立的空間,和其引用的實體共用一個空間。
但我們發現,引用其實和指針很像,它更像一個頂層const的指針,所以我們可以進入反匯編看看他們之間有沒有關系
int main()
{int x = 5;int& y = x;int* z = &x;return 0;
}
反匯編下我們可以看到,指針和引用在匯編下的實現是一模一樣的。
所以我們可以得出一個結論:引用是按照指針來實現的,在指針的基礎上又給他封裝了新的功能。
引用和指針的不同點:
- 引用在定義時必須初始化,指針沒有要求
- 引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型 實體
- 沒有NULL引用,但有NULL指針
- 在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32位平臺下占 4個字節)
- 引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
- 有多級指針,但是沒有多級引用
- 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
- 引用比指針使用起來相對更安全
內聯函數
什么是內聯函數?
用inline關鍵字修飾的函數就是內聯函數,在編譯時編譯器會將函數的代碼在調用內聯函數的地方展開,減去了函數壓棧的開銷,提升程序運行的效率(犧牲空間換取時間)。
例如這樣一個簡單的代碼
如果我們直接調用它
在匯編下可以看到,他會創建一個新的棧幀,將參數3,4壓棧,然后計算完再返回結果
而如果在函數前面加上inline使其變為內聯函數
這時再看,就會發現它直接把函數的代碼在調用處直接展開,不會再創建新的棧幀。
內聯函數的特性
- 內聯函數是一種用空間換時間的做法,省去了創建棧幀和壓棧的開銷,但也因此代碼很復雜和具有循環或遞歸之類的函數不適合作為內聯函數,就算聲明為內聯函數編譯器也會自動將其忽略。
- 內聯函數不能聲明和定義分離,因為一旦聲明為內聯函數,在調用的時候就會直接展開,沒有了函數的地址,就無法將其鏈接到定義的部分。
值得一提的是,內聯函數與C語言中的宏函數有些類似,雖然宏的性能不錯,但是因為宏缺乏類型的安全檢查和無法調試(在預處理階段就進行了宏替換),在C++中宏函數被內聯函數替代,宏常量定義被const取代。
內聯函數的好處
- 較之等價的表達式更易于閱讀
- 可以被其他應用重復利用,省去了重新編寫的代價
- 如需修改計算過程,顯然修改函數比先找到等價表達式所有出現的地方再逐一修改更容易。
類的內聯成員函數的聲明
我們可以在類內把 inline
作為聲明的一部分顯式地聲明成員函數,同樣的,也能在類的外部用 inline
關鍵字修飾函數的定義(當然在聲明和定義的地方同時說明 inline
也是合法,只是沒有必要)。
內聯函數的使用
- 濫用內聯將導致程序變得更慢;
- 最好不要內聯超過
10
行的函數; - 謹慎對待析構函數,析構函數往往比其表面看起來要更長,因為有隱含的成員和基類析構函數被調用;
- 內聯那些包含循環或
switch
語句的函數常常是得不償失 (除非在大多數情況下,這些循環或switch
語句從不被執行); - 有些函數即使聲明為內聯的也不一定會被編譯器內聯:比如虛函數和遞歸函數就不會被正常內聯。
- 通常,遞歸函數不應該聲明成內聯函數。(遞歸調用堆棧的展開并不像循環那么簡單, 比如遞歸層數在編譯時可能是未知的,大多數編譯器都不支持內聯遞歸函數)。
- 虛函數內聯的主要原因則是想把它的函數體放在類定義內,為了圖個方便,亦或是當作文檔描述其行為,比如精短的存取函數。
constexpr函數
概念
能用于常量表達式的函數
特征
- 函數的返回類型及所有形參的類型都得是字面值類型
- 函數體中必須有且只有一條return語句
- 編譯器把對constexpr函數的調用替換成其結果值(constexpr函數被隱式地指定為內聯函數)
- 函數體內允許包含 運行時不執行任何操作的語句
- 允許返回一個非常量,應用時編譯器會進行檢查。(constexpr不一定返回常量表達式)
內聯函數和constexpr函數放在頭文件內
和其他函數不同,內聯函數和constexpr可以在程序中多次定義(每一次展開就是一次定義)。但多個定義必須完全一致,基于這個原因,內聯函數和constexpr函數通常定義在頭文件。