{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# C++ Notes on [The Cherno Videos](https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb)\n", "\n", "### 1. Welcome to C++\n", "### 2. How to Setup C++ on Windows\n", "\n", "* [The Cherno's VS Settings](https://youtu.be/1OsGXuNA5cc?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=259)\n", "\n", "### 3. How to Setup C++ on Mac\n", "### 4. How to Setup C++ on Linux" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5. [How C++ Works](https://www.youtube.com/watch?v=SfGuIVzE_Os&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=5)\n", "\n", "* int main() 可以不用 return 0 。只有 main() 可以這樣\n", "* << operator 也是一個函數\n", "* 一個 cpp 檔裡只要所有用到的變數函數都找的到宣告,compiler 就相信它存在,實際定義在哪是 linker 要負責找到。\n", "* 在同一個 cpp file 裡只要所有函數變數都找的宣告就可以編譯。不需要另外 include\n", "* 把 text 編譯成 binary file 的三個步驟:\n", " * preprocess: h -> cpp\n", " * compile: cpp -> obj\n", " * link: obj files -> exe\n", "* platform & configuration :\n", " * platform 如 x86 是 target platform(x86==win32)\n", " * configuration 如 Debug/Release 是 build config\n", " * 檢查跟 property 裡的一樣。VS 有時候會弄錯\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6. [How the C++ Compiler Works](https://www.youtube.com/watch?v=3tIqpEmWMLI&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=7&t=0s)\n", "\n", "* preprocessor 只負責複製貼上。把 ```}``` 改成 ```#include \"EndBrace.h\"``` 結果是一樣的\n", "* \\*.i file 是 preprocess 過的 cpp file\n", "* compile 是 compile cpp files individually 的意思,在 windows 裡編譯完得 obj 檔被 VS 丟到 /Debug 裡\n", "* C++ 沒有 file 的概念,只有 translation unit :\n", " * 用 include 把 a.cpp 貼到 b.cpp,再把 b.cpp 貼到 c.cpp,最後只編譯 c.cpp,這樣就只有一個很大的 translation unit\n", "* compile 得到的 obj file 是 binary。要得到可讀的 assembly code 可以在 VS 改:\n", " * Porperty -> C/C++ -> Output Files -> Assumbler Output 改成 Assembling only listing\n", " * 改好之後重新編譯,除了 obj 還會得到 asm file 是可讀的\n", "* O2 編譯是優化速度。還可以優化別的:\n", " * 下面這兩行會被 O2 會編成 ```return a*b```,在 assembly 省下一個 mov " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "int f(int a, int b)\n", "{\n", " int c = a*b;\n", " return c;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 因為 O2 不是一行一行編譯所以沒辦法 debug\n", "* Constant Folding:\n", " * ```return 5*2;``` 會直接編譯成 ```return 10;``` 就算在 debug mode 也一樣\n", " * 所有編譯時期能決定的 constant 就會直接算出結果放在 assembly\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 7. [How the C++ Linker Works](https://www.youtube.com/watch?v=H4s55GgAg0I&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=7)\n", "\n", "* 一個函數只要在某個檔案定義過了 linker 就找的到,和 include 無關,include 只需要 include 宣告\n", "* linker 找的是 function signature\n", "* 一個程式的 entry point 不一定就是 main function,雖然大部份時候都是\n", "* 分清楚 error 是 compiling(error code C 開頭)還是 linking error(LNK 開頭)\n", "* 兩種常見 linking error:\n", " * unresolved external symbol:有變數或函數找不到定義\n", " * one or more multiple defined symbols found:有變數或函數重覆定義,有可能是 preprocessor 貼兩次的結果,solution:\n", " * 把函數宣告成 static,只有那個 translation unit 看的到\n", " * 宣告成 inline\n", " * 不要把函數定義在 h 檔裡,而是定義在 cpp 檔自成一個 translation unit\n", " * [除了第一次定義之外,其它都用 extern](https://youtu.be/f3FVU-iwNuA?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=150),如 ```extern int a;```\n", "* linker 也用來 link 其它 library 如 STL,platform API\n", "* linking 有分 static 和 dynamic" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 8. [Variables in C++](https://www.youtube.com/watch?v=zB9RI8_wExo&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=8)\n", "\n", "\n", "\n", "|Type|Size (Bytes)|\n", "|--|--|\n", "|bool|1|\n", "|char|1|\n", "|int|4|\n", "|double|8|\n", "\n", "\n", "* \\* 代表 compiler dependent\n", "* 可以用 sizeof 來查\n", "* C++ 所有 primitive type 其實都是數字,差別只在暫記憶體空間大小\n", "* bool 其實只需要一個 bit,可是 memory 都是以 byte 為單位讀,所以就用 1 byte 來存\n", "* float 宣告會自動變成 double 除非宣告成這樣 ```float a = 5.5f;```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 9. [Functions in C++](https://www.youtube.com/watch?v=V9zuox47zr0&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=9)\n", "\n", "* 常常複製貼上一段 code 之後忘了改一些細節。如果一段 code 會被複製貼上很多次就該包成函數\n", "* 練習把 code 拆成很多很多函數\n", "* 但不要 over do it 因為有 context switch overhead 會變慢。function 的 assembly code 在 binary 的其它位置" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 10. [C++ Header Files](https://www.youtube.com/watch?v=9RJTQmK0YPI&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=10)\n", "\n", "* 如果沒有 header file,每一個 translation unit 都要把自己用到的所有函數重新宣告一次。有 header file 就可以用 include 的就好\n", "* 在一個 header file 的最前面放 ```#pragma once``` 可以避免這個 header file [被重覆 include](https://en.wikipedia.org/wiki/Pragma_once#Example),效果同把整個 header file 的內容包在 ifndef 裡:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#ifndef _LOG_H_\n", "#define _LOG_H_\n", "#endif" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 現在已經差不多所有編譯器都支援 ```#pragma once``` 了\n", "* ```#include
``` 和 ```#include \"header.h\"``` 的差別:\n", " * 編譯的時候可以指定一些 include path,用括號的 include 是從這些 path 中找,用雙引號的 include 是用該 cpp file 的相對位置找\n", " * ```#include \"header.h\"``` 代表這個 header 和 cpp 在同一個 folder\n", " * 其實雙引號也會去 include path 找所以 ```#include \"iostream\"``` 是可以的\n", "* C++ 自帶的 header file 是沒有副檔名的,所以不能 ```#include ```,會找不到。但是 C 自帶的是有的,例如 ```#include ```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 11. How to DEBUG C++ in VISUAL STUDIO\n", "### 12. CONDITIONS and BRANCHES in C++ (if statements)\n", "### 13. BEST Visual Studio Setup for C++ Projects!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 14. Loops in C++ (for loops, while loops)\n", "\n", "* ```for(i=0 ; i<=4 ; i++)``` 會比 ```for(i=0 ; i<5 ; i++)``` 慢因為 ```i<=4``` 其實是兩次比較。只要情況允許應該永遠寫 ```i<5```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 15. Control Flow in C++ (continue, break, return)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 16. [POINTERS in C++](https://www.youtube.com/watch?v=DTxHyVn0ODg&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=16)\n", "\n", "* 不管是 ```int*```, ```char*```, ```double*``` 都一樣是整數\n", "* 以下三個都相同:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "{\n", " void* ptr1 = nullptr;\n", " void* ptr2 = NULL;\n", " void* ptr3 = 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 記憶體零的位置不能讀寫,如果試著去讀程式會 crash,但上面這些 statement 是合法的編譯可以過\n", "* [VS 是有辦法看到 memory 裡有什麼的!](https://youtu.be/DTxHyVn0ODg?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=412)\n", " * 看到的是反的。```int a = 5;``` 在記憶體裡會看到 ```05 00 00 00```\n", "* [還能看著 memory debug](https://youtu.be/DTxHyVn0ODg?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=668),[直接打 pointer 的變數名稱也行](https://youtu.be/DTxHyVn0ODg?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=808)\n", "* ```&a```:a 的位址,```*p```:p 裡面住的人\n", "* cast ```int*``` into ```double*``` (反正所有 ptr 都是整數)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "{\n", " int var = 8;\n", " double* ptr = (double*)&var;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* cstring memset 把一塊連續的記憶體設成同一個值" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "# include \n", "{\n", " char* buffer = new char[8];\n", " std::memset(buffer, 0, 8);\n", " delete[] buffer;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* ```int* a, b;``` [這樣宣告只有 a 是指標](https://youtu.be/4fJBrditnJU?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=470),b 還是整數。如果要兩個指標要宣告成 ```int *a, *b;```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 17. [REFERENCES in C++](https://www.youtube.com/watch?v=IzoFn3dfsPA&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=17)\n", "\n", "* 函數傳參考呼叫的寫法\n", "* 宣告參考的時候一定要初始化(沒有本名哪有別名)(it's not a real variable, it's a reference)\n", "* 一旦初始化就不能改變。下面這段 code 只會把 a 的值設成 8,不會把 ref 變成 b 的別名" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "{\n", " int a = 5;\n", " int b = 8;\n", " int& ref = a;\n", " ref = b;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 18. CLASSES in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 19. [CLASSES vs STRUCTS in C++](https://www.youtube.com/watch?v=fLgTtaqqJp0&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=19)\n", "\n", "* 差別真的就只有 struct 預設是 public,class 預設是 private\n", "* The Cherno 遇到真的只用來裝 data 的才用 struct,然後從來不繼承 struct " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 20. How to Write a C++ Class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 21. [Static in C++](https://www.youtube.com/watch?v=f3FVU-iwNuA&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=22&t=0s)\n", "\n", "* static 分成在 class 裡的和在 class 外的,意義不同,這裡講在 class 外的\n", "* external linkage:在不同 translation unit 裡重覆定義非 static 變數會出現 linking error。要避免這個錯誤可以寫 ```extern int a;``` 叫 linker 去其它 translation unit 找\n", " * `extern int a;` 是宣告而非定義,見 Stroustrup p.430\n", " * `extern` 和(class 外的)`static` 是用來控制 linker 的行為\n", "* internal linkage:在一個 translation unit 的全域範圍裡的 static 只有該 translation unit 看的到。linker 從外部找不到,就算用 extern 也找不到。這個變數就像是這個 translation unit 的 private 變數\n", "* static 函數也一樣,只有該 translation unit 看的到。這樣定義:```static void Function(){}```\n", "* 如果在標頭檔裡定義 static 變數然後這個標頭檔被兩個 cpp file include,由於 preprocessor 只是單純的複製貼上,這相當於在兩個 translation unit 裡各自定義 static 變數\n", "* 就像 class 裡的儘量把外部用不到的變數函數定義成 private 一樣,全域變數也應該儘量定義成 static,除非真的需要它被 linker 找到" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 22. [Static for Classes and Structs in C++](https://www.youtube.com/watch?v=V-BFlMrBtqQ&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=23&t=0s)\n", "\n", "\n", "* 在 class 裡的 static variable 只有一個 instance。不管宣告多少這個 class 的 instances 都共用同一個該 variable。然後在全域要用 scope resolution operator 再宣告一次" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "// won't run in xeus-cling but perfectly legal in VS\n", "\n", "class Entity\n", "{\n", "public: \n", " static int a;\n", " static void Print()\n", " {\n", " std::cout << a << std::endl;\n", " }\n", "};\n", "\n", "int Entity::a;\n", "\n", "int main()\n", "{\n", " Entity e; \n", " e.a;\n", " e.Print();\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 上面的 ```e.a``` 是合法的編譯會過,但觀念正確的呼叫方式應該是 ```Entity::a```\n", "* static 成員變數可以是 private,所以比全域變數更好控制權限\n", "* 非 static 的所有 class 成員函數經過編譯其實都有一個像 python 的 self 把呼叫自己的物件傳進去,static 成員函數沒有,所以如果要存取其它成員變數,只能存取 static 成員變數。沒有 self 就沒辦法透過 self 物件存取裡面的變數" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 23. [Local Static in C++](https://www.youtube.com/watch?v=f7mtWD9GdJ4&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=23)\n", "\n", "* 在一個 local scope(如函數,if statement)裡的 static 變數 lifetime 會變成永遠,但 scope 不變" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "void Function()\n", "{\n", " static int i = 0;\n", " i++;\n", " std::cout << i << std::endl;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 上面這段 code 有點像把 ```int i = 0;``` 移到全域。如果呼叫這個函數五次,i 就會被遞增五次(所以可以計算這個函數被呼叫了多少次)\n", " * 上面這段 code 的初始化 ```i = 0``` 只會做一次,因為第二次以後呼叫這個函數時 i 已經存在了不需要初始化\n", "* 那為什麼不用全域變數就好了?因為 local static 變數不會改變 scope,只有這個函數能計算自己被呼叫的次數。如果宣告成全域變數,在呼叫五次的過程中可能有其它 code 會改變 i 的值\n", "* 兩種 Singleton 的寫法,行為完全一樣,用 local static 寫比較乾淨" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Singleton\n", "{\n", "private:\n", " static Singleton* s_Instance;\n", "public:\n", " static Singleton& Get() { return *s_Instance; }\n", " void Hello() {}\n", "};\n", "\n", "Singleton* Singleton::s_Instance = nullptr;\n", "\n", "int main()\n", "{\n", " Singleton::Get().Hellow();\n", "}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Singleton\n", "{\n", "public:\n", " static Singleton& Get() \n", " { \n", " static Singleton instance;\n", " return instance; \n", " }\n", " void Hello() {}\n", "};\n", " \n", "int main()\n", "{\n", " Singleton::Get().Hellow();\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 上面第二種寫法裡的 ```static Singleton instance;``` 只有第一次被呼叫時真正 new 了一個 Singleton,之後就一直用同一個 instance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 24. [ENUMS in C++](https://www.youtube.com/watch?v=x55jfOd5PEE&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=25&t=0s)\n", "\n", "\n", "* 幫整數取名字,增加可讀性" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "{\n", " enum Example : unsigned char // 如果不指定預設為 int。不可以用 float 因為一定要整數\n", " {\n", " A=0, B=2, C=6 // 如果不指定而只寫 A, B, C 預設為 0, 1, 2\n", " };\n", " \n", " Example value = B;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Log class 例子:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[WARNING]: Hello!\n" ] } ], "source": [ "#include \n", "\n", "class Log\n", "{\n", "public:\n", " enum Level\n", " {\n", " LevelError=0, LevelWarning, LevelInfo // 這樣初始化可以增加可讀性。代表 0, 1, 2\n", " };\n", "private: \n", " Level m_LogLevel = LevelInfo;\n", "public: \n", " void SetLevel(Level level)\n", " {\n", " m_LogLevel = level;\n", " }\n", " void Error(const char* message)\n", " {\n", " if (m_LogLevel >= LevelError)\n", " std::cout << \"[ERROR]: \" << message << std::endl;\n", " }\n", " void Warn(const char* message)\n", " {\n", " if (m_LogLevel >= LevelWarning)\n", " std::cout << \"[WARNING]: \" << message << std::endl;\n", " }\n", " void Info(const char* message)\n", " {\n", " if (m_LogLevel >= LevelInfo)\n", " std::cout << \"[INFO]: \" << message << std::endl;\n", " }\n", "};\n", "\n", "Log log;\n", "log.SetLevel(Log::LevelInfo); // scope resolution operator\n", "log.Warn(\"Hello!\");\n", "log.SetLevel(Log::LevelError);\n", "log.Warn(\"Hello in level error\");\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果 ```m_LogLevel``` 是 int,有可能會不小心被改成 5。宣告成 Level 會比較安全\n", "* SetLevel 裡用 scope resolution operator ```Log::LevelError``` 就像 LevelError 是一個成員變數一樣。enum Level 裡並不是一個 namespace 而是有一個 enum class\n", " * 所以這個例子裡 enum 變數不能叫 Error,會跟成員函數重名\n", " * 像這個例子的 enum 裡變數名前面灌上該 enum 的名稱也是 common practice" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 25. Constructors in C++\n", "### 26. Destructors in C++\n", "### 27. Inheritance in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 28. [Virtual Functions in C++](https://www.youtube.com/watch?v=oIV2KchSyGQ&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=29&t=0s)\n", "\n", "\n", "* 父類別指標可以指向子類別物件,然後通過 arrow operator 呼叫子類別版本的 virtual 成員函數" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "// won't run in cling but perfectly legal in VS\n", "\n", "class Entity\n", "{\n", "public: \n", " virtual std::string GetName() { return \"Entity\"; }\n", "};\n", "\n", "class Player : public Entity\n", "{\n", "private:\n", " std::string m_Name;\n", "public:\n", " Player(const std::string& name) : m_Name() {}\n", " std::string GetName() override { return m_Name; }\n", "};\n", "\n", "void PrintName(Entity* entity)\n", "{\n", " std::cout << entity->GetName() << std::endl;\n", "}\n", "\n", "Player* p = new Player(\"Cherno\");\n", "PrintName(p)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 這裡被傳進 PrintName 的指標實際上是指向子類物件還是父類物件,編譯時期是不知道的\n", "* C++11 之後子類裡的 override 成員函數定義裡可以加上 override 關鍵字,不是強制的但\n", " 1. 可以增加可讀性\n", " 1. 可以避免函數名字打錯(如果打成 GETNAME() override 編譯會出錯)\n", " 1. 如果父類裡忘了宣告這個函數為 virtual 編譯也會出錯\n", "* overhead\n", " 1. 父類裡多出 4 bytes 用來存指向子類 vtable 的指標\n", " 1. 到了子類要查 vtable 找到想要的函數\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### [Vtable](https://www.youtube.com/watch?v=VdvL8kFBubU)\n", "\n", "* 如果沒有任何 virtual function,sizeof 一個類別就是所有成員變數總和(成員函數不佔空間)。一旦出現 virtual function 而成為一個父類,sizeof 會多出 4 bytes 用來存指向子類 vtable 的指標\n", "* 每一個子類有一個自己的 vtable,同一個子類的所有 instance 都共用這個 vtable\n", "* vtable 裡有很多函數指標,指向所有在這個子類裡有 override 的 virtual function\n", "* vtable 簡介在 Stroustrup 的 The C++ Programming Language, 4th Ed 第三章(p.67)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 29. [Interfaces in C++ (Pure Virtual Functions)](https://www.youtube.com/watch?v=UWAdd13EfM8&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=30&t=0s)\n", "\n", "* 在父類裡把某個成員函數的 body 拿掉換成 ```=0;```,這個父類就變成 interface,不能有自己的 object,並且強迫所有子類要 override 這個函數" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "virtual std::string GetName() = 0;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 30. Visibility in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 31. [Arrays in C++](https://www.youtube.com/watch?v=ENDaJi08jCU&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=32&t=0s)\n", "\n", "\n", "* Lua array index 從 1 開始\n", "* 如果存到超出 array 的範圍,在 debug mode 會 crash 但在 release mode 不會有任何警告,然後就會很難 debug\n", "* pointer arithmetic: main 裡面的三行做的事一樣" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "int main()\n", "{\n", " int a[5];\n", " int* ptr = a;\n", " \n", " a[2] = 5;\n", " *(ptr + 2) = 5;\n", " *(int*)((char*)ptr + 8) = 5;\n", " \n", " return 0;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* ```ptr + 2``` 實際上在記憶體裡要 offset 幾個 byte 取決於 ptr 是哪一類的指標。int 佔 4 bytes,char 只佔 1 byte,所以如果把 ptr cast 成 char* 之後要加 8 才會指到同一個位址(然後還要 cast 回來)\n", "* stack 和 heap based array 的差別\n", " 1. lifetime:stack based 只看 scope,heap based 就是 new 出來的,要一直到看到 delete 才會消失\n", " 1. new 比較慢因為會有 memory fragmentation,cache misses\n", "* C++11 之後有 std::array。儘量用 std::array 取代 raw array" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "int main()\n", "{\n", " std::array a = {1, 2, 3};\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* stack based array 例如 ```int a[5];``` 可以這樣算出長度 ```sizeof(a)/size(int)```,heap based 就沒辦法了(其實有辦法但是 compiler dependent,不要用)\n", "* 用一個變數來 manage size:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "{\n", " static const int size = 5;\n", " int a[size];\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 這裡一定要用 static const 因為 stack based array 長度必需在編譯時期就知道(實測不用?甚至直接 ```int size = 5;``` [也行](http://cpp.sh/6me6u))。如果是 heap based 就不用" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 32. How Strings Work in C++ (and how to use them)\n", "### 33. String Literals in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 34. [CONST in C++](https://www.youtube.com/watch?v=4fJBrditnJU&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=35&t=0s)\n", "\n", "\n", "\n", "* ```int* const a = new int;``` 不能改指向別的位址,但可以改裡面住的人(從右往左讀:a 是一個 const)\n", "* ```const int* a = new int;``` 可以改指向別的位址,但不能改裡面住的人(從右往左讀:a 是一個指標,指向 const int)\n", "* ```int const* a = new int;``` 同上。只有 const 放在 \\* 前後會有差別。總結:\n", " 1. 只有直接接在 const 後面的東西才是真正不能改 \n", " 2. ```int*``` 的 ```int``` 和 ```*``` 要分開來看\n", " 3. `int const` = `const int`\n", "* ```const int* const a = new int;``` 位址和內容都不能改\n", "* const 成員函數,不能改任何成員變數" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "class Entity\n", "{\n", "private:\n", " int m_x, m_y;\n", " mutable int val;\n", "public: \n", " int GetX() const\n", " {\n", " val = 2; // 宣告成 mutable 的成員變數可以改\n", " return m_x;\n", " }\n", "};" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* ```const int* const GetPtr() const``` 回傳一個指標,位址和內容都不能改,並且這個成員函數也不更改任何成員變數\n", "* 下面這個函數裡 e 只能呼叫 const 成員函數,因為傳進來的時候就被限定是 const ref,不能改 e 裡面的任何東西。所以有時候會看到同樣的成員函數被定義兩次,一次是 const 另一次不是" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "void PrintEntity(const Entity& e)\n", "{\n", " std::cout << e.GetX() << std::endl;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 35. [The Mutable Keyword in C++](https://www.youtube.com/watch?v=bP9z3H3cVMY&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=35)\n", "\n", "\n", "* mutable 關鍵字有兩個不同的意義:\n", " 1. 使某成員變數在 const 成員函數裡能被改寫,看上面的例子。大部份時候是為了 debug,如果直接把該成員函數改成 non-const 可能會 break 別的東西\n", " 1. lambda(The Cherno 實務上從來沒看過人這麼寫,從來不需要)\n", " * ```auto f = [=]()``` 把 scope 裡所有東西傳進去,by value\n", " * ```auto f = [&]()``` 把 scope 裡所有東西傳進去,by ref\n", " " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "int main()\n", "{\n", " int x = 8;\n", " auto f = [=]() mutable\n", " {\n", " x++; // call by value 的時候如果不把這個 lambda 函數宣告成 mutable,就不能改內容\n", " std::cout << x << std::endl;\n", " };\n", " f();\n", " // x 還是 8,並沒有被遞增,因為 call by value\n", "} " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 36. Member Initializer Lists in C++ (Constructor Initializer List)\n", "### 37. Ternary Operators in C++ (Conditional Assignment)\n", "### 38. How to CREATE/INSTANTIATE OBJECTS in C++\n", "### 39. The NEW Keyword in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 40. Implicit Conversion and the Explicit Keyword in C++\n", "\n", "* Implicit Conversion (Construction):像下面的例子,當有對應的 ctor 時可以直接 initialize 成 `Entity a = 22;`\n", " * C#, Java 沒有\n", "* 雖然合法但不要寫 `Entity a6 = 22;`,不好讀" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#include \n", "#include \n", "\n", "class Entity\n", "{\n", "private:\n", " std::string m_Name;\n", " int m_Age;\n", "public: \n", " Entity(const std::string& name)\n", " : m_Name(name), m_Age(-1) {}\n", "\n", " Entity(int age)\n", " : m_Name(\"Unknown\"), m_Age(age) {}\n", "};\n", "\n", "{\n", " // normal initialization\n", " Entity a1(\"Cherno\");\n", " Entity a2(22);\n", " Entity a3 = Entity(\"Cherno\");\n", " Entity a4 = Entity(22);\n", "\n", " // implicit conversion\n", " Entity a6 = 22;\n", " // Entity a5 = \"Cherno\"; // For some reason The Cherno 可以這樣寫但在 cling 不行\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* compiler 只允許做一次 implicit conversion。下面的例子裡 `PrintEntity(\"Cherno\");` 企圖先把 `\"Cherno\"` (7 個字元的 const char array) convert 成 `const std::string` 再 convert 成 Entity,做不了兩次 conversion 會報錯" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "void PrintEntity(const Entity& entity){ \n", " // Printing \n", "}\n", "\n", "{\n", " PrintEntity(22);\n", " // PrintEntity(\"Cherno\"); // 不合法,\"Cherno\" 是 const char [7] \n", " PrintEntity(std::string(\"Cherno\"));\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果 ctor 宣告成 explict,implicit conversion 就會 disable,`Entity a6 = 22;` 也會報錯\n", "* 但是可以 cast:`Entity a5 = (Entity)22;`\n", "* explicit key word 不常用,重要的是知道 complier 是有做 implicit conversion 的" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "#include \n", "\n", "class Entity\n", "{\n", "private:\n", " std::string m_Name;\n", " int m_Age;\n", "public: \n", " explicit Entity(const std::string& name)\n", " : m_Name(name), m_Age(-1) {}\n", "\n", " explicit Entity(int age)\n", " : m_Name(\"Unknown\"), m_Age(age) {}\n", "};\n", "\n", "{\n", " // normal initialization\n", " Entity a1(\"Cherno\");\n", " Entity a2(22);\n", " Entity a3 = Entity(\"Cherno\");\n", " Entity a4 = Entity(22);\n", " Entity a5 = (Entity)22; // casting\n", "\n", " // implicit conversion disable, 下面三個都會報錯\n", " // Entity a6 = 22;\n", " // Entity a7 = \"Cherno\";\n", " // PrintEntity(std::string(\"Cherno\"));\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 41. OPERATORS and OPERATOR OVERLOADING in C++\n", "### 42. The \"this\" keyword in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 43. [Object Lifetime in C++ (Stack/Scope Lifetimes)](https://www.youtube.com/watch?v=iNuTwvD6ciI)\n", "\n", "* 直接宣告變數獲得的記憶體空間都是 stack based\n", " * 每一層 scope 都是 stack 上的一個 frame。每新建一個 scope 就是在 push,就像把一本書放到一整疊書的最上面。當在這個 scope 裡宣告變數就等於是把它寫在這本書裡。離開這個 scope 就是 pop,把這本書連同所有內容全部丟掉\n", "* 用 new 配置的記憶體空間是 heap based,在 delete 之前這塊空間是不會消失的\n", "* 常見錯誤:在函數裡配置 stack based 記憶體空間然後回傳指標,這樣做一出函數時配置給 a 的記憶體空間就消失了" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "int* CreateArray()\n", "{\n", " int array[50];\n", " return array;\n", "}\n", "\n", "int main()\n", "{\n", " int* a = CreateArray();\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Scoped class:寫一個 class 當作 python 的 with 來用。用 ctor 和 dtor 來當作 ```__enter__``` 和 ```__exit__```,可以用來寫:\n", " * timer\n", " * mutex locking for multithreading\n", " * scoped pointer,如 unique pointer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ScopedPtr\n", "{\n", "private: \n", " Entity* m_Ptr;\n", "public: \n", " ScopedPtr(Entity* ptr): m_Ptr(ptr){}\n", " ~ScopedPtr()\n", " {\n", " delete m_Ptr;\n", " }\n", "};\n", "\n", "int main()\n", "{\n", " {\n", " ScopedPtr e = new Entity();\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 44. [SMART POINTERS in C++ (std::unique_ptr, std::shared_ptr, std::weak_ptr)](https://www.youtube.com/watch?v=UOB7-B2MfwA&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=45&t=0s)\n", "\n", "* new 完不用 delete,在 out of scope 時記憶體會自動被釋放(物件毀滅)\n", "* 試著用 smart pointers 取代 raw pointers。能用 unique_ptr 就用 unique_ptr,真正需要 share 的時候才用 shared_ptr\n", "\n", "#### unique_ptr\n", "\n", "* 不能被複製。因為如果 ptr1 = ptr2,後來 ptr1 先 go out of scope 了,所指向的物件就會毀滅,繼續使用 ptr2 就會出錯(或者當 ptr2 也 go out of scope 也會出錯)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "int main()\n", "{\n", " {\n", " // std::unique_ptr e1 = new Entity(); 不合法因為 unique_ptr 的 ctor 是 explicit\n", " std::unique_ptr e1(new Entity());\n", " std::unique_ptr e2 = std::make_unique();\n", " // std::unique_ptr e3 = e2; 不合法因為不能複製。unique_ptr 的 copy ctor 和 assignment 都被設成 delete 了\n", " e1 -> Print();\n", " e2 -> Print();\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 呼叫 Entity 成員的方式完全同 raw pointer\n", "* 一出 scope 物件自動毀滅\n", "* 用 make_unique 作初始化比較安全,處理了 constructor 出 exception 的情況\n", "* 幾乎沒有 overhead\n", "\n", "#### shared_ptr\n", "\n", "* 有一個 reference counting system 計算這個 ptr 被複製幾次,變成零的時候釋放記憶體" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "int main()\n", "{\n", " {\n", " std::shared_ptr e1(new Entity()); //是合法的但不要用。這樣比較慢\n", " {\n", " std::shared_ptr e2 = std::make_shared();\n", " std::weak_ptr e3 = e1;\n", " e1 = e2;\n", " }// 內層\n", " }// 外層\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果用 make_shared,reference counting 所需要的記憶體和 Entity instance 是一起配置的所以比較快\n", "* 出內層的時候雖然 e2 死了,但物件還在,到離開外層的時候因為連 e1 都死了,物件才被消滅\n", "\n", "#### weak_ptr\n", "\n", "* 和 shared_ptr 一起用。上面那段程式裡的 ```e3 = e1``` 不會增加 reference count\n", "* 不想 take ownership of entity 的時候用\n", "* 可以透過 weak_ptr 問一個物件是否還活著,但 weak_ptr 不會 keep it alive" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 45. Copying and Copy Constructors in C++\n", "### 46. The Arrow Operator in C++\n", "### 47. Dynamic Arrays in C++ (std::vector)\n", "### 48. Optimizing the usage of std::vector in C++\n", "### 49. [Using Libraries in C++ (Static Linking)](https://www.youtube.com/watch?v=or1dAmUO8k0&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=50&t=0s)\n", "### 50. [Using Dynamic Libraries in C++](https://www.youtube.com/watch?v=pLy69V2F_8M&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=51&t=0s)\n", "### 51. [Making and Working with Libraries in C++ (Multiple Projects in Visual Studio)](https://www.youtube.com/watch?v=Wt4dxDNmDA8&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=52&t=0s)\n", "\n", "* 原來 GDA 是這樣設置的" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 52. How to Deal with Multiple Return Values in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 53. [Templates in C++](https://www.youtube.com/watch?v=I-hZkUa9mIs&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=53)\n", "\n", "\n", "* 叫 compiler 幫你寫 code(template will be compiled by your usage)\n", "* 下面這個例子如果要改 Print 的定義就要改兩次" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "void Print(int value)\n", "{\n", " std::cout << value << std::end;\n", "}\n", "\n", "void Print(float value)\n", "{\n", " std::cout << value << std::end;\n", "}\n", "\n", "int main()\n", "{\n", " Print(5);\n", " Print(5.5f);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* template 版定義:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "template\n", "void Print(T value)\n", "{\n", " std::cout << value << std::end;\n", "}\n", "\n", "int main()\n", "{\n", " Print(5);\n", " Print(5.5f);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 這個 Print 不是真正意義上的函數,而是一個 template。只有被呼叫到的時候才編譯,所以如果沒有被呼叫過,template 裡面就算有文法錯這份 code 還是會編譯成功\n", "* 呼叫時可以 ```Print(5);``` 也可以直接 ```Print(5);```,編譯器會自己 infer\n", "* std::array template 的定義是像這樣:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "#include \n", "\n", "template\n", "class Array\n", "{\n", "private:\n", " T m_Array[N];\n", "public: \n", " int GetSize() const { return N; }\n", "};\n", "\n", "int main()\n", "{\n", " Array a;\n", " std::cout << a.GetSize() << std::endl;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 用 template 是在「program the compiler」,可以改到很複雜。有些公司直接禁用\n", "* The Cherno 認為不該全面禁用因為 template 還是很強大的。一個很好的應用是 logging system,因為可能想 log 的 type 有無限多種\n", "* 但如果改到太複雜會很難 debug,只能紙筆寫下追蹤到底編譯了什麼 code" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 54. [Stack vs Heap Memory in C++](https://www.youtube.com/watch?v=wJ1L2nSIV1s&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=54)\n", "\n", "* [C++ 程式開始執行時系統配置給該程式的記憶體會被分成五大區塊](https://stackoverflow.com/questions/14588767/where-in-memory-are-my-variables-stored-in-c)\n", " 1. Code Segment \n", " * text, code, functions\n", " 1. Uninitialized Data Segment\n", " 1. Initialized Data Segment\n", " * static and global variables \n", " 1. Stack Segment\n", " 1. Heap Segment\n", "* Stack 跟 Heap 都是在記憶體裡,不會預設放在 CPU cache\n", "* Stack Based Memory:\n", " * new 出來的記憶體空間就是 Heap based,其它都是 stack based\n", " * 空間大小編譯時期就必需知道,這樣才能知道程式的 stack 區塊要預留多大\n", " * Stack based 記憶體只有在該變數的 scope 內才活著\n", " * Stack memory allocation 只是改 stack pointer(釋放記憶體也一樣),在 assembly 裡就是一個 mov,只有一個 CPU instruction,非常快!\n", " * Stack 裡所有變數都是擠在一起的,除了中間可能會有一些 safety guard\n", " * [大部份 stack implementation 都是 grow the stack backward](https://youtu.be/wJ1L2nSIV1s?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=419) 所以 VS 裡看到的記憶體內容是倒過來的\n", "* Heap Based Memory:\n", " * new 出來之後會一直活到被 delete\n", " * new 其實是呼叫 malloc,在執行期間去找目前能用且夠大的記憶體空間。系統預設會給一個程式一些 free list 空間,如果那些也不夠,程式就要另外向系統要多的空間,系統負責去找出夠大的空間配置給程式並聲明這塊空間被佔用了。很多 bookkeeping 要做,比 stack allocation(1 CPU instruction)慢很多\n", "* 當情況允許時應該盡量用 stack allocation\n", "* [這裡](https://youtu.be/wJ1L2nSIV1s?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=828)比較了兩種 allocation 的 assembly" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 55. Macros in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 56. [The \"auto\" keyword in C++](https://www.youtube.com/watch?v=2vOPEuiGXVo&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=56)\n", "\n", "* The Cherno 自己只有 type 很長的時候時候才會用 auto,例如\n", " * ```std::vector::iterator```\n", " * ```std::unordered_map>```\n", "* 但其實 type 很長時也可以用 ```typedef``` 或 ```using```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "using DeviceMap = std::unordered_map>;" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "typedef std::unordered_map> DeviceMap;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果要用 auto 接 reference,還是要加 ```&``` 不然 auto 就會複製一份\n", "* ```const``` 也一樣,要寫 ```const auto&```,不能省略 ```const``` 和 ```&```\n", "* 如果有 template,有些情況下會必需要用 auto,因為不知道函數會回傳什麼類的物件。遇到這種 code 就太複雜了,應該避免" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 57. Static Arrays in C++ (std::array)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 58. [Function Pointers in C++](https://www.youtube.com/watch?v=p4sDgQ-jao4&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb)\n", "\n", "* 下面的 ```&PrintValue``` 指出該函數在記憶體區塊中的位址。```&``` 可以省略,有 implicit conversion" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Values: 5\n" ] } ], "source": [ "#include \n", "void PrintValue(int a){\n", " std::cout << \"Values: \" << a << std::endl;\n", "}\n", "{ \n", " auto function = &PrintValue;\n", " function(5);\n", "}" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Values: 5\n" ] } ], "source": [ "{\n", " auto function = PrintValue;\n", " function(5);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果不用 ```auto```:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Values: 5\n" ] } ], "source": [ "{\n", " void(*cherno)(int) = PrintValue;\n", " cherno(5);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 太複雜了所以用 ```typedef```" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Values: 5\n" ] } ], "source": [ "{\n", " typedef void(*PrintValueFunction)(int);\n", " \n", " PrintValueFunction cherno = PrintValue;\n", " cherno(5);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* ForEach example" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Values: 1\n", "Values: 5\n", "Values: 4\n", "Values: 2\n", "Values: 3\n" ] } ], "source": [ "#include \n", "\n", "void ForEach(const std::vector& values, void(*func)(int))\n", "{\n", " for(int value : values)\n", " func(value);\n", "}\n", "{\n", " std::vector values = {1, 5, 4, 2, 3};\n", " ForEach(values, PrintValue);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 59. [Lambdas in C++](https://www.youtube.com/watch?v=mWgmBBz0y8c&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb)\n", "\n", "* (延續上面的 ForEach example)傳 lambda 給 ForEach" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1, 5, 4, 2, 3, \n", "1, 5, 4, 2, 3, " ] } ], "source": [ "#include \n", "#include \n", "\n", "void ForEach(const std::vector& values, void(*func)(int))\n", "{\n", " for(int value : values)\n", " func(value);\n", "}\n", "{\n", " int b = 17;\n", " \n", " std::vector values = {1, 5, 4, 2, 3};\n", " ForEach(values, [](int a){ std::cout << a << \", \"; });\n", " \n", " std::cout << std::endl;\n", " \n", " auto lambda = [](int a){ std::cout << a << \", \"; };\n", " ForEach(values, lambda);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* ```[]``` 是 capture,用來抓 lambda 外面的變數進來,例如上面例子裡的 ```b```\n", " * ```[]```:no capture\n", " * ```[&]```:capture all by-reference\n", " * ```[&, i]```:all by-reference, except i is captured by copy\n", " * ```[=]```:capture all by-copy\n", " * ```[=, &i]```:all by-copy capture, except i is captured by reference\n", " * ```[a, &b]```:a is by-copy and b is by reference\n", " * 去 [cppreference](https://en.cppreference.com/w/cpp/language/lambda) 看完整的 list\n", "* 就算用 by-value capture 還是沒辦法在 lambda 裡 assign 值給外面的變數,例如上面的例子在 lambda 裡加 ```b = 5``` 是不行的。除非把 lambda 宣告成 mutable\n", "* 用 ```std::find_if``` 找 vector 裡第一個大於 3 的數:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n" ] } ], "source": [ "#include \n", "#include \n", "#include \n", "{\n", " std::vector values = {1, 5, 4, 2, 3};\n", " auto it = std::find_if(values.begin(), values.end(), [](int value){ return value > 3; });\n", " std::cout << *it << std::endl;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 60. [Why I don't \"using namespace std\"](https://www.youtube.com/watch?v=4NYC-VU-svE&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=60)\n", "\n", "* ```std::``` 可以增加可讀性,如果 project 用了像 [EASTL](https://github.com/electronicarts/EASTL) 這種類 STL library,看到 ```std::vector``` 一下就知道是在用 STL 版而不是 EASTL 版\n", "* 如果同一個 scope 中同時有 ```using namespace std;``` 和 ```using namespace eastl;```,直接宣告 ```vector``` 時會編譯不過(ambiguous)\n", "* 更糟的狀況是兩個 library 有 signature 類似但不完全相同的函數,如果不指定呼叫的是哪一個函數,有可能因為 implicit conversion 而呼叫錯誤的版本(silent runtime error)\n", " * 下面這個例子會呼叫到 ```orange::print```" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#include \n", "#include \n", "\n", "namespace apple {\n", " void print(const std::string& text){\n", " std::cout << text << std::endl;\n", " }\n", "}\n", "namespace orange {\n", " void print(const char* text){\n", " std::string tmp = text;\n", " std::reverse(tmp.begin(), tmp.end());\n", " std::cout << tmp << std::endl;\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "olleH\n" ] } ], "source": [ "using namespace apple;\n", "using namespace orange;\n", "\n", "print(\"Hello\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果要用 ```using namespace```,在越小的 scope 用越好,函數裡,if statement 區塊\n", "* The Cherno 在用自己寫的小型 library 時才會用 ```using namespace```。對 STL 這種從來不用\n", "* 永遠不要在標頭檔裡 ```using namespace```!!!會被 preprocessor 貼的到處都是很難 debug\n", "* [有四種變數名稱 convention](https://medium.com/better-programming/string-case-styles-camel-pascal-snake-and-kebab-case-981407998841):\n", " * camelCase\n", " * PascalCase\n", " * snake_case\n", " * kebab-case" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 61. Namespaces in C++\n", "### 62. Threads in C++\n", "### 63. Timing in C++\n", "### 64. Multidimensional Arrays in C++ (2D arrays)\n", "### 65. Sorting in C++\n", "### 66. Type Punning in C++\n", "### 67. Unions in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 68. Virtual Destructors in C++\n", "\n", "* 下面這個例子如果 `~Base()` 前面沒有寫 virtual,`poly` 被 delete 的時候就不會呼叫 `~Derive()` 而出現 memory leak\n", "* [不同](https://youtu.be/jELbKhGkEi0?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=277)於一般 virtual function,base class virtual destructor 不會被 overwritten,它只是一個機制去讓 C++ 呼叫 derived class destructor\n", "* 所有會被 derived 的 class 都強烈建議把 destructor 宣告成 virtual" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Base Constructor\n", "Base Destructor\n", "-----------------------------------\n", "Base Constructor\n", "Derived Constructor\n", "Derived Destructor\n", "Base Destructor\n", "-----------------------------------\n", "Base Constructor\n", "Derived Constructor\n", "Derived Destructor\n", "Base Destructor\n" ] } ], "source": [ "#include \n", "\n", "class Base\n", "{\n", "public: \n", " Base() { std::cout << \"Base Constructor\" << std::endl; }\n", " virtual ~Base() { std::cout << \"Base Destructor\" << std::endl; }\n", "};\n", "\n", "class Derived: public Base\n", "{\n", "public: \n", " Derived() { \n", " m_Array = new int[5];\n", " std::cout << \"Derived Constructor\" << std::endl; \n", " }\n", " ~Derived() { \n", " delete[] m_Array;\n", " std::cout << \"Derived Destructor\" << std::endl; \n", " }\n", "private: \n", " int* m_Array;\n", "};\n", "\n", "{\n", " Base* base = new Base();\n", " delete base; \n", " std::cout << \"-----------------------------------\" << std::endl;\n", " Derived* derived = new Derived();\n", " delete derived; \n", " std::cout << \"-----------------------------------\" << std::endl;\n", " Base* poly = new Derived();\n", " delete poly; \n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 69. Casting in C++\n", "### 70. Conditional and Action Breakpoints in C++\n", "### 71. Safety in modern C++ and how to teach it\n", "### 72. Precompiled Headers in C++\n", "### 73. Dynamic Casting in C++\n", "### 74. BENCHMARKING in C++ (how to measure performance)\n", "### 75. STRUCTURED BINDINGS in C++\n", "### 76. How to Deal with OPTIONAL Data in C++\n", "### 77. Multiple TYPES of Data in a SINGLE VARIABLE in C++?\n", "### 78. How to store ANY data in C++\n", "### 79. How to make C++ run FASTER (with std::async)\n", "### 80. How to make your STRINGS FASTER in C++!\n", "### 81. VISUAL BENCHMARKING in C++ (how to measure performance visually)\n", "### 82. SINGLETONS in C++\n", "### 83. Small String Optimization in C++\n", "### 84. Track MEMORY ALLOCATIONS the Easy Way in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 85. [lvalues and rvalues in C++](https://youtu.be/fbYknr-HPYE?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=179)\n", "\n", "\n", "* 在記憶體裡有位址的就是 lvalue,沒有的(temporary object)就是 rvalue,例如 ```int i = 10;``` 裡 i 是 lvalue,10 是 rvalue\n", "* 函數如果回傳暫存物件也是 rvalue,例如 ```int i = GetValue();```,這裡 GetValue 是一個回傳整數 10 的函數 \n", "* 不能 assign 給 rvalue,例如 ```10 = i;``` 因為 temporary object 沒有記憶體位址沒辦法存 assign 進來的東西\n", " * (所以左邊就一定是 lvalue?)\n", " * 不一定在右邊就是 rvalue,例如可以 ```a = i;```\n", " * 如果 GetValue() 回傳 rvalue (如 int),```GetValue() = 5;``` 編譯器會報錯 expression must be a modifiable (non-const) lvalue \n", "* 要讓 ```GetValue() = 5;``` 合法需要回傳 lvalue ref" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "int& GetValue()\n", "{\n", " static int value = 10;\n", " return value;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* lvalue ref 不能接收 rvalue!!!lvalue ref 就是 lvalue 的別名,只能接收 lvalue" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "void SetValue(int& value){}\n", "\n", "int i = 10;\n", "int j = 5;\n", "SetValue(i); // 合法\n", "SetValue(10); // 不合法\n", "SetValue(i+j); // 不合法" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 但是!!有一個例外:const lvalue ref 可以接收 rvalue" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "const int& a = 10; // 合法\n", "int& b = 10; // 不合法" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* expression 像 ```a + b``` 是 rvalue,所以上面的 ```SetValue(i+j);``` 不合法,但是用上面的例外把定義改成 ```void SetValue(const int& value){}``` 就合法了\n", " * 所以 C++ 才會那麼多函數的輸入型別都是 const lvalue ref\n", " * 這樣寫的好處是函數可以接收 lvalue 也可以接受 rvalue\n", "* C++ 11 開始有只能接收 rvalue 的 rvalue ref ```int&& value```(相對於只能接收 lvalue 的 (non-const) lvalue ref):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "void SetValue(int&& value){}\n", "\n", "int main()\n", "{\n", " int i = 10;\n", " int j = 5;\n", " SetValue(i); // 不合法\n", " SetValue(10); // 合法\n", " SetValue(i+j); // 合法\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果 overload(像下面這樣定義兩次),雖然 const lvalue ref 可以接收 rvalue,但傳入 rvalue 時還是會呼叫 rvalue ref 的版本" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "void SetValue(const int& value){}\n", "void SetValue(int&& value){}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* [使用 rvalue 的優點(會比較快?)](https://youtu.be/fbYknr-HPYE?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=692)\n", " * 例如上面的 ```SetValue(i+j);``` 不用為 ```i + j``` 配置記憶體" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 86. Continuous Integration in C++\n", "### 87. Static Analysis in C++\n", "### 88. Argument Evaluation Order in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 89. [Move Semantics in C++](https://youtu.be/ehMg6zvXuMY?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=209)\n", "\n", "* 有兩種情況需要複製一個物件:\n", " * return 一個物件時需要的 temp object:有 return value optimization 可以處理\n", " * 把物件傳進函數裡,想要 take ownership 但不想要實際上 copy 時,其實應該用 move 才對\n", "* 下面這段程式因為 String 沒有 move ctor,執行會印出 ```Created! Copied! Cherno```,String 的 ctor 和 copy ctor 加起來總共 new 了兩次記憶體,一次在 ```String(\"Cherno\")``` 被構造出來的時候,一次是在它被傳進 e1 裡給 ```m_Name``` 的時候 Entity 的 ctor 做了一次 copy。如果有 move ctor,```Entity e1(\"Cherno\");``` 就可以真正傳 rvalue 進到 Entity 裡,那個 copy 就可以省下來" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#include\n", "\n", "class String\n", "{\n", "public: \n", " String() = default;\n", " String(const char* string) // ctor\n", " {\n", " printf(\"Created!\\n\");\n", " m_Size = strlen(string);\n", " m_Data = new char[m_Size];\n", " memcpy(m_Data, string, m_Size);\n", " }\n", " String(const String& other) // copy ctor\n", " {\n", " printf(\"Copied!\\n\");\n", " m_Size = other.m_Size;\n", " m_Data = new char[m_Size];\n", " memcpy(m_Data, other.m_Data, m_Size);\n", " }\n", " ~String()\n", " {\n", " printf(\"Destroyed!\\n\")\n", " delete m_Data;\n", " }\n", " void Print()\n", " {\n", " for(uint32_t i= 1; i < m_Size; i++)\n", " printf(\"%c\", m_Data[i]);\n", " printf(\"\\n\");\n", " }\n", "private:\n", " char* m_Data;\n", " uint32_t m_Size;\n", "};\n", " \n", "class Entity\n", "{\n", "public:\n", " Entity(const String& name): m_Name(name){}\n", " void PrintName()\n", " {\n", " m_Name.Print();\n", " }\n", "private:\n", " String m_Name;\n", "};\n", " \n", "int main()\n", "{\n", " Entity e1(\"Cherno\"); // 沒有 move,同 Entity e1(String(\"Cherno\"));\n", " e1.PrintName();\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 為了省下那次 copy,需要寫\n", " 1. 可接收 rvalue 的 Entity ctor\n", " 2. String 的 move ctor。只有 rewire 指標而已,沒有 new\n", " \n", "* Entity ctor:下面這兩個做一樣的事情:把 input cast 成 rvalue。少了這個 cast 還是會呼叫到 String 的 ctor(實務上都是寫 ```std::move``` 而不會直接 cast)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Entity(String&& name): m_Name((String&&)name){}\n", "Entity(String&& name): m_Name(std::move(name)){}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* String 的 move ctor" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "String(String&& other) noexcept // move ctor\n", "{\n", " printf(\"Moved!\\n\");\n", " m_Size = other.m_Size;\n", " m_Data = other.m_Data;\n", " other.m_Size = 0;\n", " other.m_Data = nullptr;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* [為什麼需要](https://youtu.be/ehMg6zvXuMY?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=600) ```other.m_Data = nullptr;```?因為傳進 Entity 裡的 rvalue String 在毀滅的時候一樣會呼叫 dtor,如果沒有在 move ctor 裡把 other 設成 hollow object,當 move 完成的時候 other 和 m_Data 會指著同一塊記憶體,而這塊記憶體馬上就被 rvalue String(在這個例子裡是傳進 Entity ctor 裡的 ```String&& name```)呼叫的 dtor delete 掉了\n", "* 改完之後執行會印出 ```Created! Moved! Destoried! Cherno```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### 90. [std::move and the Move Assignment Operator in C++](https://youtu.be/OWNeCTd7yQE?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=146)\n", "\n", "* 用上面的例子,這兩行 code 會呼叫 copy ctor:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "String string = \"Hello\";\n", "String dest = string;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果想讓它呼叫 move ctor 而不是 copy,可以用下面任意一種:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "String dest = (String&&)string;\n", "String dest((String&&)string);\n", "String dest = std::move(string);\n", "String dest(std::move(string));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* ```std::move``` 做的事就是把傳進來的物件 cast 成 rvalue。如果要 move 一個已經是 rvalue 的物件,就不需要 ```std::move```。如果要 move 一個新構造的物件,也可以不用 ```std::move``` 因為 ctor 本來也就會耗資源。但如果是一個已經存在的 lvalue 物件,用 ```std::move``` 可以省下 copy 造成的額外負擔\n", "* ```std::move``` 比直接 cast 好因為\n", " * [有些情況下](https://youtu.be/OWNeCTd7yQE?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&t=306) cast 會失敗\n", " * 增加 code 可讀性\n", "* 以上所有 dest 都是新宣告的,所以呼叫 move ctor。如果 dest 已經存在,就需要 move assignment:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "String dest1 = std::move(string); // 呼叫 move ctor\n", "String dest2;\n", "dest2 = std::move(string); // 呼叫 move assignment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* move assignment:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "String& operator=(String&& other) noexcept\n", "{\n", " printf(\"Moved!\\n\");\n", " if (this != &other)\n", " {\n", " delete[] m_Data;\n", " m_Size = other.m_Size;\n", " m_Data = other.m_Data;\n", " other.m_Size = 0;\n", " other.m_Data = nullptr;\n", " }\n", " return *this;\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 和 move ctor 有三處不同:\n", " * 以 ref 的型式回傳自己(*this)\n", " * 需要 ```delete[] m_Data;``` 因為 move assign 之後原本的 data 就不需要了,新的 m_Data 指標被指向新的 data,如果不先 delete 舊的 data 會有 memory leaking\n", " * 檢查 ```this != &other``` 是為了防止有人呼叫 ```dest = std::move(dest);```(把自己傳給自己)\n", "* 下面這段 code 在 move 之前 dest 是空的,move 完之後變成 apple 是空的:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "String apple = \"Apple\";\n", "String dest;\n", "dest = std::move(apple);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 所以現在 C++ 物件必寫函數從四個變成六個:\n", " * ctor\n", " * dtor\n", " * copy ctor\n", " * copy assignment\n", " * move ctor\n", " * move assignment\n", "* [Rule of three/five/zero](https://en.cppreference.com/w/cpp/language/rule_of_three)\n", " * 3: 如果需要下列三者之一,幾乎可以肯定三者同時需要:custom dtor,copy ctor,copy assignment\n", " * 5: 需要 move semantics 的 class 要另外寫 move ctor 和 move assignment\n", " * 0: 如果一個 class 沒有 ownership 的概念,應該上面五個都不要寫(單一職掌原則)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 91. ARRAY - Making DATA STRUCTURES in C++\n", "### 92. VECTOR/DYNAMIC ARRAY - Making DATA STRUCTURES in C++" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### [93. ITERATORS in C++](https://www.youtube.com/watch?v=SgcHcbQ0RCQ&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=93)\n", "\n", "* 遍歷 vector 的三種方法:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1, 2, 3, 4, 5, " ] } ], "source": [ "#include \n", "#include \n", "\n", "std::vector values = {1, 2, 3, 4, 5};\n", "\n", "for(int i=0 ; i::iterator it=values.begin() ; it!=values.end() ; it++)\n", " std::cout << *it << \", \";" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* range based for loop 是 iterator 版本的簡寫\n", "* 必需要有 ```.begin()``` 和 ```.end()``` 才能用 range based for loop 和 iterator\n", " * ```values.end()``` 指的不是最後一個 element 而是 the element after the last\n", "* 為什麼需要 iterator?\n", " * 有時候會需要 index 而不只是 element 本身(不然永遠用 range based 就行了),例如想 erase 一個 element 的時候\n", " * 很多 container 如 tree 和 map 沒有 indexing system\n", "* 有四種不同的 iterator:\n", " * ```const_iterator```\n", " * ```const_reverse_iterator```\n", " * ```iterator```\n", " * ```reverse_iterator```\n", "* iterator 用起來跟 pointer 一樣,因為 implement 了 dereference operator ```*```\n", "* ```unordered_map``` 例子:\n", " * iterator 指向一個 ```std::pair```" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C++ = 2\n", "Cherno = 5\n" ] } ], "source": [ "#include\n", "#include\n", "\n", "std::unordered_map map;\n", "map[\"Cherno\"] = 5;\n", "map[\"C++\"] = 2;\n", "\n", "for(std::unordered_map::const_iterator it=map.begin();\n", " it!=map.end() ; it++)\n", "{\n", " auto& key = it->first;\n", " auto& value = it->second;\n", " \n", " std::cout << key << \" = \" << value << std::endl;\n", "} " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* iterator 名字太長了,可以用 ```using``` 來簡化" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C++ = 2\n", "Cherno = 5\n" ] } ], "source": [ "#include\n", "#include\n", "\n", "using ScoreMap = std::unordered_map;\n", "ScoreMap map;\n", "map[\"Cherno\"] = 5;\n", "map[\"C++\"] = 2;\n", "\n", "for(ScoreMap::const_iterator it=map.begin(); it!=map.end() ; it++)\n", "{\n", " auto& key = it->first;\n", " auto& value = it->second;\n", " \n", " std::cout << key << \" = \" << value << std::endl;\n", "} " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* range based 版本:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C++ = 2\n", "Cherno = 5\n" ] } ], "source": [ "for(auto kv : map)\n", "{\n", " auto& key = kv.first;\n", " auto& value = kv.second;\n", " \n", " std::cout << key << \" = \" << value << std::endl;\n", "} " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* range based + structured binding(C++17 新功能):" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C++ = 2\n", "Cherno = 5\n" ] } ], "source": [ "for(auto [key, value] : map) \n", " std::cout << key << \" = \" << value << std::endl;" ] } ], "metadata": { "kernelspec": { "display_name": "C++17", "language": "C++17", "name": "xcpp17" }, "language_info": { "codemirror_mode": "text/x-c++src", "file_extension": ".cpp", "mimetype": "text/x-c++src", "name": "c++", "version": "17" } }, "nbformat": 4, "nbformat_minor": 4 }