# C++ ###### tags: `cpp` --- ## basic ### macro `#include <filename>` or `#include "filename"` 把 __filename__ 這個文件加進來一起編譯 `#define name value` 當在程式碼看到 __name__,會把它取代成 __value__ `#define name` 定義一個給編譯器看的識別字,通常在標頭檔看到,搭配條件判斷可以解決重複 __include__ 的問題 `#undef name` 取消 __name__ 的define `#ifdef` `#ifndef` `#endif` 搭配define使用,可以解決重複 __include__ 的問題 測試用法如下 __有寫入保護__ ```cpp // Test.h #ifndef Test #define Test #include <ostream> std::cout << "just appear one time." << std::endl; #endif // main.cpp int main(){ #include "Test.h" #include "Test.h" } // output: // just appear one time. ``` __沒寫入保護__ ```cpp // Test.h #include <ostream> std::cout << "just appear one time." << std::endl; // main.cpp int main(){ #include "Test.h" #include "Test.h" } // output: // just appear one time. // just appear one time. ``` #### pragma once 也可以用 `#pragma once` 來告訴編譯器這個檔案只要引入一次 > 有些編譯器不支援 ```cpp // Test.h #pragma once #include <ostream> std::cout << "just appear one time." << std::endl; // main.cpp int main(){ #include "Test.h" #include "Test.h" } // output: // just appear one time. ``` --- ### namespace 為了解決名稱重複,可以用`namespace` namespace 裡可以包含class-name、function-name、variable-name、enum-name。 ```cpp namespace test{ // if unnamed: global int var; class C{}; void f(){} enum e{a,b,c}; } int var; // global ``` 要使用時用 `namespace::name` 來指定名稱空間,或是用`::name`來指定用全域的 ```cpp test::var = 100; ::var = 10; ``` #### using 如果覺得每次都要加`namespace::name`很麻煩,也可以用`using`,它會告訴編譯器: 在這個block內的[name]都是[namespace]的[name] ```cpp { using std::cout; cout<<""; } ``` `using` 也可以直接指定整個 namespace ```cpp { using namespace std; cout << string("") << endl; } ``` `using`也可以指定一個名稱的別名,和typedef很像,但這個有區域限制 另外他也支持template ```cpp { using Str = std::string; using StrList = std::list<Str>; StrList sl{"hello", " world", "\n"}; for(auto e : sl) std::cout<<e; } // output: // hello world ``` #### class namespace class也算是一種命名空間,所以也可以使用`class::`來指定成員,在繼承上可能會用來呼叫父類別的function ```cpp struct Base{ virtual void action(){ cout << "I'm Base." << endl; } }; struct Derive:Base{ virtual void action() override{ Base::action(); cout << "I'm Derive too." << endl; } }; // use Base* obj = new Derive(); obj->action(); // output: // I'm Base. // I'm Derive too. ``` --- ### pointer , reference 每個變數都是儲存在記憶體上的一個空間,那個空間也會有有它的位址來讓CPU找得到 #### 指標 儲存記憶體位址的變數,它的大小都是固定的。 宣告一個指標必須先確認他所要指向的變數的型態。 也可以用`+ -`運算來偏移指向的位址。(較不安全) #### 參考 定義上是所參考物件的別名,實際上是指向所參考物件的const指標。 使用上和所參考物件的差別只在於名字不一樣,因為以定義上來講,這兩個是同樣的東西。 宣告一個參考就要直接定義他所參考的對象。 指標可以指向其他位址,參考定義好就不能改。(較安全) ```cpp int var; int* ptr = &var; int& ref = var; ``` 通過 __&var__ 來回傳 __變數var__ 的記憶體位置 通過 __*ptr__ 來回傳 __指標ptr__ 所指向的變數 使用 __ref__ 就等同使用 __var__ --- ### template 樣板可以讓 C++ 實現泛型, C++ 的容器也都用樣板來實作。 樣板類似於取代,會將class或function內的指定詞替換成另一個 如 ```cpp // 宣告 template<class T> class Test{ T var; }; // 使用 Test<int> intVar; Test<char> charVar; ``` 若有使用這個樣板,編譯器在編譯前就會自動產生對應的程式碼。 以上述程式碼為例,編譯器會自動產生以下這兩個class > 這裡的className只是示範用,並不是真實的 ```cpp // Test<int> class TestInt{ int var; }; // Test<char> class TestChar{ char var; }; ``` #### special template 樣板會根據`<>`內的型別來產生程式碼,只要型別不同產生出來的class就是不同的class,所以其實可以寫兩個名字一樣但內容不同的樣板。 ```cpp template<class T> struct Test{ void func(){} }; template<> struct Test<int>{ void hi(){} }; // use Test<char> testChar; Test<int> testInt; testChar.func(); // OK testChar.hi(); // not OK testInt.func(); // not OK testInt.hi(); // OK ``` --- ### \++i , i\++ `++i` 會將i+1再回傳i ```cpp int operator++(){ *this += 1; return *this; } ``` `i++` 會先將i的值暫存於一變數,再將i+1,再回傳暫存的值 ```cpp int operator++(int){ int tmp = *this; *this += 1; return tmp; } ``` 所以若只是要將i遞增, ++i 會更快。 但其實很多編譯器都會做優化,可能運行速度一樣。 --- ### const #### 應用於物件 宣告並定義一個不可改變的物件(常數) ```cpp const int zero = 0; int const one = 1; ``` #### 應用於指標、參考 ```cpp int i = 1; const int ci = 10; // 指向 const int 的指標 const int* ptr_constInt = &ci; // 指向 int 的 const 指標 int* const constPtr_int = &i; // 指向 const int 的 const 指標 const int* const constPtr_constInt = &ci; // 指向 const int 參考 const int& ref_constInt = ci; // 沒有這種東西,ref本身就是const了 const int& const constRef_constInt = ci; ``` > 規則:由 `*` 開始, 往左邊看 : 是指標指向的類型 往右邊看 : 是修飾指標的修飾詞 #### 應用於函式參數 參數a與b都只能被讀取,不可修改 ```cpp int Add(const int& a, const int& b); ``` #### 應用於成員函式 確認該函式不會修改該class內的成員 ```cpp class Test{ int var; public: void DoSomething()const{ ++var; // 編譯錯誤,var唯讀 } }; ``` #### const_cast<>() ```cpp int i = 10; const int* ptr = &i; *ptr = 20; // error i = 20; // OK ``` 由上所述,變數i的值是可修改的,但如果一個 __指向const int__ 的指標要修改它是不允許的。 如果要強行修改的話,可以用 ```cpp // 把 ptr 轉型為 int* *const_cast<int*>(ptr) = 20; ``` `const_cast<>`可將指向const的指標轉型為一般的指標。 也可用在成員函式上 ```cpp class Test{ int var; public: void DoSomething()const{ ++const_cast<Test*>(this)->var; } }; ``` 實際上const成員函式長這樣 ```cpp void DoSomething(const Test* const this); ``` 因為它的this是指向const物件的指標,所以把this轉換成指向一般物件的指標就可以使用一般成員了。 #### mutable 就算物件被宣告為const,也可以修改標記為mutable的成員變數 ```cpp class Test{ mutable int var; public: void DoSomething()const{ ++var; // OK } }; ``` #### mutable const ?? 有時會看到以下情況 ```cpp class Test{ mutable const int* ptr; // mutable是修飾[指向const int的指標] public: void DoSomething()const{ ++ptr; // OK } }; ``` --- ### function pointer (delegate) 若要實現委派,可以用function pointer ```cpp // 宣告一個函式指標,可以指向無參數,回傳型別為void的函式 void (*fptr)(); void funcA(){ cout << "A"; } void funcB(){ cout << "B"; } // 使用 fptr = funcA; fptr(); // A fptr = funcB; fptr(); // B ``` #### typedef fptr 如果覺得宣告太長,也可用typedef將他視為一種型態 ```cpp // 宣告函式指標型態Fptr,回傳型態int,參數為2個int typedef int (*Fptr)(int,int); int add(int a, int b){ return a+b; } int sub(int a, int b){ return a-b; } // 使用 Fptr a = add; cout << a(1,2); // 3 a = sub; cout << a(1,2); // -1 ``` #### in class 在class當中就比較複雜,不能把物件的function當作一個一般的function,而是一個class的function。 但如果是static method就視為一般的function。 事實上的成員函式: ```cpp class Test{ public: static void s_func(); // => void Test::s_fun(); void m_func(); // => void Test::m_func(Test*); }; ``` 用法範例 ```cpp class Test{ string name; public: Test(string name):name(name){} void A(){ cout<<name<<":A"<<endl; } void B(){ cout<<name<<":B"<<endl; } static void C(){ cout<<"C"<<endl; } }; class Tester{ private: typedef void (Test::*FptrOfTest)(); typedef void (*Fptr)(); FptrOfTest fptr; Test* obj; public: void attach(Test* o, FptrOfTest f){ obj = o; fptr= f; } void action(){ (obj->*fptr)(); } void action(Fptr f){ f(); } }; // use Test x("x"), y("y"); Tester tr; tr.attach(&x, &Test::A); tr.action(); // x:A tr.attach(&y, &Test::B); tr.action(); // y:B tr.action(&Test::C); // C ``` --- ### OO #### 建構式 解構式 利用建構式將class實例化 #### 預設建構式 ```cpp class Test{ private: int var;// 成員 public: Test(){}// 沒有參數的建構子 // 同 Test() = default; }; ``` - 當你寫一個class卻沒有寫他的建構式時,編譯器會自動幫你寫一個,他會將所有成員用他們的預設建構式初始化,若成員沒有預設建構式會出錯。 - 如果成員中有指標的話,最好將它初始化,不然會指向奇怪的地方。 - 如果你要讓這個class不能直接生出物件,可以把預設建構式寫在private或protected,也可以寫成`Constructor()=delete;`代表不要產生預設建構子。 #### 一般建構式 ```cpp class Test{ private: int var;// 成員 public: Test(int v) // 有參數的建構式 :var(v) // 初始化列表 {} // 建構式主體 }; ``` - 可以用初始化列表來初始化每個成員,如果沒有寫,會自動用該成員的預設建構子初始化他。 - 如果成員有const,就一定要用初始化列表來初始化它,無法在建構式主體修改const常數。 - 如果是衍生類別,要在初始化列表對父類別初始化。 #### 複製建構式 ```cpp class Test{ private: int var;// 成員 public: Test(const Test& other):var(other.var){}// 將另一個實例當作參數來初始化 // 同 Test(const Test& other) = default; }; ``` - 如果沒有寫的話,編譯器也會幫你自動產生,會將所有成員用他的複製建構子初始化。 - 如果成員中有指標的話,自動產生的複製建構式只會複製指標的值,也就是記憶體位址,而不會複製指標指向的物件(淺複製),如果想要深度複製的話也要自己寫。 - 一般當作函式參數時,會使用複製建構式來複製參數。 #### 解構式 ```cpp class Test{ public: virtual ~Test(){} // 解構式 }; ``` - 解構式只能有一個,這個實例不再被用到時(離開作用域or釋放),會自動呼叫解構式。 - 如果沒有寫解構式的話編譯器一樣會自動產生,將所有成員按順序解構。 - 但是若成員中有指標,他只會將這個指標刪除不會將這個指標所指向的物件刪除,所以當成員有指標時,要記得自己寫解構式。 --- ### return *this ```cpp cout<<"Hello"<<" world"<<endl; // (cout<<"Hello") -> return cout // (cout<<" world") -> return cout // (cout<<endl) -> return cout // cout -> no return ``` 如果成員函式的回傳型態為自己型態的參考,就可以做類似以上的事。 用法如下: ```cpp template<class T> class Stack{ private: vector<T> vec; public: Stack& push(T&& var){ vec.push_back(var); return *this; } Stack& pop(){ vec.pop_back(); return *this; } T& top(){ return vec.back(); } }; // main Stack<int> s; s.push(1).push(2).push(3); s.pop().pop(); cout<<s.top(); ``` --- ### operator overloading C++ 可以重新定義運算子,以`+`為例 ```cpp struct Int{ int val; Int(int v):val(v){} Int operator+ (const Int& obj){ return Int( this->val + obj.val ); } }; // use Int a(10), b(20); cout << (a+b).val; // cout << ( a.operator+(b) ).val; // output: 30 ``` #### operator++() 另外,因為C++有前置`++`和後置`++`的運算子,如果要重載的話,他們的宣告式長這樣: ```cpp Int operator++(); // 前置 Int operator++(int); // 後置 ``` #### function object 運算子當中有一個`()`運算子,它可以被重載很多次,只要參數不一樣。 它可以讓這個 class 產生的物件有 function 一樣的行為。 ```cpp struct Test{ int operator() (char c){ return (int)c; } char operator() (int i){ return char(i); } // ... }; // use Test t; cout << t(65); // A cout << t('A'); // 65 ``` #### conversation operator ```cpp class Int{ int val; public: Int(int v):val(v){} operator int() { return val; } operator bool(){ return val!=0; } }; // use Int i(10); if( (bool)i ) // call operator bool() cout << (int)i; // call operator int() // output: 10 ``` --- ### static #### 用在函式中的變數 當這個變數離開作用域後也不會被釋放,會一直存在直到程式關閉 ```cpp void test(){ static int calledNum=0; ++calledNum; cout << calledNum << endl; } ``` #### 用在class中 - 宣告成員變數,只要是這個class的實例都可以使用,但要在class外部初始化。 - 宣告成員函式,不需要實例化即可使用此函式,但此函式只能使用static成員。 > 在VS中static成員可以直接在class內定義。 ```cpp class Test{ private: static int instanceNum; // class的共用變數 public: Test(){ ++instanceNum; } ~Test(){ --instanceNum; } static int InstanceNum(){ // 不須實例化即可使用 return instanceNum; } }; int Test::instanceNum = 0; // 要在外部初始化 ``` --- ### 繼承 C++ Class的成員分成三種權限 : public, protected, private C++ 的繼承方式也分成三種 : public, protected, private 在繼承時,成員會依照繼承方式而轉為不同權限 > private virtual function can be override, but cannot be used | 原始權限 | public繼承 | protected繼承 | private繼承 | | :------: | :-------: | :----------: | :--------: | | public | public | protected | private | | protected| protected | protected | private | | private | - | - | - | 使用方法 ```cpp class Base{ // ... }; class DeriveA:public Base{ // public 繼承 // ... public,protected權限不變 }; class DeriveB:protected Base{ // protected 繼承 // ... public權限 -> protected權限 }; class DeriveC:private Base{ // private 繼承 // ... public,protected權限 -> private權限 }; ``` #### 建構子 ```cpp class Base{ int val; public: Base(int v):val(v){} }; class Derive:public Base{ int val; // 和Base::val不一樣 public: Derive(int v1, int v2):Base(v1), val(v2){} }; ``` --- ### 虛擬繼承 在多重繼承中有可能有菱形繼承,容易引發以下問題 ```cpp struct Base{ int v; }; struct Derive1 : Base{}; struct Derive2 : Base{}; struct C : Derive1, Derive2{}; // use class C's v C c; c.v; // 編譯器不知道是Derive1::v 還是Derive2::v ``` 在C++要解決這個問題可以用虛擬繼承,如下 ```cpp struct Base{ int v; }; struct Derive1 : virtual Base{}; struct Derive2 : virtual Base{}; struct C : Derive1, Derive2{}; // use class C's v C c; c.v; // 編譯器會找Base::v ``` #### virtual Base class 如上方程式碼,Derive1和Derive2都虛擬繼承自Base,他們會 __共用同一個Base物件__ 。 如果不是虛擬繼承的話,他們會各自有一份Base物件,所以編譯器才不知道要找哪一個。 當class `C` 在建構時,會先建構繼承體系中的`virtual base class`也就是`Base`,而後再建構其他一般class如Derive1,Derive2,原本Derive1,Derive2的建構式裡面都會建構Base,但這裡因為共用的Base已經被建構過了,所以不會再建構一次。 有一點要注意的是,在這裡負責建構Base的責任在C,因為是建構C時才會造成菱形繼承問題,如下: ```cpp struct Base{ int v; Base(int v):v(v){ cout << "Base()" << endl; } }; struct Derive1 : virtual Base{ Derive1():Base(1){ cout << "Derive1()" << endl; } }; struct Derive2 : virtual Base{ Derive2():Base(2){ cout << "Derive2()" << endl; } }; struct C : Derive1 , Derive2{ // 雖然把Base()寫在後面,但因為它是virtual-base-class所以會先被建構 C():Derive1(),Derive2(),Base(10){ cout << "C()" << endl; } }; // use C C c; cout << c.v; // output: // Base() // Derive1() // Derive2() // C() // 10 ``` 由此可知,就算把Base(10)寫在後面,也會先建構它,並且在建構Derive1和Derive2時也不會再建構Base。 --- ### virtual funtion C++ 要實現多型的話就是用virtual function 用法 ```cpp class Base{ public: virtual void VirtualFunc(){ cout<<"A"; } }; class Derive:public Base{ public: virtual void VirtualFunc() override{ cout<<"B"; } }; // use Base *baseA = new Base(); Base *baseB = new Derive(); baseA->VirtualFunc(); // A baseB->VirtualFunc(); // B ``` 繼承自virtual function的function,即使不宣告virtual,也會被視為virtual function。 因此以上範例的Derive可寫成 ```cpp class Derive:public Base{ public: void VirtualFunc()override{ cout<<"B"; } }; ``` #### pure virtual 若不實作virtual function,也可宣告為 ```cpp virtual void VirtualFunc() = 0; ``` 這樣就會視為抽象類別或介面,把實作留給繼承者。 #### override C++11 新增了 override 關鍵字,可檢查是否真的有override 例如 ```cpp class Base{ public: virtual void VirtualFunc(){ cout<<"A"; } }; class Derive:public Base{ public: virtual void VirtualFun() override{ // 錯誤: Base 沒有 VirtualFun 而是VirtualFunc cout<<"B"; } }; ``` #### final C++11 新增了 final 關鍵字,可讓class或function不被繼承或覆寫 例一 class ```cpp class FianlClass final{ // ... }; class Derive:FianlClass{// 錯誤: FinalClass不能被繼承 // ... }; ``` 例二 function ```cpp class Base{ public: virtual void Func() = 0; }; class DeriveA:public Base{ public: void Func() override final{ cout<< "A"; } }; class DeriveB:public DeriveA{ public: void Func() override{ // 錯誤: Func不能再覆寫 cout<<"B"; } }; ``` --- ## 容器 負責儲存一群相同型態的物件,並可以依照規則訪問、插入、刪除、走訪 ### vector 資料結構為array,但是大小可動態改變 優點是省空間、要找指定位置的元素很快 缺點是插入刪除慢(但如果是在資料最後面沒有這個問題) 要做插入或刪除的話最好從後面效率比較好 最好用resize()或reserve來動態調整大小,不要在push_back()時自動配置,會花額外的時間。 常用的有以下方法 ```cpp push_back() // 插入元素到最末尾 pop_back() // 從最末尾移除元素 // push可改為emplace(C++11)效率較高 resize() // 更改容量大小,但可能刪除一些元素 reserve()// 擴大容量 clear() // 清空元素 at() // 與[]相同,但有範圍檢查 capacity()// 回傳現在容量大小 size() // 回傳現有元素數量 empty() // 回傳容器是否為空 // 雖然也有insert()和erase(),但vector比較少用,用法和list一樣。 ``` --- ### list 資料結構為doubly linked list 插入時間快(但要先知道插入到哪) 搜尋時間慢,要一個一個找 比vector占空間、 因為插入速度快,適合用來排序 常用的有以下方法 ```cpp push_front()// 插入元素到最前端 pop_front() // 從最前端移除元素 push_back() // 插入元素到最末尾 pop_back() // 從最末尾移除元素 // push可改為emplace(C++11)效率較高 insert() // 在指定位置插入元素,可插入一個或數個相同元素 emplace() // 功能與insert()一樣,但效率較高 erease() // 刪除指定位置的元素,或是刪除一個範圍 sort() // 由小到大排序內部元素,也可指定由大到小 front() // 回傳第一個元素 back() // 回傳最後一個元素 size() // 回傳現有元素數量 empty() // 回傳容器是否為空 ``` --- ### map 資料結構為紅黑樹 插入時間略慢,因為要自動排序 用key搜尋快 比list占空間 如果key是自訂型別,要實作他的比較運算子 使用方法像是其他語言的dictionary,是一個key搭配一個value,查詢時用key查詢。 常用的方法有 ```cpp [] // 根據key回傳value的參考,如果找不到key,會自動建立一個元素 at() // 功能同上,但若找不到key,會拋出一個例外 find() // 根據key回傳指向該元素的迭代器,若找不到則回傳end迭代器 erase() // 可根據迭代器刪除一個或一串元素,也可根據key刪除元素 size() // 回傳現有元素數量 empty() // 回傳容器是否為空 ``` --- ### iterator 只要是有順序的容器,就會有迭代器可以循序走訪全部元素 循序容器有以下方法可以回傳迭代器 迭代器的型態為class_name::iterator 如`vector<int>::iterator` ```cpp // iterator begin() // 與end()搭配,用來從頭到尾走訪,指向第一個元素 end() // 指向最後一個再後面一個元素 // reverse_iterator rbegin() // 與rend()搭配,用來從尾到頭走訪,指向最後一個元素 rend() // 指向第一個再前面一個元素 ``` 迭代器的用法跟指向array的指標很像,他可以 ```cpp ++iter, --iter, iter++, iter-- *iter // 回傳iter所指向物件的參考 iter-> // 使用iter所指向物件的成員或方法 ``` --- ## C++11 ### 右值參考 以前C++在傳參數時,都是用複製建構子將物件複製過去,但有時候被複製的物件是臨時物件,如`func(Class());`,會先創造一個臨時物件,在傳參數時再用複製建構子創造一個物件,所以會做兩次建構,如果是深度複製的話就要花更多時間,所以 C\++11多了右值參考,可以自動判斷是否要用複製建構子來傳參數,還是要用移動建構子來傳參數。 #### move建構式 因為移動建構式是用來傳臨時物件的,要被移動的對象在建構式結束後就不需要了,所以可以把它所有指標的成員直接複製指標就好,不用深度複製,也要把臨時物件的成員指標指向nullptr,否則當它被解構時,指標所指向的物件也可能被解構。 基本寫法如下 ```cpp class Test{ private: int var; int* ptr; public: // ... 其他建構子 ... Test(Test&& t):var(t.var),ptr(t.ptr){ // 移動建構子 t.ptr = nullptr; } }; ``` #### move() 一般編譯器會自己判斷要用複製建構子或移動建構子,如果要強制用移動建構子可以用 `move(obj)`。 > 使用move()要`#include<utility>` --- ### nullptr C++11的 null分為兩種 `NULL` 繼承自C的寫法,型態為int的常數0 `nullptr` 型態為nullptr_t 當遇到函式多載時,較好判斷該選擇哪一個 如下 ```cpp void test(int){ cout<<"test int"; } void test(char*){ cout<<"test char pointer"; } // test test(0); // test int test(NULL); // 錯誤: 編譯器無法決定 test(nullptr); // test char pointer ``` --- ### foreach C++11也像其他語言引入了foreach的寫法,用來遍歷循序容器或陣列內的元素。 用法如下 ```cpp int arr[] = {1,2,3}; for(int e : arr) cout << e << endl; ``` --- ### auto C++11也可以自動判定型別,可以在宣告時將物件的型別宣告為auto但要同時定義他,這樣編譯器才知道它的型別。 例 ```cpp auto a = int(0); auto b = char('A'); auto c = vector<int>{1,2,3}; for(auto& e : c) ++e; for(const auto& e : c) cout<<e; ``` --- ### lambda 假如有些函式只會用到一次,不想花時間想他的名字,可以寫成匿名函式。 基本寫法 ```cpp cout << [](int a, int b)->int{ return a+b; }(10,20); // out: 30 ``` ```cpp auto greater = [](int a, int b)->bool{ return a>b; }; if(greater(10,0)) cout << "10 is greater than 0"; // output: 10 is greater than 0 ``` 其中 `[]` : 外部傳入物件,若寫`=`or`&`就是可以使用所有外部物件 `()` : 函數的傳入參數,寫法跟一般函數一樣 `->type` : 函數的回傳型態 `{...}` : 函式主體 都定義完後他就是一個函數物件,可以像函數一樣使用。 #### 閉包 因為變數都有它的作用域,如果要在它的作用域外使用它,可以用閉包。 ```cpp function<void()> test_lambda(){ int a = 10; return [a]()->void{ for(int i=0;i<a;++i){ cout << i; } }; } // use test_lambda()(); // 0123456789 ``` 如上述,lambda建構式中會傳入一個外部物件`a`,回傳的function物件也可以繼續使用`a`,但要注意的是 : 裝進閉包的物件並不是這個function物件的static成員,而是一般成員。 lambda表達式可以想像成 : 建構一個匿名函數物件,閉包裡的東西就是物件的成員。 如下: ```cpp class lambda{ int a; public: lambda(int capture):a(capture){} int operator()(int i){ return a+i; } }; function<int(int)> test_lambda(){ int a = 10; return lambda(a); // 等同於: [a](int i)->int{ return a+i; } } // use cout << test_lambda()(12); // 22 ``` #### function C++11還有一個新型別樣板叫function<> 它可以接收各種行為像function的函式指標、函式物件、匿名函式物件 其中樣版的部分為 `function<ReternType(Argument1, Argument2...)>` 用法如下 ```cpp void func(){ // do something } class FuncObj{ public: void operator()(){ // do something } }; // use function<void()> f1 = &func; //函式指標 function<void()> f2 = FuncObj(); // 函式物件 function<void()> f3 = []()->void{ /* do something*/ }; // lambda f1(); f2(); f3(); ``` 如果要指向成員函式 `function<ReternType(ClassName, Argument1, Argument2...)>` ```cpp class Test{ public: int add(int a, int b){ return a+b; } }; // use function<int(Test,int,int)> f = &Test::add; Test t; cout << f(t,1,3); ``` #### bind 可以用來綁定一個函式物件和數個參數,生成新的函式物件。 參數可以用`_1, _2, ...`來取代,成為新函式物件的參數。 `_1, _2, ...`宣告在`namespace std::placeholders`裡,數量並不是無限的。 ```cpp auto sub = [](int a, int b)->int{ return a-b; }; using namespace std::placeholder; auto sub_10 = bind(sub,_1,10); cout << sub_10(1); // -9 ``` --- ### regex > regex有些語法在VS上不能用 ```cpp // regex("/* 正則表達式*/"); // 如果有 '\' 符號,需要再打一個 '\' regex ex("[A-Za-z]\\d{9}"); // 首字為英文字,後9碼為數字 ``` #### match 比對字串是否有全部符合 ```cpp string id[]{ "A123456789", "B219876543", "C852156", "d159623487", "aa22s54c5" }; for(auto e : id){ if(regex_match(e , regex("[A-Za-z]\\d{9}"))) cout << e << " is an id number." << endl; } // A123456789 is an id number. // B219876543 is an id number. // d159623487 is an id number. ``` #### search 比對字串當中是否有部分符合 ```cpp string text("I am a beginner."); if(regex_search(text , regex("^I.*\\."))) cout<<"searched"<<endl; // searched ``` 也可以搜尋字串中符合條件的字串 但 C++ 只會找第一個符合的結果 ```cpp string text("apple app wrapper"); regex e("\\bapp[\\S]*"); smatch m; // 用來接收比對結果 if(regex_search(text, m, e)){ cout << m.str(); } // apple ``` #### regex_results 上面的 smatch 是一個接收型態,用來接收比對的結果,結果可能有一個也可能有數個(群組) 共有4種 match_results | | s | c | | :-----: | :-----: | :-----: | | | smatch | cmatch | | w | wsmatch | wcmatch | - s 代表接收 string 型態的 `regex_search(string, smatch, regex)` - c 代表接收 c_string 型態的 `regex_search(c_string, cmatch, regex)` - w 代表接收寬字元的 它算是一個容器,儲存比對結果[0]和比對群組[1,2...] 可以用 smatch[n] 或 smatch.str(n) 來回傳結果,也可用foreach來遍歷 用法如下 ```cpp string email("admin@test.tw"); regex e("(.*)@(.*)"); smatch m; if(regex_search(email, m, e)){ cout << "email : " << m.str(0) << endl; cout << "id = " << m.str(1) << endl; cout << "domain = "<< m.str(2) << endl; } // email : admin@test.tw // id = admin // domain = test.tw ``` regex_results還有另外兩個方法 - prefix() : 比對結果前面的字串 - suffix() : 比對結果後面的字串 用法如下 ```cpp string email("admin@test.tw"); regex e("@"); smatch m; if(regex_search(email, m, e)){ cout << "email : " << email << endl; cout << "id = " << m.prefix() << endl; cout << "domain = "<< m.suffix() << endl; } // email : admin@test.tw // id = admin // domain = test.tw ``` #### sregex_iterator() 因為 C++ regex_search() 只會找第一個符合的結果 如果你也想顯示所有結果,可以使用sregex_iterator() 它是指向 smatch 的迭代器 ```cpp string text("apple app wrapper"); regex e("\\bapp[\\S]*"); sregex_iterator it(text.begin(), text.end(), e); sregex_iterator it_end; // default建構子會回傳一個sregex_iterator的end while(it != it_end){ cout << it->str(0) << " "; ++it; } // apple app ``` #### replace 把字串中符合比對的部分替換成指定字串 ```cpp string text("I am a enginner."); text = regex_replace(text, regex("a (a|e|i|o|u)"),"an $1"); // $1: smatch.str(1) cout << text; // I am an enginner. ``` 在替換的參數中 ```cpp $n : 同 match_results.str(n) $` : 同 match_results.prefix() $' : 同 match_results.suffix() ``` --- ### 智慧指標 指標的其中一個缺點就是,如果有數個指標指向同一個物件,其中一個指標delete掉這個物件,其他指標使用物件時就會出問題。 所以就產生了智慧指標,根據不同情況來使用不同智慧指標,智慧指標的用法也和一般指標差不多,但不能有記憶體位址的位移。 #### unique_ptr 一個物件只能被一個指標擁有。 當這個指標被消除掉了,它所指向的物件也會被delete。 當這個指標被賦值給其他指標,必須用move()。 可以指向傳統array ```cpp { unique_ptr<int> a(new int(10));// the obj be new unique_ptr<int> b; b = a; // cannot b = move(a); // ownner: a->b if(!a) // 可以轉型為bool型態來判斷是否沒有指向nullptr (false:empty) cout<<"a is empty"; } // the obj be delete ``` #### shared_ptr 一個物件可以被多個指標擁有。 當所有指向該物件的指標都被消除掉,它們所指向的物件才會被delete。 ```cpp { shared_ptr<int> s1; { unique_ptr<int> u(new int(10)); // obj be new shared_ptr<int> s2; s2 = move(u); // ownner: u->s2 s1 = s2; // s2 share obj to s1 cout << s1.use_count(); // 2 } // s2 destroyed, obj still excist cout << s1.use_count(); // 1 } // obj be delete ``` #### weak_ptr 不是擁有者,但可以查看擁有者(shared_ptr)。 可以使用shared_ptr的一些功能,但不能使用shared_ptr所擁有的物件。 ```cpp void test(weak_ptr<int> w){ if(w.expired()) // 檢查shared_ptr是否為empty cout << "the shared_ptr is empty" << endl; else{ auto s = w.lock(); // 回傳一個shared_ptr的複本 cout << *s << endl; } } // use shared_ptr<int> s; test(s); // the shared_ptr is empty s = shared_ptr<int>(new int(10)); test(s); // 10 ``` #### circle reference ```cpp struct A; struct B; struct A{ shared_ptr<B> b; A (){ cout<<"A ctor"<<endl; } ~A(){ cout<<"A dtor"<<endl; } }; struct B{ shared_ptr<A> a; B (){ cout<<"B ctor"<<endl; } ~B(){ cout<<"B dtor"<<endl; } }; // use weak_ptr<A> wA; weak_ptr<B> wB; { shared_ptr<A> sA(new A()); shared_ptr<B> sB(new B()); sA->b = sB; sB->a = sA; wA = sA; wB = sB; cout << wA.use_count() << endl; // 2 cout << wB.use_count() << endl; // 2 }// sA,sB be destructed cout << wA.use_count() << endl; // 1 cout << wB.use_count() << endl; // 1 // A和B的物件應該要被delete但卻沒有,因為還有人在使用他們,但是沒有指標指向他們。 ``` 要解決這個問題,只要把class內的shared_ptr改成weak_ptr就好,因為weak_ptr可以間接參考到物件,又不會增加use_count,要使用時再用lock()回傳一個shared_ptr就好。 如下: ```cpp struct A{ weak_ptr<B> b; A (){ cout<<"A ctor"<<endl; } ~A(){ cout<<"A dtor"<<endl; } void SayHello(){ cout << "Hello I am A" << endl; } }; struct B{ weak_ptr<A> a; B (){ cout<<"B ctor"<<endl; } ~B(){ cout<<"B dtor"<<endl; } void CallA(){ auto sA = a.lock(); // 用lock()就能使用A的物件 sA->SayHello(); } }; ``` #### make_shared<>() 如果你的shared_ptr建構時是這樣寫: ```cpp auto s1 = shared_ptr<int>(new int(10)); ``` 那你也可以這樣寫 ```cpp auto s2 = make_shared<int>(10); ``` 這兩個在意思上是一樣的,但用make_shared()效能更好 ---
{"metaMigratedAt":"2023-06-15T01:32:39.656Z","metaMigratedFrom":"Content","title":"C++","breaks":true,"contributors":"[{\"id\":\"59b7d5c0-4fe2-48a3-b257-aa47ae754898\",\"add\":29560,\"del\":3935}]"}
    2367 views
   owned this note