Chapter 7函式——C++的程式設計模組
本章需要重點掌握的內容:
- 設計函式
- 使用const指標引數
- 呼叫自身的函式
- 指向函式的指標
####7.1 函式的基本知識 要使用C++函式,必須完成如下工作:
- 提供函式定義
- 提供函式原型
- 呼叫函式
#####7.1.1 定義函式 無返回值的函式通用格式如下:
void functionName(parameterList)
{
statement(s);
}
有返回值的函式:
typeName functionName(parameterList): { statement(s); return vlaue; // value is type cast to type typeName }
#####7.1.2 函式原型和函式呼叫 函式原型通常隱藏在inclue檔案中。
- 為什麼需要原型 原型提供了函式的引數型別和數量以及返回值的型別(如果有的話)告訴編譯器。
- 原型的語法 函式原型是一條語句,必須以分號結束。 函式原型不要求提供變數名,有函式列表就足夠了。
- 原型的功能 原型確保以下幾點:
- 編譯器正確處理函式返回值
- 編譯器檢查使用引數數目是否正確
- 編譯器檢查使用引數型別是否正確如果不正確,則轉換為正確的型別(如果可以的話)
####7.2 函式引數和按值傳遞 引數(argument)表示實參,參量(parameter)表示形參,引數傳遞將引數賦給參量。 #####7.2.1 多個引數 函式的多個引數用逗號隔開。
####7.3 函式和陣列
#####7.3.1 函式如何使用指標來處理陣列
在大多數情況下,cookies == &cookies[0]
,&cookies將返回整個陣列的地址,通常是一個大的記憶體塊。
在函式宣告中,int * arr
與int arr[]
是等效的。
#####7.3.2 將陣列作為引數意味著什麼 陣列名與指標對應:
- 將陣列地址作為引數可以節省複製整個陣列所需的時間和記憶體
- 使用原始資料增加了破壞資料的風險。C++使用const限定符解決該問題。
注:指標本身並沒有指出陣列的長度,將陣列型別和元素數量告訴陣列處理函式時,請通過兩個不同的引數來傳遞他們;而不要試圖用方括號表示法來傳遞陣列長度。void fillArray(int arr[size])
//NO -- bad prototype
#####7.3.3 更多陣列函式示例 構思程式時將儲存屬性與操作結合起來,便是朝oop思想邁進了重要的一步。
- 填充陣列 在陣列填滿之前停止讀取資料,在函式中建立這種特性。
int fill_array(double arr[], int limit)
{
using namespace std;
double temp;
int i;
for(i = 0; i < limit; i++)
{
cout << "Enter value #" << (i + 1) << ": ";
cin >> temp;
if(!cin)
{
cin.clear();
while(cin.get() != '\n')
coutinue;
cout << "Bad input; input process terminated.\n";
break;
}
else if(temp < 0)
break;
arr[i] = temp;
}
return i;
}
- 顯示陣列及用const保護陣列 建立顯示陣列內容的函式很簡單,需要確保顯示函式不修改原始陣列。
void show_array(const double ar[], int n)
{
using namespace std;
for(int i = 0; i < n; i++)
{
cout << "Property #" << i << " : $";
cout << ar[i] << endl;
}
}
- 修改陣列
void revalue(double r, double ar[], int n)
{
for(int i = 0; i < n; i++)
ar[i] *= r;
}
- 陣列處理函式常用的編寫方式 修改陣列:
void f_modify(double ar[], int n)
不修改陣列:
void f_no_change(const double ar[], int n);
這兩種編寫方式不能用sizeof來獲悉原始陣列的長度。
#####7.3.4 使用陣列區間的函式 另一種陣列引數傳遞的方法,指定元素區間,通過兩個指標來完成。 注:使用const時,在函式中定義指標也需要const.
#####7.3.5 指標和const 兩種不同的方式將const關鍵字用於指標。
- 讓指標指向一個常量物件,這樣可以防止使用該指標來修改所指向的值
首先,宣告一個指向常量的指標pt: int age = 39; const int * pt = &age; 該宣告指出,pt指向一個const int(這裡為39),因此不能使用pt來修改這個值。*pt的值為const,不能被修改: *pt += 1;// INVALID beacuse pt point to a const int cin >> pt;// INVALID for the same reason
- 將指標本身宣告為常量(const),這樣可以防止改變指標指向的位置 兩種可能:將const變數的地址賦給指向const的指標,將const的地址賦給常規指標。 第一種操作可行,第二種不可行。 const float g_earth = 9.80; const float * pe = &g_earth; // VALID
const float g_moon = 1.63; float * pm = &g_moon; // INVALID 如果上面的操作可以,則可以通過pm修改g_moon的值,這使得const的狀態很荒謬。因此禁止該情況。
儘可能使用const 將指標引數宣告為指向常量資料的指標有兩條理由:
- 這樣可以避免由於無意修改資料而導致的程式設計錯誤;
- 使用const是的函式能夠處理const和非const實參,否則只能接受非const資料。 如果條件允許,則應該將指標形參宣告為指向const的指標。
####7.4 函式和二維陣列 二維陣列作為引數的函式宣告和呼叫:
- 使用指標
int sum_ar2((*ar2)[4], int size)
- 使用陣列
int sum_ar2(int ar2[][4], int size)
要想得到二維陣列的資料: * ((* ar2 + r) + c) 得到r行c列的資料。
####7.5 函式和C-風格字串 將字串作為引數時意味著傳遞的是地址,可以使用const來禁止對字串引數進行修改。 #####7.5.1 將C-風格字串作為引數的函式 處理字串中字元的標準方式:
while(*str)
{
statements;
str++;
}
#####7.5.2 返回C-風格字串的函式
返回的是一個地址,接受函式也應該是一個地址,使用動態儲存記得使用delete釋放空間。
char * buildstr(char ch, int n);
呼叫:
char * pt = bulid('+',20);
完成後記得 delete[]pt
####7.6 函式和結構 結構名只是結構的名稱,陣列名是第一個陣列元素的地址。結構必須使用&地址運算子。 #####7.6.1 傳遞和返回結構 當結構比較小時,按值傳遞結構最合理。
#####7.6.2 傳遞結構的地址 傳遞結構的地址可以節省時間和空間。 修改:
- 呼叫函式時,將結構的地址(&pplace)而不是結構本身傳遞;
- 形參宣告為指向結構的指標,即polar * 的型別,在不修改結構時使用const修飾符
- 形參是指標,因此應接成員運算子
->
而不是.
;
####7.7 函式和string物件
string物件與結構的用途更相似。
string陣列,string物件讀行getline(cin,str);
####7.8 函式與array物件 類物件是基於結構的。在傳遞給函式時有兩種方式:
- 按值傳遞給函式,這種情況下,函式處理的時原始物件的副本;
- 傳遞指向物件的指標,函式可以操作原始物件。
####7.9 遞迴 函式自己呼叫自己,該功能成為遞迴。(C++不允許main()函式呼叫自己)。 #####7.9.1 包含一個遞迴呼叫的遞迴
void recurs(argumentlist)
{
statements1
if (test)
recurs(arguments)
statements2
}
該程式recurs進行5次遞迴呼叫,則第一個statements1順序執行5次,第二個statements2將與函式相反的順序執行5次。
#####7.9.2 包含多個遞迴呼叫的遞迴 樣例程式:
// ruler.cpp -- using recursion to subdivide a ruler
#include<iostream>
const int Len = 66;
const int Divs = 6;
void subdivide(char ar[], int low, int high, int level);
int main()
{
char ruler[Len];
int i;
for(i = 1; i < Len - 2; i++)
ruler[i] = ' ';
ruler[Len - 1] = '\0';
int max = Len - 2;
int min = 0;
ruler[min] = ruler[max] = '|';
std::cout << ruler << std::endl;
for(i = 1; i <= Divs; i++)
{
subdivide(ruler,min,max, i);
std::cout << ruler << std::endl;
for (int j = i; j < Len - 2; j++)
ruler[j] = ' ';
}
return 0;
}
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);
}
該程式使用變數level來控制遞迴層,函式呼叫自身時,將level-1,當level為0時,函式將不在呼叫自己。subdivide呼叫自己兩次,一次針對左半部分,一次針對右半部分。注意,呼叫次數將呈幾何級數增長.當遞迴層次較少時,該方法很適合。
####7.10 函式指標 與資料項類似,函式也有地址。函式的地址是儲存其機器語言程式碼的記憶體的開始地址。
#####7.10.1 函式指標的基礎知識 將程式設計師要使用的演算法函式的地址傳給estimate(),為此,必須完成以下工作:
- 獲取函式的地址; 使用函式名,後面不跟引數就是該函式的地址。 process(think); // passes address of think() thought(think()); // passes the return value of think
- 宣告一個函式指標; 正確的宣告如下:
double pam(int);
double (*pt) (int);// () is must
pt = pam;
double *pf (int)
意味著聲明瞭一個函式,返回一個double型別的指標
- 使用函式指標來呼叫函式。 使用指標函式是,將他看成函式名即可。
#####7.10.2 深入討論函式指標 使用auto來簡化簡化初始化,注:auto只能用於單值初始化
#####7.10.3 使用typedef進行簡化 宣告函式指標型別的別名
typedef const double *(*p_fun)(const double * ,int);
p_fun p1 = f1;
####7.11
- 使用函式的3個步驟是什麼?
- 函式宣告(提供原型)
- 函式定義
- 函式呼叫
- 請建立與下面的描述匹配的函式原型。
- igor沒有引數,且沒有返回值。
void igor();
- tofu()接受一個int引數,並返回一個float。
float tofu(int);
- mpg()接受兩個double引數,並返回一個double引數。
double mpg(double miles, double gallons);
- summation()將long陣列名和陣列長度作為引數,並返回一個long值。
long summation(long[], int);
- doctor()接受一個字串引數(不能修改該字串),並返回一個double值。
double doctor(const std::string s); //wrong
double doctor(const char * str);
- ofcourse()將boss結構作為引數,不返回值。
void ofcourse(boss b);
- plot將map結構的指標作為引數,,並返回一個字串。
string plot(map *pt); //wrong, can't return string, can return point
char * plot(map *pmap);
- 編寫一個接受三個引數的函式,int陣列名、陣列長度和一個int值,並將陣列的所有元素都設定為該int值。
void setar(int ar[], int len, int n)
for(int i = 9; i < len; i++)
ar[i] = n;
- 編寫一個接受三個引數的函式,指向陣列區間第一個元素的指標、指向陣列區間最後一個元素的指標和一個int值,並將陣列的所有元素都設定為該int值。
void setar(int * begin, int * end, int n)
int * pt;
pt = begin;
while(pt != end)
{
*pt = n;
pt++;
}
- 編寫將double陣列名和陣列長度作為引數,並返回該陣列中最大值的函式。該函式不應該修改陣列的內容。
double find_max(const double ar[], int len)
{
double max = ar[0];
for(int i = 0; i < len; i++)
if(max < ar[i])
max = ar[i];
return max; //
}
- 為什麼不對型別為基本型別的函式引數使用const限定符? 函式傳遞基本型別的引數按值傳遞使用的是引數副本,不會修改原始資料,因此不需要const限定符,使用指標怕對原始資料產生影響,因此使用const限定符。
- C++程式可以使用哪3中C-風格字串格式?
- 字元陣列
- 字串常量
string物件指向字串第一個字元的指標
- 編寫一個函式,其原型如下:
int replace(char *str, char c1, char c2);
該函式將字串中所有的c1都替換為c2,並返回替換次數;
int replace(char *str, char c1, char c2)
{
int count = 0;
while(*str != '\0')
{
if(*str == c1)
{
*str = c2;
count++;
}
str++;
}
return count;
}
- 表示式
*"pizza"
的含義是什麼?"taco"[2]
呢?
*"pizza"
代表常字串"pizza"的地址字串"pizza"的字元p的地址,"taco"[2]
存在一個字串陣列,有兩個元素,每個元素都是常字串"taco"指的是字串陣列的第三個字元,即c。 - C++允許按值傳遞結構,也允許傳遞該結構的地址。如果glitz是一個結構變數,如何按值傳遞它?如何傳遞它的地址?這兩種方法有何利弊? 函式會建立glitz的一個副本,使用該函式的副本,不會對glitz的原始資料產生影響,但會浪費時間和儲存空間;使用地址式函式直接使用指標指向該結構,利用指標直接訪問該結構,該方法可以節約空間和時間,但可能會改變原始資料。
- 函式judge()的返回型別為int,它將這樣一個函式的地址作為引數:將const char指標作為引數,並返回一個int值。請編寫judge()函式的原型。
int judge((*int)(const char*));//wrong
int judge(int(*pt)(const char *));
- 假設有如下結構宣告:
struct applicant{
char name[30];
int credit_ratings[3];
}
- 編寫一個函式,它將application結構作為引數,並顯示該結構的內容。
- 編寫一個函式,它將application結的地址構作為引數,並顯示該結構的內容。
void display(const applicant application)
{
cout << "Name: " << application.name << endl;
for(int i = 0; i < 3; i++)
cout << "credit_rate #" << i+1 << ": " << application.credit_ratings << endl;
}
void display(const applicant * application)
{
cout << "Name: " << application->name << endl;
for(int i = 0; i < 3; i++)
cout << "credit_rate #" << i+1 << ": " << application->credit_ratings << endl;
}
- 假設函式f1()和f2()的原型如下:
void f1(applicant * a);
const char * f2(const applicant * a1, const applicant * a2);
請將p1和p2分別宣告為指向f1和f2的指標;將ap宣告為一個數組,它包含5個型別與p1相同的指標;將pa宣告為一個指標,它指向的陣列包含10個型別與p2相同的指標。使用typedef來完成這項工作。
typedef void (*pf1)(applicant *a);//typedef void (*pf1)(applicant *)
pf1 p1;
p1 = f1; // pf1 p1 = f1;
typedef const char * (*pf2)(applicant *, applicant *);
pf2 p2;
p2 = f2;// pf2 p2 = f2;
pf1 ap[5];
pf2 ap2[10];
auto pa = &ap2
void *(*(*pf2))[10])(applicant *a1, applicant *a2) = &ap2;
//pf2 (*pa)[10]