1. 程式人生 > 實用技巧 >11C++11通用為本,專用為末_2

11C++11通用為本,專用為末_2

8. 顯式轉換操作符

​ C++中的explicit關鍵字只能用於修飾只有一個引數的類建構函式, 它的作用是表明該建構函式是顯式的, 而非隱式的, 跟它相對應的另一個關鍵字是implicit, 意思是隱藏的,類建構函式預設情況下即宣告為implicit(隱式)。

class TestString
{
public:
	explicit TestString(const int strLen)
     //explicit TestString(const int strLen)
	{
		m_len = strLen;
		m_data = new char[m_len + 1];
		memset(m_data, m_defChar, m_len);
		m_data[m_len] = 0;
	}

	char m_defChar = '0';
	int m_len = 10;
	char* m_data = nullptr;
};

void showStr(TestString str)
{
	cout << str.m_data << endl;
}

int main()
{
   	TestString s1(3);
	showStr(s1);
    //如果建構函式上添加了 explicit 關鍵字,則不能進行隱式型別構造,以下兩句會編譯報錯
	//showStr(3); 		//編譯報錯
	//showStr('a');		//編譯報錯

	getchar();
	return 0;
}

​ 在 c++11 中,標準將 explicit 的使用範圍擴充套件到了自定義的型別轉換操作符上,以支援所謂的 “顯示型別轉換”。explicit 關鍵字作用於型別轉換操作符上,意味著只有在直接構造目標型別或顯式型別轉換的時候可以使用該型別。

9. 初始化列表
9.1 普通變數

​ 在 c++11 中,自動變數和全域性變數的初始化在 C++11 中被豐富了。程式設計師可以使用以下幾種形式完成初始化工作:

  • 等號 “=” 加上賦值表示式, 比如 int a = 3 + 4;

  • 等號 “=” 加上花括號式的初始化列表, 比如 a = {3 + 4};

  • 圓括號式的表示式列表, 比如 int a(3 + 4);

  • 花括號式的初始化列表, 比如 int a{3 + 4};

而後兩種形式也可以用於獲取堆記憶體 new 操作符中, 比如:

int* i = new int(1);
double* d = new double{1.2f};
9.2 容器也支援列表初始化
vector<int>  v{ 1, 3, 5 };
map<int, float> m = { {1, 1.00f}, {2, 2.00f} };
9.3 自定義型別使用列表初始化

​ 標準模板庫中容器對初始化列表的支援源自 <initializer_list> 標頭檔案,並且宣告一個以 initializer_list<T> 模板類為引數的建構函式,同樣可以使得自定義的類使用列表初始化。

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>

enum Gender {BOY, GIRL};
class People
{
public:
	People(initializer_list<pair<string, Gender>> l)
	{
		for (const auto &i : l)
		{
			m_data.push_back(i);
		}
	}
private:
	vector<pair<string, Gender>> m_data;
};

void test()
{
  People ship2012 = { {"Rock", BOY}, {"Lily", GIRL} };     
}

​ 同樣的,函式的引數列表也可以使用初始化列表。

#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>

void func(initializer_list<int> iv) 
{
	for (const auto &i : iv)
	{
		cout << i << "\t";
	}
	cout << endl;
};

void test()
{
    func({});
	func({ 1, 3 ,5 ,7 });
}
9.4. 使用列表初始化,可以防止型別收窄

​ 型別收窄一般指一些可以使得資料比哪壺啊或者精度丟失的隱式型別轉換。可能導致型別收窄的典型情況如下:

  • 從浮點數隱式型別轉換為整型數。

  • 從高精度浮點數轉為低精度浮點數,比如從 long double 隱式地轉換為 double, 或者從 double 轉為 float。

  • 從整型(或者非強型別的列舉)轉化為浮點型,如果整型數大到浮點數無法精確地表示,則可以視為型別收窄。

  • 從整型(或者非強型別的列舉)轉化為較低長度的整型,比如 unsigned char = 1024, 1024 明顯不能被一般長度為 8 位的 unsigned char 所容納,所以也可以視為型別收窄。

      型別收窄可以簡單地理解為新型別無法表示原有型別資料的值的情況。事實上,發生型別收窄通常也是危險的,應引起程式設計師的注意。因此,在 C++11 中,使用初始化列表進行初始化的資料編譯器是會檢查其是否發生型別收窄的。
    
const int x = 1024;
const int y = 10;

char a = x;					//C++98 寫法, 收窄,但可以通過編譯,編譯器警告
char* b = new char(1024);	//C++98 寫法, 收窄,但可以通過編譯,編譯器警告

//char c = { x };            //收窄, 無法通過編譯
char d = { y };				//未收窄,可以通過編譯
//unsigned char e{ -1 };		//收窄, 無法通過編譯

