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;
		}

	編譯執行便可.
============================================================================================
相關文章
相關標籤/搜索