P5:編譯器會把每個cpp文件都編譯成一個obj文件,而我們的項目則被編譯成一個可執行文件(exe)。而每個cpp文件或者每個定義和申明之間是因為編譯器可以自動鏈接(linking)他們。
P6:頭文件就是編譯器在欲編譯時把頭文件的內容復制過來。所以在我們的頭文件中,盡量不要去定義函數,否則會導致錯誤。
P8:在window32程序下char型占用一個字節,short2個,int4個,long long8個,其中char型實際上存儲也是數字,因為是根據ASCII表存儲的。float占用四個字節,double占用8個,而對於bool只用一個字節,其是存儲的只是0或1,那為什么只給他1bit呢,因為我們的電腦在尋址的時候最小單位就是字節。數據類型本質上都是一樣的,他們的區別在於他們的所占內存的大小。
P10:為了頭文件中的定義不被重復復制到編譯單元(cpp)中,我們需要做保護機制,之前常用的方式是在我們的頭文件中加入預處理指令,比如
1 #ifndef _LOG_H 2 #define _LOG_H 3 4 void log(const char* message); 5 #endif
但是現在我可以用#pragma once在頭文件中就可以了 #pragma once
P17:對於引用變量,其實只是一個我們引用變量的一個別名,他在內存中沒有實際的存儲,我們可以把他當成其變量一樣使用。
P19:類和結構本質上沒什么區別,唯一的區別就是類默認是私有的,不管是繼承還是成員。而結構是公開的。所以結構之所以存在是因為和c兼容,因為c中沒有類,只有結構。習慣中我們使用結構來存儲一些數據,而至於其他的盡量使用類。
對於為什么要用指針參數或者引用參數主要有兩點:
1、我們可以修改調用函數中的數據對象
2、因為他們只是一個地址或者一個別名,而不需要拷貝副本,所以提高了程序的運行速度
對於合適用值傳遞、指針傳遞、引用傳遞
對於使用傳遞的值而不作修改的函數:
- 如果數據對象較小,如內置數據類型或者小型結構,則按值傳遞。
- 如果數據對象是數組,則只能使用指針,並且將指針聲明為指向const的指針。
- 如果數據對象較大,則使用const指針或者const引用,節省復制結構所需的時間和空間。
- 如果數據對象是類對象,則使用const引用。類設計的語義常常要求使用引用。這是C++增添引用這個特性的主要原因。
對於修改調用函數中數據的函數
- 如果數據對象是內置數據類型,則使用指針。
- 如果數據對象是數組,則只能使用指針。
- 如果數據對象是結構,則使用引用或者指針。
- 如果數據對象是類對象,則使用引用。
使用引用參數,應盡可能使用const
- 使用const可以避免無意中修改數據的編程錯誤。
- 使用const使得函數能夠處理const和非const實參。否則將只能接收非const數據。
- 使用const引用使函數能夠正確生成並使用臨時變量。
P21:對static:一、如果在類或者結構外邊,他的主要作用就是這個數據類型只是在這個范圍內。比如靜態局部變量的作用域只是在函數內部,而靜態全局變量作用域這個文件,靜態函數也是這個文件。當然因為靜態數據類型是存儲在常量和靜態區的所以他會一直存在到程序結束。二、對於成員數據類型,主要作用就是不管是這個成員函數還是變量他都是這個類的實例所共享的。所以他的數據作用於整個類,比如定義一個靜態成員變量,那么他的值不會隨着新的實例而重新初始化。因為他儲存在靜態區域,不會隨着類的實例重新分配內存。
另外extern是他會去尋找外部的數據類型。
1 struct Entity 2 { 3 static int x, y; 4 5 void Print() 6 { 7 std::cout << x << "," << y << std::endl; 8 } 9 }; 10 11 int Entity::x; //靜態變量初始化 12 int Entity::y; 13 int main() 14 { 15 Entity e; 16 e.x = 2; //我們這里因為是靜態變量跟我們的實例e沒有關系,可以寫成 Entity::x = 2 17 e.y = 3; 18 19 Entity e1; 20 e1.x = 5; //我們這里因為是靜態變量跟我們的實例e1沒有關系,可以寫成 Entity::x = 5 21 e1.y = 8; 22 23 e.Print(); //這里打印x,y是5和8 24 e1.Print(); //這里是打印x,y也是5和8 25 26 return 0; 27 }
最后靜態方法不能訪問非靜態變量和方法,因為他們不是確定的,是隨着實例的變化而變化的。相反卻可以。當然靜態方法是可以訪問靜態變量的。因為靜態變量是確定的。
P24:枚舉Enum
1 enum Example 2 { 3 A, B, C //這里可以不指定值,他會默認為0,1,2 4 }; 5 6 enum Example1 7 { 8 A1 = 5, B2 = 7, C3 //這里也可以指定值,他會按照你給定的值遞增 9 }; 10 11 int main() 12 { 13 14 Example value = B; //給value分配值,實際上只能分配枚舉里面的值 15 if (value == 1) //而這里value也就是B在枚舉中是1,因為他是從0開始遞增的,A是0 16 { 17 std::cout << "B=" << value << std::endl; 18 } 19 Example1 value1 = C3; 20 if (value1 == 8) 21 { 22 std::cout << "C3=" << value1 << std::endl; 23 } 24 std::cin.get(); 25 return 0; 26 }
結果為:B=1 C3=8
P28:虛函數

1 class Entity 2 { 3 public: 4 virtual std::string GetName() { return "Entity"; } //定義虛函數 5 }; 6 7 class Player : public Entity 8 { 9 private: 10 std::string m_Name; 11 public: 12 Player(const std::string& name) 13 :m_Name(name) {} 14 std::string GetName() override { return m_Name; } //這里函數進行了覆蓋,override寫不寫都ok,但是為了可讀性,還是寫一下比較好 15 }; 16 17 void PrintName(Entity* Entity) 18 { 19 std::cout << Entity->GetName() << std::endl; 20 } 21 22 23 int main() 24 { 25 Entity *e = new Entity; 26 std::cout << e->GetName() << std::endl; 27 28 Player* p = new Player("xiaodang"); 29 PrintName(p); 30 std::cin.get(); 31 return 0; 32 }
P29:純虛函數或者接口

1 class Printable //抽象類或者叫他接口 2 { 3 public: 4 virtual std::string GetClassName() = 0; //純虛函數 5 }; 6 7 class Entity : public Printable 8 { 9 public: 10 virtual std::string GetName() { return "Entity"; } //虛函數 11 std::string GetClassName() override { return "Entity"; } 12 }; 13 14 class Player : public Entity 15 { 16 private: 17 std::string m_Name; 18 public: 19 Player(const std::string& name) 20 :m_Name(name) {} 21 std::string GetName() override { return m_Name; } //覆蓋 22 std::string GetClassName() override { return "Player"; } //覆蓋 23 }; 24 25 void PrintName(Entity* Entity) 26 { 27 std::cout << Entity->GetName() << std::endl; 28 } 29 30 void Print(Printable* obj) 31 { 32 std::cout << obj->GetClassName() << std::endl; 33 } 34 35 int main() 36 { 37 Entity* e = new Entity; 38 39 Player* p = new Player("xiaodang"); 40 41 Print(e); 42 Print(p); 43 delete e; 44 delete p; 45 std::cin.get(); 46 return 0; 47 }
P34:對於Const主要作用就是限定這是個常量,無法改變,僅僅只讀。

1 int main() 2 { 3 const int MAX_AGE = 90; 4 5 const int* a = new int; 6 *a = 2; //代碼報錯,因為我們使用const,代表*a的值不可改變 7 a = (int*)&MAX_AGE; //代碼正確,因為我們改變的是a的地址,而const限定的是*a 8 9 int* const b = new int; 10 *b = 2; //代碼正確,因為我們使用的const沒有限制*b 11 b = (int*)&MAX_AGE; //代碼錯誤,因為我們改變的是b的地址,而const限定了b的地址 12 13 std::cin.get(); 14 return 0; 15 }

1 class Entity 2 { 3 private: 4 int m_X, m_y; 5 public: 6 int GetX() const 7 { 8 m_X = 2; //代碼錯誤,因為有const的限制,我們只能讀取而不能修改 9 return m_X; //代碼正確 10 } 11 };

1 class Entity 2 { 3 private: 4 std::string e_Name; 5 int e_DebugCount = 0; 6 mutable int e_DebugCount_temp = 0; 7 public: 8 const std::string &GetName() const 9 { 10 e_DebugCount++; //代碼錯誤,因為我們進行了const 11 e_DebugCount_temp++; //代碼正確,因為我們使用了mutable 12 return e_Name; 13 } 14 };
mutable的作用是讓變量可變的,是代碼更加整潔。用法有點像const。
PS:多態指在基類的方法前面有一個virtual,在派生類中重寫該函數,程序運行中則根據父類類型指向不同類型(子類或者父類)的對象來調用父類或者基類的虛函數。
為什么使用多態?
1、實現代碼的復用,避免代碼的冗余;
2、減少代碼之間的關聯性,即耦合度,方便后期對代碼的修改,功能的改善,不必牽一發而動全身,減少不必要的麻煩;
3、能夠通過重寫子類的方法,使不同的對像具有不同的功能,擴展了功能。
純虛函數是在虛函數后面去掉定義直接“=0”,所以有純虛函數的類也叫抽象類,他不能實例化,而對於他的派生類必須重寫這個純虛函數。也是為了更好的實現多態。
滿足下面條件的c++類則稱為接口
1、類中沒有定義任何的成員變量
2、所有的成員函數都是公有的
3、所有的成員函數都是純虛函數
4、接口是一種特殊的抽象類
P36:構造函數初始化

1 class Entity 2 { 3 private: 4 std::string m_Name; 5 public: 6 Entity() 7 { 8 m_Name = "xiaodang"; 9 } 10 Entity(std::string name) 11 { 12 m_Name = name; 13 } 14 std::string GetName() 15 { 16 return m_Name; 17 } 18 }; 19 20 int main() 21 { 22 Entity e; 23 e.GetName(); 24 25 Entity o("xiaozhang"); 26 e.GetName(); 27 std::cout << e.GetName() << std::endl; 28 std::cout << o.GetName() << std::endl; 29 std::cin.get(); 30 return 0; 31 }
當然我們還可以寫成這樣:這樣更加節省性能

1 class Entity 2 { 3 private: 4 std::string m_Name; 5 public: 6 Entity() 7 :m_Name("xiaodang") {} //這樣寫跟上面的例子是完全一樣的,如果我們多個參數只需要按照順序排列好就可以了 8 9 Entity() 10 { //這里可以寫成 m_Name = std::string("xiaodang") 所以如果用=的方法相當於我們創建兩塊內存,所以相對浪費性能 11 m_Name = "xiaodang"; 12 } 13 14 Entity(std::string name) 15 :m_Name(name) {} 16 17 std::string GetName() 18 { 19 return m_Name; 20 } 21 };
P37:三元運算符

1 static int s_Level = 8; 2 static int s_Speed = 2; 3 4 int main() 5 { 6 if (s_Level > 5) 7 s_Speed = 10; 8 else 9 s_Speed = 5; 10 11 s_Speed = s_Level > 5 ? 10 : 5; //顯示這種寫法比上面的寫法更好一點 12 13 s_Speed = s_Level > 5 ? s_Level > 10 ? 15 : 10 : 5; //這個的意思是s_Level跟5還有10進行比較,如果大於於5和10則是15,在他們中間則是10,都小於則是5 14 15 std::cout << s_Speed << std::endl; 16 std::cin.get(); 17 return 0; 18 }
P38:實例化對象

1 using String = std::string; 2 3 class Entity 4 { 5 private: 6 String m_Name; 7 public: 8 Entity() : m_Name("Unknown") {} 9 Entity(const String& name) : m_Name(name) {} 10 11 const String& GetName() const { return m_Name; } 12 }; 13 14 int main() 15 { 16 Entity entity; //在棧上創建一個Entity類型的對象,當然我們也可以寫成 Entity entity = Entity(); 但是我們通常會簡寫 17 18 Entity* en = new Entity("xiaodang"); //在堆上創建一個Entity類型的對象,當然我們還需要去釋放他 19 20 std::cout << en->GetName() << std::endl; 21 22 std::cin.get(); 23 delete en; 24 return 0; 25 }
P39:new關鍵字

1 Entity* e_One = new Entity(); //這兩個唯一的區別就是e_Two沒有調用構造函數,只是分配了內存 2 Entity* e_Two = (Entity*)malloc(sizeof(Entity));
P42: this作用域是在類內部,當在類的非靜態成員函數中訪問類的非靜態成員的時候,編譯器會自動將對象本身的地址作為一個隱含參數傳遞給函數
this指針的使用:一種情況就是,在類的非靜態成員函數中返回類對象本身的時候,直接使用 return *this;另外一種情況是當參數與成員變量名相同時,如this->n = n (不能寫成n = n)。
P44:智能指針(unique_ptr、shared_ptr、weak_ptr)智能指針都定義在memory頭文件中。
唯一指針(unique_ptr): std::unique_ptr<std::string> p1; 只能指向一個對象,所以不支持普通的拷貝和賦值操作,雖然我們不能拷貝或者賦值unique_ptr,但是可以通過調用release或reset將指針所有權從一個(非const)unique_ptr轉移給另一個unique

1 std::unique_ptr<std::string> p1 ; 2 std::unique_ptr<std::string> p2 ; 3 4 p1.release(); //p1放棄對指針的控制權,返回指針,並將p1置為空 5 p1.reset(); //釋放p1指向的對象 6 p1.reset(p2.release()); //如果提供了指針p2,令p1指向這個對象,否則將p1置為空
最安全的分配和使用動態內存的方法就是調用一個名為make_shared的標准庫函數,此函數在動態內存中分配一個對象並初始化它,返回指向此對象的unique_ptr。
1 std::unique_ptr<std::string> p1 = std::make_unique<std::string>(); 2 std::unique_ptr<std::string> p2 = std::make_unique<std::string>();
共享指針(shared_ptr):當進行拷貝和賦值時,每個shared_ptr都會記錄有多少個其他shared_ptr指向相同的對象。當我們給shared_ptr賦予一個新值或是shared_ptr被銷毀(例如一個局部的shared_ptr離開其作用域)時,計數器就會遞減,一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的對象。
1 std::shared_ptr<std::string> p4; 2 { 3 std::shared_ptr<std::string> p3 = std::make_shared<std::string>(); 4 p4 = p3; 5 }
弱引用指針(weak_ptr):weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那么這兩個指針的引用計數永遠不可能下降為0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過調用lock函數來獲得shared_ptr。
1 std::weak_ptr<std::string> p5; 2 { 3 std::shared_ptr<std::string> p6 = std::make_shared<std::string>(); 4 p5 = p6; //他在其作用域后就直接銷毀了 5 }
P47:動態數據(向量(vector))

1 #include<vector> 2 3 using namespace std; 4 5 struct Vertex 6 { 7 float x, y, z; 8 }; 9 10 ostream& operator<<(ostream& stream, Vertex& vertex) 11 { 12 stream << vertex.x << "," << vertex.y << "," << vertex.z; 13 return stream; 14 } 15 16 int main() 17 { 18 vector<Vertex> vertices; 19 vertices.push_back({ 1,2,3 }); 20 vertices.push_back({ 4,5,6 }); 21 for (int i = 0; i < vertices.size(); i++) 22 { 23 cout << vertices[i] << endl; 24 } 25 std::cin.get(); 26 return 0; 27 }
P52:處理多個函數返回值
1、獨立定義表示結果的結構體或類:
2、使用tuple+structure binding
公有接口不推薦,私有實現可采用。相較方式1可讀性可維護性弱,但可省去單獨的定義。
3、使用輸入輸出參數
最差的方式,多見於老舊代碼,可讀性可維護性最差,無法清晰表達哪些是輸入哪些是輸出。且要求輸入輸出參數有默認構造。且會導致調用方在調用函數前定義相關變量。
4、如果只是想增加一個函數的執行結果狀態,可以使用optional。

1 #include<iostream> 2 #include<tuple> 3 #include<string> 4 5 using namespace std; 6 7 tuple<int, string>GetReturn() 8 { 9 return make_tuple(2019, "xiaodang"); 10 } 11 12 int main() 13 { 14 int x; 15 string y; 16 tie(x, y) = GetReturn(); 17 cout << x << "和" << y << endl; 18 cin.get(); 19 return 0; 20 }
P53:模板(template)

1 template<typename T> 2 void GetPrintf(T value) 3 { 4 cout << value << endl; 5 } 6 7 int main() 8 { 9 GetPrintf<int>(25); 10 GetPrintf<string>("Hello"); 11 cin.get(); 12 return 0; 13 }
P55:宏
typedef和define都是替一個對象取一個別名,以此增強程序的可讀性,區別如下:
(1)原理不同
#define是C語言中定義的語法,是預處理指令,在預處理時進行簡單而機械的字符串替換,不作正確性檢查,只有在編譯已被展開的源程序時才會發現可能的錯誤並報錯。
typedef是關鍵字,在編譯時處理,有類型檢查功能。它在自己的作用域內給一個已經存在的類型一個別名,但不能在一個函數定義里面使用typedef。用typedef定義數組、指針、結構等類型會帶來很大的方便,不僅使程序書寫簡單,也使意義明確,增強可讀性。
(2)功能不同
typedef用來定義類型的別名,起到類型易於記憶的功能。另一個功能是定義機器無關的類型。如定義一個REAL的浮點類型,在目標機器上它可以獲得最高的精度:typedef long double REAL, 在不支持long double的機器上,看起來是這樣的,typedef double REAL,在不支持double的機器上,是這樣的,typedef float REAL
#define不只是可以為類型取別名,還可以定義常量、變量、編譯開關等。
(3)作用域不同
#define沒有作用域的限制,只要是之前預定義過的宏,在以后的程序中都可以使用,而typedef有自己的作用域。
P59:Lambda
Lambda 的語法形式如下:
[capture](parameters)->return-type{body}
[] //未定義變量,試圖使用在Lmabda內使用任何外部變量都是錯誤的 [x, &y] //x按值捕獲,y按引用捕獲 [&] //用到的任何外部變量都隱式的用引用捕獲 [=] //用到的任何外部變量都隱式的用值捕獲 [&, x] //x顯式的用值捕獲,其他變量用引用捕獲 [=. &z] //z按引用捕獲,其他的用值捕獲 [&, a, b]//出a和b用值捕獲,其他用引用捕獲 [this] //函數體內可以使用所在類中的成員變量
[&a, b](int z)->int{ z = a + b; return z; }
1 #include<iostream> 2 #include<vector> 3 #include <algorithm> 4 using namespace std; 5 int main(void) 6 { 7 std::vector<int> v = { 1, 2, 3, 4, 5 }; 8 std::for_each(v.begin(), v.end(), [](int element) { cout << element << endl; }); 9 cin.get(); 10 return 0; 11 }
P62:線程(Thread)

1 #include<iostream> 2 #include<thread> 3 4 using namespace std; 5 6 static bool s_Working = false; 7 8 void DoWork() 9 { 10 using namespace std::literals::chrono_literals; 11 cout << this_thread::get_id() << endl; 12 while (!s_Working) 13 { 14 cout << "Hello World" << endl; 15 std::this_thread::sleep_for(1s); 16 } 17 } 18 19 int main() 20 { 21 thread worker(DoWork); //開啟一個線程 22 cin.get(); 23 s_Working = true; 24 worker.join(); //線程的終結 25 cin.get(); 26 return 0; 27 }
P63:計時

1 #include <iostream> 2 #include <chrono> 3 #include <thread> 4 5 struct Timer 6 { 7 std::chrono::time_point<std::chrono::steady_clock> start, end; 8 std::chrono::duration<float> duration; 9 Timer() 10 { 11 start = std::chrono::high_resolution_clock::now(); 12 } 13 ~Timer() 14 { 15 end = std::chrono::high_resolution_clock::now(); 16 duration = end - start; 17 18 float ms = duration.count() * 1000.0f; 19 std::cout << "Time took " << ms << " ms" << std::endl; 20 } 21 }; 22 23 void GetTime() 24 { 25 Timer time; 26 for (int i = 0; i < 100; i++) 27 { 28 std::cout << "Hello World" << std::endl; 29 } 30 } 31 32 int main() 33 { 34 GetTime(); 35 std::cin.get(); 36 return 0; 37 }
P65:排序

1 int main() 2 { 3 std::vector<int> value = { 1,4,2,3,5 }; 4 //std::sort(value.begin(), value.end(), std::greater<int>()); 5 6 std::sort(value.begin(), value.end(), [](int a, int b) {return a > b; }); //可以使用stl中的模板,也可以利用lambda表達式來倒序排列 7 8 for (int values : value) //對於value集合進行循環 9 std::cout << values ; 10 11 std::cin.get(); 12 return 0; 13 }
P66:Type Punning(類型雙關)
P67:union(聯合)
union即為聯合,它是一種特殊的類。通過關鍵字union進行定義,一個union可以有多個數據成員。
1、在任意時刻,聯合中只能有一個數據成員可以有值。當給聯合中某個成員賦值之后,該聯合中的其它成員就變成未定義狀態了。
2、聯合的存儲空間至少能夠容納其最大的數據成員。也可以為聯合的成員指定長度。通過冒號操作符來實現成員長度的指定。
3、union不能含有引用類型的成員,默認情況下,union的成員都是公有的,這一點和struct相同
4、union既不能繼承自其他類,也不能作為基類使用,所以在union中不能含有虛函數。

1 int main() 2 { 3 union 4 { 5 int a; 6 char c; 7 double b; 8 }; 9 10 a = 20; 11 b = 45.6; 12 c = 'H'; 13 std::cout << "a = " << &a <<std::endl; 14 std::cout << "b = " << &b << std::endl; 15 std::cout << "c = " << &c << std::endl; //三個結果都指向同一個地址 16 std::cin.get(); 17 return 0; 18 }
P69:類型轉換
static_cast、dynamic_cast、const_cast和reinterpret_cast
P75:結構化綁定