第七章 函數(shù)(模塊)
1. 關(guān)于函數(shù)返回值
-
在C++中,函數(shù)分為有返回值和無返回值兩種。函數(shù)可以返回除數(shù)組之外的任意類型齐婴,包括基本類型、指針稠茂、結(jié)構(gòu)體和對象柠偶。所以雖然數(shù)組不能返回,但可以將數(shù)組作為結(jié)構(gòu)或?qū)ο螅惖膶?shí)例)的組成部分來返回睬关。
如果需要將數(shù)組作為參數(shù)诱担,一般的做法是傳遞數(shù)組名和數(shù)組大小。例如
int funa(int arr[], int arr_size);
电爹。因?yàn)閭鬟f的是數(shù)組名(實(shí)際上在作為函數(shù)形參后將退化成數(shù)組首元素地址)蔫仙,這種方法會改變原數(shù)組的值的風(fēng)險(xiǎn)。當(dāng)然在C++和ANSI C中丐箩,可以使用const限定符摇邦。實(shí)參(數(shù)組名)和形參(已退化成數(shù)組首元素地址)指向同一個(gè)地址,但對實(shí)參(數(shù)組名)使用sizeof將得到數(shù)組所占字節(jié)數(shù)屎勘,對形參使用sizeof將得到指針?biāo)甲止?jié)數(shù)施籍。
函數(shù)通過函數(shù)定義將特定類型的返回值復(fù)制到指定的寄存器或內(nèi)存地址將其返回。隨后概漱,調(diào)用程序?qū)⒉榭丛摰刂烦笊鳎⑼ㄟ^函數(shù)原型得知數(shù)據(jù)類型。
所以在調(diào)用函數(shù)前犀概,一定要先聲明函數(shù)原型(prototype)立哑,一般來說,會以函數(shù)定義所在文件的名字姻灶,創(chuàng)建一個(gè)同名頭文件铛绰,并在其中集中聲明所有函數(shù)的原型。當(dāng)然产喉,如果某個(gè)靜態(tài)函數(shù)是先定義后被調(diào)用的話也可以不需要聲明捂掰。
注意:在C++中敢会,括號為空與在括號中使用void關(guān)鍵字是等效的。但在ANSI C中这嚣,括號為空意味著不明確指出參數(shù)鸥昏。在C++中,表示不明確指出參數(shù)列表使用省略號(3個(gè)連續(xù)點(diǎn)號)姐帚。通常吏垮,僅當(dāng)與接受可變參數(shù)的C函數(shù)(如printf())交互時(shí)才需要這么做。
2.關(guān)于數(shù)組作為函數(shù)參數(shù)
C++通常按值傳遞參數(shù)罐旗,即將實(shí)參(argument)賦值給形參(parameter)后進(jìn)行運(yùn)算膳汪。這樣不會因?yàn)楹瘮?shù)調(diào)用而影響到實(shí)參的值。
- 數(shù)組處理函數(shù)的常用編寫方式
假設(shè)編寫一個(gè)處理double數(shù)組的函數(shù)九秀。
如果該函數(shù)要修改數(shù)組遗嗽,其原型如下:
void func_modify(double arr[], int size);
如果該函數(shù)不修改數(shù)組,其原型如下:
void func_no_change(const double arr[], int size);
當(dāng)然鼓蜒,在原型中可以省略形參名痹换,也可以將返回類型指定為其它類型。此處的要點(diǎn)是arr實(shí)際上是一個(gè)指向數(shù)組首元素的指針都弹。注意:在函數(shù)內(nèi)部不能通過sizeof(arr) 獲取數(shù)組長度娇豫,而必須通過參數(shù)size傳入。
- 當(dāng)然也可以通過向函數(shù)傳遞數(shù)組區(qū)間信息:數(shù)組首元素地址缔杉、數(shù)組尾元素后面一個(gè)元素地址(超尾)锤躁。例如有數(shù)組
int arr[20];
,則數(shù)組區(qū)間信息就是:arr
和arr + 20
或详。
指針加減法都是以指針?biāo)赶驍?shù)據(jù)類型為單位進(jìn)行的系羞,例如:&arr[19] - &arr[0] = 19, &arr[0] + 1 = &arr[1]。
請注意:
const int *p
和int * const p
的區(qū)別霸琴,前者表示p指向的地址里存儲的數(shù)據(jù)不能改變椒振,后者表示p代表的地址不能改變,強(qiáng)調(diào)的是地址不變梧乘。將常規(guī)變量的地址賦給const指針可行(但僅限于一級指針澎迎,二級及以上指針將不再成立),但將const變量的地址賦給常規(guī)指針卻不行选调。
<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n40" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> int age = 18;
const float length = 180;
const int * p = &age; // 一級指針下合法夹供。
float * pl = &length; // 不合法!
*p = 20; //錯(cuò)誤仁堪,利用p表達(dá)式指向的地址單元存貯的元素不能變哮洽。
age = 20; //成功,因?yàn)閍ge聲明本身就是一個(gè)非常量變量弦聂,它可以改變,只是不能利用p進(jìn)行改變而已鸟辅。</pre>
- 當(dāng)二維數(shù)組需要作為函數(shù)參數(shù)時(shí)氛什,僅需要傳遞數(shù)組首地址和行數(shù),列數(shù)在原型中指出即可匪凉。
假設(shè)存在如下二維數(shù)組int arr[3][2] = {{1,2},{3,4},{5,6}};
枪眉,當(dāng)其作為參數(shù)需要被傳入函數(shù)sum()時(shí),可以采用如下兩種原型:
int sum(int (*arr)[2], int row);
或者int sum(int arr[][2], int row);
arr[3]里存的3個(gè)元素是含有2個(gè)int元素的數(shù)組地址再层,因?yàn)?code>*arr和arr[]
是可以相互轉(zhuǎn)換的贸铜,所以存在以上兩種寫法,但顯然后一種更容易理解树绩。
因?yàn)閍rr指針類型決定了sum函數(shù)只能接受2列二維數(shù)組萨脑,但row變量指定了行數(shù)隐轩。
- 當(dāng)字符串作為函數(shù)參數(shù)時(shí)饺饭,可以將其看作是字符數(shù)組來對待,但不需要傳遞數(shù)組個(gè)數(shù)职车,因?yàn)樽址哪┪泊娴氖?code>'\0'瘫俊。在定義函數(shù)的形參時(shí)有:
char str[]、char *str悴灵、"hello world!"
三種形式扛芽。
3.關(guān)于結(jié)構(gòu)體作為函數(shù)參數(shù)
結(jié)構(gòu)體作為函數(shù)參數(shù)或返回值時(shí)是作為一個(gè)整體進(jìn)行值傳遞的,且結(jié)構(gòu)體變量名并不代表結(jié)構(gòu)體地址积瞒,而必須前綴&符號川尖。但如果結(jié)構(gòu)體很大,則也可以采用結(jié)構(gòu)體地址作為參數(shù)茫孔,另外C++中還提供了引用傳遞來解決這個(gè)問題叮喳。
4.關(guān)于string對象作為函數(shù)參數(shù)
雖然字符串和string類對象的用途幾乎相同,但與數(shù)組相比缰贝,string對象與結(jié)構(gòu)體更相似馍悟。它們都可以作為一個(gè)整體傳遞給函數(shù),也可以相互間直接賦值剩晴。如果需要多個(gè)字符串锣咒,可以聲明多個(gè)string對象,而無需建立二維數(shù)組赞弥。
下面的程序聲明了一個(gè)string對象數(shù)組毅整,并將該數(shù)組傳遞給一個(gè)函數(shù)以顯示字符內(nèi)容:
<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n57" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> //XXX.cpp -- handing an array of string objects
include <iostream>
include <string>
using namespace std;
const int SIZE = 5;
void display(const string sa[], int n);
int main()
{
string list[SIZE];
cout << "Enter your " << SIZE << "favorite astronomical sights:\n";
for (int i = 0; i < SIZE; i++)
{
cout << i+1 << ": ";
getline(cin, list[i])
}
cout << "Your list:\n";
display(list, SIZE);
return 0;
}
void display(const string sa[], int n)
{
for (int i = 0; i<n; i++)
cout << i+1 <<": " << sa[i] << endl;
}</pre>
5.關(guān)于array對象作為函數(shù)參數(shù)
因?yàn)轭悓ο笫腔诮Y(jié)構(gòu)的,因此類對象和結(jié)構(gòu)體一樣可按值將對象傳遞給函數(shù)绽左,此時(shí)函數(shù)處理的是類對象的副本悼嫉;也可傳遞對象的地址,這樣可以處理原始對象妇菱。要使用array類承粤,需要包含頭文件array暴区,且命名快進(jìn)為std。
如果要使用array對象(名為arr_name)存儲4個(gè)double型元素辛臊,可以按此定義:std::array<double, 4> arr_name;
當(dāng)該array對象按值傳遞作為函數(shù)參數(shù)時(shí)仙粱,聲明如下:
void func_name(std::array<double, 4> arr_name);
。(形參名可省略)當(dāng)該array對象按地址傳遞作為函數(shù)參數(shù)時(shí)彻舰,聲明如下:
void func_name(std::array<double, 4> * arr_p);
伐割。(形參名可省略)
6. 關(guān)于遞歸
通常將遞歸調(diào)用放在if語句中,其結(jié)構(gòu)如下:
<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n68" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> void recurs(參數(shù)列表)
{
語句1
if (測試語句)
recurs(參數(shù)列表)
語句2
}</pre>
測試語句終會為false刃唤,調(diào)用解開隔心。
只要測試語句為真,每個(gè)recurs()調(diào)用都將執(zhí)行語句1尚胞,然后再調(diào)用下一層recurs()硬霍,而不會執(zhí)行語句2,直到測試語句為假笼裳,當(dāng)前調(diào)用解開循環(huán)鏈唯卖,返回后將控制權(quán)交予上一層調(diào)用,而上一層執(zhí)行語句2后再網(wǎng)上一層釋放控制權(quán)躬柬,依次類推拜轨。如果recurs()進(jìn)行了5次遞歸調(diào)用,則語句1將按照函數(shù)調(diào)用順序執(zhí)行5次允青,然后語句2將以相反的順序執(zhí)行5次橄碾。
請看示例:
<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n72" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> #include <iostream>
void countdown(int n);
int main()
{
countdown(4);
return 0;
}
void countdown(int n)
{
using namespace std;
count << "Counting down ... " << n << endl;
if (n>0)
countdown(n-1);
count << n << ": Kaboom!\n";
}
下面是程序輸出:
Counting down ... 4 //第1層
Counting down ... 3 //第2層
Counting down ... 2 //第3層
Counting down ... 1 //第4層
Counting down ... 0 //第5層
0: Kaboom! //第5層,開始返回
1: Kaboom! //第4層颠锉,開始返回
2: Kaboom!
3: Kaboom!
4: Kaboom! //第1層法牲,開始返回</pre>
注意:每一層調(diào)用,函數(shù)都將創(chuàng)建自己的變量存儲區(qū)木柬,所以如果嵌套過深皆串,對系統(tǒng)內(nèi)存消耗很大。
- 另外一種遞歸是包含多次調(diào)用自身的形式眉枕,例如:
<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n77" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> void subdivide(char ar[], int low, int high, int level)
{
if (level == 0) //遞歸終止條件
return;
int mid = (high + low) / 2;
ar[mid] = '|';
subdivide(ar, low, mid, level-1);
subdivide(ar, mid, high, level-1);
}</pre>
7. 關(guān)于函數(shù)指針
函數(shù)名就是函數(shù)的地址恶复;
函數(shù)聲明如下:
函數(shù)返回值類型 (*pf)(參數(shù)類型列表)
,簡單來說就是將函數(shù)原型中的函數(shù)名換成(*p)就是指向該類型函數(shù)的指針聲明速挑。例如谤牡,double (*pf)(int);
聲明了一個(gè)返回double類型,參數(shù)是一個(gè)int的函數(shù)指針姥宝,它可以指向形如double pam(int)
的函數(shù)翅萤,當(dāng)執(zhí)行pf = pam;
后就可以以(*pf)(n)
來調(diào)用函數(shù)pam(n)了(n為int變量)。但其實(shí)在C++中腊满,也可以把函數(shù)指針變量當(dāng)成函數(shù)名來使用套么,即寫成pf(n)
也可以調(diào)用pam(n)函數(shù).-
如何聲明一個(gè)包含3個(gè)函數(shù)指針的數(shù)組呢培己?
首先它是一個(gè)包含3個(gè)元素的數(shù)組:p[3]
數(shù)組的元素是指針:*p[3]
什么指針呢?指向 “返回值是double *的胚泌,參數(shù)有一個(gè)int的函數(shù)”的指針
最后形式為:double * (*p[3])(int)省咨;
-
注意區(qū)分如下聲明:
p[3]: 包含3個(gè)指針元素的數(shù)組*。
(p)[3]: 一個(gè)指向 ”有3個(gè)元素的數(shù)組“ 的指針*玷室。
-
假如存在數(shù)組arr[8]零蓉,arr、&arr[0]穷缤、&arr均指向同一個(gè)地址敌蜂,但代表的意義不一樣:
arr:數(shù)組名,代表數(shù)組首元素地址
&arr[0]:數(shù)組首元素地址
&arr:整個(gè)數(shù)組的地址津肛,其+1后等于整個(gè)數(shù)組后面8個(gè)內(nèi)存塊的地址章喉。
**&arr == *arr == arr[0]
C++11中有一個(gè)叫做
auto
的類型,它會根據(jù)其右值變量的類型自動定義左值類型快耿,在用于比較難以準(zhǔn)確聲明的類型時(shí)非常有用囊陡。例如存在函數(shù)原型:double * func(const char[], int);
當(dāng)我需要定義一個(gè)指針變量用于指向該類函數(shù)時(shí)非常難寫其類型,此時(shí)只需要用:anto pf = func_name;
就可以申明pf的類型了掀亥。func_name為一個(gè)func類型的函數(shù)名。
<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n115" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> double * func(const char[], int n)
{
...
}
auto pf = func; //auto將自動推斷出pf的類型應(yīng)該為指向func()一類函數(shù)的指針妥色;</pre>
- 除了自動類型可以減少變量類型的書寫外搪花,
typedef
關(guān)鍵字也可以省略繁瑣的類型聲明。例如:
<pre spellcheck="false" class="md-fences md-end-block md-fences-with-lineno ty-contain-cm modeLoaded" lang="c++" cid="n120" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px 0px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> typedef const double (p_fun)(const double *, int); //p_fun作為某型函數(shù)指針類型
p_fun p1 = fun_1; //利用p_fun來定義變量p1</pre>