摘要:不能進行隱式轉換跟它相對應的另一個關鍵字是,意思是隱藏的,類構造函數默認情況下即聲明為隱式。通過排他性來定義,每個表達式不是就是。返回容器中倒數第一個元素的常量迭代器。這種轉換的安全性也要開發人員來保證。
目錄
?explicit???/?k?spl?s?t/???明確的;清楚的;直率的;詳述的
作用是表明該構造函數是顯示的, 而非隱式的。不能進行隱式轉換!跟它相對應的另一個關鍵字是implicit,意思是隱藏的,類構造函數默認情況下即聲明為implicit(隱式)。
例如我們定義一個類
class student {public: student(int age) { this->age = age; cout << "age=" << age << endl; } student(int age, string name) { this->age = age; this->name = name; cout << "age=" << age << " name=" << name << endl; } ~student() { } int getAge() { return this->age; } string getName() { return this->name; }private: string name; int age;};
在主函數中分別使用顯示構造和隱式構造
int main() { //顯示構造,直接在括號里面寫 student xiaoM(18); //顯示構造 student xiaoH(19, "小花"); //隱式構造 student xiaoW = 18; //隱式構造(C++11前編譯器無法通過) student xiaoZ = { 19,"小張" }; system("pause"); return 0;}
執行結果
當給其中一個構造函數加上explicit關鍵字后
class student {public: explicit student(int age) { this->age = age; cout << "age=" << age << endl; }private: string name; int age;};
這種聲明就表示:這個構造函數只能顯示構造,無法進行隱式轉換
explicit關鍵字的作用在于:在實際開發中 等號會產生一些歧義,等號本身又有賦值的作用,無法在一瞬間辨別等號是用于賦值還是調用隱式構造,不便于閱讀。
我們在日常編譯程序時,有時可能會遇到如下報錯提示:
源代碼
#includeusing namespace std;int demo() { int i = 0; return i;}int main() { //將函數返回作為左值 demo() = 888; return 0;}
這里將函數返回賦予888,編譯器給出“表達式必須是可修改的左值”的報錯提示 ,如果我們將函數返回作為右值,則不會有此報錯~
關于左值和右值的問題,這里就涉及到計算機的存儲層次結構
?存儲層次結構
例如我們計算 c = a + b ;
a 和 b 的值會保存在內存中,當程序遇到c = a + b,會將a和b的值拿到cpu的寄存器中,再將計算結果返回給內存中的c,當我們Ctrl + S后,內存中的數據就可以永久保存到磁盤中。
?左值和右值的概念
?按字面意思,通俗地說。以賦值符號 = 為界,等號(=)左邊的就是左值(lvalue),等號(=) 右邊就是右值(rvalue)。??
lvalue —— 代表一個在內存中占有確定位置的對象(換句話說就是有一個地址)。?
rvalue —— 通過排他性來定義,每個表達式不是lvalue就是rvalue。因此從上面的 ? lvalue的定義,rvalue是在不在內存中占有確定位置的表達式,而是存在在寄存器中。
所有的左值(無論是數組,函數或不完全類型)都可以轉換成右值。
?我們將下面程序轉為匯編碼觀察
#includeusing namespace std;int main() { int a = 1; int b = 2; int c = 3; c = a + b; printf("%d", c); return 0;}
匯編代碼
程序將a、b、c的值都存儲在棧上,當執行 c= a + b 時,程序會將 a 和 b 的值拿到寄存器中計算,再將計算結果返回給內存中 c 的位置上去。
也就是說,程序執行的方式,是從內存中取值放到寄存器中,再將計算結果返回給內存中的某個位置!
a + b的結果保存在寄存器中,再將寄存器的值返回給內存
因此,對于? c = a + b;是合法的
而 a + b = c;也就是eax = c就是非法,左值需要在內存中有位置,而a + b是在寄存器?? 中
?上面我們講了左值與右值,當我們把函數返回值作為左值時,編譯器給出報錯提示,而C++11的新增特性“函數返回引用”又可以實現函數返回值作為左值的操作
C++引用使用時的難點:
1.當函數返回參數是引用時
????????? 若返回棧變量(自動變量/臨時變量),不能成為其它引用的初始值,不能作為左值使用。
2. 若返回靜態變量或全局變量
??????? ·可以成為其他引用的初始值
??????? ·即可作為右值使用,也可作為左值使用
? 3. ??返回形參當引用
??????? (注:C++鏈式編程中,經常用到引用,運算符重載專題) 例如在重載“<<"左移運算符后,我們想要鏈式輸出,就是返回的cout引用
下面,對第一種種情況分別通過代碼舉例 ?
1. 函數返回值為引用,返回臨時變量作為其他引用的初始值
#includeusing namespace std;int& demo() { int i = 600; return i; //返回局部變量 }int main() { int &a =demo(); //函數引用成為其他引用的初始值 cout << a << endl; system("pause"); return 0;}
編譯結果
編譯器并未報錯,也只是給出警告 “ 返回局部變量 ” 。好像目測也沒啥問題 ,下面通過一段代碼示例,來解釋這種操作的不合理性。?看操作。
我們知道,通過引用可以將兩個變量都有同一塊地址
?
因此,對于上面的代碼,a 的地址和局部變量 i 有相同的地址
?
?我們現在更改a的值,然后再調用demo函數
int main() { int &a = demo(); cout << "a的地址為:" <<&a<
運行結果:
第一次demo函數執行,i 的地址為00D3F854,a的地址也為00D3F854。
然后demo函數執行完畢后,在棧內的空間釋放。
第二次demo函數也是在棧的相同位置存儲,所以 i 的位置沒有改變,因此 i =600后也影響了a的值。
?
如果我們在棧中使用兩個函數,會是什么結果呢?
#include#includeusing namespace std;int demo1() { int i = 100; cout << "demo1()函數中,i的地址為:" << &i << endl; return 0;}int& demo() { int i = 600; cout << "i的地址為:" << &i << endl; return i; //返回局部變量}int main() { int &a = demo(); cout << "a的地址為:" <<&a<
執行結果
也就是說,如果用函數返回引用,返回局部變量和引用初始化,程序會繼續認為這個地址還是屬于計算機的,并非是程序員自己申請的。
2.返回局部變量作為左值
?情況與上面類似
#include#includeusing namespace std;int demo1() { int i = 0; cout << "demo1()函數中,i的地址為:" << &i << endl; return 0;}int& demo(int **p) { int i = 600; *p = &i; cout << "i的地址為:" << &i << " i的值為:" << i << endl; return i; //返回局部變量}int main() { int* p = NULL; demo(&p) = 888; cout << "p的地址為:" << p << " p的值為:" << *p << endl; demo1(); cout << "p的地址為:" << p << " p的值為:" << *p << endl; system("pause"); return 0;}
執行結果
?總結:若返回棧變量(自動變量/臨時變量),不能成為其它引用的初始值,不能作為左值使用,雖然編譯器只會警告,但在實際開發中是非常危險的操作!
3.例如返回static或者全局變量就不出出現這種情況
#include#includeusing namespace std;int& demo() { static int i = 600; cout << "i的地址為:" << &i << endl; return i; //返回靜態變量}int main() { int a = demo(); a = 888; cout << "調用了demo(),a的值為:" << a << endl; demo(); cout << "再次調用了demo(),a的值為:" << a << endl; system("pause"); return 0;}
執行結果
其實,主要還是看返回值的生命周期,如果是立刻銷毀的,則應注意使用~
?array容器概念?
- array 容器是?C++?11 標準中新增的序列容器,簡單地理解,它就是在 C++ 普通數組的基礎上,添加了一些成員函數和全局函數。
- array是將元素置于一個固定數組中加以管理的容器。
- array可以隨機存取元素,支持索引值直接存取, 用[]操作符或at()方法對元素進行操作,也可以使用迭代器訪問
- 不支持動態的新增刪除操作
- array可以完全替代C語言中的數組,使操作數組元素更加安全!
- #include
?array特點
- array 容器的大小是固定的,無法動態的擴展或收縮,這也就意味著,在使用該容器的過程無法增加或移除元素而改變其大小,它只允許訪問或者替換存儲的元素。
- STL?還提供有可動態擴展或收縮存儲空間的 vector 容器
?array對象的構造
?array采用模板類實現,array對象的默認構造形式
array
arrayT; ???//T為存儲的類型, 為數值型模板參數 //構造函數
#include
array a1; //一個存放5個int的array容器array a2; //一個存放6個float的array容器array a3; //一個存放7個student的array容器
?array的賦值
array?的賦值
a1.assign(0); //玩法一?改變array中所有元素(注:將被廢棄,不推薦使用)
在目前版本,編譯器會報錯
如果非要使用assign的話可以根據報錯提示加一個宏
#define _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING 1
a1.fill(666); //玩法二 用特定值填充array中所有元素
array
test={1, 2, 3, 4};// 玩法三 定義時使用初始化列表,列表內的數量不能超過定義的大小
array
test; test={1,2,3,4}; ??//玩法四 定義后使用列表重新賦值
array
a1,a2; a1={1,2,3,4};
a2 = a1;//玩法五,賦值運算
a1.swap(a2); ?//玩法六 ?和其它array進行交換
?array的大小
array.size(); 返回容器中元素的個數array.empty(); 判斷容器是否為空,逗你玩的,因為容器是固定大小,永遠為 falsearray.max_size(); 返回容器中最大元素的個數,數組是固定大小,這也是逗你玩的同size()。
array的數據存取 ?
第一 ?使用下標操作 a1[0] = 100;
第二 ?使用at 方法 如: a1.at(2) = 100;
第三 ?接口返回的引用 a1.front() 和 a1.back() ?
注意: ??第一和第二種方式必須注意越界
array 迭代器訪問?
#include
using namespace std;array.begin(); 返回容器中第一個數據的迭代器。array.end(); 返回容器中最后一個數據之后的迭代器。array.rbegin(); 返回容器中倒數第一個元素的迭代器。array.rend(); 返回容器中倒數最后一個元素的后面的迭代器。array.cbegin(); 返回容器中第一個數據的常量迭代器。array.cend(); 返回容器中最后一個數據之后的常量迭代器。array.crbegin(); 返回容器中倒數第一個元素的常量迭代器。array.crend(); 返回容器中倒數最后一個元素的后面的常量迭代器。 #include
#include using namespace std;int main() { array arrayInt = { 1,2,3,4,5 }; 普通迭代器 for (array ::iterator ite = arrayInt.begin(); ite != arrayInt.end(); ite++) cout << *ite << " "; 常量迭代器,常量迭代器無法修改 array ::const_iterator ite = arrayInt.cbegin(); 逆向迭代器,逆向迭代器也是++ for (array ::reverse_iterator ite = arrayInt.rbegin(); ite != arrayInt.rend(); ite++) cout << *ite << " ";} set.rbegin()與set.rend()。略。
舊式轉型? C風格的強制類型
??????????????? type? b? =? (? type? )? a
??????????????? 例如:
??????????????? int i = 48;
??????????????? char? c = (char ) i;
double PI = 3.1415926;int i = PI; 隱式轉換int i1 = (int) PI ; 強制類型轉換強制類型轉換,整數直接變指針int * addr = (int*) 0x888888; void *p;int * int_arg = (int*) p; 強制轉換
?
?
新式轉型C++風格的類型轉換提供了4種類型轉換操作符來應對不同場合的應用。
格式:
??? type b?= 類型操作符<type> (?a?)???
???????類型操作符= static_cast | reinterpreter_cast | dynamic_cast | const_cast
?
靜態類型轉換(斯文的勸導,溫柔的轉換)(編譯器會檢查轉換是否合理)。如int轉換成char
char a = "A";int b = static_cast
(a); 主要用法:
- 用于類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換。上行指針或引用(派生類到基類)轉換安全,下行不安全(上行可以,下行不安全)
- 用于基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
- 把空指針轉換成目標類型的空指針。
- 把任何類型的表達式轉換成void類型
?
1.用于類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換?
class Animal {public: virtual void cry() = 0;};class dog :public Animal {public: void cry() { cout << "小狗汪汪汪" << endl; }};int main() { dog* d = new dog(); Animal* animal = d;//子類對象賦給基類 使用static_cast指針轉換 Animal* al = static_cast(d); 引用轉換 dog d2; Animal& a2 = static_cast(d2); system("pause"); return 0;}
那為什么說上行轉換可以,下行轉換不安全呢?
我們再增加一個Animal的派生類
class cat :public Animal {public: void cry() { cout << "小貓喵喵喵" << endl; }};
int main() { dog* d = new dog(); //上行轉換安全 Animal* animal = static_cast(d); //下行轉換,這種也是安全的 d = static_cast(animal); //animal已經是dog類轉過來的,再轉成cat的就不安全的 cat* c; c = static_cast(animal); system("pause"); return 0;}
因此,并不是說下行轉換不可用,例如上面的將從dog類轉過來的animal再轉到cat就是不安全的,還是取決于程序員的使用 。
2.基本數據類型之間的轉換?
int a = 10;char b = static_cast(a);
?3.把空指針轉換成目標類型的空指針。
int* p = static_cast
(NULL); NULL在C++里定義的是void*0
?4.把任何類型的表達式轉換成void類型
int* p = new int[10];void* vp = static_cast(p);
總結:使用static_cast可以使編譯器做一些合法性檢查,不寫也沒關系。
?
重新解釋類型(掛羊頭,賣狗肉)?不同類型間的互轉,數值與指針間的互轉
用法: ?
??????? type b?= reinterpret_cast
( ?a?)????????type 必須是一個指針、引用、算術類型、函數指針。
例如強制將整數轉為指針int* addr = reinterpret_cast
(0x88888);如果使用static_cast則會報錯int* addr1 = static_cast (0x88888); ?
?
忠告:濫用 reinterpret_cast 運算符可能很容易帶來風險。 除非所需轉換本身是低級別的,否則應使用其他強制轉換運算符之一。
用法1,數值與指針之間轉換?
int* p = reinterpret_cast(0x88888);int val = reinterpret_cast(p);
用法2, 不同類型指針和引用之間的轉換
?定義一個父類和兩個派生類
class Animal {public: void cry() { cout << "動物叫" << endl; }};class cat :public Animal {public: void cry() { cout << "小貓喵喵喵" << endl; }};class dog :public Animal {public: void cry() { cout << "小狗汪汪汪" << endl; }};
1.隱式上行? 轉換? 與強制上行? 轉換
dog d1;Animal* animal = &d1;animal->cry();//如果能用static_cast強轉,static_cast優先dog* d2 = reinterpret_cast(animal);dog* d3 = static_cast(animal);
?2.不同類型指針轉換不能用static_cast
?
?
3.但是可以使用reinterpre_cast轉換
int main() { dog d1; cat* c1 = reinterpret_cast(&d1); c1->cry(); system("pause"); return 0;}
編譯器不管程序是將什么類型轉的,只曉得最后要獲得一個cat類型,然后按cat的存儲類型,訪問里面的方法。如果濫用reinterpreter_cast,類型之間轉來轉去,可能會導致程序快速崩潰。
4.引用的強制類型轉換
dog d1;Animal& animal = d1;dog& d2 = reinterpret_cast(animal);
?
?reinterpreter_cast? 再加一個 static_cast 就已經可以替換C語言的轉換
?
?動態類型轉換
- 將一個基類對象指針cast到繼承類指針,dynamic_cast 會根據基類指針是否真正指向繼承類指針來做相應處理。失敗返回null,成功返回正常cast后的對象指針;
- 將一個基類對象引用cast 繼承類對象,dynamic_cast 會根據基類對象是否真正屬于繼承類來做相應處理。失敗拋出異常bad_cast
注意:dynamic_cast在將父類cast到子類時,父類必須要有虛函數一起玩。
?
第一種情況 示例代碼
#includeusing namespace std;class Animal {public: //父類必須有虛函數 virtual void cry() = 0;};class cat :public Animal {public: void cry() { cout << "小貓喵喵喵" << endl; }};class dog :public Animal {public: void cry() { cout << "小狗汪汪汪" << endl; }};int main() { dog d1; Animal* animal = &d1; cat* ca= dynamic_cast(animal); if (ca==NULL) { cout << "這是只狗!" << endl; } else { cout << "這是只貓" << endl; } system("pause"); return 0;}
運行結果
?
第二種情況示例代碼
int main() { dog d1; Animal& animal = d1; dog& d2 = dynamic_cast(animal); system("pause"); return 0;}
這種是沒有任何問題,因為dog與animal是子類與父類的關系,并且animal指針真正指向dog。
但是,如果我們用其他類型接受又會是什么情況呢?
int main() { dog d1; Animal& animal = d1; cat& d2 = dynamic_cast(animal); system("pause"); return 0;}
基類指針并沒有指向cat,這種情況編譯是沒有任何問題,因為編譯器會認為這是父類與子類之間的轉換,但是當我們運行時,編譯器就會拋出異常?
?
?
對于拋出異常,我們就可以利用try—catch語句捕獲異常,保證程序能繼續運行
int main() { dog d1; Animal& animal = d1; try { cat& d2 = dynamic_cast(animal); } catch (std::bad_cast bc) { cout << "不是貓,應該是狗" << endl; } system("pause"); return 0;}
運行結果
?
去const屬性。(僅針對于指針和引用)?
?例如
#includeusing namespace std;void demo(const char* p) { char* p1 = const_cast(p); p1[0] = "A";}int main() { //字符數組 char p[] = "1234567"; demo(p); cout << p << endl; system("pause"); return 0;}
運行結果
或者直接使用const_cast,運行結果也是一樣
int main() { char p[] = "1234567"; const_cast(p)[0] = "A"; cout << p << endl; system("pause"); return 0;}
?但是,對于常量字符串不能去掉const修改
例如
#includeusing namespace std;void demo(const char* p) { char* p1 = const_cast(p); p1[0] = "A";}int main() { const char *p = "1234567"; demo(p); cout << p << endl; system("pause"); return 0;}
可以編譯,但是運行會報錯
因為常量所處位置是常量區,內存無法訪問常量區。
?
?5.2 類型轉換使用建議
1)static_cast靜態類型轉換,編譯的時c++編譯器會做編譯時的類型檢查;隱式轉換;
基本類型轉換,父子類之間合理轉換
2)若不同類型之間,進行強制類型轉換,用reinterpret_cast<>() 進行重新解釋
???建 ?議:
????????C語言中 ?能隱式類型轉換的,在c++中可用 static_cast<>()進行類型轉換。因C++編譯器在編譯檢查一般都能通過;C語言中不能隱式類型轉換的,在c++中可以用 reinterpret_cast<>() 進行強制類型解釋。
????????總結:static_cast<>()和reinterpret_cast<>() 基本上把C語言中的 強制類型轉換給覆蓋,注意reinterpret_cast<>()很難保證移植性。
3)dynamic_cast<>(),動態類型轉換,安全的虛基類和子類之間轉換;運行時類型檢查
4)const_cast<>(),去除變量的只讀屬性
最后的忠告:程序員必須清楚的知道: 要轉的變量,類型轉換前是什么類型,類型轉換 后是什么類型,轉換后有什么后果。
C++大牛建議:一般情況下,不建議進行類型轉換;避免進行類型轉換。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/125067.html
摘要:今天逛了逛,順手精選出了一下近幾個月以來上最熱門的個項目。相關閱讀正式開源,幫助應用快速容器化未來可能會上熱門的項目地址介紹哈哈,皮一下很開心。這是我自己開源的一份文檔,目前仍在完善中,歡迎各位英雄好漢一起完善。 showImg(https://segmentfault.com/img/remote/1460000015766827?w=391&h=220);今天逛了逛Github,順...
showImg(https://segmentfault.com/img/remote/1460000019961426); 今天在 Apache Flink meetup ·北京站進行 Flink 1.9 重大新特性進行了講解,兩位講師分別是 戴資力/楊克特,zhisheng 我也從看完了整個 1.9 特性解讀的直播,預計 Flink 1.9 版本正式發布時間大概是 7 月底 8 月初左右正式發...
閱讀 1032·2021-11-25 09:43
閱讀 1412·2021-11-18 10:02
閱讀 1814·2021-11-02 14:41
閱讀 2366·2019-08-30 15:55
閱讀 1066·2019-08-29 16:18
閱讀 2552·2019-08-29 14:15
閱讀 1389·2019-08-26 18:13
閱讀 733·2019-08-26 10:27