1. 程式人生 > 其它 >Chapter 7函式——C++的程式設計模組

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檔案中。

  • 為什麼需要原型 原型提供了函式的引數型別和數量以及返回值的型別(如果有的話)告訴編譯器。
  • 原型的語法 函式原型是一條語句,必須以分號結束。 函式原型不要求提供變數名,有函式列表就足夠了。
  • 原型的功能 原型確保以下幾點:
  1. 編譯器正確處理函式返回值
  2. 編譯器檢查使用引數數目是否正確
  3. 編譯器檢查使用引數型別是否正確如果不正確,則轉換為正確的型別(如果可以的話)

####7.2 函式引數和按值傳遞 引數(argument)表示實參,參量(parameter)表示形參,引數傳遞將引數賦給參量。 #####7.2.1 多個引數 函式的多個引數用逗號隔開。

####7.3 函式和陣列

#####7.3.1 函式如何使用指標來處理陣列 在大多數情況下,cookies == &cookies[0],&cookies將返回整個陣列的地址,通常是一個大的記憶體塊。 在函式宣告中,int * arrint arr[]是等效的。

#####7.3.2 將陣列作為引數意味著什麼 陣列名與指標對應:

  • 將陣列地址作為引數可以節省複製整個陣列所需的時間和記憶體
  • 使用原始資料增加了破壞資料的風險。C++使用const限定符解決該問題。

注:指標本身並沒有指出陣列的長度,將陣列型別和元素數量告訴陣列處理函式時,請通過兩個不同的引數來傳遞他們;而不要試圖用方括號表示法來傳遞陣列長度。

void fillArray(int arr[size]) //NO -- bad prototype

#####7.3.3 更多陣列函式示例 構思程式時將儲存屬性與操作結合起來,便是朝oop思想邁進了重要的一步。

  1. 填充陣列 在陣列填滿之前停止讀取資料,在函式中建立這種特性。
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;
}
  1. 顯示陣列及用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;
	}
}
  1. 修改陣列
void revalue(double r, double ar[], int n)
{
	for(int i = 0; i < n; i++)
		ar[i] *= r;
}
  1. 陣列處理函式常用的編寫方式 修改陣列:
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

  1. 使用函式的3個步驟是什麼?
  • 函式宣告(提供原型)
  • 函式定義
  • 函式呼叫
  1. 請建立與下面的描述匹配的函式原型。
  1. igor沒有引數,且沒有返回值。 void igor();
  2. tofu()接受一個int引數,並返回一個float。 float tofu(int);
  3. mpg()接受兩個double引數,並返回一個double引數。 double mpg(double miles, double gallons);
  4. summation()將long陣列名和陣列長度作為引數,並返回一個long值。 long summation(long[], int);
  5. doctor()接受一個字串引數(不能修改該字串),並返回一個double值。double doctor(const std::string s); //wrong
    double doctor(const char * str);
  6. ofcourse()將boss結構作為引數,不返回值。 void ofcourse(boss b);
  7. plot將map結構的指標作為引數,,並返回一個字串。 string plot(map *pt); //wrong, can't return string, can return point
    char * plot(map *pmap);
  1. 編寫一個接受三個引數的函式,int陣列名、陣列長度和一個int值,並將陣列的所有元素都設定為該int值。
void setar(int ar[], int len, int n)
	for(int i = 9; i < len; i++)
		ar[i] = n;
  1. 編寫一個接受三個引數的函式,指向陣列區間第一個元素的指標、指向陣列區間最後一個元素的指標和一個int值,並將陣列的所有元素都設定為該int值。
void setar(int * begin, int * end, int n)
	int * pt;
	pt = begin;
	while(pt != end)
	{
		*pt = n;
		pt++;
	}
  1. 編寫將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; // 
}
  1. 為什麼不對型別為基本型別的函式引數使用const限定符? 函式傳遞基本型別的引數按值傳遞使用的是引數副本,不會修改原始資料,因此不需要const限定符,使用指標怕對原始資料產生影響,因此使用const限定符。
  2. C++程式可以使用哪3中C-風格字串格式?
  • 字元陣列
  • 字串常量
  • string物件指向字串第一個字元的指標
  1. 編寫一個函式,其原型如下: 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;
}
  1. 表示式*"pizza"的含義是什麼?"taco"[2]呢?
    *"pizza"代表常字串"pizza"的地址字串"pizza"的字元p的地址"taco"[2]存在一個字串陣列,有兩個元素,每個元素都是常字串"taco"指的是字串陣列的第三個字元,即c
  2. C++允許按值傳遞結構,也允許傳遞該結構的地址。如果glitz是一個結構變數,如何按值傳遞它?如何傳遞它的地址?這兩種方法有何利弊? 函式會建立glitz的一個副本,使用該函式的副本,不會對glitz的原始資料產生影響,但會浪費時間和儲存空間;使用地址式函式直接使用指標指向該結構,利用指標直接訪問該結構,該方法可以節約空間和時間,但可能會改變原始資料。
  3. 函式judge()的返回型別為int,它將這樣一個函式的地址作為引數:將const char指標作為引數,並返回一個int值。請編寫judge()函式的原型。
int judge((*int)(const char*));//wrong
int judge(int(*pt)(const char *));
  1. 假設有如下結構宣告:
struct applicant{
	char name[30];
	int credit_ratings[3];
}
  1. 編寫一個函式,它將application結構作為引數,並顯示該結構的內容。
  2. 編寫一個函式,它將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;
}
  1. 假設函式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]