函數基礎
- 我們用調用運算符來執行函數,它是一對圓括號,它作用於一個表達式,該表達式是函數或者指向函數的指針;括號之內是一個用逗號隔開的實參列表
(1)編寫函數
int fact(int val){
int ret=1;
while (val>1)
ret*=val--;
return ret;
}
(2)調用函數
int main(){
int j=fact(5);
cout<<"5! is "<<j<<endl;
return 0;
}
函數的調用完成兩項工作:一是實參初始化函數對應的形參,二是將控制權轉移給被調用函數
(3)形參和實參
(4)函數的形參列表
(5)函數的返回類型
局部對象
在C++中,名字有作用域,對象有生命週期
- 名字的作用域是程序文本的一部分,名字在其中可見
- 對象的生命週期是程序執行中該對象存在的一段時間
(1)自動對象
(2)局部靜態對象
// 統計函數它自己被調用了多少次
size_t count_calls(){
static size_t ctr=0;
return ++ctr;
}
int main(){
for(size_t i=0;i!=10;++i)
cout<<count_calls()<<endl;
retunr 0;
}
- 在控制流第一次經過ctr的定義之前,ctr被創建並初始化爲0。
- 每次調用將ctr加1並返回新值
- 每次執行函數時,變量ctr的值都已經存在並且等於函數上一次退出時ctr的值
函數聲明
void print(vector<int>::const_iterator beg,
vector<int>::const_iterator end);
- 函數的三要素(返回類型、函數名、形參列表)描述了函數的接口,說明了調用該函數所需的全部信息。函數聲明也稱作函數原型
(1)在頭文件中進行函數聲明
分離式編譯
(1)編譯和鏈接多個源文件
參數傳遞
- 但形參是引用類型時,我們說它對應的實參被引用傳遞或者函數被傳引用調用
- 當實參被拷貝給形參時,形參和實參是兩個獨立的對象
傳值參數
(1)指針形參
- 當執行指針拷貝操作時,拷貝的是指針的值。拷貝之後,兩者爲不同的指針
- 因爲指針使我們可以間接地訪問它所指的對象,所以通過指針可以修改它所指對象的值
int n=0,i=42;
int *p=&n,*q=&i; //p指向n;q指向i
*p=42; //n的值改變;p不變
p=q; //p現在指向了i;但是i和n的值不變
- 指針形參的行爲與之相似
void reset(int *ip){
*ip=0; // 改變指針ip所指對象的值
ip=0; // 只改變了ip的局部拷貝,實參並未改變
}
- 調用該函數需要傳遞對象的地址
熟悉C的程序員經常使用指針類型的形參來訪問函數外部的對象。在C++語言中,建議使用引用類型的形參替代指針
傳引用參數
// 該函數接受一個int對象的引用,然後將對象的值置爲0
void reset(int &i){ // i是傳給reset函數的對象的另一個名字
i=0; // 改變了i所引對象的值
}
- 調用該函數時,我們直接傳入對象,而無須傳入對象地址
(1)使用引用避免拷貝
如果函數無須改變引用的形參的值,最好將其聲明爲常量引用
// 比較兩個string對象的長度
bool isShorter(const string &s1,const string &s2){
return s1.size()<s2.size();
}
(2)使用引用形參返回額外信息
- 但我們需要獲得兩個或以上的返回數據時,函數只能有一個返回值,有兩種辦法
- 定義一個新的數據類型,讓它包含不同的成員,作爲返回值
- 給函數傳入一個額外的應用實參,令其保存另外的返回數據
// 返回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;
}
const形參和實參
- 關於頂層const作用於對象本身:
const int ci=42; //不能改變ci,const時頂層的
int i=ci; //正確:當拷貝ci時,忽略了它的頂層const
int * const p=&i; //const是頂層的,不能給p賦值
*p=0; //正確:通過p改變對象的內容是允許的,現在i變成了0
- 當用實參初始化形參時會忽略掉頂層const
- 即當形參有頂層const時,傳給它常量對象或者非常量對象都是可以的
void fcn(const int i){
// fcn能夠讀取i,但是不能向i寫值
}
(1)指針或引用形參與const
- 形參的初始化方式和變量的初始化方式時一樣的
- 我們可以使用非常量初始化一個底層const對象,當反過來不行
- 一個普通的引用必須用同類型對象來初始化
int i =42;
const int *cp=&i; //正確:但是cp不能改變i
const int &r=i; //正確:但是r不能改變i
const int &r2=42; //正確
int *p=cp; //錯誤:p的類型與c的類型不匹配
int &r3=r; //錯誤:r3的類型和r的類型不匹配
int &r4=r2; //錯誤:不能用字面值初始化一個非常量引用
(2)儘量使用常量引用
數組形參
- 數組的兩個特殊性質對我們定義和使用作用在數組上的函數又影響,分別是:
- 不允許拷貝數組
- 使用數組會將其轉換成指針
- 儘管不能以值傳遞的方式傳遞數組,當可以把形參寫成類似數組的形式
void print(const int*);
void print(const int[]);
void print(const int[10]);
和其他使用數組的代碼一樣,以數組作爲形參的函數也必須確保使用數組時不會越界
- 因爲數組是以指針的形式傳遞給函數的,所以一開始函數並不知道數組的尺寸,調用者應該爲此提供額外信息。管理指針形參有三種常用技術
(1)使用標記指定數組長度
void print(const char *cp){
if(cp) //若cp不是一個空指針
while(*cp) //只要指針不是空字符
cout<<*cp++;
}
(2)使用標準庫規範
- 傳遞指向數組首元素和尾後元素的指針
void print(const int *beg,consst int *end){
//輸出beg到end之間全部元素
while(beg!=end)
cout<<*beg++<<endl;
}
上述函數的調用可以通過:
int j[2]={0,1};
print(begin(j),end(j));
(3)顯式傳遞一個表示數組大小的形參
void print(cosnt int ia[],size_t size){
for(size_t i=0 ; i!=size() ; ++i){
cout<<ia[i]<<endl;
}
}
(4)數組形參和const
(5)數組引用形參
void print(int (&arr)[10]){
for(auto elem:arr)
cout<<elem<<endl;
}
(6)傳遞多維數組
void print(int (*matrix)[10] ,int rowSize){}
/*等價定義*/
void print(int matrix[][10],int rowSize){}
上述語句將matrix聲明成指向含有10個整數的數組的指針
mian:處理命令行選項
含有可變形參的函數
(1)initializer_list形參
- 如果函數的實參數量未知但是全部實參的類型都相同,可以使用initializer_list類型的形參
操作 | 結果 |
---|---|
initializer_list lst; | 默認初始化:T類型元素的空列表 |
initializer_list lst{a,b,c…> | lst的元素數量和初始值一樣多,lst是初始值的副本,列表種的元素是const |
lst2(lst) | 拷貝或賦值一個initalizer_list對象不會拷貝列表中的元素;拷貝後兩列表共享與元素 |
lst2=lst | 同上 |
lst.size() | 列表中的元素數量 |
lst.begin() | 返回首元素指針 |
lst.end() | 返回尾元素下一位置指針 |
void error_msg(initalizer_list<string> il){
for(auto beg=il.begin() ; beg!=il.end() ; ++beg)
cout<<*beg<<"";
cout<<endl;
}
- 如果想向initialzer_list形參中傳遞一個值的序列,必須把序列放進花括號中
error_msg( {"functionX",expected,actual} );
(2)省略符形參
返回類型和return語句
無返回值函數
- 可以直接執行return語句處推出
void swap(int &v1,int &v2){
if(v1==v2)
return;
int tmp=v2;
v2=v1;
v1-tmp;
}
有返回值函數
(1)值是如何被返回的
string make_plural(size_t str,const string &word,const string &ending){
return (ctr>1)?word+ending:word;
}
(2)不要返回局部對象的引用或指針
- 函數完成後,它說佔用的空間也隨之被釋放。因此,局部變量的引用不再有效
(3)返回類類型的函數和調用運算符
// shorterString函數返回istring,訪問其size成員
auto sz=shorterString(s1,s2).size();
(4)引用返回左值
(5)列表初始化返回值
- 如果返回值是引用,它是左值;其它則是右值
- 如果返回值是常量引用,就不能給調用的結果賦值
(5)列表初始化返回值
vectot<sting> process(){
...
...
return {"funtionX","OKay"};
}
(6)主函數main的返回值
- return EXIT_FALLURE或者return EXIT_SUCCESS 這兩個預處理變量定義再頭文件cstdlib頭文件中
(7)遞歸
返回數組指針
- 最直接的辦法就是使用類型別名
typedef int arrT[10]; // arrT是一個類型別名,表示含有10個整數的數組
using arrT=int[10]; // arrT的等價聲明
arrT* func(int i); // func返回一個指向含有10個整數的數組的指針
(1)聲明一個返回數組指針的函數
- 要想再聲明func時不適用類型別名
int arr[10];
int *p1[10];
int (*p2)[10]=&arr; //p2時一個指針,它指向含有10個整數的數組
- 返回數組指針的形式如下所示
Type (*function(parameter_list))[10];
// 例子
int (*func(int i))[10];
(2)使用尾置返回類型
// func接受一個int類型的實參,返回一個指針,該指針指向含有10個整數的數組
auto func(int i) -> int(*)[10];
(3)使用decltype
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表示它的返回類型是一個指針,並且所指對象類型與odd的類型一致
- decltype並不負責把數組轉換成對應指針,所以decltype的結果是個數組,要想表示返回指針,還必須再函數聲明時加一個符號
函數重載
(1)定義重載函數和
Record lookup(const Account&);
Record lookup(const Phone&);
(2)判斷兩個形參的類型是否相同
(3)重載和const形參
- 一個擁有頂層const的形參無法和另一個沒有頂層const的形參區別開來
- 如果形參時某種類型的指針或引用,則區分其指向是否常量可以實現函數重載
(4)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)
}
(5)調用重載的函數
重載與作用域
特殊用途語言特性
默認實參
typedef string::size_type sz;
string screen(sz ht=24,sz wid=80,char backgrnd='');
- 其中,爲每個形參都提供了默認實參
- 注意:只能給全部形參同時提供默認值
(1)使用默認實參調用函數
- 直接再調用時省略該實參即可
- 注意:只能省略尾部的實參,或者省略全部實參
(2)默認實參聲明
- 通常在頭文件中,進行函數實參的聲明
通常,應該在函數聲明中指定默認實參,並將該聲明放在合適的頭文件中
(3)默認實參初始值
- 除局部變量之外,表達式能實現對應轉化則可以作爲默認實參
內聯函數和constexpr函數
(1)內聯函數可避免函數調用的開銷
- 在函數的返回類型前面加上關鍵字inline,就聲明成爲內聯函數了
很多編譯器都不支持遞歸內聯函數
(2)constexpr函數
- constexpr函數的全部形參都必須時字面值類型,而且函數體中必須有且只有一條return
調試幫助
(1)assert預處理宏
- assert是一種預處理變量,它的行爲類似於內聯函數
assert(expr);
- 首先,對expr求值,如果爲假(0),assert輸出信息並終止程序的執行;如果爲真,assert聲明也不做
- 常常用來檢查“不能發生的事”
(2)NDEBUG預處理變量
- assert的行爲依賴於一個名爲NDEBUG的預處理變量狀態。如果定義了NDEBUG,則assert什麼也不做。默認狀態下沒有定義NDEBUG,此時assert將執行檢查
- 定義NDEBUG能避免檢查各種條件所需的運行時開銷
- 小例子:
void print(const int ia[],size_t size){
#ifndef NDEBUG
cerr<<__func__<<":array size is "<<size<<endl;
#endif
}
如果NDEBUG未定義,將執行 #ifndef 和 #endif之間代碼,如果定義了NDEBUG,將被忽略
編譯器定義的用於調試的名字
代碼 | 結果 |
---|---|
_ _ func _ _ | 存放當前調試的函數的名字的const char靜態數組 |
_ _ FILE _ _ | 存放文件名的字符串字面值 |
_ _ LINE _ _ | 存放當前行號的整型字面值 |
_ _ TIME _ _ | 存放文件編譯時間的字符串字面值 |
_ _ DATE _ _ | 存放文件編譯日期的字符串字面值 |
if(word.size() < threshold)
cerr<<"Error:"<<_ _FILE_ _
<<":in function"<<_ _func_ _
<<"at line"<<_ _LINE_ _<<endl
<<" Compiled on"<<_ _DATE_ _
<<"at"<<_ _TIME_ _<<endl
<<" Word read was \""<<word
<<"\":Length too short"<<endl;
函數匹配
(1)確定候選函數
(2)尋找最佳匹配(如果有的話)
(3)含有多個形參的函數匹配
實參類型轉換
(1)需要類型提升和算術類型轉換的匹配
(2)函數匹配和const實參
函數指針
bool (*pf)(const string &,const string &);
(1)使用函數指針
pf=lengthCompare; //pf指向名爲lengthCompare的函數
pf=&lengthCompare; //等價的賦值語句
另外,還可以直接使用指向函數的指針條用函數
bool b1=pf("hello","goodbye");
bool b2=(*pf)("hello","goodbye"); //一個等價的調用
(2)重載函數指針
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int)=ff; //pf1指向ff(unsigned)
編譯器通過指針類型決定選用那個函數,指針類型必須與重載函數中的某一個精確匹配
(3)函數指針參數
- 函數雖然不能定義數組、函數類型的形參,但是形參也可以是指向
void useBigger(const string &s1,const string &s2,bool pf(const string &,const &));
// 等價的聲明
void useBigger(const string &s1,const string &s2,bool (*pf)(const string &,const string &));
(4)返回指向函數的指針
- 最簡單的辦法是聲明一個類型別名作爲返回類型
using F=int(int*,int); //F是函數類型,不是指針
using PF=int(*)(int*,int); //PF是指針類型
- 將F定義成函數類型,將PF定義成指向函數類型的指針
PF f1(int); //正確:PF是指向函數的指針,f1返回指向函數的指針
F=f1(int); //錯誤:F是函數類型,f1不能返回一個函數
F *f1(int); //正確:顯式地指定返回類型是指向函數的指針
- 處於完整性考慮,可以使用尾置返回類型的方式
auto f1(int)->int(*)(int*,int);
(5)將auto和decltype用於函數指針類型
string::size_type sumLength(const string&,const string&);
string::size_type larger(const string&,const string&);
// 根據其形參的取值,getFcn函數返回指向sumLength或者largerLength的指針
decltype(sumLength) *getFcn(const string &);