說明
看《C++ Primer Plus》時整理的學習筆記,部分內容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯,人民郵電出版社,只做學習記錄用途,
目錄- 說明
- 7.1 函式的基本知識
- 7.1.1 函式原型
- 7.1.2 函式定義
- 7.1.3 函式呼叫
- 7.2 函式引數和按值傳遞
- 7.3 函式和一維陣列
- 7.3.1 const 和一級指標
- 7.3.2 將一維陣列作為函式引數
- 7.3.3 使用陣列區間的函式
- 7.4 函式和二維陣列
- 7.4.1 二維陣列和二級指標
- 7.4.2 const 和二級指標
- 7.4.3 將二維陣列作為函式引數
- 7.5 函式和 C - 風格字串
- 7.5.1 將 C - 風格字串作為函式引數
- 7.5.2 回傳 C - 風格字串的函式
- 7.6 函式和 string 物件
- 7.7 函式和結構
- 7.7.1 按值傳遞和回傳結構
- 7.7.2 傳遞結構的地址
- 7.8 函式和 array 物件
- 7.9 函式遞回
- 7.9.1 包含一個遞回呼叫的遞回
- 7.9.2 包含多個遞回呼叫的遞回
- 7.10 函式指標
- 7.10.1 函式指標型別
- 7.10.2 函式指標陣列
- 7.10.3 使用 typedef 進行簡化
本章介紹 C++ 中編程模塊函式的基礎用法,
7.1 函式的基本知識
要使用 C++ 函式,必須完成以下作業:
- 提供函式原型;
- 提供函式定義;
- 呼叫函式,
7.1.1 函式原型
函式原型描述了函式到編譯器的介面,它將函式回傳值的型別以及引數的型別和數量告訴編譯器,一般情況下 C++ 程式在首次使用函式之前都需要提供函式原型,避免使用函式原型的唯一方法是:在首次使用該函式前提供它的定義,例如將函式定義寫在 main()
函式前面,函式原型是一條陳述句,常包含于 include
檔案中,此時通過 #include
編譯指令即可提供函式原型,
//方式一:包含頭檔案
#include <cmath>
//方式二:函式原型(省略引數名)
double sqrt(double);
//方式三:函式原型(引數名等價于占位符)
double sqrt(double x);
C++ 函式原型與 ANSI C 函式原型是有區別的:ANSI C 函式原型不需指出引數的型別和數量,例如原型 void say_hi();
能與對應的無參函式匹配,也能與對應的有參函式匹配,對函式引數沒有做限制;而在 C++ 中,原型 void say_hi();
與原型 void say_hi(void);
是等效的,它們都只能與對應的無參函式匹配,在 C++ 中,不指定引數串列時應使用省略號:
//不指定引數串列(C++語法)
void say_hi(...);
//不指定引數串列(ANSI C語法)
void say_hi();
函式原型有以下幾個作用:
- 使編譯器正確處理函式回傳值;
- 使編譯器檢查使用的引數數目是否正確;
- 使編譯器檢查使用的引數型別是否正確,若不正確,則強制轉換為正確的型別(可能丟失資料),條件是轉換前后的型別都是算術型別,且沒有函式多載所出現的二義性,
在編譯階段進行的原型化被稱為靜態型別檢查,可以捕獲許多在運行階段非常難以捕獲的錯誤,
7.1.2 函式定義
函式分成兩類:沒有回傳值的函式和有回傳值的函式,沒有回傳值的函式被稱為 void
函式,其通用格式如下,其中,parameterList
指定了傳遞給函式的引數型別和變數,陳述句 return;
是對 void
函式而言是可選的,也可以使用 return;
提前結束函式,
//沒有回傳值的函式
void functionName(parameterList)
{
statement(s);
return;
}
有回傳值的函式將生成一個值,并將它回傳給呼叫函式,其通用格式如下,其中 value
的型別必須或者可以被強制轉換為 typeName
,C++ 對回傳值的型別有限制:不能是陣列,但可以是其他任何型別(可以將陣列作為結構或者物件組成部分來回傳),
//有回傳值的函式
typeName functionName(parameterList)
{
statement(s);
return value;
}
7.1.3 函式呼叫
函式呼叫比較簡單,一個例子如下:
//提供std::cout<<函式原型
#include <iostream>
//提供say_hi()函式原型
void say_hi();
//在main()中呼叫
int main()
{
//呼叫say_hi()函式
say_hi();
return 0;
}
//提供say_hi()函式定義
void say_hi()
{
std::cout << "Hello World.\n";
}
執行函式 say_hi()
時,將暫停執行 main()
中的代碼,等 say_hi()
執行完畢后,再繼續執行 main()
中的代碼,對于有回傳值的函式,函式通過將回傳值復制到指定的 CPU 暫存器或記憶體單元中來將其回傳;隨后,呼叫程式將查看該記憶體單元,被呼叫函式和呼叫函式必須就該記憶體單元中存盤的資料型別達成一致,函式原型將回傳值型別告知呼叫函式,而函式定義命令被呼叫函式應回傳什么型別的資料,
7.2 函式引數和按值傳遞
函式可以有多個引數,默認情況下,C++ 將按值傳遞函式的引數,函式引數以及在函式中宣告的變數都是該函式私有的,通常情況下,在函式被呼叫時,計算機將為這些變數分配記憶體,在函式結束時,計算機將釋放這些變數使用的記憶體,用于接收傳遞值的變數被稱為形參,給函式傳遞值的變數被稱為實參;C++ 標準使用引數(argument)來表示實參,使用參量(parameter)來表示形參,
//提供函式原型
#include <iostream>
double square(double);
//函式呼叫
int main()
{
double argument = 2.0;
std::cout << square(argument);
return 0;
}
//提供函式定義
double square(double parameter)
{
return parameter * parameter;
}
如上例所示,main()
函式中的變數 argument
為實參,square()
函式中的變數 parameter
為形參,當 square()
函式被呼叫時,它將創建名為 parameter
的變數,并將其初始化為 argument
的值,初始化賦值必須符合 C++ 語法規則,例如不可將 const int *
實參傳遞給 int *
形參,但可將 int *
實參傳遞給 const int *
形參,因此,square()
函式使用的是 argument
的副本,而不是原來的資料,按值傳遞的方式不會更改原來的資料,
7.3 函式和一維陣列
7.3.1 const 和一級指標
可用三種不同的方式將 const
關鍵字用于一級指標,如下所示:
//方式一:指向常量資料的指標
const int * ptc;
int const * ptc;
//方式二:指標本身為常量,需在宣告時初始化
int x = 55;
int * const cpt = &x;
//方式三:指向常量資料且本身也為常量的指標,需在宣告時初始化
int x = 55;
const int * const cptc = &x;
int const * const cptc = &x;
在 Microsoft Visual Studio 中連續多個 const
會被編譯器解釋成一個,即 const const const const int
與 const int
等效,除此之外,const int const
在 Microsoft Visual Studio 中也與 const int
等效,
以上三種型別指標的特性如下:
- 型別為
const int *
的指標ptc
表示*ptc
為常量,不能用該指標修改所指物件的值,但可修改其所指向的地址(指標自身的值),可將int
或const int
資料的地址、int *
或const int *
型別的指標、以及int * const
或const int * const
型別的指標賦給ptc
(接受資料或指標修飾為const
或非const
), - 型別為
int * const
的指標cpt
表示cpt
為常量,能用該指標修改所指物件的值,但不可修改其所指向的地址(指標自身的值),只能將int
資料的地址 、int *
型別的指標、以及int * const
型別的指標賦給cpt
(只接受資料修飾為非const
),且必須在宣告時初始化, - 型別為
const int * const
的指標cptc
表示*cptc
和cptc
都為常量,不能用該指標修改所指物件的值,也不可修改其所指向的地址(指標自身的值),和const int *
型別的指標一樣,可將int
或const int
資料的地址、int *
或const int *
型別的指標、以及int * const
或const int * const
型別的指標賦給cptc
(接受資料或指標修飾為const
或非const
),且必須在宣告時初始化,
對于型別為 int *
的常規指標,有以下特性:
- 型別為
int *
的指標pt
表示*pt
和pt
都不為常量,能用該指標修改所指物件的值,也能修改其所指向的地址(指標自身的值),和int * const
型別的指標一樣,只能將int
資料的地址 、int *
型別的指標、以及int * const
型別的指標賦給pt
(只接受資料修飾為非const
),
7.3.2 將一維陣列作為函式引數
可使用以下方式將陣列作為函式引數,它的函式原型如下,當且僅當用于函式頭或函式原型中時,int * arr
和 int arr[]
的含義才是相同的,但陣串列示法 int arr[]
只能將初始指標指向陣列的第一個元素,指標表示法 int * arr
則可以將初始指標指向陣列的任一個位置,
//函式原型一:可更改陣列資料,以下兩種方式等效
int sum_arr(int arr[], int arrSize);
int sum_arr(int * arr, int arrSize);
定義函式 sum_arr()
時,可用陣串列示法 arr[i]
或指標表示法 *(arr+i)
訪問陣列元素,
//sum_arr()函式定義
int sum_arr(int arr[], int arrSize)
{
int total = 0;
for (int i = 0; i < n; i++)
total = total + arr[i];
return total;
}
在使用函式 sum_arr()
時,將一維陣列名作為實參傳入即可,這并不違反 C++ 按值傳遞的規則,只不過此時傳遞的值是一個地址,而不是陣列的內容,但訪問時是訪問的原始陣列,并非其副本,需要說明的一點時,對形參使用 sizeof()
并不會獲得整個陣列的記憶體量大小,而是相應指標變數的記憶體量大小,以 32 位系統為例,sizeof(arr)=4
,
//初始化陣列
const int ArSize = 8;
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
//使用函式sum_arr()求總和
int sum = sum_arr(cookies, ArSize);
//使用函式sum_arr()求前3個元素和
int sum = sum_arr(cookies, 3);
//使用函式sum_arr()求后4個元素和
int sum = sum_arr(cookies + 4, 4);
由于形參的型別為 int *
非常量指標,因此可在函式體內對陣列資料進行更改,也就是對原始陣列資料進行更改,若要確保函式不修改原始陣列,可使用關鍵字 const
進行保護,如下所示,此時形參的型別為 const int *
,
//函式原型二:不可更改陣列資料,以下四種方式等效
int sum_arr(const int arr[], int arrSize);
int sum_arr(int const arr[], int arrSize);
int sum_arr(const int * arr, int arrSize);
int sum_arr(int const * arr, int arrSize);
7.3.3 使用陣列區間的函式
另一種傳遞陣列資訊的方法是,指定元素區間(range),這可以通過傳遞兩個指標來完成:一個指標標識陣列的開頭,另一個指標標識陣列的尾部,C++ 標準模板庫 STL 就使用了“超尾”概念來指定區間,上面 sum_arr()
函式原型的另一種寫法如下:
//函式原型三:不可更改陣列資料,指定元素區間
int sum_arr(const int * begin, const int * end);
對應的函式定義如下:
int sum_arr(const int * begin, const int * end)
{
const int * pt;
int total = 0;
for (pt = begin; pt != end; pt++)
total = total + *pt;
return total;
}
對應的函式呼叫如下:
//初始化陣列
const int ArSize = 8;
int cookies[ArSize] = {1,2,4,8,16,32,64,128};
//使用函式sum_arr()求總和
int sum = sum_arr(cookies, cookies + ArSize);
//使用函式sum_arr()求前3個元素和
int sum = sum_arr(cookies, cookies + 3);
//使用函式sum_arr()求后4個元素和
int sum = sum_arr(cookies + 4, cookies + 8);
7.4 函式和二維陣列
7.4.1 二維陣列和二級指標
以下面的程式為例,和一維陣列類似,C++ 將二維陣列名解釋為其第一個元素的地址,而二維陣列的第一個元素為一維陣列,因此,二維陣列名 array2d
和 &array2d[0]
等效,它們的型別都為 short (*)[5]
;對陣列名應用地址運算子時,得到的是整個陣列的地址,它的型別為 short (*)[5][5]
,假設 short
寬 2 位元組,系統為 32 位,陣列首地址為0x00BCF8FC,例子中幾種表示的區別為:
- 陣列名
array2d
和&array2d[0]
等效,型別都為short (*)[5]
,存盤的是一個 10 位元組記憶體塊的地址,它們指向的物件是包含 5 個元素的short
陣列,但在運用sizeof()
時,這兩者會有區別,sizeof(array2d)=50
而sizeof(&array2d[0])=4
, - 表示
&array2d
的型別為short (*)[5][5]
,存盤的是一個 50 位元組記憶體塊的地址,它指向的物件是 5 行 5 列的二維short
陣列, - 表示
&array2d[0][0]
和array2d[0]
等效,型別都為short *
,存盤的是一個 2 位元組記憶體塊的地址,它指向的物件是short
型別資料,但在運用sizeof()
時,這兩者會有區別,sizeof(&array2d[0][0])=4
而sizeof(array2d[0])=10
, - 型別
short **
,存盤的是一個 4 位元組記憶體塊的地址,它指向的物件是short*
型別資料,
//宣告并初始化陣列
short array2d[5][5] = {{5,2,8,4,1},
{2,2,4,6,8},
{1,5,8,9,4},
{5,7,6,2,5},
{7,6,5,8,1}};
//宣告并初始化指標一:以下幾種為等效表示
short (*ptra)[5] = array2d; //方式一:值為0x00BCF8FC
short (*ptra)[5] = &array2d[0]; //方式二:值為0x00BCF8FC
//宣告并初始化指標二
short (*ptrb)[5][5] = &array2d; //值為0x00BCF8FC
//宣告并初始化指標三:以下幾種為等效表示
short *ptrc = &array2d[0][0]; //方式一:值為0x00BCF8FC
short *ptrc = array2d[0]; //方式二:值為0x00BCF8FC
//宣告并初始化指標四:以下幾種為等效表示
short *ptrTmp[5] = {array2d[0],array2d[1],array2d[2],array2d[3],array2d[4]};
short** ptrd = ptrTmp; //方式一:值為0x00BCF8A4
short** ptrd = new short*[5]{
array2d[0],
array2d[1],
array2d[2],
array2d[3],
array2d[4]}; //方式二:值為0x01156470,需配合使用delete[]釋放記憶體
short** ptrd = new short*[5]();
ptrd[0] = array2d[0];
ptrd[1] = array2d[1];
ptrd[2] = array2d[2];
ptrd[3] = array2d[3];
ptrd[4] = array2d[4]; //方式三:值為0x01046AE0,需配合使用delete[]釋放記憶體
//訪問陣列第3行第4列的元素
cout << array2d[2][3]; //結果為9
cout << *(array2d[2]+3); //結果為9
cout << *(*(array2d+2)+3);//結果為9
cout << ptra[2][3]; //結果為9
cout << *(ptra[2]+3); //結果為9
cout << *(*(ptra+2)+3); //結果為9
cout << (*ptrb)[2][3]; //結果為9
cout << *((*ptrb)[2]+3);//結果為9
cout << *(*(*ptrb+2)+3);//結果為9
cout << ptrc[2*5+3]; //結果為9
cout << *(ptrc+2*5+3); //結果為9
cout << ptrd[2][3]; //結果為9
cout << *(ptrd[2]+3); //結果為9
cout << *(*(ptrd+2)+3); //結果為9
//應用指標算術時單位1表示的位元組數
cout << int(array2d+1)-int(array2d);//結果為10
cout << int(ptra+1)-int(ptra); //結果為10
cout << int(ptrb+1)-int(ptrb); //結果為50
cout << int(ptrc+1)-int(ptrc); //結果為2
cout << int(ptrd+1)-int(ptrd); //結果為4
//應用sizeof()獲得記憶體量大小
cout << sizeof(array2d);//結果為50
cout << sizeof(ptra); //結果為4
cout << sizeof(ptrb); //結果為4
cout << sizeof(ptrc); //結果為4
cout << sizeof(ptrd); //結果為4
7.4.2 const 和二級指標
可用七種不同的方式將 const
關鍵字用于二級指標,如下所示:
//方式一:所指一級指標指向的資料為常量
const int ** pptc;
int const ** pptc;
//方式二:所指一級指標為常量
int *const* pcpt;
//方式三:二級指標本身為常量,需在宣告時初始化
int x = 55;
int * pt = &x;
int ** const cppt = &pt;
//方式四:二級指標本身為常量,所指一級指標也為常量,所指一級指標指向的資料也為常量,需在宣告時初始化
int x = 55;
const int * pt = &x;
const int *const* const cpcptc = &pt;
//方式五:二級指標本身為常量,所指一級指標也為常量,需在宣告時初始化
int x = 55;
int * pt = &x;
int *const* const cpcpt = &pt;
//方式六:二級指標本身為常量,所指一級指標指向的資料也為常量,需在宣告時初始化
int x = 55;
const int * pt = &x;
const int ** const cpptc = &pt;
//方式七:所指一級指標也為常量,所指一級指標指向的資料也為常量
int x = 55;
const int * pt = &x;
const int *const* pcptc = &pt;
以上七種型別指標的特性如下:
- 型別為
const int **
的指標pptc
表示**pptc
為常量,不能用該指標修改所指一級指標指向的資料的值,但可修改其所指一級指標的值,也可修改其所指向的地址(指標自身的值),只能將const int *
型別的一級指標地址、const int **
或const int ** const
型別的二級指標值賦給pptc
, - 型別為
int * const *
的指標pcpt
表示*pcpt
為常量,能用該指標修改所指一級指標指向的資料的值,不可修改其所指一級指標的值,但可修改其所指向的地址(指標自身的值),只能將int *
或int * const
型別的一級指標地址、int **
、int ** const
、int * const *
或int * const * const
型別的二級指標值賦給pcpt
, - 型別為
int ** const
的指標cppt
表示cppt
為常量,能用該指標修改所指一級指標指向的資料的值,也可修改其所指一級指標的值,但不可修改其所指向的地址(指標自身的值),只能將int *
型別的一級指標地址、int **
或int ** const
型別的二級指標值賦給cppt
,且必須在宣告時初始化, - 型別為
const int *const* const
的指標cpcptc
表示**cpcptc
、*cpcptc
和cpcptc
都為常量,不能用該指標修改所指一級指標指向的資料的值,不可修改其所指一級指標的值,也不可修改其所指向的地址(指標自身的值),能將int *
、int * const
、const int *
或const int * const
型別的一級指標地址、const int **
、const int ** const
、int **
、int ** const
、int * const *
、int * const * const
、const int * const *
或const int *const* const
型別的二級指標值賦給cpcptc
,且必須在宣告時初始化, - 型別為
int *const* const
的指標cpcpt
表示*cpcpt
和cpcpt
都為常量,能用該指標修改所指一級指標指向的資料的值,不可修改其所指一級指標的值,也不可修改其所指向的地址(指標自身的值),能將int *
、int * const
型別的一級指標地址、int **
、int ** const
、int * const *
或int * const * const
型別的二級指標值賦給cpcpt
,且必須在宣告時初始化, - 型別為
const int ** const
的指標cpptc
表示**cpptc
和cpptc
都為常量,不能用該指標修改所指一級指標指向的資料的值,可修改其所指一級指標的值,但不可修改其所指向的地址(指標自身的值),只能將const int *
型別的一級指標地址、const int **
或const int ** const
型別的二級指標值賦給cpptc
,且必須在宣告時初始化, - 型別為
const int *const*
的指標pcptc
表示**pcptc
和*pcptc
都為常量,不能用該指標修改所指一級指標指向的資料的值,也不可修改其所指一級指標的值,但可修改其所指向的地址(指標自身的值),能將int *
、int * const
、const int *
或const int * const
型別的一級指標地址、const int **
、const int ** const
、int **
、int ** const
、int * const *
、int * const * const
、const int * const *
或const int *const* const
型別的二級指標值賦給pcptc
,
對于型別為 int **
的常規指標,有以下特性:
- 型別為
int **
的指標ppt
表示**ppt
、*ppt
和ppt
都不為常量,能用該指標修改所指一級指標指向的資料的值,也可修改其所指一級指標的值,也可修改其所指向的地址(指標自身的值),只能將int *
型別的一級指標地址賦給ppt
,
7.4.3 將二維陣列作為函式引數
//函式原型一
int sum_arr2d(int ** arr2d, int rowSize, int columnSize);
若函式原型按如上宣告,且二維陣列由 new
或 malloc
所分配,例如 int ** ARR2d = new int *[rowSize];...
,則可直接將二維陣列名 ARR2d
傳遞給函式形參 arr2d
,對于不是由 new
或 malloc
所分配的二維陣列,做如下轉換后,可將指標 ptrd
傳遞給函式形參 arr2d
,還有其他轉換方式,可參考前面內容,
//宣告并初始化陣列
int array2d[5][5] = {{5,2,8,4,1},
{2,2,4,6,8},
{1,5,8,9,4},
{5,7,6,2,5},
{7,6,5,8,1}};
//轉換
int *ptrTmp[5] = {array2d[0],array2d[1],array2d[2],array2d[3],array2d[4]};
int** ptrd = ptrTmp;
//函式呼叫
int sum = sum_arr2d(ptrd, 5, 5);
若二維陣列的列數固定,比如需處理的二維陣列列數都固定為 5,則可用以下方式宣告函式原型,這時函式實參的型別不能為 int **
,例如不接受將 int ** ARR2d = new int *[rowSize];...
方式所得的 ARR2d
直接傳遞給函式形參 arr2d
,
//函式原型二,以下兩種格式等效
int sum_arr2d(int (*arr2d)[5], int rowSize);
int sum_arr2d(int arr2d[][5], int rowSize);
依據函式原型二所定義的函式可直接接受常規二維陣列名,但其列數必須為 5,否則計算結果可能與預期不符:
//宣告并初始化陣列
int array2d[5][5] = {{5,2,8,4,1},
{2,2,4,6,8},
{1,5,8,9,4},
{5,7,6,2,5},
{7,6,5,8,1}};
//函式呼叫
int sum = sum_arr2d(array2d, 5);
由于由 new
或 malloc
所分配的二維陣列,只能保證同一行的記憶體連續,而行與行之間的記憶體不一定連續(即第 1 行行尾不一定與第 2 行行首元素在記憶體上相鄰),因此無法將 int **
轉換為 int (*)[5]
,然后將其傳遞給型別為 int (*)[5]
的形參 arr2d
,見如下例子,使用 new
分配出來的二維陣列相鄰行之間的地址差并不等于每行占用的記憶體量寬度,但常規二維陣列相鄰行之間的地址差等于每行占用的記憶體量寬度,
//宣告并初始化陣列(new動態分配)
int ** array2dnew = new int *[5];
array2dnew[0] = new int[5]{5,2,8,4,1};
array2dnew[1] = new int[5]{2,2,4,6,8};
array2dnew[2] = new int[5]{1,5,8,9,4};
array2dnew[3] = new int[5]{5,7,6,2,5};
array2dnew[4] = new int[5]{7,6,5,8,1};
//顯示地址
cout << array2dnew[0]; //0x00F569B0
cout << array2dnew[1]; //0x00F563D8
cout << array2dnew[2]; //0x00F56418
cout << array2dnew[3]; //0x00F56240
cout << array2dnew[4]; //0x00F56280
//宣告并初始化陣列(常規二維陣列)
int array2d[5][5] = {{5,2,8,4,1},
{2,2,4,6,8},
{1,5,8,9,4},
{5,7,6,2,5},
{7,6,5,8,1}};
//顯示地址
cout << array2d[0]; //0x008FF8F8
cout << array2d[1]; //0x008FF90C
cout << array2d[2]; //0x008FF920
cout << array2d[3]; //0x008FF934
cout << array2d[4]; //0x008FF948
7.5 函式和 C - 風格字串
7.5.1 將 C - 風格字串作為函式引數
將 C - 風格字串作為函式引數時,函式原型可按以下方式宣告:
//方式一:可修改原始字串
int cstrlen(char * cstr);
int cstrlen(char cstr[]);
//方式二:不可修改原始字串
int cstrlen(const char * cstr);
int cstrlen(const char cstr[]);
int cstrlen(char const * cstr);
int cstrlen(char const cstr[]);
由于 C - 風格字串以空值字符 \0
結尾,因此可在函式體內以此來判斷字串是否結束,呼叫這類函式時,可用以下三種方式傳遞 C - 風格字串:
char
陣列,- 用引號括起來的字串常量(也稱字串字面值),
- 被設定為字串的地址的
char
指標,
7.5.2 回傳 C - 風格字串的函式
若要回傳 C - 風格字串,可回傳字串的地址,函式原型可按以下方式宣告:
//回傳C-風格字串
char * buildcstr(char c, int n);
這個函式使用 new
創建一個長度為 n
的字串,然后將每個字符都初始化為 c
,函式定義如下:
char * buildcstr(char c, int n)
{
char * pstr = new char[n+1];
pstr[n] = '\0';
while(n-- > 0)
pstr[n] = c;
return pstr;
}
由于函式內部使用了 new[]
,因此在呼叫該函式后,必須記住使用 delete[]
釋放記憶體,
7.6 函式和 string 物件
將 C - 風格字串作為函式引數或將其回傳時,由于涉及到了指標操作,可能還會有動態記憶體分配,這對程式員來說不是很方便,增加了犯錯的可能性,因此可使用 string
物件來替代 C - 風格字串,C++ 像對待內置型別(如 int
)一樣對待 string
物件,
7.7 函式和結構
7.7.1 按值傳遞和回傳結構
將結構作為函式引數或回傳值回傳時,默認方式是按值傳遞,就像普通變數那樣,函式將使用原始結構的副本,若結構非常大,則復制結構將增加記憶體要求,降低系統運行速度,此時可傳遞結構的地址或按參考傳遞結構,將在后面介紹,
//定義結構
struct rect
{
double x;
double y;
};
//函式原型:按值傳遞與回傳結構
rect convertRect(rect xypos);
7.7.2 傳遞結構的地址
假設要傳遞結構的地址而不是整個結構以節省時間和空間,可按如下方式宣告函式原型:
//函式原型一:可修改原始結構資料
void convertRect(rect* xypos);
//函式原型二:不可修改原始結構資料
rect* convertRect(const rect* xypos);
回傳 rect *
時,可回傳形參結構的地址,也可回傳新 new
出來的結構地址,但不可回傳函式體內臨時結構的地址(臨時結構在函式執行完后會被回收),
7.8 函式和 array 物件
可按值將物件傳遞給函式,此時函式處理的是原始物件的副本;也可傳遞指向物件的指標,這讓函式能夠操作原始物件,以 array
物件為例,可宣告以下函式原型:
//函式原型一:指標傳遞,可修改原始資料
void fill(std::array<double, 4> * pa);
//函式原型二:指標傳遞,不可修改原始資料
void fill(const std::array<double, 4> * pa);
//函式原型三:按值傳遞,無法修改原始資料
void fill(std::array<double, 4> da);
若要在函式內訪問 array
物件元素,訪問方式如下:
//函式原型一時的訪問方式
cout << (*pa)[i];
//函式原型二時的訪問方式
cout << (*pa)[i];
//函式原型三時的訪問方式
cout << da[i];
7.9 函式遞回
C++ 函式可以自己呼叫自己,但不允許 main()
呼叫自己,這種功能被稱為遞回,
7.9.1 包含一個遞回呼叫的遞回
如果遞回函式呼叫自己,則被呼叫的函式也將呼叫自己,應在代碼中包含終止呼叫鏈的內容,通常的方法是使用 if
陳述句繼續遞回或終止遞回:
//包含一個遞回呼叫的遞回
void recurs(argumentlist)
{
statements1;
if (test-expression)
recurs(arguments);
statements2;
}
每個遞回呼叫都創建自己的一套變數,它們彼此獨立,互不影響,
7.9.2 包含多個遞回呼叫的遞回
在需要將一項作業不斷分為兩項較小的、類似的作業時,遞回非常有用,這有時被稱為分而治之策略(divide-and-conquer strategy),以下為書中的例子:
//包含多個遞回呼叫的遞回
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);
}
若遞回層次很多,選擇遞回將是一種糟糕的選擇,不僅時間和空間消耗較大,還可能會造成呼叫堆疊溢位,若遞回層次較少,選擇遞回將是一種精致而簡單的選擇,
7.10 函式指標
函式的地址是存盤其機器語言代碼的記憶體的開始地址,可以撰寫將另一個函式的地址作為引數的函式,它允許在不同的時間傳遞不同函式的地址,這意味著可以在不同的時間使用不同的函式,
7.10.1 函式指標型別
宣告指向函式的指標時,必須指定函式的回傳型別以及函式的特征標(引數串列),可以首先撰寫這種函式的原型,然后用 (*pf)
替換函式名,這樣 pf
就是這類函式的指標,以下面的程式為例,要獲取函式的地址,只需使用函式名即可(后面不跟引數),這與陣列地址有幾分相似,函式指標 pf
的型別是 double (*)(int)
,由于 pf
是指向 pam()
函式的指標,因此 (*pf)
是函式,使用函式指標呼叫函式時,C++ 將 pf
與 (*pf)
看作是等價的(雖然前者是函式指標,后者是函式),將 pf()
用作函式呼叫與將 (*pf)()
用作函式呼叫,效果一樣,
//函式原型
double pam(int);
//宣告對應的函式指標
double (*pf)(int);
//賦值,也可在宣告時進行
pf = pam;
//使用函式指標呼叫函式,以下幾種方式等效
double x = pam(4); //方式一
double x = (*pf)(4); //方式二
double x = pf(4); //方式三
//輸出函式地址
cout << pam; //值為0x001F1384
cout << pf; //值為0x001F1384
對函式指標進行賦值時,對應函式的特征標和回傳型別必須與 pf
相同,如果不相同,編譯器將拒絕這種賦值,例如函式指標型別 const double * (*)(const double *, int)
與下面幾種函式匹配,它們的特征標看似不同,但實際上相同,還可使用 auto
關鍵字自動推斷函式指標的型別,
//函式原型
const double * f1(const double ar[], int n);
const double * f2(const double * ar, int n);
const double * f3(const double [], int);
const double * f4(const double *, int);
//宣告函式指標
const double * (*pf)(const double *, int);
//賦值
pf = f1;
pf = f2;
pf = f3;
pf = f4;
//可使用自動型別推斷
auto pff = f1;
假設要設計一個名為 estimate()
的函式,用于估算撰寫指定行數的代碼所需的時間,而且允許每個程式員提供自己的演算法來估算時間,此時可將函式地址作為該函式的輸入引數,其函式原型可使用如下宣告,需保證每個程式員提供的函式特征標和回傳型別都一致且與 double (*)(int)
匹配,
//將函式指標作為函式引數
void estimate(int lines, double (*pf)(int));
7.10.2 函式指標陣列
指向函式指標陣列的指標通常用于類的虛方法實作中,細節通常由編譯器自動處理,如下程式所示,函式指標陣列大體性質與一維陣列相似,其中需要注意的是:
- 運算子
()
與[]
的優先級比*
要高,因此需在合適的地方使用括號()
提高*
的優先級, - 無法將指標算術運用于函式名,即出現
fb+1
或arrpf[i]+1
時編譯器會報錯, - 無法將
sizeof()
運用于函式名fb
,但可用于arrpf[i]
,即出現sizeof(fb)
時編譯器會報錯,但sizeof(arrpf[i])
則不會,
//函式原型
double fa(int);
double fb(int);
double fc(int);
double fd(int);
//宣告并初始化函式指標陣列
double (*arrpf[4])(int) = {fa,fb,fc,fd};
//宣告并初始化指向函式指標陣列第一個元素的指標,以下三種方式對arrpfb等效
double (**arrpfb)(int) = arrpf; //方式一
double (**arrpfb)(int) = &arrpf[0]; //方式二
auto arrpfb = &arrpf[0]; //方式三
//宣告并初始化指向整個函式指標陣列的指標,以下兩種方式對arrpfc等效
double (*(*arrpfc)[4])(int) = &arrpf; //方式一
auto arrpfc = &arrpf; //方式二
//呼叫函式fb,以下幾種方式等效
int x = 5;
double y = fb(x);
double y = arrpf[1](x);
double y = (*arrpf[1])(x);
double y = (*(arrpf+1))(x);
double y = (**(arrpf+1))(x);
double y = arrpfb[1](x);
double y = (*arrpfb[1])(x);
double y = (*(arrpfb+1))(x);
double y = (**(arrpfb+1))(x);
double y = (*arrpfc)[1](x);
double y = (*(*arrpfc)[1])(x);
double y = (*(*arrpfc+1))(x);
double y = (**(*arrpfc+1))(x);
//應用指標算術時單位1表示的位元組數(32系統)
cout << int(arrpf+1)-int(arrpf); //結果為4
cout << int(&arrpf[0]+1)-int(&arrpf[0]);//結果為4
cout << int(&arrpf+1)-int(&arrpf); //結果為16
cout << int(arrpfb+1)-int(arrpfb); //結果為4
cout << int(arrpfc+1)-int(arrpfc); //結果為16
//應用sizeof()獲得記憶體量大小(32系統)
cout << sizeof(arrpf); //結果為16
cout << sizeof(&arrpf[0]);//結果為4
cout << sizeof(&arrpf); //結果為4
cout << sizeof(arrpf[0]); //結果為4
cout << sizeof(arrpfb); //結果為4
cout << sizeof(arrpfc); //結果為4
7.10.3 使用 typedef 進行簡化
可將 typedef
關鍵字用于函式指標型別,簡化程式的撰寫,如下所示,此時 p_fun
就是函式指標型別 double (*)(int)
的別名,上述陣列以及指標宣告可采用以下等效形式,
//指定函式指標別名
typedef double (*p_fun)(int);
//宣告并初始化函式指標陣列,與前面等效
p_fun arrpf[4] = {fa,fb,fc,fd};
//宣告并初始化指向函式指標陣列第一個元素的指標,與前面等效
p_fun* arrpfb = &arrpf[0];
//宣告并初始化指向整個函式指標陣列的指標,與前面等效
p_fun (*arrpfc)[4] = &arrpf;
本文作者:木三百川
本文鏈接:https://www.cnblogs.com/young520/p/16691674.html
著作權宣告:本文系博主原創文章,著作權歸作者所有,商業轉載請聯系作者獲得授權,非商業轉載請附上出處鏈接,遵循 署名-非商業性使用-相同方式共享 4.0 國際版 (CC BY-NC-SA 4.0) 著作權協議,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/508040.html
標籤:C++