6.1 函數基礎
- 通過調用運算符(call operator)來執行函數。調用運算符的形式是一懟圓括號(),它作用於一個表達式,該表達式是函數或者指向函數的指針;調用表達式的類型就是函數的返回類型。 int function() 是一個調用表達式,是用調用運算符()作用於函數名function(實際上是指針),int類型爲調用表達式的類型,也是函數的返回類型;
- 函數的三要素(返回類型,函數名,形參類型) 描述了函數的接口,說明了調用該函數所需的全部信息。函數聲明也稱作函數原型;
6.2 參數傳遞
- 使用引用來避免拷貝,當拷貝打的類類型對象或者容器對象時,由於拷貝比較低效甚至有的對象不支持拷貝構造,這是使用引用形參是比較明智的選擇;
- 當函數無需改變引用形參的值時,最好將其聲明爲常量引用;
- 當用實參初始化形參是會忽略掉頂層const(形參的頂層const被忽略了),當形參有頂層const時,傳給他常量或者非常量對象都是可以的;
//下面兩句語句是等效的,因此不能視作函數重載 void fun(const int i) {} ; void fun(int i) {};
-
練習6.17
bool hasUpper(const string&str) { for (auto ch : str) { if (isupper(ch)) return true; } return false; } void strtolower(string& str) { for (auto &ch : str) { ch = tolower(ch); } return; }
-
數組的兩個特殊性質:不允許拷貝數組,使用時將轉化爲指針;導致無法以之傳遞的形式使用數組參數,但是可以把形參寫成類似數組的形式,以下三種聲明方式等效:
void print(const *int); void print(const int[]); //可以看出函數的意圖是作用一個數組 void print(const int[10]); //這裏的維度表示期望數組有多少個元素,無論寫多少都是將第一個元素的指針傳入
-
3種辦法管理指針型形參使其不越界:
// 1. 使用標記指定數組長度,這裏C風格字符串結尾標記是'\0' void print(const char *p) { if(cp) while(*p) cout<<*cp++; } // 2. 使用標準庫規範, begin(), end()函數可以返回容器內容的第一個元素的指針迭代器和最後一個元素下一位的指針迭代器 void print(const int *beg, const int *end) { while(beg!=end) cout<<*beg++<<endl; } print(begin(j),end(j)); // 3. 顯式傳遞一個表示數組大小的形參 void print(consti int ia[], size_t size) { for(size_t i=0;i<size;++i) { cout<<ia[i]<<endl; } }
-
傳遞多維數組的兩種形參方式:
void print(int (*matrix)[10],int rowsize); void print(int matrix[][10], int rowsize);
-
main函數的兩個參數 int main(int argc, char*argv[]), 第一個argc表示命令行輸入的行數,第二個argv表示指向C字符串風格的指針(即指向指針的指針,類型可以表示爲 char**),第一行是指向程序的名字或者空字符串,下面的元素依次傳遞命令行提供的實參,最後一個指針之後的元素值保證爲0;例子如下:
prog -d -o ofile data0 // 命令行的參數 int main(int argc, char **argv) //上面命令等同於如下過程, prog爲可執行程序 argv[0] = "prog"; argv[1] = "-d"; argv[2] = "-o"; argv[3] = "ofile"; argv[4] = "data0"; argv[5] = 0;
-
含有可變形參的函數聲明方式,傳入一個initializer_list類型的形參,此類型裏的成員都是常量不可改變;第二種方式是省略符形參;詳細見198頁
void err_msg(initializer_list<string>il) { for(auto beg=il.begin(); beg!=il.end();++begin) { cout<<*beg<<" ";} cout<<endl; } // 省略符形參 void foo(param_list, ...); //逗號可以省略 void foo(...);
6.3 返回類型和return語句
- 返回局部對象的引用是錯誤的;同樣,返回局部對象的指針也是錯誤的
- 調用運算符()的優先級與點運算符.和箭頭運算符->一樣,同時滿足左結合律因此下面語句可以正常執行
auto sz = shorterString(s1,s2).size();
-
函數的返回類型決定函數調用是否爲左值。調用一個返回引用的函數得到左值,可以爲其結果賦值(常量引用例外)
//這裏函數返回的是char型的引用,作爲左值可以爲其賦值 char &get_val(string &str, string::size_type ix) { return str[ix];} int main() { string s("a value"); cout<<s<<endl; get_val(s,0) = 'A'; cout<<s<<endl; return 0; }
-
函數返回值允許用列表初始化, return {1,2,3,4} 合法,只要其類型符合返回值的類型;
-
聲明一個返回數組指針的函數有兩種方式,閱讀理解順序從內到外,從右到左
int (*func(int i))[10] ;//函數func有一個參數i,返回一個指針,指向有10個int型的數組 auto func(int i) -> int(*)[10] ; //㞑置返回類型,顯式提示真正的返回類型
6.4 函數重載
-
不允許兩個函數除了返回類型外其他所有的要素都相同;對於重載的函數來說,他們應該在形參數量或者形參類型上有所不同;
-
頂層const不影響傳入函數的對象。一個擁有頂層const的形參無法和另一個沒有頂層const的形參區分開來
int lookup(phone); int lookup(const phone); //重複聲明
形參如果是某種類型的指針或者引用,則可以通過const區分函數重載,此時的const是底層的
record lookup(account&); record lookup(const account&); //新函數,作用於常量引用 record lookup(account*); //新函數,作用於指向account的指針 record lookup(const account*); //新函數,作用於指向常量的指針
-
同名函數聲明置於局部作用於內實現的是隱藏而不是重載,詳見210頁
6.5 特殊用途語言特性
- 一旦某個形參被賦予了默認值,那麼它後面所有的形參都必須有默認值;
- 多次聲明一個函數是合法的,但是在給定的作用域中一個形參只能被賦予一次默認實參;換句話說,函數的後續聲明只能爲之前沒有默認值的形參添加默認實參,而且該形參右側的所有形參必須都有默認值;
- 局部變量不能作爲默認實參
typedef string::size_type sz; sz wd = 80; char def = ' '; sz ht(); string screen(sz = ht(), sz = wd, char = def); string window = screen(); //調用 screen(ht(), 80, ' ') void f2() //函數局部作用域 { def = '*'; //修改了外部定義的def值 sz wd = 100; //隱藏了外層定義的wd,但是沒有改變默認值 window = screen(); //調用screen(ht(), 80, '*') }
- constexpr函數不一定返回常量表達式,當constexpr函數實參是常量表達式時其返回值爲常量表達式,實參不是是返回的就不是常量表達式;內聯函數和constexpr函數可以多次定義,但是對於給定的內聯函數或者constexpr函數來說,它的多個定義必須完全一致,因此這兩個函數通常定義在頭文件中;
- assert是預處理宏,定義在<cassert>頭文件中,它的名字由預處理器管理因此不需要提供using assert或者std::assert; 很多頭文件都包含了<cassert>,因此即使你沒有直接包含cassert,它也有可能通過其他途徑包含在程序中;
- NDEBUG預處理變量用於調試時檢查,詳見216頁
6.6 函數匹配
- 函數匹配的過程分3步,第一步確定候選函數,即找到同名的重載函數;第二步確定可行函數,匹配實參數量類型和形參數量類型;第三步尋找最佳匹配,完美匹配參數數量和類型的優先級最高,其次是可以類型轉換的參數;當無法找到完美匹配的函數時編譯器最終將因爲調用具有二義性而拒絕請求;詳見218頁;
- 實參類型轉換排序等級 a,精準匹配, b通過const轉換實現的匹配, c通過類型提升實現的匹配, d通過算術類型轉換或者指針轉換實現的匹配, e通過類類型轉換實現的匹配;從a到e優先級依次降低;
6.7 函數指針
- 當我們把函數名作爲一個值使用時,該函數自動地轉換成指針
pf = lengthCompare; //pf指向名爲lengthCompare的函數 pf = &lenghtCompare; //等價的賦值語句:取地址符可選
可以用指向函數的指針調用該函數,無需提前解引用指針
bool b1 = pf("hello","goodbye"); bool b2 = (*pf)("hello","goodbye"); bool b3 = lengthCompare("hello","goodbye"); //以上三種調用等價
-
把函數直接作爲實參使用,此時它會自動轉換成指針;直接使用函數指針閒的冗長繁瑣,因此可以使用類型別名(using操作)和decltype簡化寫函數指針的代碼
-
返回指向函數的指針,建議用㞑置返回類型的方式,顯示的聲明太複雜不容易看懂;
int(*f1(int))(int* int); auto f1(int) -> int(*)(int*,int); //以上兩種聲明方式等價,返回指向參數爲(int*,int),返回值爲int的函數指針