1. 程式人生 > >C++ primer plus書之--C++函式和C語言字串, 結構體, string

C++ primer plus書之--C++函式和C語言字串, 結構體, string

函式和C風格字串

要將C風格字串作為引數傳遞給函式, 表示字串的方式有三種:

1.char陣列

2.用""擴起來的字串常量

3.被設定為字串地址的char指標

來看一個例子:

// c風格字串例子
#include "iostream"
using namespace std;
unsigned int c_in_str(const char* str, char ch);

int main() {
	// char型陣列表示字串
	char str1[10] = "qwertqwqt";
	// char型指標 表示字串
	char* p_str = "asdfasa";
	
	unsigned int n_str1 = c_in_str(str1, 'q');
	unsigned int n_p_str = c_in_str(p_str, 'a');
	cout << str1 << " has " << n_str1 << " q " << endl;
	cout << p_str << " has " << n_p_str << " a " << endl;
	return 0;
}


unsigned int c_in_str(const char* str, char ch)
{
	unsigned int count = 0;
	// 判斷是不是\0 空字元
	while(*str)
	{
		if (*str == ch)
			count++;
		str += 1;
	}
	return count;
}

程式執行結果:

 

返回c風格的字串的函式

c語言中沒有字串, 但是我們可以返回一個char型指標, 該指標指向字串的首地址即可, 看一個demo:

// c風格字串例子
#include "iostream"
using namespace std;
char* getstr(char ch, int n);

int main() {
	int times;
	char ch;
	
	cout << "Enter a character: ";
	cin >> ch;
	cout << "Enter a num : ";
	cin >> times;
	
	char* ps1 = getstr(ch, times);
	cout << ps1 << endl;
	// 用完記得回收
	delete ps1;
	ps1 = getstr('*', 10);
	cout << ps1 << endl;
	delete [] ps1;
	
	return 0;
}


char* getstr(char ch, int n)
{
	// 要考慮\0字元
	char* p_ch = new char[n + 1];
	while (n-- > 0)
	{
		p_ch[n] = ch;
	}
	return p_ch;
}

 程式執行結果:

注意, 變數p_ch的作用域是在函式getstr函式內部, 當函式執行完畢後, p_ch(而不是字串)使用的記憶體將被釋放, 但由於函式返回了p_ch的值, 因此程式仍可以通過main()裡的指標ps1來訪問新建的字串, mian中當new建立的字串不再需要時通過delete釋放該字串佔用的記憶體.

 

函式和結構體

雖然結構變數和陣列一樣可以儲存多個數據項, 單在涉及到函式時, 結構變數的行為更接近於基本單值變數, 也就是說與陣列不同, 結構將其資料組成單個實體或資料物件, 該實體被視為一個整體. 按值傳遞結構, 就像普通變數那樣, 函式將使用原始結構的副本. 另外函式也可以返回結構. 與陣列名就是陣列的第一個元素的地址不同的是, 結構名只是結構的名稱, 要獲得結構的地址, 必須使用地址運算子&.

 

傳遞和返回結構

當結構比較小時, 按值傳遞結構最合理.

看個簡單的結構體做函式引數的demo:

// 函式和結構體
#include "iostream"
using namespace std;

struct travel_time
{
	int hours;
	int mins;
};

const int Mins_per_hour = 60;

travel_time sum(travel_time t1, travel_time t2);
void show(travel_time t);

int main() {
	
	// 定義並初始化一個結構體變數
	travel_time d1 = {2, 38};
	travel_time d2 = {3, 46};
	
	travel_time sum1 = sum(d1, d2);
	// 列印的是sum1的首地址可以和sum方法中的地址進行比較, 可以看出來兩個地址不是同一個
	// 也就是這種傳遞結構體的方式, 傳遞的是副本而不是地址
	cout << "address of sum1 in main fun : " << &sum1 << endl;
	cout << "two-day total : ";
	show(sum1);
	
	travel_time d3 = {6, 58};
	cout << "Three-day total:";
	show(sum(sum1, d3));
	return 0;
}

travel_time sum(travel_time t1, travel_time t2)
{
	travel_time total;
	total.hours = t1.hours + t2.hours + (t1.mins + t2.mins) / Mins_per_hour;
	total.mins = (t1.mins + t2.mins) % Mins_per_hour;
	// 列印total的首地址
	cout << "address of total in sum fun : " << &total << endl;
	return total;
}

void show(travel_time t)
{
	cout << "hours : " << t.hours << ", mins : " << t.mins << endl;
}

執行結果為:

注意看程式碼中的註釋, 理解到底是傳遞的地址還是傳遞的是值的副本

 

再看一個簡單的demo, 就是座標系的轉換, 一種是記錄點到圓點的距離以及和x軸的夾角, 一種是記錄點在座標系中的x, y值:

// 函式和結構體
#include "iostream"
#include "cmath"
using namespace std;

struct polar
{
	double distance;
	double angle;
};

struct rect
{
	double x;
	double y;
};

// 函式原型
// 按值傳遞, 也就是函式內使用的是結構體的副本
polar rect_to_polar(rect xypos);
void show(polar polar);

