# 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}]"}