介紹
- 首先介紹函數的定義和聲明,包括如何傳入參數以及函數如何返回結果。
- C++語言允許使用重載函數,即幾個不同的函數可以使用向同一個名字。所以接下來介紹重載函數的方法,以及編譯器選擇如何從函數的若干重載的形式中選取一個與調用模板相互匹配的版本進行使用。
- 最后介紹一些關于函數指針的知識。
6.1 函數的基礎
- 一個函數的定義包括以下幾個部分:返回類型、函數的名字、0或者多個形參組成的列表或者函數體。
- 形參以逗號分隔,形參列表位于一對圓括號里面。
- 函數具體實現的代碼寫在一對花括號里面,整體稱之為函數體。
編寫函數
#include<iostream>
using namespace std;int fact(int val){int rel = 1;for(int i = val;i != 0;i--){rel *= i;}return rel;
}int main(){cout << "The result of rel is "<< fact(5) << endl;return 0;
}
調用函數
- 調用函數主要完成兩項工作:1,實參初始化函數對應的形參;2,將控制權交給調用函數,這個時候,被調的函數開始執行。
形參和實參
- 實參數量和形參數量、類型一致
- 如果定義沒有參數的函數,使用void關鍵字void f1();// 隱式定義空的形參列表? ? void f2(void); //顯式定義空的形參列表
- 任意兩個形參不可以同名,而且函數最外層作用域中的變量也不可以和形參同名。
函數的返回類型
- 一種特殊的返回類型是void
- 函數的返回類型不可以是數組或函數類型,但可以是指向函數或者數組的指針。
6.1.1 局部對象
- 形參和函數體內部定義的變量統一稱為局部變量,對于函數而言是局部的、是隱藏的。
- 函數執行的時候創建,函數執行結束的時候會銷毀的變量叫做自動對象。
- 對于局部變量對應的自動對象來說,如果變量本身含有初始化的數值,就采用初始化的數值,否則就采用默認的初始化的值。
局部靜態對象
- 將局部變量定義成static的類型,這種使用static類型修飾的變量,只會在程序第一次進入的時候進行初始化,直到程序的終止才會銷毀,在此期間,即使對象所在的函數結束也不會對它有任何的影響。
6.1.2 函數聲明
- 函數聲明不需要函數體,也就是無需形參的名字。
- 函數的三個要素(返回類型、函數名字、形參類型)描述了函數的接口,說明了調用這個函數的
- 一般將函數的聲明放在頭文件中,就可以確保同一函數的聲明保持一致,一旦想要改變函數的接口,只需要改變一條聲明語句即可。
- 定義函數的源文件應該包含所有函數聲明的頭文件,這樣編譯器就會負責驗證函數的定義和聲明是否匹配
6.1.3 分離式編譯
- 將程序拆分成不同的部分分別存儲,分離式編譯器允許將程序分割到幾個文件中,對于每個文件進行獨立編譯。
編譯和鏈接多個源文件
- 頭文件 存放函數的聲明
- 源碼文件 存放函數的具體實現的代碼
- 主函數 調用函數的具體執行,需要引入頭文件
例子
int fact(int val){int rel = 1;for(int i = val;i != 0;i--){rel *= i;}return rel;
}
int fact(int val);
#include<iostream>
#include "factHead.h"
using namespace std;int main(){cout << "The result is " << fact(5) << endl;return 0;
}
6.2 參數傳遞
- 每次調用函數的時候,都會重新創建它的形參,并用傳入的實參進行初始化
- 和其他變量一樣,形參的類型決定了形參和實參的交互方式。如果形參是引用類型,它將綁定到對應的實參上;否則,將實參的數值拷貝后賦值給形參。
- 當形參是引用類型時,它對應的實參被引用傳遞或者函數被傳引用調用。和其他引用一樣,引用形參也是它綁定的對象的別名,即引用形參是他對應的實參的別名。
- 當實參的數值被拷貝給形參的時候,形參和實參是兩個相互獨立的對象。這樣的實參被值傳遞或者函數被傳值調用。
6.2.1 傳值參數
- 當初始化一個非引用類型的變量時,初始值被拷貝給變量,此時對于變量的改動不會影響初始值。
int n = 0;//int類型的初始變量
int i = n;//i是n的副本
i = 42;// 對于i的改變不會影響到n的數值
指針形參
- 指針的行為和其他非引用的類型一致。執行指針拷貝操作的時候,拷貝的是指針的數值。拷貝之后,兩個指針是不同的指針。因為指針使我們可以間接地訪問它所指的對象,所以通過指針可以修改它所指的對象的數值。
int main(){int n=0,i=42;int *p = &n,*q = &i;//p指向n,q指向i*p = 44;//n的數值改變,p不變p=q;//p現在指向i了,但是i和n的數值都不變cout << "The n result is " << n << endl; //44cout << "The i result is " << i << endl; //42cout << "The p result is " << *p << endl;//42cout << "The q result is " << *q << endl;//42return 0;
}
//該函數接受一個指針,然后將只針所指向的位置設為0
void reset(int *ip){*ip = 0;// 改變了指針ip所指向的數值ip = 0;//只改變了ip的局部拷貝,實參未被改變
}
int main(){int i = 42;reset(&i);cout << "i = " << i << endl;return 0;
}
6.2.2 傳引用參數
int main(){int n = 0,i=42;int &r = n; //r綁定了n,r是n的另外一個名字r = 42; //改變了n的數值,n也是42cout << "n = " << n << endl;//42cout << "r = " << r << endl;//42return 0;
}
//該函數接受一個int對象的引用,然后將對象的數值設為0
void reset(int &i){//i是傳給函數對象的另外一個名字i = 0;//改變了i所引對象的數值
}int main(){int j = 42;reset(j);cout << "j = " << j << endl;//42return 0;
}
- 和其他引用的類型一致,引用形參綁定初始化他的對象。當調用reset函數的時,i就會綁定我們傳給函數的int對象,改變i的數值也就是改變i引用的數值。
使用引用避免拷貝
- 拷貝大的類類型對象或者容器對象比較低效,甚至有的類類型就根本不支持拷貝操作。當某種類型不支持拷貝操作時,函數只能通過引用形參來訪問該類型的對象。
- 比如一個比較兩個字符串大小的函數,考慮到字符串都比較長,就要避免直接拷貝他們,這個時候使用引用形參就是比較明智的選擇,因為比較長度無法改變string對象的內容,所以把形參定義成對于常量的引用。
bool isShorter(const string &s1,const string &s2){return s1.size() < s2.size();
}
使用形參返回額外的信息
- 使用形參可以一次返回多個結果提供了有效的途徑。
- 例子:函數返回在string中某個字符第一次出現的位置以及該字符總共出現的次數
- 如何使函數既可以返回位置也返回出現的次數呢?一種方法是定義一個新的數據類型,讓他包含位置和次數兩個成員;另外一種方法是給函數傳入一個額外的引用實參,另其保存字符出現的次數
//返回s中c第一次出現的位置索引
//引用形參occurs負責統計c出現的總的次數
string::size_type find_char(const string &s,char c,string::size_type &occurs){auto ret = s.size();//第一次出現的位置(如果存在的話)occurs = 0;for(decltype(ret) i = 0;i!= s.size();i++){if(s[i] == c){if(ret == s.size()){ret = i;}++ occurs;}}return ret;
}bool isShorter(const string &s1,const string &s2){return s1.size() < s2.size();
}
int main(){string s1 = "HelloWorld";string s2 = "Hello";string::size_type ctr;auto index = find_char(s1,'o',ctr);cout << "index = " << index << " ctr = " << ctr << endl;bool s3 = isShorter(s2,s1);cout << "" << s3 << endl;return 0;
}
- 其中,給ret賦值為最大長度的目的是為了在后面判斷的時候,查看是否改變,從而確定是不是第一次遇到這個數值,可以將大于數組長度的任意值作為判定的條件。
6.2.3 const形參和實參
- 當形參是const的時候,頂層的const作用于對象的本身。
const int ci = 42; // 不能改變ci,const是頂層的int i = ci; // 正確,當拷貝ci的時候,會忽略他的頂層的數值int * const p = &i;//const是頂層的,不可以給p賦值*p = 0;//正確,通過p改變對象的的內容是允許的,現在i的數值變為了0
- 和其他初始值一樣,當使用實參初始化形參的時候會忽略掉頂層的const。即,形參的頂層const被忽略掉了。
- 當形參有頂層的const的時候,傳給他的常量對象或者非常量對象都是可以的。
- void fun(const int i)//fun可以能夠讀取i,但是不可以向i中寫值
- void fun(const int i)//錯誤,重復定義了fun(int) C++允許定義若干具有相同的名字的函數,前提是不同函數的形參列表應該有明確的區別。此處是因為const被忽略掉了,因IC兩個函數沒有任何的區別,不可以重復的定義。
指針或者引用形參與const
- 形參的初始化和變量的初始化的方式是一樣的。我們可以使用非常量初始化一個底層的const對象,但是反過來不可以,同時一個普通的引用必須使用相同類型的對象初始化。
int i = 42;const int *cp = &i; //正確,cp不能改變i,const是頂層的const int &r = i; //正確,r不能改變i,const是頂層的const int *r2 = &i; //正確,r2不能改變i,const是頂層的int *p = cp;//錯誤,類型不符int &r3 = r;//錯誤,類型不符int &r4 = 42;//錯誤,不能用字面值初始化一個非常量的引用
盡量使用常量引用
- 把函數不會改變的形參定義成(普通的)引用是一種常見的錯誤,這么做會給調用者一種誤導,即函數不會改變它的實參的數值。使用引用而非常量引用也會極大地限制函數所能接受的實參類型。
- 不能把const對象、字面值或者需要類型轉換的對象傳遞給普通的引用形參。這種錯誤很難排解
- 這個是上面提到的代碼,對其進行修改string::size_type find_char(const string &s,char c,string::size_type &occurs),改為string::size_type find_char(const string &s,char c,string::size_type &occurs),將string類型的形參定義成常量引用。假如將其定義成普通的string&,沒有const進行修飾。那么在使用的時候只可以auto index = find_char(“Hello ”,'o',ctr);,編譯會發生錯誤。
- 假如其他函數將他們的形參定義成常量的引用,那么第二個版本的函數無法在此類函數上正常使用。假設在一個判斷string對象是否是句子的函數中使用find_char;
bool is_sentsence(const string &s){//如果s的末尾有一個句號且僅有一個,則是句子string::size_type ctr = 0;return find_char(s,'.',ctr) == s.size()-1 && ctr == 1;
}
- 如果find_char()第一個形參類型是string & ,那么會發生編譯錯誤,是因為s是常量的引用,但是函數find_char被定義成可以只能接受普通引用。如果修改is_sentsence函數的形參類型只會轉移錯誤,使得is_sentsence函數只可以接受非常量的string對象。
- 正確的思路是改變find_char的函數的形參。實在不行才修改is_sentsence函數,在其內部定義一個string類型的變量,另其為s的副本,然后把這個string對象傳遞給find_char()。
6.2.4 數組形參
- 數組的兩個特殊的性質使得我們定義和使用作用在數組上的函數有影響,這兩個性質是:1,不允許拷貝數組;2,使用數組的時候通常要將其轉換為指針,所以當為函數傳遞一個數組的時候,實際上傳遞的是指向數組首元素的指針。
- 雖然不能以數值的方式傳遞數組,但是可以形參寫成類似數組的形式。
//盡管形式不同,但是這三個print的函數是等價的//每個函數都會有一個const int*類型的形參void print(const int *);void print(const int[]); //函數的意圖是作用于一個數組void print(const int[10]); //這里的維度表示我們期望數組會含有多少個元素,實際上不一定//盡管表現形式不同,但是上面這三個函數是等價的,每個函數的唯一形參都是const int *類型的//當編譯器處理對print函數的調用的時候,只會檢查傳入的參數是否是const int *類型的int i = 0,j[2] = {0,1};print(&i); //正確,&i的類型是int *print(j); //正確,j轉化成int *并且指向j[0]return 0;
- 如果傳遞給print函數是一個數組,則實參自動轉化成指向數組首元素的指針,數組的大小對于函數的調用沒有關系,但是在使用數組的時候需要注意到數組越界的問題。
使用標記指定數組的長度
- 要求數組本身擁有一個結束的標記,典型問題是C風格的字符串中,在字符后面跟著一個空的字符。
void print(const char *cp){if(cp) //如果cp不是一個空的指針while(*cp) //只要指針指向的字符不是空的字符串cout << *cp++ ; //輸出當前字符的數值,并將指針向前移動一個位置
}
int main(){char s[] = "Hello World!";print(s);return 0;
}
- 這種方法適用于那些有著明顯的結束標記,但是該標記不會與普通的數據相互混淆的情形,但是不適用于所有的取值都是合法的情形。
使用標準庫規范
- 管理數組實參的第二種方法是傳遞指向數組首元素和尾后元素的指針
void print(const char *beg,const char *end){//輸出beg到end之間(不包含end)的所有元素while (beg != end){cout << *beg++;}
}int main(){char s[] = "Hello";print(begin(s),end(s));return 0;
}
- while循環使用解引用運算符和后置遞減運算符輸出當前元素并且在數組內將beg向前移動一個元素,當beg和end指針相等的時候結束循環
- 為了調用這個函數,需要傳入兩個指針,一個指向要輸出的首元素,一個指向尾元素的下一個位置。具體的調用方法如上圖所示。此處使用bigin和end函數提供所需的地址。
顯式傳遞一個表示數組大小的形參
- 第三種管理數組實參的方法是專門定義一個表示數組大小的形參,在C程序和過去的C++程序中常常使用這種方法。
//const int ia[] 等效于const int* ia
//size表示數組的大小,將它顯式地傳遞給函數用于控制對ia元素的訪問
void print(const int ia[],size_t size){for(size_t i = 0;i != size;i++){cout << ia[i] << endl;}
}
int main(){int j[] = {0,1,2,3,4,5,6,7,8,9};print(j,end(j) - begin(j));return 0;
}
數組形參和const
- 前三個函數都將數組的形參定義成指向const的指針,對于引用類型也同樣適用于指針。當函數不需要對于數組元素執行寫操作的時候,數組的形參應該是指向const的指針。只有當函數確實要改變元素數值的時候,才會把形參定義成指向常量的指針。
數組引用形參
- C++允許將變量定義成數組的引用,基于同樣的道理,形參也是數組的引用。這個時候,引用形參綁定到對應的實參上,也就是綁定到數組上。
//正確,形參是數組的引用,維度是類型的一部分
void print(int (&arr)[10]){for(auto elem : arr){cout << elem << endl;}
}
- 因為數組的大小是構成數組類型的一部分,所以只要不超過維度,在函數體內就可以放心的使用數組。
- 但是,如果采用上面的代碼格式就會限制了print函數的可用性,即只能將函數作用于大小是10的數值。
注意事項?
- &arr的兩端的括號必不可少
- f(int &arr[10]) //錯誤,將arr聲明成了引用類型
- f(int (&arr)[10]) //正確,arr是具有10個整數的整型數組的引用
傳遞多維數組
- C++實際上并沒有多維數組,所謂的所謂數組其實是數組的數組。
- 和所有數組一樣,當將多維數組傳遞給函數的時候,真正傳遞的是指向數組首個元素的指針。因為多維數組就是數組的數組,因此首元素就是一個數組,指針是一個指向數組的指針。數組的第二維度以及后面所有的維度的大小都是數組類型的一部分,不可以忽略。
//matrix指向數組的首個元素,該數組的元素是由10個整數構成的數組void print(int (*matrix)[10],int rowSize){}
- 上面的語句是將matrix聲明成指向10個整數的數組的指針。
//再一次強調,*matrix兩端的括號必不可少int *matrix[10]; //10個指針構成的數組int (*matrix)[10]; //指向含有10個整數的數組的指針
- 也可以使用數組的語法定義函數,此時編譯器會一如既往地忽略掉第一個維度,所以最好不要把它包括在形參的列表內,所以最好不要把它包括在形參列表內。
- //等價定義 void print(int matrix[][10] , int rowSize){}
- matrix的聲明看起來是一個二維數組,實際上形參是指向含有10個整數的數組的指針。
6.2.5 main 處理命令行選項
- main函數是演示C++程序如何向函數傳遞數組的好例子。到目前為止,使用的是main函數都是只有空的形參列表的形式。比如int main(){}
- 有時候確實需要給main函數傳遞實參,一種常見的情況下是用戶通過設置一組選項來確定函數所要執行的操作。
- 例如,假定main函數位于執行prog之內,我們可以向程序傳遞下面的選項。
- prog -d -o ofile data0? ?這些命令的選項通過兩個可以選擇的形參傳遞給main函數
- int main (int argc, char *argv[]){? ?}
- 第二個形參argv是一個數組,他的元素是指向C風格的字符串的指針,第一個形參argc表示數組中字符串的數量。因為,第二個形參是數組,所以main函數也可以定義成
- int main(int argc,char **argv[]){? ? },其中argv指向char *
- 當形參傳給main函數之后,argv的第一個元素指向程序的名字或者一個空的字符串,接下來的元素依次傳遞命令行提供的實參。最后一個指針之后的元素數值保證為0。
- 當使用arrgv中的實參時,一定要記得可選的實參從arrgv【1】開始,arrgv[0]保存程序的名字,并非用戶的輸入
6.2.6 含有可變形參的函數
- 一般使用在無法提前預知向函數傳遞幾個實參的情形。
- 為了處理不同數量實參的函數,C++11提供了兩個主要的方法:1,如果所有的類型都相同,可以傳遞一個名為initializer_list的標準庫類型;2,如果實參的類型不同,可以編寫一個特殊的函數,也就是所謂的可變參數模板。
- C++還有一種特殊的形參類型,即省略符號,可以用它傳遞可變數量的實參。介紹的省略符形參一般只適用于與C函數交互的接口程序。
initializer_list形參
- 適用于函數的實參數量未知但是全部的實參類型是一致的情形。
- initializer_list是一種標準庫類型,用于表示某種特定類型的數值和數組,其定義在同名的頭文件中。
initializer_list<T>lst; //默認初始化,T類型元素的空的列表initializer_list<T>lst{a,b,c...}; //list元素的數量和初始值一樣多,lst的元素是對應初始值的副本;列表中的元素是constlst2(lst); //拷貝或者賦值一個initializer_list對象不會拷貝列表中的元素,拷貝之后原始列表和副本共享元素lst.size();//類表中元素的數量lst.begin();//返回指向lst中首元素的指針lst.end();//返回指向lst中尾元素下一個未知的指針
- initializer_list和vector一樣,也是一種模板類型,定義initializer_list的時候也必須說明列表中所包含元素的類型
- initializer_list<string> ls;? //initializer_list的元素類型是string
- initializer_list<int> li;? ? //initializer_list的元素類型是int
- initializer_list中的元素永遠是常量,這一點不同于vector,因此無法改變initializer_list對象中元素的數值。
- 使用如下代碼就可以編寫輸出錯誤信息的函數,使其作用于可以改變的數量的實參。
void error_msg(initializer_list<string> il){for(auto beg = il.begin();beg != il.end();++beg){cout << *beg << " "<< endl;}
}
- 如果想向initializer_list形參中傳遞一個數值的序列,則必須將序列放在一對花括號內。
省略符形參
- 省略符形參是為了C++程序訪問特定的C代碼而設置的,這些代碼使用了名為varargs的C的標準庫的功能。
- 通常省略形參不應該用于其他的目的。
- 省略形參應該用于C和C++通用的類型,但是值得注意的是,大多數的類類型的對象在傳遞給省略符形參的時候都無法正確的拷貝。
- 省略形參只能出現在形參列表的最后一個位置,形式無外乎兩種
- void foo(parm_list,...);指定了foo函數的部分形參的類型,對應于這些形參的實參將會執行正常的類型檢查,形參后面的逗號是可以選擇的。
- void foo(...);? 省略符形參所對應的實參不需要類型的檢查
6.3 返回類型和return語句
- return語句終止當前正在執行的函數并將控制權返回到該函數被調用的地方。
- 兩種形式:return ; 和 return expression;
6.3.1 無返回值函數
- 沒有返回值的return語句只能用于返回類型是void的函數中。返回void的函數不需要非得有return語句,因為在該類函數的最后一句會隱式執行return。
- 通常情況下,void函數如果想在他的中間位置退出,可以使用return語句,這個用法類似break。
- 例如:寫一個swap函數,使其在參與交換的數值相等的時候,什么也不做,直接退出
void swap(int &v1,int &v2){if(v1 == v2){return ;}int tmp = v2;v2 = v1;v1 = tmp;
}
int main(){int s1 = 1;int s2 = 1;swap(s1,s2);return 0;
}
- 一個返回類型是void的函數也可以使用return的第二種形式,不過此時return語句的expression必須是另外一個void的函數。強行令void函數返回其他類型的表達式將產生編譯錯誤。
6.3.2 有返回值的函數
- 只要函數的返回類型不是void,那么函數內部的每一個return語句必須返回一個數值。
- return的返回類型必須和函數的返回類型相互一致,或者隱式轉換成函數的返回類型
- 在含有return語句的循環后面的應該也有一條return語句,如果沒有的話該程序是錯誤的,很多編譯器都無法發現這個錯誤
值是如何返回的
- 數值的返回和初始化一個變量或者形參的方式是完全一樣的,返回的數值用于初始化調用點的一個臨時變量,該臨時變量就是函數調用的結果。
- 必須注意函數返回局部變量時的初始化規則。
- 例子:給定計數值、單詞和結束符之后,判斷計數值是否大于1,是的話,返回單詞的復數;否的話,返回單詞的原型
//如果str的數值大于1,返回word的復數的形式
string make_plural(size_t ctr, const string &word, char ending){return (ctr > 1) ? word + ending : word;
}
int main(){string s1 = "Hello Hello string world";cout << make_plural(2,s1,'s') ;return 0;
}
- 這個函數返回的類型是string,意味著返回值將會被拷貝到調用點。因此,該函數將會返回一個word的副本或者一個未命名的臨時的string對象,該對象是word和string的和。
- 同其他引用的類型一樣,如果函數返回引用,則該引用僅僅是它所引用對象的一個別名。
- 例子:函數返回兩個string中形參較短的那個并且返回其引用,其中形參和返回類型都是const string的引用,不管是調用函數還是返回的結果都不會真正的拷貝string對象。
//跳出兩個string對象中較短的那個,并且返回其引用
const string &shorterString(const string &s1,const string &s2){return s1.size() < s2.size() ? s1 : s2;
}
int main(){string s1 = "Hello Hello string world";string s2 = "Hello Hello";cout << shorterString(s1,s2) ;return 0;
}
不要返回局部對象的引用或者指針
- 函數執行完畢之后,會釋放掉占用的存儲空間,因此函數的終止意味著將局部變量的引用指向不再有效的內存區域。
- 錯誤的原因在于試圖訪問未定義的變量
- 返回局部對象的指針也是錯誤的,函數完成,局部對象釋放,指針將會指向一個不再可用的內存空間
//嚴重錯誤:這個函數試圖返回局部對象的引用
cosnt string &manip(){string ret;//通過某種凡是改變一下retif(!ret.empty()){return ret;//錯誤,返回的是一個對于局部變量的引用}else{return "Empty!";//錯誤:"Empty是一個局部臨時的變量"}
}
返回類類型函數和調用運算符
- 調用運算符存在優先級和結合律,其優先級和點運算符號和箭頭運算符號等同,并且也符合左結合律。
- 如果函數返回的指針、引用或者類對象,就可以通過函數調用的結果來訪問結果對象的成員。
- 例如,通過如下的形式得到較短的string對象的長度(這個例子相當于調用對象的子函數)
//跳出兩個string對象中較短的那個,并且返回其引用
const string &shorterString(const string &s1,const string &s2){return s1.size() < s2.size() ? s1 : s2;
}int main(){string s1 = "Hello Hello string world";string s2 = "Hello Hello";
// cout << shorterString(s1,s2) ;auto sz = shorterString(s1,s2).size();cout << sz << endl;return 0;
}
引用返回左值
- 函數的返回類型決定了函數的調用是否是左值。
- 返回引用的函數得到左值,其他類型的函數返回右值。可以像使用其他左值的使用方式一樣來返回引用的函數的調用,特別的是,可以為返回類型是非常量的引用的函數的結果進行賦值。
char &get_val(string &str,string::size_type ix){return str[ix];
}
int main(){string a("a Value"); //輸出a valuecout << a << endl;get_val(a,0) = 'A';//將s[0]的數值改為Acout << a << endl; //將輸出A valuereturn 0;
}
- 返回讀的值是引用,因此調用是一個左值,和其他的左值一樣他也能出現在賦值運算符號的左側
- 如果返回的類型是對于常量的引用,不能給調用的結果賦值
列表初始化返回值
- 函數可以返回花括號內包圍的熟知的列表。類似于其他返回的結果,此處的列表也用來對于對于表示函數返回的臨時量進行初始化。如果列表為空,臨時量執行數值初始化,否則返回的數值由函數的返回的類型決定。
主函數main的返回數值
- 函數的返回類型不是void,必須返回一個數值,但是mian函數例外,允許main函數沒有return語句直接結束。如果控制到達了main函數的結尾而且沒有return語句,編譯器會隱式插入一個返回為0的return語句。
- mian函數的返回數值可以看做是狀態的指示器。返回0表示執行成功,其他數值表示返回失敗,其中非0的數值由具體機器而定。為了使得返回的類型和機器無關,可以引入兩個預處理的變量,分別用來表示成功和失敗。
if(1){return EXIT_SUCCESS;}else{return EXIT_FAILURE;}
- 其中EXIT_SUCCESS和EXIT_FAILURE定義在頭文件cstdlib頭文件中
- 因為上面兩個變量屬于預處理變量,因此既不需要在前面加上std::也不能在using聲明中出現。
遞歸
- 如果一個函數調用了它自身,無論是直接還是間接調用都稱該函數是遞歸函數。
- 例子:使用遞歸函數實現求階乘的功能
int factorial(int val){if(val > 1 ){return factorial(val - 1) * val;return 1;}
}
int main(){cout << factorial(5) << endl;return 0;
}
- 注意事項,在使用遞歸函數的時候,一定會包含一支路徑是不包含遞歸調用的,否則函數將會永遠的執行下去。
6.3.3 返回數組的指針
- 因為數組不能被拷貝,因此函數不會返回數組。但是,函數可以返回數組的指針或者引用,其中最為有效的方法是使用類型別名的方式。
typedef int arrT[10];//arrT是一個類型的別名,它表示的類型是含有10個整數的數組using arrT = int[10];//arrT的等價聲明arrT* func(int i);//func返回一個指向含有10個整數的數組的指針
聲明一個返回數組指針的函數
- 要想在聲明func時不使用類型別名,必須牢記被定義的名字后面的數組的維度
int arr[10]; //arr是一個含有10個整數的數組int *p1[10]; //p1是一個含有10個指針的數組int (*p2)[10] = &arr; //p2是一個指針,指向含有10個整數的數組
- Type (*function(parameter_list))[dimension]
- 其中Type表示元素的類型,dimension表示數組的大小,(*function(parameter_list))兩邊的括號必須存在,就像上面對于p2的定義一樣,如果沒有這對括號,函數的返回類型是指針的數組。
例子
- int (*func(int i))[10];
- func(int i)表示調用func函數的時候需要一個int類型的實參
- (*func(int i))[10] 表示解引用func的調用將得到一個大小是10的數組
- int (*func(int i))[10]表示數組中的元素類型是int類型
使用尾置返回類型
- 可以使用尾置返回類型來替代上面提到的func聲明的方法。
- 任何函數都可以使用尾置返回,即使是返回類型比較復雜的函數也仍然可以使用,比如返回類型是數組的指針或者數組的引用。
- 尾置返回類型跟在形參列表的后面,并且以一個->符號開頭。為了表示函數真正的返回類型跟在形參列表之后,在本應該出現返回類型的地方放置一個auto。
//func接受一個int類型的實參,返回一個指針,這個指針指向含有10個整數的數組
auto func(int i) -> int(*)[10];
- 把函數的返回值類型放在了形參列表的后面,所以func函數返回的是一個指針,并且這個指針指向了含有10個整數的數組
使用decltype(這一部分有問題)
- decltype關鍵字一般用于知道函數返回的指針指向哪個數組的時候進行對于返回類型的聲明。
- 例子:函數返回一個指針,該指針可以根據參數i的不同指向兩個已知數組中的一個
int odd[] = {1,3,5,7,9};int even[] = {0,2,4,6,8};//返回一個指針,這個指針指向含有5個整數的數組decltype (odd) *arrPtr (int i){return (i % 2) ? &odd :&even;}
- 程序無法執行
- decltype并不負責把數組類型轉化成對應的指針,所以decltype的結果是一個數組,要想表示attPtr返回指針還必須在函數生命的時候加上一個*號。