1、函數基礎
典型的函數定義包括:返回類型、函數名、由0個或多個形參組成的列表以及函數體。
2、參數傳遞
形參初始化的機理和變量初始化一樣。
有兩種方式:引用傳遞和值傳遞
2.1 傳值參數
當形參是非引用類型時,形參初始化和變量初始化一樣,將實參的值拷貝給形參。
指針形參
當執行指針拷貝操作時,拷貝的是指針的值,拷貝之後,兩個指針是不同的指針。但通過指針可以修改它所指的對象。
2.2 傳引用參數
使用引用避免拷貝
拷貝大的類類型對象或者容器對象比較低效,甚至有的類類型根本就不支持拷貝操作。當某種類型不支持拷貝操作時,函數只能通過引用形參訪問該類型的對象。
使用引用形參返回額外信息
2.3 const形參和實參
當用實參初始化const形參時會忽略頂層const。因此,當形參有頂層const時,傳給它常量對象或者非常量對象都是可以的。
void fun1(const int i){.......} void fun2(int i){.....}
上述兩個函數不能算是重載,兩個函數是一樣的,程序會報錯,fun2重複定義了fun1.
指針或引用形參與const
可以使用非常量初始化一個底層const對象,但是反過來不行。同時一個普通的引用必須用同類型的對象初始化。
儘量使用常量引用
2.4 數組形參
數組有兩個重要的特性:
- 不允許拷貝
- 使用數組時會轉換成指針
儘管不能以值傳遞的方式傳遞數組,但是可以將形參寫成類似數組的形式
void print(const int*); void print(const int[]); void print(const int[10]); 以上三種形式的聲明等價
NOTE:當函數不需要對數組元素執行寫操作的時候,數組形參應該是指向const的指針。只有當函數確實要改變元素值的時候,才把形參定義成指向非常量的指針。
當數組作爲函數形參時,因此應該提供一些額外信息來確定數組的確切尺寸,管理數組形參有三種常用的技術:
使用標記指定數組長度
要求數組本身包含一個結束標記。例如C風格字符串以空字符結尾。
使用標準庫規範
傳遞指向數組首元素和尾元素的指針。
void print(const int *beg,const int *end) { while(beg!=end) { cout<<*beg++<<endl; } }
int arr[2]={0,1};
print(begin(arr),end(arr));
顯式傳遞一個表示數組大小的形參
專門定義一個表示數組大小的形參。
void print(const int ia[], size_t size); int j[]={0,1}; print(j, end(j)-begin(j));
數組引用形參
形參可以是數組的引用,此時,引用形參綁定到對應的實參上,也就是綁定到數組上。
void print(int (&arr)[10]) { for(auto elem:arr) { cout<<elem<<endl; } } 形參是數組的引用,維度是類型的一部分
NOTE:arr兩端的括號必不可少
f(int &arr[10]); //錯誤,將arr聲明成了引用的數組 f(int (&arr)[10]); //正確,arr是具有10個整數的整型數組的引用
傳遞多維數組
數組第二維的大小都是數組類型的一部分,不能省略。傳遞多維數組傳遞的是指向數組的指針,實際還是指向首元素的指針。(多維數組就是數組的數組,數組的首元素還是數組,所以是指向數組的指針)。
void print(int (*matrix)[10],int size); matrix是一個指針,指向有10個整數的數組
也可以用:
void print(int matrix[][10],int size); matrix和上面一樣的意義
2.5 含有可變形參的函數
C++提供兩種方法:
實參類型相同,可以傳遞一個名爲initializer_list的標準庫類型
initializer_list形參
lnitializer_list和vector一樣都是模板類型,不同的是initializer_list對象中的元素永遠是常量值,不能改變。
void error_msg(initializer_list<string> ls) { for (auto beg = ls.begin(); beg != ls.end(); ++beg) { cout << *beg << " "; } cout << endl; }
error_msg({ "hello" });
error_msg({ "hello!", "world!!" }); //注意值的傳遞要放在花括號裏
省略符形參
省略符形參是爲了便於C++程序訪問某些特殊的C代碼而設置的。通常,省略符形參不應用於其他目的。省略符形參只能出現在形參列表的最後一個位置。
實參類型不同,使用可變參數模板
3、返回類型和return語句
3.1 無返回值函數
返回類型是void類型的函數
3.2 有返回值函數
值是如何被返回的
返回一個值的方式和初始化一個變量或形參的方式完全一樣:返回的值用於初始化調用點的一個臨時量,該臨時量就是函數調用的結果。
不要返回局部對象的引用或指針
返回類類型的函數和調用運算符
auto sz=getstring().size(); //getstring返回的string對象再調用size函數
引用返回左值
調用一個返回引用的函數得到左值,其他返回類型得到右值。
列表初始化返回值
函數可以返回花括號包圍的值的列表。
vector<string> process() { return {"ni","hao"}; }
遞歸
如果一個函數調用了它自身,不管這種調用是直接還是間接的,都稱該函數爲遞歸函數。
int factorial(int val) { if(val>1) return factorial(val-1)*val; return 1; } 求1x2x3x4......
3.3 返回數組指針
因爲數組不能被拷貝,所以函數不能返回數組。不過,函數可以返回數組的指針或引用。
最直接的方法是使用類型別名
typedef int arrT[10]; using arrT=int[10];
聲明一個返回數組指針的函數
int arr[10]; //arr是一個含有10個整數的數組 int *p1[10]; //p1是一個含有10個整型指針的數組 int (*p2)[10]=&arr; //p2是一個指針,其指向一個有10個整數的數組
如果要定義一個返回數組指針的函數,則數組的維度必須跟在函數名字之後,並且函數的形參列表應該先於數組的維度。
int (*func(int a,int b))[10];
此函數返回的是一個指向有10個整數數組的指針。
使用尾置返回類型
任何函數的定義都能使用尾置返回,但是這種形式對於返回類型比較複雜的函數最有效,比如返回類型是數組的指針或引用。
尾置返回類型跟在形參列表後面並以一個->符號開頭。爲了表示函數真正的返回類型跟在形參列表之後,我們在本應該出現返回類型的地方放置一個auto。
auto func(int i)->int (*)[10];
使用decltype
4、函數重載
如果同一作用域內的幾個函數名字相同但形參列表不同,稱爲函數重載。注意必須是形參列表不同,僅僅只是返回類型不同不可以稱爲重載。
重載和const形參
頂層const不影響傳入函數的對象。一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來。
int f1(int i); int f1(const int i); //不構成重載,重複聲明瞭f1 int f2(int *i); int f2(int *const i); //不構成重載,重複聲明瞭f2
但底層const不同,可以構成重載
int f1(int &i); int f1(const int &i); //重載,新函數 int f2(int *i); int f2(const int *i); //重載,新函數
NOTE:最好只重載那些確實非常相似的操作。
const_cast和重載
const_cast在重載函數的情景中最有用。
const string &shorterString(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; } string &shorterString(string &s1, string &s2) { auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2)); return const_cast<string&>(r); }
4.1 重載與作用域
如果在內層作用域中聲明名字,它將隱藏外層作用域中聲明的同名實體。
void func() { } int main() { int func=0; func(); //錯誤,此時func是int類型的變量,不是函數,隱藏了外層的函數定義 return 0; }
5、特殊用途語言特性
默認實參、內聯函數和constexpr函數。
5.1 默認實參
一旦某個形參被賦予了默認值,它後面的所有形參都必須有默認值。
string screen(int i=10, int a=1, stirng s=" ");
使用默認實參調用函數
在調用函數的時候省略該實參就可以。
默認實參聲明
在給定的作用域中一個形參只能被賦予一次默認實參。
默認實參初始值
局部變量不能作爲默認實參。除此之外,只要表達式的類型能轉換成形參所需的類型,該表達式就能作爲默認實參。
5.2 內聯函數和constexpr函數
調用函數一般比求等價表達式的值要慢一些。
內聯函數可避免函數調用的開銷
在函數的返回類型前面加上關鍵字inline。
一般來說,內聯機制用於優化規模較小、流程直接、頻繁調用的函數。
constexpr函數
constexpr函數是指能用於常量表達式的函數。
定義constexpr函數要遵循:函數的返回類型及所有形參的類型都得是字面值類型,而且函數體中必須有且只有一條return語句。
constexpr int new_sz() { return 42; } constexpr int foo=new_sz(); //foo是一個常量表達式
爲了能在編譯過程中隨時展開,constexpr函數被隱式地指定爲內聯函數。
NOTE:constexpr函數不一定返回常量表達式。
把內聯函數和constexpr函數放在頭文件內
和其他函數不一樣,內聯函數和constexpr函數可以在程序中多次定義。不過,對於某個給定的內聯函數或者constexpr函數來說,它的多個定義必須完全一致。因此,內聯函數和constexpr函數通常定義在頭文件中。
5.3 調試幫助
兩項預處理功能:assert和NDEBUG
assert預處理宏
assert宏常用於檢查“不能發生”的條件。
assert(expr);
如果expr爲假,assert輸出信息並終止程序執行,如果爲真,assert什麼也不做。
NDEBUG預處理變量
assert的行爲依賴於一個名爲NDEBUG的預處理變量的狀態。如果定義了NDEBUG,則assert什麼也不做,默認情況下沒有定義NDEBUG。
可以使用#define語句定義NDEBUG,從而關閉調試狀態。
6、函數指針
函數指針指向的是函數而非對象。和其他指針一樣,函數指針指向某種特定類型。函數的類型由它的返回類型和形參類型共同決定,與函數名無關。
int func(int a, string s);
該函數的類型是int(int , string).要想聲明一個可以指向該函數的指針,只需要用指針替換函數名即可。
int (*p)(int ,string ) //未初始化
NOTE:*p的括號必須加上
使用函數指針
當把函數名作爲一個值使用時,該函數自動地轉換成指針。
int (*p)(int ,string )=func;
可以使用函數指針直接調用該函數,而不需要解引用該指針。
指向不同函數類型的指針之間不存在相互轉換,可以給函數指針賦值nullptr和0,表示指針沒指向任何一個函數。
重載函數的指針
如果定義了指向重載函數的指針,編譯器通過指針類型決定選用哪個函數,指針類型必須與重載函數中的某一個精確匹配。
函數指針形參
和數組類似,雖然不能定義函數類型的形參,但是形參可以是指向函數的指針。可以直接把函數作爲實參使用,此時它會自動轉換成指針。
返回指向函數的指針
將auto和decltype用於函數指針類型
注意將decltype用於函數名時,返回的是函數類型,而非指針類型,如果要表示函數指針,需要自己加上*。