float f{ 7 };				//未收窄,可以通過編譯
//int g{ 2.0f };				//收窄, 無法通過編譯
float h = 1.2l;				//未收窄,可以通過編譯
10. POD型別

​ POD 是英文中 Plain Old Data 的縮寫。通常用於說明一個型別的屬性,尤其是使用者自定義型別的屬性。Plain 表示了 POD 是個普通的型別,在 c++ 中常見的都是這樣的屬性,而不像一些存在著虛擬函式虛繼承的型別那麼特別。而 Old 則體現了器與 C 的相容性,比如可以用最老的 memcpy() 函式進行復制, 使用 memset() 進行初始化等。當然,這樣的描述都太過籠統,具體地, c++11 將 POD 劃分成兩個基本概念的合集,即:平凡的(trivial)和標準佈局的(standard layout)。

​ 我們先來看一下平凡的定義。通常情況下,一個平凡的類或者結構體應該符合以下定義:

  • 擁有平凡的預設建構函式和解構函式 —— 平凡的預設建構函式就是說建構函式“什麼都不幹”。通常情況下,不定義類的建構函式,編譯器就會為我們生成一個平凡的預設建構函式。

  • 擁有平凡的拷貝建構函式和移動建構函式

  • 擁有平凡的拷貝賦值運算子和移動賦值運算子

  • 不能包含虛擬函式以及虛基類

以上 4 點雖然看似複雜,不過在 c++11 中,我們可以通過一些輔助的類模板來幫助我們進行以上屬性的判斷。

//template <typename T> struct std::is_trivial;  
struct Trivial1 {};
struct Trivial2
{
public:
	int a;
private:
	static int b;
};
struct NonTrivial1
{
	NonTrivial1() :c(11) {}
	int c;
};

void test()
{
    cout << "is_trivial<Trivial1>::value: " << is_trivial<Trivial1>::value << endl;    //1
	cout << "is_trivial<Trivial2>::value: " << is_trivial<Trivial2>::value << endl;		//1
	cout << "is_trivial<NonTrivial1>::value: " << is_trivial<NonTrivial1>::value << endl;	//0
    
    cout << "is_pod<Trivial1>::value: " << is_pod<Trivial1>::value << endl;		//1
    cout << "is_pod<Trivial2>::value: " << is_pod<Trivial2>::value << endl;		//1
    cout << "is_pod<NonTrivial1>::value: " << is_pod<NonTrivial1>::value << endl;		//0
}
POD 包含的另外一個概念是標準佈局。標準佈局的類或者結構體應該符合以下定義:

(1)所有非靜態成員有相同的訪問許可權(public、private、protected)

//成員 a 與成員 b 擁有不同的訪問許可權,因此該結構體不是標準佈局
//如果去掉 private 關鍵字的話,那麼該結構體就符合標準佈局的定義了
struct Test
{
public:
  int a;
private:
  int b;
}

(2)在類或者結構體繼承時,滿足以下兩種情況之一:

  • 派生類中有非靜態成員,且只有一個僅包含靜態成員的基類
  • 基類有非靜態成員,而派生類沒有非靜態成員

(3)類中第一個非靜態成員的型別與其基類不同

(4)沒有虛擬函式和虛基類

(5)所有非靜態資料成員均符合標準佈局型別,其基類也符合標準佈局。這是一個遞迴的定義。

​ 以上 5 點構成了標準佈局的含義,最為重要的應該是前兩條。

struct B1{};
struct B2{};

struct D1 : B1
{
	B1 b;
	int i;
};

struct D2 : B1
{
	B2 b;
	int i;
};
int main()
{
    D1 d1;
    D2 d2;
    cout << "is_standard_layout<D1>::value: " << is_standard_layout<D1>::value << endl;	//0
	cout << "is_standard_layout<D2>::value: " << is_standard_layout<D2>::value << endl;	//1
    
    cout << "is_pod<D1>::value: " << is_pod<D1>::value << endl;	//0
	cout << "is_pod<D2>::value: " << is_pod<D2>::value << endl;	/1
    return 0;
}

​ 那麼使用 POD 用什麼好處呢?

(1)位元組賦值,程式碼中我們可以安全地使用 memset 和 memcpy 對 POD 型別進行初始化和拷貝等操作。

(2)提供對 C 記憶體佈局相容。 C++ 程式設計師可以與 C 函式進行相互操作,因為 POD 型別的資料在 C 與 C++ 間的操作總是安全的。

(3)保證了靜態初始化的安全有效。靜態初始化在很多時候能夠提供程式的效能,而 POD 型別的物件初始化往往更加簡單。

11. 非受限聯合體
12. 使用者自定義字面值
13. 內聯名字空間
14. 模板的別名
15. 一般化的 SFINEA 規則