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

来源:https://www.cnblogs.com/fengbo1113/archive/2018/12/08/10089738.html
-Advertisement-
Play Games

Primer C++第五版 讀書筆記(一) (如有侵權請通知本人,將第一時間刪文) 1.1-2.2 章節 關於C++變數初始化: 初始化不是賦值,初始化的含義是創建變數時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,以一個新值來替代. 定義一個名為a的int變數並初始化為0,有以下4種方法: ... ...


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

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

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python 項目的組織結構 - 包 -- 模塊 類 函數、變數 Python是利用包和模塊來組織一個項目的。 包: 包的物理表現是一個文件夾,但是一個文件夾卻不一定是個包,要想讓個文件夾成為一個包,就必須在這個文件夾下增加個特定的文件 __init__.py __init__.py裡面可以什麼也不 ...
  • springMVC第一個案例---hellospringMVC ...
  • 建造者模式通過將複雜對象逐一拆解成單一的簡單對象,然後通過對簡單對象的創建,最終構建出一個複雜對象。 介紹 在現實世界中,和建造者模式最為相似的是我們到餐廳點餐的流程。在點餐的過程中,我們是不用關係點餐的先後順序,等我們點完後,點餐系統會自動將我們的所有餐品列表和消費情況全部一次性羅列出來,並且最後 ...
  • 單例模式就是指單例類在一定的生命周期內只能有一個對象實例,單例類的創建必須是本身,並能給使用者提供自身。 介紹 在現實世界中,每個生命體都可以被看做是一個單例對象,唯一且具體,具有不可複製性。同樣的,在軟體開發領域中,有時我們需要保證客戶端在當前的客戶機上只能運行一個實例這個時候,我們就應該考慮使用 ...
  • 系統架構設計師-軟體水平考試高級-理論-架構風格。其中涉及架構風格,ABSD,軟體架構評估,軟體產品線,中間件技術,典型應用架構,Web架構設計等。 ...
  • 抽象工廠是基於簡單工廠發展而來的,通過抽象工廠,我們可以創建多種類型的工廠,並且依據具體業務需求而在具體工廠裡面進行任意拼裝組合。 介紹 在現實世界中,汽車製作行業有各種各樣的工廠,每個工廠都需要具有生產輪胎、汽車引擎等部件的能力,但是針對具體的工廠,每個部件的生產又各不相同,所有在軟體開發過程中, ...
  • 胡扯 因為先學習的treap,而splay與treap中有許多共性,所以會有很多地方不會講的很細緻。關於treap和平衡樹可以參考這篇博客 ...
  • Python編碼轉換問題;utf-8/gbk/unicode/ASCII ;統計字元串中數字,字母,漢字的個數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...