1. 程式人生 > >c++ primer plus書之--c++函式, 陣列引數與指標的關係2

c++ primer plus書之--c++函式, 陣列引數與指標的關係2

陣列怎麼在函式的引數中進行傳遞:

// simple function
#include "iostream"
using namespace std;

const int Size = 8;
// 函式原型, 可以不寫變數名, 變數名也可以與函式定義的變數名不同
int sum_arr(int arr[], int n);
// 也可以寫成下面這中方式:
// int sum_arr(int* arr, int n);

int main() {
	
	int cookies[Size] = {1, 2, 3, 4, 5,6 ,7, 8};
	int sum = sum_arr(cookies, Size);
	
	cout << "sum = " << sum << endl;
	
	return 0;
}

// 函式定義
int sum_arr(int arr[], int n) 
{
	int total = 0;
	for (int i = 0; i < n; i++)
	{
		total += arr[i];
	}
	return total;
}

程式執行結果為:

對於陣列而言:

arr[i] = *(arr + i);

&arr[i] = arr + i;

要記住這兩個恆等式, 注意: 將指標(包括陣列名)加1, 實際上是加了一個與指標指向的型別的長度相等的值, 對於遍歷陣列而言, 使用指標加法和陣列下標是等效的.

 

陣列作為函式的引數有什麼需要注意的

來看個demo 

// simple function
#include "iostream"
using namespace std;

const int Size = 8;
// 函式原型, 可以不寫變數名, 變數名也可以與函式定義的變數名不同
int sum_arr(int arr[], int n);
// 也可以寫成下面這中方式:
// int sum_arr(int* arr, int n);

int main() {
	
	int cookies[Size] = {1, 2, 3, 4, 5,6 ,7, 8};
	int sum = sum_arr(cookies, Size);
	
	// 陣列名就是陣列的首地址
	cout << "cookies's address = " << cookies << endl;
	// sizeof跟陣列名獲取的是陣列的總長度byte
	cout << " sizeof cookies = " << sizeof cookies << endl;
	
	sum = sum_arr(cookies, Size);
	cout << "Total cookies eaten : " << sum << endl;
	sum = sum_arr(cookies, 3);
	cout << "First three eaters ate : " << sum << " cookies" << endl;
	// 相當於使用cookies[4]
	sum = sum_arr(cookies + 4, 4);
	cout << "Last four eaters ate : " << sum << " cookies" << endl;
	return 0;
}

// 函式定義
int sum_arr(int arr[], int n) 
{
	int total = 0;
	// 列印的是陣列arr的首地址
	cout << "arr = " << arr;
	// 列印的是指標arr的大小
	cout << " sizeof arr = " << sizeof arr << endl;
	for (int i = 0; i < n; i++)
	{
		total += arr[i];
	}
	return total;
}

程式執行結果為:

陣列作為引數傳遞意味著什麼:

在上面這個demo中函式呼叫sum_arr(cookies, Size);將陣列第一個元素地址和陣列中的元素數目傳遞給sum_arr函式, sum_arr函式將陣列cookies的地址賦給了指標變數arr, 將Size賦給了int變數n, 因此sizeof cookies是cookies的陣列長度, 而sizeof arr則是指標變數的長度. 這也意味著程式實際並沒有將陣列內容傳遞給函式, 而是將陣列的位置, 包含的元素種類及元素數目傳遞給了函式.

注意:為將陣列型別和元素數量告訴陣列處理函式, 請通過兩個不同的引數來傳遞它們:

void fillArray(int arr[], int size);

而不要試圖使用方括號表示法來傳遞陣列長度

void fillArray(int arr[Size]);

 

const保護陣列

void show_array(const double arr[], int n);

如果陣列前面有const修飾, 那麼表明在這個函式內, 指標arr指向的是常量資料, 這意味著不能使用arr修改該資料, 也就是可以使用arr[0]這樣的值, 但是不能修改.

C++將宣告const double arr[]解釋為const double* arr, 說明arr指向的是一個常量值.

 

來看一個數組作為引數傳遞進函式的完整的例子:

// 陣列作為變數傳遞進函式的demo
// 解釋了為什麼在函式內部列印通過引數傳遞進來的陣列的sizeof列印的不是陣列長度
#include "iostream"

