1. 程式人生 > >Primer C++第五版 讀書筆記(一)

Primer C++第五版 讀書筆記(一)

Primer C++第五版 讀書筆記(一)
(如有侵權請通知本人,將第一時間刪文)


1.1-2.2 章節
關於C++變數初始化:
	初始化不是賦值,初始化的含義是建立變數時賦予其一個初始值,而賦值的含義是把物件的當前值擦除,以一個新值來替代.

定義一個名為a的int變數並初始化為0,有以下4種方法:
	int a = 0;
	int a = {0};	// 列表初始化
	int a{0};		// 列表初始化
	int a(0);
當列表初始化方法用於內建型別的變數時,如果初始值存在資訊丟失的風險,則編譯器將報錯.
示例:
	long double ld = 3.1415926536;
	int a{ ld }, b{ ld };		// error C2397	從"long double"轉換到"int"需要收縮轉換
	int c(ld), d = ld;			// warning C4244: "初始化": 從"long double"轉換到"int",可能丟失資料

定義變數的錯誤示範:
double salary = wage = 9999.99;  // error:沒定義變數 wage
std::cin >> int input_value;     // 不允許

下列變數的初始值是什麼?
string global_str;				// 全域性變數,值為""
int global_int;					// 全域性變數,值為 0
int main()
{
	int local_int;				// 區域性變數,是未定義狀態,無意義資料
	string local_str;			// 哪怕它是區域性變數,它也是string定義的,值為""
}

未初始化的變數可能引發執行時的故障.
建議初始化每一個內建型別的變數.雖然並非必須,但如果我們不能確保初始化後程序安全,那麼這麼做不失為一種簡單可靠的方法.

注意:
	變數只可以被定義一次,但可被宣告多次.
	如果想宣告一外變數而非定義它,可在變數名前新增關鍵字 extern , 而不要顯示地初始化變數,示例如下:
		extern int i;	// 宣告變數 i 而非定義它
		int j;			// 宣告並定義變數 j
	任何包含了顯示初始化的宣告即成為定義.我們能給由extern關鍵字標記的變數賦一個初始值,但這麼做就抵消了extern的作用.
	extern語句如果包含了初始值就不再是宣告,而變成了定義:
		extern double pi = 3.1415; // 定義
	在函式體內部,如果試圖初始化一個由extern關鍵字標記的變數,將引發錯誤.

============================================================================================
2.3 C++複合型別:
引用:
	引用(reference)為物件起了另外一個名字,引用型別引用(refers to)另外一種型別.
	int ival = 1024;
	int &refVal = ival;			// refVal指向ival(是ival的另一個名字)
	int &refVal2;				// 錯誤!!引用必須被初始化
	引用即別名:引用並非物件,相反的,它只是為一個已經存在的物件所起的另外一個名字.
	為引用賦值,實際上是把值賦給了與引用繫結的物件.獲取引用的值,實際上是獲取了與引用繫結的物件的值.
	同理,經引用作為初始值,實際上是以與引用繫結的物件作為初始值.
	因為引用本身不是一個物件,所以不能定義引用的引用.
	絕大多數情況 下,引用的型別都要和與之繫結的物件嚴格匹配.而且,引用只能繫結到物件上,而不能與字面值或某個表示式的計算結果繫結在一起.
	int &refVal4 = 10; 			// 錯誤:引用型別的初始值必須是一個物件.
	const int &refVal4 = 10;  	// 正確
	double dval = 3.14;
	int &reVal5 = dval;			// 錯誤:引用型別要與與之繫結的物件嚴格匹配!

