前面是掃盲部分,可直接看后面的精華部分。
/* 掃盲
一、隱式的類類型轉換
1. 轉換構造函數
如果構造函數只接受一個實參,則它實際上定義了轉換為此類類型的隱式轉換機制
能通過一個實參調用的構造函數定義了一條從構造函數的參數類型向類類型隱式轉換的規則
2. 從參數類型向類類型隱式轉換的實例
//在Sales_data類中,接受string的構造函數和接受istream的構造函數分別定義了從這兩種類型向Sales_data隱式轉換的規則。 //即,在需要使用Sales_data的地方,我們可以使用string或istream作為替代。 Sales_data total("9-999-99999-9", 2, 25.0); string book = "9-999-99999-9"; //構造一個臨時的Sales_data對象,其units_sold和revenue等於0,bookNo等於book total.combine(book); //我們用一個string實參調用了Sales_data的combine成員。 //在該調用中,編譯器用給定的string自動創建了一個Sales_data對象,這個新生成的臨時對象被傳遞給combine
【從istream到Sales_data的轉換】
//使用istream構造函數創建一個函數傳遞給combine total.combine(cin); //隱式地把cin轉換成Sales_data,這個轉換執行了接受一個istream的Sales_data構造函數 //該構造函數通過讀取標准輸入創建了一個臨時的Sales_data對象,隨后將得到的對象傳遞給combine
3. 編譯器只會自動地執行一步類型轉換
//下面的代碼隱式地使用了兩種轉換規則(錯誤) //錯誤:需要用戶定義的兩種轉換 //(1):把"9-999-99999-9"轉換成string //(2):再把這個臨時的string轉換成Sales_data total.combine("9-999-99999-9"); //可以顯式地把字符串轉換成string或者Sales_data對象 //正確:顯式地轉換成string,隱式地轉換成Sales_data total.combine(string("9-999-99999-9")); //正確:隱式地轉換成string,顯式地轉換成Sales_data total.combine(Sales_data("9-999-99999-9"));
二、抑制構造函數定義的隱式轉換
1. 將構造函數聲明為explicit 來阻止類類型隱式轉換
構造函數將不能隱式地創建Sales_data對象
2. 關鍵字explicit 只對一個實參的構造函數有效
需要多個實參的構造函數不能用於執行隱式轉換
3. explicit 構造函數只能用於直接初始化
發生隱式轉換的一種情況是:執行拷貝形式的初始化時(使用=)
string book = "9-999-99999-9"; //正確:直接初始化 Sales_data total(book); //錯誤:不能將explicit構造函數用於拷貝形式的初始化過程 Sales_data total = book;
4. 在類內聲明構造函數時使用explicit 關鍵字,在類外部定義時不應重復
class Sales_data{ public: Sales_data() = default; Sales_data(const string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n); explicit Sales_data(const string &s): bookNo(s) {} explicit Sales_data(istream &) };
三、例題
1. 說明接受一個string 參數的Sales_data 構造函數是否應該是explicit 的,並解釋這樣做的優缺點。
答:這個隨便,因為這是一把雙刃劍
不帶explicit的優點:可以從構造函數的參數類型向類類型隱式轉換
帶explicit的優點:任何構造函數(尤其是帶一個參數的)都不能隱式地創建類對象
帶explicit的缺點:該構造函數只能以直接初始化的形式使用
2. vector 將其單參數的構造函數定義成explicit 的,而string則不是,你覺得原因何在?
答:string接受的單參數是const char*類型,如果我們得到了一個常量指針,則把它看做string對象是自然而然的過程,編譯器自動把參數類型轉換成類類型也非常符合邏輯,因此我們無須指定為explicit。
與string相反,vector接受的單參數是int類型,這個參數的原意是指定vector的容量。如果我們在本來需要vector的地方提供一個int值並且希望這個int值自動轉換成vector,則這個過程顯得比較牽強,因此把vector的單參數構造函數定義成explicit的更加合理。
例如:定義為explicit是為了防止隱式的類型轉換
void fun(vecor<int> vi); fun(10); //如果允許這樣,那就意味不明了
四、更新
1. 如果我們使用的初始化值要求通過一個explicit 的構造函數來進行類型轉換,那么使用拷貝初始化還是直接初始化就很關鍵了:
vector<int> v1(10); //正確:直接初始化 vector<int> v2 = 10; //錯誤:接受大小參數的構造函數是explicit的 void f(vector<int>); //f的參數進行拷貝初始化 f(10); //錯誤:不能用一個explicit的構造函數拷貝一個實參 f(vector<int>(10)); //正確,從一個int直接構造一個臨時vector
解讀:直接初始化v1是合法的,而拷貝初始化v2則是錯誤的,因為vector的接受單一大小參數的構造函數是explicit的(於是10就不能隱式轉換為vector<int>類型,當然不能賦值給v2了)。同理,當傳遞一個實參或從函數返回一個值時,我們不能隱式使用一個explicit構造函數。如果希望使用explicit構造函數,那就需要顯式地使用,正如最后一行代碼那樣。
2. 我們可以使用new返回的指針來初始化智能指針,但接受指針參數的智能指針構造函數是explicit的,因此我們不能夠將一個內置指針隱式轉換為一個智能指針,而必須使用直接初始化形式來初始化一個智能指針:
//正確,使用了直接初始化形式,shared_ptr有相應的構造函數 shared_ptr<int> p(new int(1024)); //錯誤,賦值符右側是內置指針,不能隱式轉換為智能指針 shared_ptr<int> p = new int(1024);
*/
/* 精華 */
一、單參數構造函數——一步類類型轉換
- 當類中含有單參數構造函數時,則表明定義了一條從構造函數的參數類型向類類型隱式轉換
#include <iostream> using namespace std; class A { public: A() = default; A(string str) : s(str) {} // 單參數 void print(const A &a) { cout << a.i << " " << a.d << " " << a.s << endl; } private: int i = 2; double d = 3.14; string s = "haha"; }; int main() { A a; string ss("trans"); a.print(ss); // string類型轉換為A類型 return 0; }
輸出:
二、explicit構造函數
- 破壞上面的那條規則
- 只能用於直接初始化
class A { public: A() = default; explicit A(string str) : s(str) {} // 單參數 private: int i = 2; double d = 3.14; string s = "haha"; }; int main() { string ss("explicit"); A a(ss); // A a1 = ss; // 錯誤 return 0; }
注:發生隱式轉換的一種情況是當我們執行拷貝形式的初始化時(使用=,即上面代碼行中的),故此處發生錯誤。