主頁 > 後端開發 > 第7章 函式-C++的編程模塊

第7章 函式-C++的編程模塊

2022-09-15 06:23:50 後端開發

說明

看《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 intconst int 等效,除此之外,const int constMicrosoft Visual Studio 中也與 const int 等效,

以上三種型別指標的特性如下:

  • 型別為 const int * 的指標 ptc 表示 *ptc 為常量,不能用該指標修改所指物件的值,但可修改其所指向的地址(指標自身的值),可將 intconst int資料的地址、int *const int * 型別的指標、以及 int * constconst int * const 型別的指標賦給 ptc(接受資料或指標修飾為 const 或非 const),
  • 型別為 int * const 的指標 cpt 表示 cpt 為常量,能用該指標修改所指物件的值,但不可修改其所指向的地址(指標自身的值),只能將 int 資料的地址 、int * 型別的指標、以及 int * const 型別的指標賦給 cpt(只接受資料修飾為非 const),且必須在宣告時初始化,
  • 型別為 const int * const 的指標 cptc 表示 *cptccptc 都為常量,不能用該指標修改所指物件的值,也不可修改其所指向的地址(指標自身的值),和 const int * 型別的指標一樣,可將 intconst int資料的地址、int *const int * 型別的指標、以及 int * constconst int * const 型別的指標賦給 cptc(接受資料或指標修飾為 const 或非 const),且必須在宣告時初始化,

對于型別為 int * 的常規指標,有以下特性:

  • 型別為 int * 的指標 pt 表示 *ptpt 都不為常量,能用該指標修改所指物件的值,也能修改其所指向的地址(指標自身的值),和 int * const 型別的指標一樣,只能將 int 資料的地址 、int * 型別的指標、以及 int * const 型別的指標賦給 pt(只接受資料修飾為非 const),

7.3.2 將一維陣列作為函式引數

可使用以下方式將陣列作為函式引數,它的函式原型如下,當且僅當用于函式頭函式原型中時,int * arrint 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)=50sizeof(&array2d[0])=4
  • 表示 &array2d 的型別為 short (*)[5][5],存盤的是一個 50 位元組記憶體塊的地址,它指向的物件是 5 行 5 列的二維 short 陣列,
  • 表示 &array2d[0][0]array2d[0] 等效,型別都為 short *,存盤的是一個 2 位元組記憶體塊的地址,它指向的物件是 short 型別資料,但在運用 sizeof() 時,這兩者會有區別,sizeof(&array2d[0][0])=4sizeof(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 ** constint * const *int * const * const 型別的二級指標值賦給 pcpt
  • 型別為 int ** const 的指標 cppt 表示 cppt 為常量,能用該指標修改所指一級指標指向的資料的值,也可修改其所指一級指標的值,但不可修改其所指向的地址(指標自身的值),只能將 int * 型別的一級指標地址、int **int ** const 型別的二級指標值賦給 cppt,且必須在宣告時初始化,
  • 型別為 const int *const* const 的指標 cpcptc 表示 **cpcptc*cpcptccpcptc 都為常量,不能用該指標修改所指一級指標指向的資料的值,不可修改其所指一級指標的值,也不可修改其所指向的地址(指標自身的值),能將 int *int * constconst int *const int * const型別的一級指標地址、const int **const int ** constint **int ** constint * const *int * const * constconst int * const *const int *const* const 型別的二級指標值賦給 cpcptc,且必須在宣告時初始化,
  • 型別為 int *const* const 的指標 cpcpt 表示 *cpcptcpcpt 都為常量,能用該指標修改所指一級指標指向的資料的值,不可修改其所指一級指標的值,也不可修改其所指向的地址(指標自身的值),能將 int *int * const 型別的一級指標地址、int **int ** constint * const *int * const * const 型別的二級指標值賦給 cpcpt,且必須在宣告時初始化,
  • 型別為 const int ** const 的指標 cpptc 表示 **cpptccpptc 都為常量,不能用該指標修改所指一級指標指向的資料的值,可修改其所指一級指標的值,但不可修改其所指向的地址(指標自身的值),只能將 const int * 型別的一級指標地址、const int **const int ** const 型別的二級指標值賦給 cpptc,且必須在宣告時初始化,
  • 型別為 const int *const* 的指標 pcptc 表示 **pcptc*pcptc 都為常量,不能用該指標修改所指一級指標指向的資料的值,也不可修改其所指一級指標的值,但可修改其所指向的地址(指標自身的值),能將 int *int * constconst int *const int * const型別的一級指標地址、const int **const int ** constint **int ** constint * const *int * const * constconst int * const *const int *const* const 型別的二級指標值賦給 pcptc

對于型別為 int ** 的常規指標,有以下特性:

  • 型別為 int ** 的指標 ppt 表示 **ppt*pptppt 都不為常量,能用該指標修改所指一級指標指向的資料的值,也可修改其所指一級指標的值,也可修改其所指向的地址(指標自身的值),只能將 int * 型別的一級指標地址賦給 ppt

7.4.3 將二維陣列作為函式引數

//函式原型一
int sum_arr2d(int ** arr2d, int rowSize, int columnSize);

若函式原型按如上宣告,且二維陣列由 newmalloc 所分配,例如 int ** ARR2d = new int *[rowSize];...,則可直接將二維陣列名 ARR2d 傳遞給函式形參 arr2d,對于不是由 newmalloc 所分配的二維陣列,做如下轉換后,可將指標 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);

由于由 newmalloc 所分配的二維陣列,只能保證同一行的記憶體連續,而行與行之間的記憶體不一定連續(即第 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+1arrpf[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++

上一篇:WSL中的npm顯示“NPMELF:未找到錯誤”

下一篇:OpenGL ES 2.0 和 3.0區別

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more