int main() {
	// 定義兩個結構體變數
	rect rplace;
	polar pplace;
	cout << "Enter the x and y value : ";
	// 藉助cin來判斷使用者輸入的是否合法, 因為x,y是double型別, 所以只有輸入double型別的時候才是合法的
	while (cin >> rplace.x >> rplace.y)
	{
		pplace = rect_to_polar(rplace);
		show(pplace);
		cout << "Next x, y nums, q to quit : ";
	}
	cout << "******End********" << endl;
	return 0;
}

polar rect_to_polar(rect xypos)
{
	polar answer;
	// 開方
	answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
	// 計算角度
	answer.angle = atan2(xypos.y, xypos.x);
	return answer;
}

void show(polar dapos)
{
	const double Rad_to_deg = 57.296;
	cout << "distance = " << dapos.distance;
	cout << ", angle = " << dapos.angle * Rad_to_deg;
	cout << " degrees" << endl;
}

看執行結果:

程式碼沒有什麼特殊的地方是為了, 和傳遞結構體地址的函式做對比的程式碼, 只需要注意while迴圈判斷中的判斷方法即可

 

傳遞結構體的地址

假設要傳遞結構的地址而不是整個結構以節省時間和空間, 應該使用指向結構的指標, 需要修改以上程式碼的三個地方:

1.呼叫函式時, 將結構的地址(&pplace)而不是結構本身(pplace)傳遞給函式

2.將形參生命為指向polar的指標(polar *)型別, 如果函式內不需要修改結構體, 可以宣告成 const polar * 型別

3.由於形參是指標而不是結構, 因此應該使用間接成員運算子(->), 而不是成員運算子(.)

以下是修改之後的程式:

// 函式和結構體
#include "iostream"
#include "cmath"
using namespace std;

struct polar
{
	double distance;
	double angle;
};

struct rect
{
	double x;
	double y;
};

// 函式原型
// 宣告為接收結構體的地址, 這樣就不再是值傳遞了
polar rect_to_polar(rect* xypos);
// 需要用指標來接受結構體的地址
void show(const polar* polar);
void changeValue(polar* polar);

int main() {
	// 定義兩個結構體變數
	rect rplace;
	polar pplace;
	cout << "Enter the x and y value : ";
	// 藉助cin來判斷使用者輸入的是否合法, 因為x,y是double型別, 所以只有輸入double型別的時候才是合法的
	while (cin >> rplace.x >> rplace.y)
	{
		// 這裡要使用&傳遞地址
		pplace = rect_to_polar(&rplace);
		show(&pplace);
		cout << "Next x, y nums, q to quit : ";
	}
	cout << "******End********" << endl;
	
	cout << "測試是否是傳遞的地址" << endl;
	cout << "before change : " << endl;
	show(&pplace);
	// 這裡將pplace的內容做了修改, 看是否傳遞的是地址, 也就是在changeValue裡修改, 在函式外是否也有效
	changeValue(&pplace);
	cout << "after change : " << endl;
	show(&pplace);
	return 0;
}

polar rect_to_polar(rect* xypos)
{
	polar answer;
	// 開方
	// 注意這裡xypos已經是指標了, 所以不能使用.呼叫屬性, 而應該使用->
	answer.distance = sqrt(xypos->x * xypos->x + xypos->y * xypos->y);
	// 計算角度
	answer.angle = atan2(xypos->y, xypos->x);
	return answer;
}

void show(const polar* dapos)
{
	const double Rad_to_deg = 57.296;
	cout << "distance = " << dapos->distance;
	cout << ", angle = " << dapos->angle * Rad_to_deg;
	cout << " degrees" << endl;
}

void changeValue(polar* polar)
{
	// 將資料都設定成10
	polar->distance = 10;
	polar->angle = 10;
}

看執行結果:

需要注意的地方都在註釋裡寫清楚了.

 

函式和string

雖然c風格字串和string物件的用途幾乎相同, 但與陣列相比, 寫法上string物件與結構題更為相似

// 函式和結構體
#include "iostream"
#include "string"
using namespace std;

const int Size = 3;

// 函式原型
void show(string sarr[], int n);

int main() {
	// 聲明瞭一個字串陣列
	string list[Size];
	cout << "Enter your " << Size << " favorite num : ";
	for (int i = 0; i < Size; i++)
	{
		cout << i + 1 << " : ";
		getline(cin, list[i]);
	}
	cout << "Your list : " << endl;
	show(list, Size);
	// 重新列印list看看資料是否發生了改變
	cout << "list[0]" << " : " << list[0] << endl;
	cout << "*(list + 1)" << " : " << *(list + 1) << endl;
	return 0;
}

void show(string sarr[], int n)
{
	for(int i = 0; i < n ; i++)
	{
		// 兩種輸出方法均可
		cout << i + 1 << " : " << sarr[i] << endl;
		cout << i + 1 << " : " << *(sarr + i) << endl;
	}
	
	// 這裡做一個修改, 看看傳遞的是字串陣列的副本還是傳遞的是地址
	sarr[0] = -1;
	*(sarr + 1) = -2;
}

看一下輸出結果:

這裡傳遞的是string陣列, 所以陣列名仍然是指標, 也就是如果在方法內對資料進行了修改, 在外面的string陣列也響應的發生了改變