指標:
	指標(pointer)是指向另外一種型別的複合型別.
	指標與引用相比有很多不同點(重點):
		0.引用是已經存在的物件的另一個名稱,而指標是一個物件,它遵循自己的使用規則.
		1.指標本身就是一個物件,允許對指標賦值和拷貝,而且在指標的生命週期內它可以先後指向幾個不同的物件.
		2.指標無須在定義時賦初值,和其他內建型別一樣,在塊作用域內定義的指標,如果沒有被初始化,也將擁有一個不確定的值.引用在定義時就必須初始化.
		3.最大不同:引用本身並非一個物件,一旦定義了引用,就無法令其再繫結到另外的物件,之後每次使用這個引用都是訪問它最初繫結的那個物件.

		int *ip1, *ip2;  // ip1 和 ip2 都是指向 int 型物件的指標
		double dp, *dp2; // dp2 是指向 double 型物件的指標,dp 是 double 型物件.
	獲取物件的地址:
		指標存放某個物件的地址,想要獲取該地址,需要使用取地址符(&):
		int ival = 42;
		int *p = &ival;  // p 存放變數ival的地址,或者說p是指向變數ival的指標.
	示例:
		double dval;
		double *a = &dval;  // 正確:初始值是double型物件的地址
		double *a2 = a;     // 正確:初始值是指向double開進物件的地址
		int * b = a;		// 錯誤:指標b的型別與指標a的型別不匹配!
		int * b2 = &vdal;   // 錯誤:試圖把double型物件的地址賦給int型指標
	因為在宣告語句中指標的型別實際上被用於指定它所指向物件的型別,所以二者必須匹配.如果指標指向了一個其他型別的物件,對該物件的操作將發生錯誤.

	指標值:
		指標的值(即地址)應屬下列4種狀態之一:
		1.指向一個物件
		2.指向緊鄰物件所佔空間的下一個位置
		3.空指標,意味著指標沒有指向任何物件
		4.無效指標,也就是上述情況之外的其他值.

	試圖拷貝或以其他方式訪問無效指標的值都將引發錯誤.編譯器並不負責檢查此類錯誤,這一點和試圖使用未經初始化的變數一樣.因此程式設計師必須清楚任意給定的指標是否有效.

	利用指標訪問物件:
		如果指標指向了一個物件,則允許使用解引用符(*)來訪問該物件:
		int ival = 42;
		int *p = &ival;		// p存放著變數ival的地址,或者說p是指向變數ival的指標.
		cout << *p;			// 由符號*得到指標p所指向的物件,輸出42.
	對指標解引用會得到所指的物件,因此如果給解引用的結果賦值,實際上也就是給指標所指的物件賦值:
		*p = 0;
		cout<<*p;			// 輸出0.
	解引用操作僅適用於那些確實指向了某個物件的有效指標.

	空指標:
		空指標不指向任何物件,在試圖使用一個指標之前可先檢查它是否為空,以下列出幾個生成空指標的方法:
		int *p1 = nullptr;		// 等價於 int *p1 = 0; 這是C++11新標準.nullptr是一種特殊型別的字面值,它可以被轉化成任意其他的指標型別.
		int *p2 = 0; 			// 直接將p2初始化為字面常量0.
		// 下面方法需要首先#include cstdlib
		int *p3 = NULL;			// 等價於 int *p3 = 0;
	建議:初始化所有的指標!!!
		建議初始化所有的指標,並且在可能的情況下,儘量等定義了物件之後再定義指向它的指標.如果實在不清楚指標會指向何處,
		就把它初始化為nullptr或者0,這樣程式就能檢測並知道它沒有指向任何具體的物件了.
	任何非0指標對應的條件值都是true.示例如下:
	int ival = 1024; int *pi = 0; int *pi2 = &ival;
	if(pi)	// false
	if(pi2) // true

	void* 指標
		void* 是一種特殊的指標型別,可用於存放任意物件的地址.我們對該地址中到底是什麼型別的物件不清楚.
		概括來說,以 void* 的視角來看記憶體空間也就僅僅是記憶體空間,沒辦法訪問記憶體空間中所存的物件.

	問題:給定指標p,你能知道它是否指向了一個合法的物件嗎?如果能?敘述判斷思路,如果不能,說明原因.
	答:不能,因為需要更多的資訊來確定該指標是否有效.

	定義多個變數:
		int* p;			// 合法但容易產生誤導
		int *p1, p2;	// p1是指向int的指標,p2是int型別
		int *p1, *p2;   // p1和p2都是指向int的指標(本書推薦)

	指向指標的指標:
		int ival = 1024;
		int *pi = &ival;	// pi指向一個int型的數
		int **ppi = π	// ppi指向了指標pi的地址
	示例:
	int main()
	{
		int ival = 1024;
		int *pi = &ival;						// pi指向一個int型的數
		int **ppi = π						// ppi指向了指標pi的地址
		cout << "ival = " << ival << endl;		// ival 1024
		cout << "*pi = " << *pi << endl;		// *pi = 1024
		cout << "**ppi = " << **ppi << endl;	// **ppi = 1024
		cout << "pi = " << pi << endl;			// pi = 000000F3B439F8B4
		cout << "*ppi = " << *ppi << endl;		// *ppi = 000000F3B439F8B4
		cout << (*ppi == pi) << endl;			// 1
		return 0;
	}

	指向指標的引用(難點):
		引用本身不是一個物件,因此不能定義指向引用的指標.但指標是物件,所以存在對指標的引用.
	示例如下:
	int main()  												// 這個示例的關鍵是p,r都存的是i的地址(即&i).
	{
		int i = 42;
		int *p = &i;											// p是一個int型指標,它指向i,是變數i的地址.
		int *&r = p;											// r是一個對指標p的引用
		cout << "&r = " << &r << endl;							// &r = 00000081A017FB18
		cout << "&i == p == r 嗎?下面開始列印: " << endl;
		cout << "&i = " << &i << endl;							// &i = 00000081A017FAF4
		cout << "p = " << p << endl;							//	p = 00000081A017FAF4
		cout << "r = " << r << endl;							//  r = 00000081A017FAF4
		*r = 0;													// 解引用r得到i,也就是p指向的物件,將i的值改為0.
		cout << "*r = 0之後 i = " << i << endl;					// *r = 0之後 i = 0
		system("pause");
		return 0;
	}
