函數(shù)
函數(shù)基礎(chǔ)
- 函數(shù)是一個(gè)命名了的代碼塊,我們通過調(diào)用函數(shù)執(zhí)行相應(yīng)的代碼,函數(shù)可以有0個(gè)活多個(gè)參數(shù)落蝙,通常會(huì)產(chǎn)生一個(gè)結(jié)果,可以重載函數(shù)暂幼,也就是說筏勒,同一個(gè)名字可以對(duì)應(yīng)多個(gè)不同的函數(shù)
- 一個(gè)典型的函數(shù)(function)定義包括以下部分
- [x] 返回類型(return type)
- [x] 函數(shù)名字
- [x] 0個(gè)或多個(gè)參數(shù)(parameter)組成的列表
- [x] 函數(shù)體
- 函數(shù)的調(diào)用完成兩項(xiàng)工作
- [x] 用實(shí)參初始化函數(shù)相對(duì)應(yīng)的參數(shù)
- [x] 將控制權(quán)轉(zhuǎn)移給被調(diào)用函數(shù)
- 主調(diào)函數(shù)(calling function)的執(zhí)行被暫時(shí)中斷,被調(diào)函數(shù)(called function)開始執(zhí)行
- return語句完成兩項(xiàng)工作:
- [x] 返回return語句中的值
- [x] 將控制權(quán)從被調(diào)函數(shù)轉(zhuǎn)移回主調(diào)函數(shù)
void f1(){/*......*/} //隱式地定義空形參列表
void f2(void){/*......*/} //顯式地定義空形參列表
- 形參列表中的每一個(gè)形參都是含有一個(gè)聲明符的聲明管行,必須寫出類型
- 任意兩個(gè)形參不能同名
局部對(duì)象
- 在C++語言中,名字有作用域邪媳,對(duì)象有生命周期
- [x] 名字的作用域是程序文本的一部分捐顷,名字在其中可見
- [x] 對(duì)象 的生命周期是程序執(zhí)行過程中該對(duì)象存在的一段時(shí)間
- 局部變量的生命周期依賴于定義的方式
- 自動(dòng)對(duì)象
- [x] 對(duì)于普通局部變量對(duì)應(yīng)的對(duì)象來說,當(dāng)函數(shù)的控制路徑經(jīng)過變量定義語句時(shí)創(chuàng)建該對(duì)象雨效,當(dāng)?shù)竭_(dá)定義所在的塊末尾時(shí)銷毀它迅涮,只存在于塊執(zhí)行期間的對(duì)象被稱為`自動(dòng)對(duì)象`
- [x] 形參是一種自動(dòng)對(duì)象,函數(shù)開始時(shí)為形參申請(qǐng)存儲(chǔ)控件徽龟,因?yàn)樾螀⒍x在函數(shù)體作用域之內(nèi)叮姑,所以一旦函數(shù)終止,形參也就被銷毀
- [x] 當(dāng)有必要令局部變量的生命周期貫穿函數(shù)調(diào)用之后的時(shí)間時(shí)顿肺,可以將局部變量定義成`static`類型從而獲得這樣的對(duì)象
- [x] 如果局部靜態(tài)變量沒有顯式的初始值戏溺,它將執(zhí)行初始化渣蜗,內(nèi)置類型的局部靜態(tài)變量初始化為0
- [x] 局部靜態(tài)對(duì)象在程序的執(zhí)行路徑第一次經(jīng)過對(duì)象定義語句時(shí)初始化,并且直到程序終止才被銷毀旷祸,在此期間即使對(duì)象所在的函數(shù)結(jié)束執(zhí)行也不會(huì)對(duì)它有影響
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;
}
return 0;
}
//上述程序?qū)⑤敵鰪?到10的數(shù)字
函數(shù)聲明
- 函數(shù)可以只聲明不定義
- 函數(shù)的聲明和函數(shù)的定義類似耕拷,唯一的區(qū)別是函數(shù)聲明無需函數(shù)體,用一個(gè)分號(hào)替代
- 函數(shù)的三要素:
返回類型
函數(shù)名
形參類型
- 函數(shù)聲明也稱作
函數(shù)原型
- 函數(shù)的聲明以及變量的聲明都放在頭文件
- 含有函數(shù)聲明的頭文件應(yīng)該被包含到定義函數(shù)的源文件中
參數(shù)傳遞
- 每次調(diào)用函數(shù)時(shí)都會(huì)重新創(chuàng)建它的形參托享,并用傳入的實(shí)參對(duì)形參進(jìn)行初始化
- 當(dāng)實(shí)參的值被拷貝給形參時(shí)骚烧,形參和實(shí)參是兩個(gè)相互獨(dú)立的對(duì)象,我們這樣的實(shí)參被
值傳遞
或者函數(shù)被傳值調(diào)用
傳值參數(shù)
- 當(dāng)初始化一個(gè)非引用類型的變量時(shí)闰围,初始值被拷貝給變量赃绊,此時(shí),對(duì)變量的改動(dòng)不會(huì)影響初始值
- 熟悉C的程序猿常常使用指針類型的形參訪問函數(shù)外部的對(duì)象羡榴,在C++語言中碧查,建議使用引用類型的形參替代指針
- 使用引用形參避免拷貝
- [x] 拷貝大的類類型對(duì)象或者容器對(duì)象比較低效,甚至有的類類型(包括IO類型在內(nèi))根本就不支持拷貝操作校仑,當(dāng)某種類型不支持拷貝操作時(shí)忠售,函數(shù)只能使用引用形參訪問該類型的對(duì)象
- 如果函數(shù)無須改變引用形參的值,最好將其聲明為常量引用
const形參和實(shí)參
- 當(dāng)形參有頂層const時(shí)迄沫,傳給它常量對(duì)象或者非常量對(duì)象都是可以的
數(shù)組形參
- 和其他使用數(shù)組的代碼一樣稻扬,以數(shù)組作為形參的函數(shù)也必須確保使用數(shù)組時(shí)不會(huì)越界
- 數(shù)組以指針的形式傳遞給函數(shù),所以一開始函數(shù)并不知道數(shù)組的確切尺寸羊瘩,調(diào)用這應(yīng)該為此提供一些額外的信息以管理泰佳,如以下三種:
- [x] 使用標(biāo)記指定數(shù)組的長(zhǎng)度
- 數(shù)組本身包含一個(gè)結(jié)束標(biāo)記,使用這個(gè)方法的典型示例是C風(fēng)格字符串尘吗,
- C風(fēng)格字符串存儲(chǔ)在字符數(shù)組中逝她,并且在最后一個(gè)字符后面跟著一個(gè)空字符,函數(shù)在處理C風(fēng)格字符串時(shí)遇到空字符停止
void print(const char * cp)
{
if(cp)
{
while(*cp)
cout<<*cp++;
}
}
- [x] 使用標(biāo)準(zhǔn)庫規(guī)范
- 傳遞數(shù)組首元素和尾后元素的指針
- 通過`begin()` && `end()`函數(shù)可以獲取數(shù)組的首地址以及尾后地址
- [x] 顯式傳遞一個(gè)表示數(shù)組大小的形參
- 專門定義一個(gè)表示數(shù)組大小的形參睬捶,在C程序和過去的C++程序中經(jīng)常使用這樣的方法
void print(const char * cp , size_t size)
{
for(size_t i = 0 ; i < size ; ++i)
{
...
}
}
- [x] 當(dāng)函數(shù)不需要對(duì)數(shù)組元素執(zhí)行寫操作的時(shí)候汽绢,數(shù)組形參應(yīng)該是指向const的指針,只有當(dāng)函數(shù)確實(shí)要改變?cè)刂档臅r(shí)候侧戴,才把形參定義成指向非常量的指針
- [x] 形參可以是數(shù)組的引用宁昭,引用形參綁定到對(duì)應(yīng)的實(shí)參上,也就是綁定在數(shù)組上
void print(int (&arr)[10])
{
....
}
- [x] C++語言中實(shí)際上沒有真正的多維數(shù)組酗宋,所謂多維數(shù)組實(shí)際上都是數(shù)組的數(shù)組
int * matrix[10]; \\10個(gè)int指針元素構(gòu)成的數(shù)組
int (*matrix)[10]; \\指向10個(gè)int元素的數(shù)組的指針
main:處理命令行選項(xiàng)
- 當(dāng)用戶通過設(shè)置一組選項(xiàng)來確定函數(shù)所要執(zhí)行的操作時(shí)
int main(int argc, char * argv[])
{
......
}
- 通過上述main函數(shù)的聲明方式可以給main函數(shù)傳遞參數(shù)
- 第一個(gè)形參argc表示數(shù)組中字符串的數(shù)量积仗,第二個(gè)形參argv是一個(gè)數(shù)組,它的元素是C風(fēng)格的字符串的指針
int main(int argc, char ** argv)
{
......
}
- 當(dāng)實(shí)參傳給main函數(shù)之后蜕猫,
argv的第一個(gè)元素指向程序的名字或者一個(gè)空字符串寂曹,接下來的元素依次傳遞命令行提供的實(shí)參,最后一個(gè)指針之后的元素值保證為0
當(dāng)使用argv中的實(shí)參時(shí),一定要記得可選的實(shí)參從argv[1]開始隆圆;argv[0]保存程序的名字漱挚,而非用戶的輸入
含有可變形參的函數(shù)
- 有時(shí)我們無法提前預(yù)知應(yīng)該向函數(shù)傳遞幾個(gè)實(shí)參,例如渺氧,我們想要編寫代碼輸出程序產(chǎn)生的錯(cuò)誤信息旨涝,此時(shí)最好用同一個(gè)函數(shù)實(shí)現(xiàn)該功能
- 為了編寫能處理不同數(shù)量實(shí)參的函數(shù),C++11新標(biāo)準(zhǔn)提供了兩種主要方法
- [x] 如果所有實(shí)參類型相同侣背,可以傳遞一個(gè)名為`initializer_list`的標(biāo)準(zhǔn)庫類型
- [x] 如果實(shí)參類型不同白华,可以編寫一種特殊的函數(shù),也就是所謂的可變參模板函數(shù)
- [x] C++有一種特殊符號(hào)`省略符`贩耐,可以用它傳遞可變數(shù)量的實(shí)參
initializer_list形參
- 如果函數(shù)的實(shí)參數(shù)量未知但是全部實(shí)參類型都相同弧腥,可以使用initializer_list類型形參
- 與vector不同,initializer_list對(duì)象中的元素永遠(yuǎn)是常量值潮太,我們無法改變initializer_list對(duì)象中的元素值
void error_msg(initializer_list<string> il)
{
for(auto beg = il.begin() ; beg != il.end() ; ++beg)
{
......
}
}
- 如果想向initializer_list形參中傳遞一個(gè)值的序列管搪,則必須把序列放在一堆花括號(hào)內(nèi)
error_msg({"functionx","expected","actual"});
省略符形參
- 省略符形參是為了便于C++程序訪問某些特殊的C代碼而設(shè)置的,這些代碼使用了名為varargs的C標(biāo)準(zhǔn)庫功能铡买,通常抛蚤,省略符形參不應(yīng)有于其他目的
- 省略符形參應(yīng)該僅僅用于C和C++通用的類型,特別應(yīng)該注意的是寻狂,大多數(shù)類類型的對(duì)象在傳遞給省略符形參時(shí)都無法正確拷貝
- 省略符形參只能出現(xiàn)在形參列表的最后一個(gè)位置
void foo(int n,...);
void foo(...);
返回類型和return語句
無返回值函數(shù)
- 無返回值的return語句只能用在返回類型是void的函數(shù)中
- 返回void的函數(shù)不要求非得有return語句,因?yàn)檫@類函數(shù)的最后一句后面會(huì)隱式的執(zhí)行return
有返回值的函數(shù)
- 只要函數(shù)的返回類型不是void朋沮,則該函數(shù)內(nèi)的每條return語句必須返回一個(gè)值蛇券,return語句的返回值的 類型必須與函數(shù)的返回類型相同,或者能隱式的轉(zhuǎn)換成函數(shù)的返回類型
- 在含有return語句的循環(huán)后面也應(yīng)該有一條return語句樊拓,如果沒有的話程序就是錯(cuò)誤的纠亚,而編譯器無法發(fā)現(xiàn)此類錯(cuò)誤
- 不要返回局部對(duì)象的引用或指針
- [x] 函數(shù)完成后,它所占用的空間也隨之被釋放掉筋夏,因此蒂胞,函數(shù)終止意味著局部變量的引用將指向不再有效的內(nèi)存區(qū)域
- 返回類類型的函數(shù)和調(diào)用運(yùn)算符
auto sz = shorterString(s1,s2).size();
- [x] C++11新標(biāo)準(zhǔn)規(guī)定,函數(shù)可以返回花括號(hào)包圍的值的列表
vector<string> process()
{
....
....
return {"aaaa","bbbb","cccc"};
}
- [x] 我們?cè)试Smain函數(shù)沒有return語句直接結(jié)束条篷,編譯器會(huì)隱式的插入一條return語句
- [x] main函數(shù)的返回值可以看作是狀態(tài)指示器骗随,`返回0表示執(zhí)行成功,返回其他值表示執(zhí)行失敗`赴叹,其中非0值的具體含義依機(jī)器而定
- [x] 為了使返回值與機(jī)器無關(guān)鸿染,cstdlib頭文件定義了兩個(gè)預(yù)處理變量
int main()
{
if(some_failure)
return EXIT_FAILURE;
else
return EXIT_SUCCESS;
}
- [x] 如果一個(gè)函數(shù)調(diào)用了它自身,不管這種調(diào)用是直接的還是間接的乞巧,都成該函數(shù)為遞歸函數(shù)
int factorial(int val)
{
if(val > 1)
{
return factorial(val-1)*val
}
return 1;
}
- [x] 在遞歸函數(shù)中涨椒,一定有某條路徑是不包含遞歸調(diào)用的
返回?cái)?shù)組指針
- 因?yàn)閿?shù)組不能被拷貝,所以函數(shù)不能返回?cái)?shù)組,不過蚕冬,函數(shù)可以返回?cái)?shù)組的指針或引用
- 使用尾置返回類型
auto func(int i)->int(*)[10]{......}
- 將函數(shù)返回類型放在形參列表后免猾,可以清楚的看到func函數(shù)返回的是一個(gè)指針,指向了含有10個(gè)int元素的數(shù)組
- 任何函數(shù)的定義都能使用尾置返回
函數(shù)重載
- 如果同一作用域內(nèi)的幾個(gè)函數(shù)名字相同但形參列表不同囤热,我們稱之為
重載函數(shù)
- main函數(shù)不能重載
重載與作用域
- 不要在局部作用域中聲明函數(shù)(即不要在函數(shù)中聲明函數(shù))
- 在C++語言中猎提,名字查找發(fā)生在類型檢查之前
特殊用途語言特性
默認(rèn)實(shí)參
- 某些函數(shù)有這樣一種形參,在函數(shù)的很多次調(diào)用中被賦予一個(gè)相同的值赢乓,我們將這個(gè)反復(fù)出現(xiàn)的值成為函數(shù)的默認(rèn)實(shí)參
void screen(int = 10 , string s = "hehe");
通常應(yīng)該在函數(shù)聲明中指定默認(rèn)實(shí)參忧侧,并將該聲明放在合適的頭文件中
- 默認(rèn)變量不能作為默認(rèn)實(shí)參,除此之外牌芋,只要表達(dá)式的類型能轉(zhuǎn)換成形參所需要的類型蚓炬,該表達(dá)式就能作為默認(rèn)實(shí)參
內(nèi)聯(lián)函數(shù)和constexpr函數(shù)
- 內(nèi)聯(lián)函數(shù)可以避免函數(shù)調(diào)用的開銷
- 內(nèi)聯(lián)函數(shù)將在編譯過程中展開,以消除函數(shù)運(yùn)行時(shí)的開銷
- 內(nèi)聯(lián)說明只是向編譯器發(fā)出的一個(gè)請(qǐng)求躺屁,編譯器可以選擇忽略這個(gè)請(qǐng)求
- 一般來說肯夏,內(nèi)斂機(jī)制用于優(yōu)化規(guī)模較小,頻繁調(diào)用的函數(shù)犀暑,很多編譯器都不支持內(nèi)聯(lián)遞歸的函數(shù)驯击,而且一個(gè)75行的函數(shù)也不可能在調(diào)用點(diǎn)內(nèi)聯(lián)的展開
調(diào)試幫助
assert(expr);
- 首先expr求值,如果表達(dá)式為假(0)耐亏,assert輸出信息并終止程序的執(zhí)行徊都,如果為真(非0),assert什么也不做
- assert宏定義在cassert頭文件中
- 含有cassert頭文件的程序不能再定義名為assert的變量广辰、函數(shù)或其他實(shí)體暇矫,實(shí)際編程中,即使我們沒有包含cassert頭文件择吊,最好也不要為了其他目的使用assert
-
FUNC存放當(dāng)前調(diào)試函數(shù)的名字李根,是const_char的一個(gè)靜態(tài)數(shù)組
-
FILE存放文件名的字符串字面值
-
LINE存放當(dāng)前行號(hào)的整型字面值
-
TIME存放文件編譯時(shí)間的字符串字面值
-
DATE存放文件編譯日期的字符串字面值
函數(shù)匹配
- 調(diào)用重載函數(shù)時(shí)應(yīng)盡量避免強(qiáng)制類型轉(zhuǎn)換,如果在實(shí)際應(yīng)用中確實(shí)需要強(qiáng)制類型轉(zhuǎn)換几睛,則說明我們?cè)O(shè)計(jì)的形參不合理
函數(shù)指針
- 函數(shù)指針指向的是函數(shù)而非對(duì)象
- 函數(shù)指針指向某種特定類型房轿,
函數(shù)的類型由它的返回類型和形參類型共同決定
,與函數(shù)名無關(guān)
- 如下為例:
bool lengthCompare(const string & , const string &);
bool (*ptr)(const string & , const string &); //ptr指向函數(shù)所森,該函數(shù)的參數(shù)是兩個(gè)const string &的引用囱持,返回值是bool類型
ptr = lengthCompare; //ptr指向名為lengthCompare的函數(shù)
ptr = &lengthCompare; //等價(jià)賦值語句:取地址符可選
//可以直接使用指向函數(shù)的指針調(diào)用該函數(shù),無需提前解引用指針
bool b1 = ptr("hello","goodbye");
bool b2 = (*ptr)("hello","goodbye"); //等價(jià)調(diào)用
bool b1 = lengthCompare("hello","goodbye"); //等價(jià)調(diào)用
- 在指向不同函數(shù)類型的指針之間不存在轉(zhuǎn)換貴組
- 可以為函數(shù)指針賦一個(gè)
nullptr
或者值為0的整型常量表達(dá)式