注意:
半缺省參數必須從右往左依次來給出,不能間隔著給
缺省參數不能在函數聲明和定義中同時出現
//a.h void TestFunc(int a = 10); // a.c void TestFunc(int a = 20) {} // 注意:如果生命與定義位置同時出現,恰巧兩個位置提供的值不同,那編譯器就無法確定到底該用那 // 個缺省值。
缺省值必須是常量或者全局變量
C語言不支持(編譯器不支持)
五:函數重載
自然語言中,一個詞可以有多重含義,人們可以通過上下文來判斷該詞真實的含義,即該詞被重載了。
比如:以前有一個笑話,國有兩個體育項目大家根本不用看,也不用擔心。一個是乒乓球,一個是男足。前
者是“誰也贏不了!”,后者是“誰也贏不了!”
函數重載:是函數的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的
形參列表(參數個數 或 類型 或 順序)必須不同,常用來處理實現功能類似數據類型不同的問題
示例:
int Add(int left, int right) { return left + right; } double Add(double left, double right) { return left + right; } long Add(long left, long right) { return left + right; } int main() { Add(10, 20); Add(10.0, 20.0); Add(10L, 20L); return 0; }
注:函數是否重載一定是在函數名相同下關于函數參數是否不同(函數參數的類型,個數,順序三者滿足其中之一即可)
名字修飾
為什么C++支持函數重載,而C語言不支持函數重載呢?
在C/C++中,一個程序要運行起來,需要經歷以下幾個階段:預處理、編譯、匯編、鏈接。
當前a.cpp中調用了b.cpp中定義的Add函數時:
編譯后鏈接前,a.o的目標文件中沒有Add的函數地址,因為Add是在b.cpp中定義的,所以Add的地址在b.o中
鏈接器看到a.o調用Add,但是沒有Add的地址,就會到b.o的符號表中找Add的地址,然后鏈接到一起
鏈接時,面對Add函數,連接器會根據編譯器自己的函數名修飾規則去找對應出現的函數,而C/C++的命名修飾是不同的
示例:使用gcc演示修飾后的函數名字
說明:在linux下,采用gcc編譯完成后,函數名字的修飾沒有發生改變
C不支持函數重載:
如果有重載函數(函數名相同,參數不同),根據C語言的名字修飾規則,那么在編譯后生成的符號表則會存在多個相同的函數名,在鏈接對應函數的地址時則會有歧義,無法鏈接成功,也就無法支持函數重載
采用C++編譯器編譯后結果
說明:在linux下,采用g++編譯完成后,函數名字的修飾發生改變,編譯器將函數參數類型信息根據規則添加到修改后的名字中
C++支持函數重載:
[ ] 在鏈接對應函數地址時,其函數名字修飾規則會根據參數生成不同的函數名字,從而使得呢能夠成功找到對應函數地址,并連接成功,也就支持了函數重載
注:windows命名規則比linux復雜,但本質上原理都是一致的;也因為函數名字修飾的規則,函數重載要求參數不同,而跟返回值沒關系
extern “C”
有時候在C++工程中可能需要將某些函數按照C的風格來編譯,在函數前加extern “C”,意思是告訴編譯器,將該函數按照C語言規則來編譯
例:
tcmalloc是google用C++實現的一個項目,他提供tcmallc()和tcfree兩個接口來使用,但如果是C項目就沒辦法使用,那么他就使用extern “C”來解決
例:
extern "C" int Add(int left, int right); int main() { Add(1,2); return 0; } //鏈接時報錯:error LNK2019: 無法解析的外部符號_Add,該符號在函數 _main 中被引用
六:引用
概念
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它
引用的變量共用同一塊內存空間。
類型& 引用變量名(對象名) = 引用實體;
void TestRef() { int a = 10; int& ra = a;//<====定義引用類型 printf("%p\n", &a); printf("%p\n", &ra); }
注意:引用類型必須和引用實體是同種類型的
引用特性
引用在定義時必須初始化
一個變量可以有多個引用
引用一旦引用一個實體,再不能引用其他實體
引用和指針的區別
在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間。
int main() { int a = 10; int& ra = a; cout<<"&a = "<<&a<在底層實現上實際是有空間的,因為引用是按照指針方式來實現的

引用和指針的不同點:
引用在定義時必須初始化,指針沒有要求
引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型
實體
沒有NULL引用,但有NULL指針
在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32位平臺下占
4個字節)
引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
有多級指針,但是沒有多級引用
訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
引用比指針使用起來相對更安全
內聯函數
概念
以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在***調用內聯函數的地方展開***,沒有函數壓棧的開銷,
內聯函數提升程序運行的效率。
特性
inline是一種以空間換時間的做法,省去調用函數額開銷。所以代碼很長或者有循環/遞歸的函數不適宜 使用作為內聯函數。
inline對于編譯器而言只是一個建議,編譯器會自動優化,如果定義為inline的函數體內有循環/遞歸等 等,編譯器優化時會忽略掉內聯。
inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址了,鏈接就會 找不到。
// F.h #include using namespace std; inline void f(int i); // F.cpp #include "F.h" void f(int i) { cout << i << endl; } // main.cpp #include "F.h" int main() { f(10); return 0; } // 鏈接錯誤:main.obj : error LNK2019: 無法解析的外部符號 "void __cdecl f(int)" (? //f@@YAXH@Z),該符號在函數 _main 中被引用
auto關鍵字(C++11)
C++11中,標準委員會賦予了auto全新的含義即:auto不再是一個存儲類型指示符,而是作為一個新的類型
指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期推導而得。
int TestAuto() { return 10; } int main() { int a = 10; auto b = a; auto c = 'a'; auto d = TestAuto(); cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; cout << typeid(d).name() << endl; //auto e; 無法通過編譯,使用auto定義變量時必須對其進行初始化 return 0; }
注意
使用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據初始化表達式來推導auto的實際類
型。因此auto并非是一種“類型”的聲明,而是一個類型聲明時的“占位符”,編譯器在編譯期會將auto替換為
變量實際的類型。
九:基于范圍的for循環(C++11)
范圍for的語法
在C++98中如果要遍歷一個數組,可以按照以下方式進行:
void TestFor() { int array[] = { 1, 2, 3, 4, 5 }; for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) array[i] *= 2; for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p) cout << *p << endl; } void TestFor() { int array[] = { 1, 2, 3, 4, 5 }; for(auto& e : array) e *= 2; for(auto e : array) cout << e << " "; return 0; } void TestFor(int array[]) { for(auto& e : array) cout<< e <對于一個有范圍的集合而言,由程序員來說明循環的范圍是多余的,有時候還會容易犯錯誤。因此C++11中
引入了基于范圍的for循環。for循環后的括號由冒號“ :”分為兩部分:
第一部分是范圍內用于迭代的變量,
第二部分則表示被迭代的范圍
void TestFor() { int array[] = { 1, 2, 3, 4, 5 }; for(auto& e : array) e *= 2; for(auto e : array) cout << e << " "; return 0; }
十:指針空值nullptr(C++11)
在良好的C/C++編程習慣中,聲明一個變量時最好給該變量一個合適的初始值,否則可能會出現不可預料的
錯誤,比如未初始化的指針。如果一個指針沒有合法的指向,我們基本都是按照如下方式對其進行初始化:
void TestPtr() { int* p1 = NULL; int* p2 = 0; // …… }
NULL實際是一個宏,在傳統的C頭文件(stddef.h)中,可以看到如下代碼:
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
可以看到,NULL可能被定義為字面常量0,或者被定義為無類型指針(void*)的常量。不論采取何種定義,在
使用空值的指針時,都不可避免的會遇到一些麻煩,比如:
void f(int) { cout<<"f(int)"<程序本意是想通過f(NULL)調用指針版本的f(int*)函數,但是由于NULL被定義成0,因此與程序的初衷相悖。
在C++98中,字面常量0既可以是一個整形數字,也可以是無類型的指針(void*)常量,但是編譯器默認情況下
將其看成是一個整形常量,如果要將其按照指針方式來使用,必須對其進行強轉(void *)0。
注意:
1. 在使用nullptr表示指針空值時,不需要包含頭文件,因為nullptr是C++11作為新關鍵字引入的。
2. 在C++11中,sizeof(nullptr) 與 sizeof((void)0)所占的字節數相同。
3. 為了提高代碼的健壯性,在后續表示指針空值時建議最好使用nullptr。*
總結
本章講的知識之所以比較雜和亂,是因為我們要先打好基礎,為了下一章的***類和對象***
感覺不錯的話,關注本博主!我會慢慢帶你學習C++的
C++ Linux
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。