============================================================================================
2.4 const限定符
	const是一種型別修飾符,用於說明永不改變的物件.const物件一旦定義就無法再賦新值,所以必須初始化.
	const int bufSize = 512;		//輸入緩衝區大小
	這樣定義就把bufSize定義成了一個常量,任何試圖為bufSize賦值的行為都將發生錯誤.
	因為const物件一旦建立後其值就不能再改變,所以const物件必須初始化.
	預設狀態下,const物件僅在檔案內有效.
	如果想只在一個檔案中定義const,而在其他多個檔案中宣告並使用它,需要做如下操作:
		對於const變數不管是宣告還是定義都新增extern關鍵字,這樣只需要定義一次就可以了.
	注意:如果想在多個檔案之間共享const物件,必須在變數的定義前新增extern關鍵字.

	const指標:
	常量指標(const pointer)是一種指標,它的值永不改變.
		允許把指標本身定為常量.常量指標(const pointer)必須初始化,而且一旦初始化,它的值(也就是存放在指標中的那個地址)
		就不能改變了.把*放在const關鍵字之前用以說明指標是一個常量.
	下面的定義宣告哪些合法哪些不合法?
	int i, *const cp;       // 不合法,cp必須被初始化
	int *p1, *const p2;     // 不合法,p2必須被初始化
	const int ic, &r = ic;  // 不合法,ic必須被初始化
	const int *const p3;    // 不合法,p3必須被初始化
	const int *p;           // 合法,p指標指向一個const int型別的資料

	頂層const:
		頂層const(top-level const)表示***指標本身是一個常量***,而底層const(low-level const)表示***指標所指的物件是一個常量***.
		更一般的,頂層const可以表示任意的物件是常量,這一點對任何資料型別都適用.如算術型別,類,指標等.
		底層const則與指標和引用等複合型別的基本型別部分有關.比較特殊的是,指標型別既可是頂層const,也可是底層const,這一點和其他型別區別明顯:
		int i = 0;
		int *const p1 = &ri;		// 不能改變p1的值,這是一個頂層const
		const int ci = 42;			// 不能改變ci的值,這是一個頂層const
		const int *p2 = &ci;		// 允許改變p2的值,這是一個底層const
		const int *const p3 = p2;	// 靠右的const是頂層const,靠左的是底層const
		const int &r = ci;			// 用於宣告引用的const都是底層const
		當執行拷貝時,常量是頂層const還是底層const區別明顯.其中頂層const不受什麼影響:
		i = ci;						// 正確:拷貝ci的值,ci是一個頂層cosnt,對此操作無影響
		p2 = p3;					// 正確:p2和p3指向的物件型別相同,p3頂層const的部分不影響.
		執行拷貝操作並不會改變被拷貝物件的值,因此,拷入和拷出的物件是否是常量都沒什麼影響.
		另一方面,底層const的限制卻不能忽視.當執行物件的拷貝操作時,拷入和拷出的物件必須具有相同的底層const資格,或者兩個物件的資料型別必須能轉換.
		一般來說,非常量可以轉換成常量,反之則不行.
		int *p = p3;				// 錯誤:p3包含底層const的定義,而p沒有.
		p2 = p3;					// 正確:p2和p3都是底層const
		p2 = &i;					// 正確:int *能轉換成const int *
		int &r = ci;				// 錯誤,普通的int&不能繫結到int常量上
		const int &r2 = i;			// 正確:const int& 可以繫結到一個普通int上.
		p3既是頂層const也是底層const,拷貝p3時可以不在乎它是一個頂層const,但必須清楚它指向的物件得是一個常量.因此,不能用p3去初始化p,
		因為p指向的是一個普通的(非常量)整數.另一方面,p3的值可以賦值給p2,是因為這兩個指標都是底層const,儘管p3同時也是一個常量指標(頂層const),
		僅就這次賦值不會有什麼影響.

	constexpr(const expression)和常量表達式:
		常量表達式(const expression)是指值不會改變並且在編譯過程中就能計算結果的表示式.
		const int max_files = 20;			// max_files是常量表達式
		const int limit = max_files+1;		// limit是常量表達式
		int staff_size = 27;				// staff_size不是常量表達式,因為staff_size可能會被賦予其它值
		const int sz = get_size();			// sz不是常量表達式,因為編譯過程中看不出get_size()是多少.
	constexpr變數:
		C++11新標準規定:允許將變數宣告為constexpr型別以便由編譯器來驗證變數的值是否是一個常量表達式.
		宣告為constexpr的變數一定是一個常量,而且必須用常量表達式初始化:
		constexpr int mf = 20;				// 20是常量表達式
		constexpr int limit = mf +1;		// mf+1是常量表達式
		constexpr int sz = size();			// 只有當size是一個constexpr函式時才是一條正確的宣告語句.
		新標準允許定義一種特殊的constexpr函式,這種函式應該足夠簡單以使得編譯時就可以計算其結果,這樣就能用constexpr函式去初始化constexpr變量了.
		一般來說,如果你認定變數是一個常量表達式,那就把它宣告成constexpr型別.
	指標和constexpr:
		必須明確一點:在constexpr宣告中如果定義了一個指標,限定符constexpr僅對指標有效,與指標所指物件無關:
			constexpr int *p = nullptr;			// p是一個指向整數的常量指標.constexpr把它所定義的物件置為了頂層const.
		與其他常量指標類似,constexpr指標既可以指向常量也可指向一個非常量;
		constexpr int *np = nullptr;			// np是一個指向整數的常量指標,其值為空
		int j = 0;
		constexpr int i = 42;					// i的型別是整型常量
		// i和j都必須定義在函式體之外
		constexpr const int *p = &i;			// p是常量指標,指向整型常量i
		constexpr int *p1 = &j;					// p1是常量指標,指向整數j

