C++ 歸納複習常規篇
複習之前必須說一個關鍵點 C++這門語言是強型別語言,非常的強調型別。
1. 關鍵字const
1.0 ) const 非指標
int main() { const int a = 1; int *p = (int *)&a; *p = 10; return 0; }
結果 a = 1 *p = 10
原因:a是常量,處在常量符號表中。在編譯階段,編譯器檢查到使用a這個常量時,會從常量符號表中取出這個值,而不是從該地址記憶體中取值。
當使用 &獲取地址後,才會為這個地址申請記憶體。因此*p只是操作這個地址的值,不影響a的常量符號表中的值
1.1 ) const 指標
int main() { int a = 1; int b = 2; const int *p1 = (int *)&a; int *const p2 = (int *)&b; *p1 = 10; //錯誤 p1 = &b; //正確 *p2 = 20; //正確 p2 = &a; //錯誤 return 0; }
當const在 * 左側,即程式碼中的指標p1,表示修飾的是p1指向的地址的記憶體,因此,修改p1指向地址是可以的,但修改p1指向地址的記憶體編譯器會報錯。
當const在 * 右側,即程式碼中的指標p2,表示修飾的是p2指向的地址,因此,修改p2指向地址的記憶體是可以的,但是修改p2指向地址編譯器會報錯。
2. 關鍵字const 與define的區別
發生的時期不同。
define發生在預處理階段,進行簡單的文字替換,但不會進行型別檢查作用域檢查。
const在預處理之後的編譯階段,會對型別進行檢查。
因此用define的時候要格外小心,特別是巨集定義一些函式的時候,要考慮符號的優先順序問題。
3. inline 行內函數
一般呼叫一個普通較短的函式,就是跳轉到這個函式名(地址),在一些實時性要求高的程式裡面,跳轉就要有系統開銷(儲存現場、恢復現場),會降低實時性。因此可以使用inlne關鍵字來修飾函式,類似於define,就是原地展開函式。
內聯是一種請求,如果函式過於複雜,編譯器會拒絕內聯,函式變回普通函式。
4. 函式過載
這是C++一個重要的特性。
void r1chie(void) { } void r1chie(int a) { } void r1chie(int a,int b) { } void r1chie(char a) { } void r1chie(char a,int b) { } int main() { int a; char c; r1chie(); r1chie(a); r1chie(a,c); r1chie(c); r1chie(c,a); return 0; }
我們反彙編上面這段函式
00000000004006ef <main>: 4006ef: 55 push %rbp 4006f0: 48 89 e5 mov %rsp,%rbp 4006f3: 48 83 ec 10 sub $0x10,%rsp 4006f7: e8 ba ff ff ff callq 4006b6 <_Z6r1chiev> //4006b6 4006fc: 8b 45 fc mov -0x4(%rbp),%eax 4006ff: 89 c7 mov %eax,%edi 400701: e8 b7 ff ff ff callq 4006bd <_Z6r1chiei> //4006bd 400706: 0f be 55 fb movsbl -0x5(%rbp),%edx 40070a: 8b 45 fc mov -0x4(%rbp),%eax 40070d: 89 d6 mov %edx,%esi 40070f: 89 c7 mov %eax,%edi 400711: e8 b1 ff ff ff callq 4006c7 <_Z6r1chieii> //4006c7 400716: 0f be 45 fb movsbl -0x5(%rbp),%eax 40071a: 89 c7 mov %eax,%edi 40071c: e8 b3 ff ff ff callq 4006d4 <_Z6r1chiec> //4006d4 400721: 0f be 45 fb movsbl -0x5(%rbp),%eax 400725: 8b 55 fc mov -0x4(%rbp),%edx 400728: 89 d6 mov %edx,%esi 40072a: 89 c7 mov %eax,%edi 40072c: e8 af ff ff ff callq 4006e0 <_Z6r1chieci> // 4006e0 400731: b8 00 00 00 00 mov $0x0,%eax 400736: c9 leaveq 400737: c3 retq
可以看到,雖然函式名是一樣的,但是地址卻是不一樣的。
那麼過載是根據什麼呢?
4.1 )引數的數量
4.2 )引數的型別
4.3) 引數的順序
4.4) 同一作用域
5. 關鍵字new和delete
malloc函式與關鍵字new。
5.1)malloc是庫函式,需要包含標頭檔案。new需要編譯器支援。
5.2)malloc需要指定大小。new是編譯器計算。
5.3)malloc返回的是void *型別指標,還需要進行強轉。new返回的是物件型別指標,因此不需要強轉。
使用new申請記憶體,如果記憶體不需要了就記得釋放
class r1chie { public: int a; int b; private: int c; }; int main() { int *p1 = new int(); int *p2 = new int(10); int *p3 = new int[10]; char *p4 = new char(); r1chie *p5 = new r1chie; int **p6 = new int*[10]; delete p1; delete p2; delete[] p3; delete p4; delete p5; delete[] p6[10]; return 0; }
6. namespace 名稱空間
#include <iostream> namespace r1chie { int i = 1; } namespace r1chie_2 { int i = 10; } using namespace std; int main() { int i = 100; cout << r1chie::i << endl; cout << r1chie_2::i << endl; cout << i << endl; return 0; }
輸出結果:
1
10
100
7. C++的引用 &
7.0)引用的本質就是指標。
7.1)一個變數可以取多個別名
int main() { int a = 1; int& b = a; int& c = a; cout << a << endl; cout << b << endl; cout << c << endl; a = 2; cout << a << endl; cout << b << endl; cout << c << endl; return 0; }
輸出結果:
1 1 1 2 2 2
7.2)引用必須初始化
int main() { int& b; // 編譯器報錯 return 0; }
7.3)引用只能初始化一次,不能改變為其它變數的引用
7.4)不能建立陣列的引用。因為陣列是一個由若干個元素所組成的集合,所以無法建立一個數組的別名
7.5)引用的型別要相同
int main() { int a = 1; float& b = a; //報錯 return 0; }
7.6)常引用
7.6.1) 使用常引用,則不能使用引用對目標變數的值進行改變,從而使引用的目標成為了const。
int main() { int a = 1; const int& b = a; a = 2; //正確 b = 3; //報錯 return 0; }
7.6.2)臨時物件,臨時物件都是const型別的
string fun1(); void fun2(string &s); fun2(fun1()); //報錯 fun2("Hello world"); //報錯
原因是將const型別轉換成非const,C++是強型別的語言因此報錯。
7.7)C++中引用的內部實現
int& a ---> int* const a void f(int& a) ----> void f(int* const a) { { a = 5; *a = 5; } }
關於引用這篇文章分析得很好https://www.cnblogs.com/Mr-xu/archive/2012/08/07/2626973.html
8. C++的強制型別轉換
這個又是一個比較重要的特性。
四種轉換:
8.1)static_cast
變數和物件之間的轉換 和 有繼承關係的類物件指標轉換,可通過父類物件去初始化子類物件(只會初始化父類部分)。
class Base { public: int a; Base(int i) { a = i; cout << "This is Base"<< endl; } }; class Child :public Base { public: int b; Child(int i) : Base(i) { b = i; cout << "This is Child" << endl; } }; int main() { int a = 1; char b = 'a'; a = static_cast<int>(b); Base *b1 = new Base(10); Child *c1 = static_cast<Child*>(b1); c1->b = 100; cout << "c1->a = " << c1->a << endl; cout << "c1->b = " << c1->b << endl; c1->a = 20; cout << "b1->a = " << b1->a << endl; return 0; }
輸出結果
This is Base c1->a = 10 c1->b = 100 b1->a = 20
8.2)const_cast
去除類物件的屬性,但必須要強制轉換成引用或指標
int main() { const int a = 2; // int b = const_cast<int>(a); 報錯 int& c = const_cast<int&>(a); int *p = const_cast<int*>(&a); cout << a << endl; cout << c <<endl; cout << *p << endl; c = 6; cout << endl; cout << a << endl; cout << c <<endl; cout << *p << endl; return 0; }
輸出:
2 2 2 2 //從常量符號表取出,因此a的值依然是2 6 6
8.3)dynamic_cast
用於有繼承關係的類指標(引用)間的轉換
用於有交叉關係的類指標(引用)間的轉換
具有型別檢查的功能,編譯時會去檢查使用的方法是否正確,轉換是否成功只有程式執行過程才知道
父類轉子類時,父類中必須有虛擬函式支援(換句話說必須是多型)
class Base { public: Base() { cout << "This is Base"<<endl; } /* virtual ~Base() { cout << "This is ~Base" << endl; } */ }; class Child : public Base { }; int main() { Base *p = new Base; Child *pc = dynamic_cast<Child*>(p); //報錯,原因父類沒有虛擬函式 cout << p << endl; delete p; return 0; }
8.4)reinterpret_cast
用於指標之間的轉換
int main() { int a = 1; char * b = reinterpret_cast<char*>(&a); char c = reinterpret_cast<int>(a); //報錯 return 0; }