C++ 基础 : 函数重载、引用、内联函数、auto、范围for循环
- 函數重載
- 引用
- 內聯函數
- auto
- 范圍for循環
函數重載
C++中引入了一個新特性,函數重載。
在同一個作用域下,對于相同的函數名,函數的參數不同,不同類型的參數順序不同,參數的個數不同,都可以形成函數的重載(參數名不同,返回值不同不形成重載)
函數的重載主要用于處理功能相同,類型不同的數據。
例如
int test(int i, int j) {cout << "test" << endl; }int test(double i, int j) {cout << "test" << endl; }int 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語言規則來編譯。
引用
引用的概念
引用是給某一個對象起了另外一個名字,可以通過這個別名來對原對象進行操作,同時編譯器不會對引用變量開辟空間,它和它所引用的對象共用一個空間。
用法:類型 & 引用對象名 = 引用實體
int main() {int i = 5;int& j = i;cout << i << ' ' << j << endl;j = 8;cout << i << ' ' << j << endl;return 0; }引用的特性
常引用
int main()
{const int i = 5;int& j = i;//錯誤的const int & k = i;正確的 }這里對常量i進行引用,因為i是個常量,所以它的值是無法修改的,所以我們使用普通的引用的時候,就會將它的權限放大,導致可以通過j來對i進行修改,這是不合理的,所以編譯器會報錯。只有使用常量引用才行
int main() {int x = 6;const int & y = x; }同時,我們用常量引用來引用這個x,x是可以修改的,而y無法修改,使權限收縮,變為只讀,所以這是可行的。
引用的跨類型
int main() {double x = 3.14;int& y = x;//錯誤的const int& z = x;//正確的return 0; }這里我們分別使用y和z來引用x,但是這時編譯器會提示,y報錯,z合法,同樣是跨類型,為什么會這樣呢?
因為當類型不同的時候會先用一個臨時量來引用x,然后再對這個臨時量進行引用
int main() {double x = 3.14;int& y = x;/*等價于const int &temp = x;int &y = temp;*/const int& y = x;/*等價于const int &temp = x;const int &z = temp;*/return 0; }如果是普通的引用,這時我們引用的實際上是temp,但是我們是想要對x進行修改的,但是這時不行的,因為臨時量具有常性,所以這種行為也是非法的。
如果是常量的引用,表明我們不會對x進行修改,所以就算引用的其實是臨時量temp,這個行為也是合法的
引用的使用場景
假設我們存在一個超級大的結構體,如果我們直接將結構體傳過去的話,會產生一個臨時變量來將這個結構體拷貝到形參中,這是極大的開銷,但如果我們使用引用的話,傳的只是一個別名而已,但是需要注意的和上面一樣,因為臨時量具有常性,所以如果我們要傳遞一個常量,就必須要在引用前加上const。
對于這樣一個代碼,我們可能第一眼覺得ret會是3
但是其實是7.
因為我們返回的是c的一個引用,但是c是只存在于調用時的那個棧幀的,調用結束后那個棧幀就會被銷毀,雖然銷毀后數據不會被清空,但是那片區域的訪問權限就會被放開,有可能會被下次調用的函數使用,也有可能會被哪里的一個操作給使用,所以這是一種極為不安全的行為,如果我們要返回一個引用的話,就必須要保證那個對象出了函數的作用域中還會仍然存在。
上面的7是第二次調用后修改了c的值。
所以,如果需要引用作為返回值,就必須保證出了函數作用域,返回的對象沒有歸還給系統,仍然存在。
以值作為參數或者返回值時,在傳參和返回的時候,都會傳遞或返回原變量的一個臨時的拷貝,這樣的效率是非常低下的,尤其是數據特別大的時候,但如果使用引用作為參數的話,就不會有這樣的問題。
引用和指針
前面我們說了,引用是對象的一個別名,沒有獨立的空間,和其引用的實體共用一個空間,但是這僅僅是語法概念上的。同時我們發現,引用其實和指針很像,它更像一個頂層const的指針,所以我們可以進入反匯編看看他們之間有沒有關系
int main() {int x = 5;int& y = x;int* z = &x;return 0; }
反匯編下我們可以看到,指針和引用在匯編下的實現竟然是一模一樣的。
所以我們可以得出一個結論,引用是按照指針來實現的,在指針的基礎上又給他封裝了新的功能
引用和指針的不同點:
內聯函數
用inline關鍵字修飾的函數就是內聯函數,在編譯時編譯器會將函數的代碼在調用內聯函數的地方展開,減去了函數壓棧的開銷,提升程序運行的效率
例如這樣一個簡單的代碼
如果我們直接調用它
在匯編下可以看到,他會創建一個新的棧幀,將參數3,4壓棧,然后計算完再返回結果
而如果在函數前面加上inline使其變為內聯函數
這時再看,就會發現它直接把函數的代碼在調用處直接展開,不會再創建新的棧幀。
內聯函數的特性
- 內聯函數是一種用空間換時間的做法,省去了創建棧幀和壓棧的開銷,但也因此代碼很復雜和具有循環或遞歸之類的函數不適合作為內聯函數,就算聲明為內聯函數編譯器也會自動將其忽略。
- 內聯函數不能聲明和定義分離,因為一旦聲明為內聯函數,在調用的時候就會直接展開,沒有了函數的地址,就無法將其鏈接到定義的部分。
值得一提的是,內聯函數與C語言中的宏函數有些類似,雖然宏的性能不錯,但是因為宏缺乏類型的安全檢查和無法調試(在預處理階段就進行了宏替換),在C++中宏函數被內聯函數替代,宏常量定義被const取代。
auto
在編程時我們常常需要把表達式的值賦給變量,但有時我們又不知道表達式的類型是什么,為了解決這個問題,C++11中引入了auto類型說明符,用它能夠讓編譯器代替我們來分析表達式的所屬類型。
因為auto需要讓編譯器通過初始值來推導變量的類型,所以auto必須要有初始值。
int main() {auto i = 2.7;cout << i <<"的類型為:" << typeid(i).name() << endl; }auto的使用規則
因為auto可以識別指針類型,所以加不加*都可以,但是引用必須加上&,因為C++沒有引用這個類型,引用只是一個修飾別名,所以它的本質還是int。
auto不能作為函數的參數
auto不能作為形參的類型,編譯器無法對i和j的類型進行推導。
auto不能直接用來聲明數組
auto一般會忽略頂層const
范圍for循環
對于一些有范圍的集合,我們可能不知道他的長度或者在使用循環的時候不小心越界,c++11為了解決這個問題,引入了范圍for循環。
循環分為兩部分,第一部分是變量的類型,第二部分是被迭代的范圍,兩者中間用:隔開.
int main() {vector<int> vec{ 1, 3, 5, 7, 9 };for (auto i : vec){cout << i << ends;}}
需要注意的是,這里的i是對原數據拷貝的一個臨時變量,如果要修改原數據的話需要改成&i(既對原數據的引用)
總結
以上是生活随笔為你收集整理的C++ 基础 : 函数重载、引用、内联函数、auto、范围for循环的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux 基础I/O :文件描述符,重
- 下一篇: C++ 类和对象(一):类的概念、类的访