2.5 處理型別:
	類型別名:
		有兩種方法可用於定義類型別名,傳統的方法是使用關鍵字typedef:
		typedef double wages;					// wages是double的同義詞
		typedef wages base, *p;					// base是double的同義詞,p是double*的同一詞
		新標準規定了一種新的方法,使用別名宣告(alias declaration)來定義型別的別名:
		using SI = Sales_item;					// SI是Sales_item的同義詞
	指標,常量和類型別名:
		typedef char *pstring;					// pstring是型別char *的別名
		const pstring cstr = 0;					// cstr是指向char的常量指標
		const pstring *ps;						// ps是一個指標,它的物件是指向char的常量指標
		const pstring是指向char的常量指標,並非指向常量字元的指標.
	auto型別說明符:
		auto定義的變數必須有初始值.
		// 由val1和val2相加的結果可以推斷出item的型別
		auto item = val1 + val2;				// item初始化為val1和val2相加的結果
		使用auto同時宣告多個變數時,該語句中的所有初始基本資料型別都必須一樣:
		auto i = 0, *p = &i;					// 正確:i是整數,p是整型指標
		auto sz = 0, pi = 3.14;					// 錯誤:sz和pi的型別不一致
	複合型別,常量和auto:
		編譯器推斷出來的auto型別有時候和初始值的型別並不完全一樣,編譯器會適當地改變結果型別使其更符合初始化規則.
		首先,正如我們所熟知的,使用引用其實是使用引用的物件,特別是當引用被用作初始值時,真正參與初始化的其實是引用物件的值.
		此時編譯器以引用物件的型別作為auto型別.
		int i = 0, &r = i;
		auto a = r;								// a是一個整數(r是i的別名,而i是一個整數)
		其次,auto一般會忽略掉頂層const,同時底層const則會保留下來,比如當初始值是一個指向常量的指標時:
		const int ci = i, &cr = ci;
		auto b = ci;							// b是一個整數(ci的頂層const特性被忽略掉了)
		auto c = cr;							// c是一個整數(cr是ci的別名,ci本身是一個頂層const)
		auto d = &i;							// d是一個整型指標(整數的地址就是指向整數的指標)
		auto e = &ci;							// e是一個指向整數常量的指標(對常量物件取地址是一種底層const)
		如果希望推斷出的auto型別是一個頂層const,需要明確指出:
		const auto f = ci;						// ci的推演型別是int,f是const int;
		還可以將引用的型別設定為auto,此時原來的初始化規則仍然適用:
		auto &g = ci;							// g是一個整型常量引用,繫結到ci
		auto &h = 42;							// 錯誤,不能為非常量引用繫結字面值
		const auto &j =  42;					// 正確,可以為常量引用繫結字面值
		設定一個型別為auto的引用時,初始值中的頂層常量屬性仍然保留,和往常一樣,如果我們給初始值繫結一個引用,則此時的常量就不是頂層常量了.
		要在一條語句中定義多個變數,切記,符號&和*只從屬於某個宣告符,而 非基本資料型別的一部分,因此初始值必須是同一種類型:
		auto k = ci, &l = i;					// k是整數,l是整型引用
		auto &m = ci, *p = &ci;					// m是對整型常量的引用,p是指向整型常量的指標
		auto &n = i, *p2 = &ci;					// 錯誤:i的型別是int而&ci的型別是const int
		示例:
		int main()
		{
			int i = 0, &r = i;
			auto a = r;								// int a = 0;
			const int ci = i, &cr = ci;
			auto b = ci;							// int b = 0;
			auto c = cr;							// int c = 0;
			auto d = &i;							// int *d = &i;
			auto e = &ci;							// const int *e = &ci;
			const auto f = ci;						// const int f = 0;
			auto &g = ci;							// const int &g = 0;

			system("pause");
			return 0;
		}

	decltype型別指示符:
		decltype作用是選擇並返回運算元的資料型別.在此過程中,編譯器分析表示式並得到它的型別,卻不實際計算表示式的值:
		decltype(f()) sum = x;					// sum 的型別就是函式f的返回型別
		編譯器並不實際呼叫函式f,而是使用當呼叫發生時f的返回值型別作為sum的型別.換句話說,編譯器為sum指定的型別就是
		假如f被呼叫的話就會返回的那個型別.
		decltype處理頂層const和引用的方式與auto有些許不同.如果decltype使用的表示式是一個變數,則decltype返回該變數的型別(包括頂層const和引用在內):
		const int ci = 0, &cj = ci;
		decltype(ci) x = 0;						// x的型別是const int
		decltype(cj) y = x;						// y的型別是const int &, y繫結到變數x
		decltype(cj) z;							// 錯誤:z是一個引用,必須初始化
		因為cj是一個引用,decltype(cj)的結果就是引用型別,因此作為引用的z必須被初始化.
		需要指出的是,引用從來都作為其所指物件的同義詞出現,只有用在decltype處是一個例外.
	decltype和引用:
		如果decltype使用的表示式不是一個變數,則decltype返回表示式結果對應的型別.
		有些表示式將向decltype返回一個引用型別,一般來說當這種情況發生時,意味著該表示式的結果物件能作為一條賦值語句的左值:
		// decltype的結果可以是引用型別
		int i = 42, *p = &i, &r = i;
		decltype(r+0) b;						// 正確,加法的結果是int,因此b是一個(未初始化的)int
		decltype(*p) c;							// 錯誤,c是int&,必須初始化.
		如果表示式的內容是解引用操作,則decltype將得到引用型別.
		decltype和auto的另一處重要區別是:decltype的結果型別與表示式形式密切相關.有一種情況特別要注意:對於decltype所用的表示式來說,
		如果變數名加上了一對括號,則得到的型別與不加括號會有不同.如果decltype使用的是一個不加括號的變數,
		則得到的結果就是該變數的型別,如果給變數加上一層或多層括號,編譯器就會把它當作是一個表示式.
		變數是一種可以作為賦值語句左值的特殊表示式,所以這樣的decltype就會得到引用型別:
		// decltype的表示式如果是加上了括號的變數,結果將是引用
		decltype((i)) d;						// 錯誤,d是int&,必須初始化;
		decltype(i) e;							// 正確,e是一個(未初始化的)int

		切記:decltype((variable))(注意是雙層括號)的結果永遠是引用,而decltype(variable)的結果只有當variable本身就是一個引用時才是引用.

		示例:
		1.關於下面的程式碼,指出每個變數的型別及程式結束時它們各自的值:
			int a = 3, b = 4;
			decltype(a) c = a;
			decltype((b)) d = a;
			++c;
			++d;
		c的型別是int,d的型別是int&,c與d的最終值都為4.

		賦值是會產生引用的一類典型表示式,引用的型別就是左值的型別.也就是說,如果i是int,則表示式i=x的型別就是int&.

		2.指出下面的程式碼中每一個變數的型別和值:
			int a = 3, b = 4;
			decltype(a) c = a;					// c是int型.
			decltype(a = b) d = a;				// d是int&型別

		3.auto指定型別與decltype指定型別一樣和不一樣的示例:
			int i = 0, &r = i;
			// 一樣
			auto a = i;							// a是int型
			decltype(i) b = i;					// b是int型
			// 不一樣
			auto c = r;							// c是int型
			decltype(r) d = r;					// d是int&型別