const int Max = 5;
using namespace std;

// 函式原型
int fill_array(double arr[], int limit);
// 這裡用const修飾, 是因為這個函式不會修改陣列的內容
// 因此為了防止誤修改, 用const修飾, 這樣函式內部就不允許對陣列進行修改
void show_array(const double arr[], int n);
void revalue(double r, double arr[], int n);

int main()
{
	// 宣告一個長度為5的陣列
	double properties[Max];
	
	// 將陣列名作為變數傳遞給函式, 實際傳遞進去的
	// 陣列的地址, 因此fill_array裡如果列印 sizeof properties會列印
	// 指標變數的長度, 而不是陣列的長度, 因此一定要把陣列的長度通過
	// 其他引數傳遞進去
	int size = fill_array(properties, Max);
	// 展示陣列內容
	show_array(properties, size);
	if (size > 0)
	{
		cout << "Enter revaluation factor: ";
		double factor;
		// 判斷是否是錯誤的輸入
		while(!(cin >> factor))
		{
			// 先清空
			cin.clear();
			// 後捨棄輸入
			while(cin.get() != '\n')
				continue;
			cout << "please input a num : ";			
		}
		revalue(factor, properties, size);
		show_array(properties, size);
	}
	cout << "End." << endl;
	return 0;
}


int fill_array(double arr[], int limit)
{
	double temp;
	int i;
	for (i = 0; i < limit; i++)
	{
		cout << "input value #" << i << " : " << endl;
		cin >> temp;
		if (!cin)
		{
			// 先清空
			cin.clear();
			// 後捨棄輸入
			while(cin.get() != '\n')
				continue;
			cout << "input thread terminated: ";	
			break;
		} else if(temp < 0){
			break;
		}
		arr[i] = temp;
	}
	return i;
}


void show_array(const double arr[], int n) 
{
	for (int i = 0; i < n; i++) 
	{
		cout << "Property #" << i << " = " << arr[i] << endl;
	}
}

void revalue(double r, double arr[], int n)
{
	for (int i = 0; i < n; i++)
	{
		arr[i] *= r;
	}
}

程式執行結果為:

 

怎樣使用陣列的某個區間

// 使用陣列區間的函式
#include "iostream"
using namespace std;

const int Size = 8;

// 函式原型, 引數為陣列的開始/結束地址
int sum_arr(int* begin, int* end);

int main() {
	
	int cookies[Size] = {1, 2, 3, 4, 5,6 ,7, 8};
	int sum = sum_arr(cookies, cookies + Size);
	
	// 陣列名就是陣列的首地址
	cout << "cookies's address = " << cookies << endl;
	// sizeof跟陣列名獲取的是陣列的總長度byte
	cout << " sizeof cookies = " << sizeof cookies << endl;
	
	sum = sum_arr(cookies, cookies + Size);
	cout << "Total cookies eaten : " << sum << endl;
	// 傳遞進去陣列的首地址和第四個元素的首地址, 也就是計算前三位的和
	sum = sum_arr(cookies, cookies + 3);
	cout << "First three eaters ate : " << sum << " cookies" << endl;
	// 相當於使用cookies[4]
	// 
	sum = sum_arr(cookies + 4, cookies + 8);
	cout << "Last four eaters ate : " << sum << " cookies" << endl;
	return 0;
}

// 函式定義
int sum_arr(int* begin, int* end) 
{
	int total = 0;
	int* pt;
	// 由於pt指向的是一個int陣列, 裡面的變數是int值
	// 因此pt++, 就意味著每次加上int值所佔的位元組數
	// 也就相當於pt指向下一個元素的位置
	for (pt = begin; pt != end; pt++)
	{
		total += *pt;
	}
	return total;
}

程式執行結果為:

指標和const關係

先看個例子:

int age = 39;

const int* pt = &age;

這裡pt指向了一個const int型物件, 因此不能通過pt修改age的數值也就是:

*pt += 1;

這種企圖去改變值得錯誤是不被允許的.

但是age本身不是const的, 因此可以通過age對值進行修改.

 

再看下面兩種情況:

const double price = 1.23;

// 合法的, 因為兩個都是const型變數

const double* p_price = &price;

const double price = 1.23;

// 不合法的, 因為指標p_price是非const的, 所以可以通過p_price對price進行修改

double * p_price = &price;

 總結一下就是: c++禁止將const的地址賦值給非const指標.

 

如果將指標指向指標, 情況會更復雜一點:

1.涉及的是一級間接關係, 則將非const指標賦值給const指標是可以的:

int age = 20;

// 可以通過指標pt對age變數的值進行修改

int* pt = &age;

// 合法但是不能通過pt_const對age值進行修改

const int* pt_const = pt;

2.進入兩級間接關係時(注意看這個例子是不對的, 只是用來說明問題):

const int** pp2;

int* p1;

const int n = 33;

// 不允許這樣做, 但是我們先假設可以這麼做

pp2 = &p1;

// 允許這麼做, 因為兩邊都是const型別的

*pp2 = &n;

// 允許這麼做, 因為p1不是const, 但是會改變const n

// 這會出問題, 因此當且僅當只有一層間接關係(如指標指向基本資料型別)時, 才可以將非const地址或指標賦值給const指標.

*p1 = 10;

注意:如果資料本身不是指標, 則可以將const資料或非const資料賦值給const指標, 但只能將非const資料的地址賦值給非const指標.

 

const陣列

假設有一個const陣列

const int num[3] = {1, 2, 3};

則禁止將常量陣列的地址賦值給非常量指標, 也意味著不能將陣列名作為引數傳遞給使用非常量形參的函式:

int sum(int arr[], int n);

// 非法的, 形參不是const型別的

int j = sum(num, 3);

 

將指標引數宣告為指向常量資料的指標有兩個優點:

1.可以避免由於無意間修改資料導致的問題

2.使用const使得函式能夠處理const和非const實參, 否則只能處理非const資料

如果條件允許, 則儘量將指標形參宣告為const型別的指標.

 

 

關於指標和const還有一個細節需要注意:

舉個簡單的例子:

int age = 20;

const int* pt = &age;

第二行程式碼, 只能防止通過pt去修改pt所指向的地址裡的值, 但是我們卻可以將pt指向一個新的地址, 例如:

int num = 1;

pt = &num;

這時候pt指向的地址中的值是1, 但是我們還是不能使用pt來修改它指向的值(1)

 

 

還有一種方式使得無法修改指標的值

int time = 6;

// 一個指向const int的指標

const int* pt = &time;

// 一個int型的const 指標

int* const pc = &time;

第三行程式碼規定了pc只能指向time, 但是允許通過pc來修改time的值, 第二個宣告不允許使用pt來修改time的值, 但允許將pt指向新的地址.

也就是pc和*pt是const的, 而pt和*pc是非const的.

我們可以這麼記:

const 後面緊跟著的是int則表示不能通過該指標去修改指標指向的地址裡的值, 但是可以修改指標指向的地址, 也就是值不能修改, 但可以改地址

const 後面緊跟著的是pointer則表示我們不能修改指標所指向的地址, 但是可以修改指標指向地址裡的值, 也就是指標不能修改, 但可以改值.

 

指標和二維陣列:

舉一個例子:
 

int data[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int total = sum(data, 3);


那麼函式sum的原型該怎麼宣告呢?
這裡data是個陣列名, 它有三個元素, 第一個元素本身是個陣列, 由4個int值組成. 因此data的型別是指向由4個int組成的陣列的指標:

 

int sum(int (*arr)[4], int n);


注意其中的小括號是必不可少的, 如果寫成
 

int sum(int *arr[4], int n);


上面這行程式碼表示 宣告一個由4個指向int的指標組成的陣列int *(arr[4]), 而不是由4個int組成的陣列的指標.
還有一種正確的寫法:

 

int sum(int arr[][4], int size);


這種寫法更容易理解一些.

下面是完整的函式定義:
 

int sum(int arr[][4], int size)
{
    int total = 0;
    for(int i = 0; i < size; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            total += arr[i][j];
        }
    }
    return total;
}


例子中沒有使用const, 是因為arr是指向指標的指標