2.6 自定義資料型別
		自定義資料類以關鍵字struct開始,緊跟著類名和類體(其中類體部分可以為空).類體由花括號包圍形成一個新的作用域.
		類內部定義的名字必須唯一,但可以與類外部定義的名字重複.
			struct Sales_data{ /*...*/ } accum, trans, *salesptr;
			// 與上一條語句等價,但可能更好一些
			struct Sales_data { /*...*/ };
			Sales_data accum, trans, *salesptr;
		一般來說,最好不要把物件的定義和類的定義放在一起,這麼做無異於把兩種不同實體的定義混在了一條語句裡.(不建議)

	類資料成員:
		類我是個定義類的成員,我們的類只有資料成員(data member),類的資料成員定義了類的物件的具體內容,每個物件有自己的一份
		資料成員拷貝.修改一個物件的資料成員,不會影響其他的物件.
		定義資料成員的方法和定義普通變數一樣:首先說明一個基本資料型別,隨後緊跟一個或多個宣告符.
		C++11新標準規定,可以為資料成員提供一個類內初始值(in-class initializer).建立物件進,類內初始值將用於初始化資料成員.
		沒有初始值的成員被預設初始化.

	編寫自己的標頭檔案:
		類一般都不定義在函式體內,當在函式體外部定義類時,在各個指定的原始檔中可能只有一處該類的定義.而且,如果要在不同檔案中使用
		同一個類,類的定義就必須保持一致.
		為了確保各個檔案中類的定義的一致性,類通常被定義在標頭檔案中,而且類所在標頭檔案的名字應與類的名字一樣.例如我們應該把Sales_data類定義在名為Sales_data.h的標頭檔案中.
		標頭檔案通常包含那些只能被定義一次的實體,如類,const和constexpr變數等.標頭檔案也經常用到其他標頭檔案的功能.

		注意:標頭檔案一旦改變,相關的原始檔必須重新編譯以獲取更新過的宣告.

	前處理器概述:
		確保標頭檔案多次包含仍能安全工作的常用技術是前處理器(preprocessor),它由C++語言從C語言繼承而來.前處理器在編譯之前執行一段程式,
		可以部分地改變我們所寫的程式.之前已經用到了一項預處理功能#include.
		當前處理器看到#include標記時就會用指定的標頭檔案的內容代替#include.

		C++程式還會用到的一項預處理功能就是標頭檔案保護符(header guard),標頭檔案保護符依賴於預處理變數.預處理變數有兩種狀態:已定義和未定義.
		#define指令把一個名字設定為預處理變數,另外兩個指令則分別檢查某個指定的預處理變是否已定義:
		#ifdef:當且僅當變數已定義時為真,
		#ifndef:當且僅當變數未定義時為真.
		一旦檢查結果為真,則執行後續操作直至遇到#endif指令為止.
		使用這些功能就能有效地防止重複包含的發生:
		#ifndef SALES_DATA_H
		#define SALES_DATA_H
		#include<string>
		struct Sale_data {
			std::string bookNo;
			unsigned units_sold = 0;
			double revenue = 0.0;
		};
		#endif
		第一次包含Sales_data.h時,#ifndef的檢查結果為真,前處理器將順序執行後面的操作直至遇到#endif為止.
		此時,預處理變數SALES_DATA_H的值將變為已定義,而且Sales_data.h也會被拷貝到我們的程式中來.後面如果再一次包含Sales_data.h,
		則#ifndef的檢查結果將為假,編譯器將忽略#ifndef到#endif之間的部分.

		警告:預處理變數無視C++語言中關於作用域的規則.

		整個程式中的預處理變數包括標頭檔案保護符必須唯一,通常的做法是基於標頭檔案中類的名字來構建保護符的名字,以確保其唯一性.
		為了與程式中的其他實體發生名字衝突,一般把預處理變數的名字全部大寫.

		標頭檔案即使(目前還)沒有被包含在任何其他標頭檔案中,也應該設定保護符.標頭檔案保護符很簡單,程式設計師只要習慣性地加上就可以了,沒必要太在乎你的程式到底需不需要.

	自定義標頭檔案,資料型別示例:
	1.在當前專案的"標頭檔案"資料夾內新建 Sales_data.h 檔案,內容如下:
		#ifndef CH02_EX2_42_H_
		#define CH02_EX2_42_H_

		#include <string>
		#include <iostream>

		struct Sales_data
		{
			std::string bookNo;
			unsigned units_sold = 0;
			double revenue = 0.0;

			void CalcRevenue(double price);
			double CalcAveragePrice();
			void SetData(Sales_data data);
			void AddData(Sales_data data);
			void Print();
		};
		#endif

	2.在當前專案的"原始檔"資料夾內新建 Sales_data.cpp 檔案,內容如下:
		#include<iostream>
		#include "Sales_data.h"

		void Sales_data::CalcRevenue(double price)
		{
			revenue = units_sold * price;
			std::cout << "CalcRevenue正在執行..." << "revenue = " << revenue << std::endl;
		}

		void Sales_data::SetData(Sales_data data)
		{
			bookNo = data.bookNo;
			units_sold = data.units_sold;
			revenue = data.revenue;
			std::cout << "SetData執行完畢..." << std::endl;
		}

		void Sales_data::AddData(Sales_data data)
		{
			if (bookNo != data.bookNo) return;
			units_sold += data.units_sold;
			revenue += data.revenue;
			std::cout << "AddData正在執行..." << std::endl;
			std::cout << "units_sold的值現在是: " << units_sold << std::endl;
			std::cout << "revenue的值現在是: " << revenue << std::endl;
		}

		double Sales_data::CalcAveragePrice()
		{
			if (units_sold != 0) {
				std::cout << "CalcAveragePrice正在執行..." << "AveragePrice的值為: " << revenue / units_sold << std::endl;
				return revenue / units_sold;
			}
			else
				return 0.0;
		}

		void Sales_data::Print()
		{
			std::cout << "Print正在執行..." << "bookNo = " << bookNo << ", " << "units_sold = " << units_sold << ", " << "revenue = " << revenue << std::endl;
			double averagePrice = CalcAveragePrice();
			if (averagePrice != 0.0)
				std::cout << "averagePrice = " << averagePrice << std::endl;
			else
				std::cout << "(no sales)" << std::endl;
		}

	3.在當前專案的"原始檔"資料夾內新建 main.cpp 檔案,內容如下:
		#include<iostream>
		#include"Sales_data.h"
		using namespace std;
		#endif

		int main()
		{
			Sales_data total;
			double totalPrice;
			std::cout << "請依次輸入bookNo, units_sold, totalPrice這三個變數,以空格作為分隔符,以回車符結束: " << std::endl;
			if (std::cin >> total.bookNo >> total.units_sold >> totalPrice)
			{
				total.CalcRevenue(totalPrice);
				Sales_data trans;
				double transPrice;
				while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice)
				{
					trans.CalcRevenue(transPrice);
					if (total.bookNo == trans.bookNo)
					{
						total.AddData(trans);
					}
					else
					{
						total.Print();
						total.SetData(trans);
					}
				}
				total.Print();
				return 0;
			}
			else
			{
				std::cerr << "No data?!" << std::endl;
				return -1;  // indicate failure
			}


			system("pause");
			return 0;
		}

	編譯執行即可.
============================================================================================