C++11部分特性

初識C++的時候,以爲會個STL就差很少了,後來發現了C++11這個東西,以及C++14,C++17QAQ,看了一下,好高深不學,emmmm真香= =node

這裏就只講一下對ACM寫代碼有很高幫助的部分特性,由於大部分OJ和比賽只支持11,因此14和17就不講了,而後還有C++11增長的元組tuple和幾個容器另談。python


1、nullptr

在以前版本的c++中,NULL的值其實就是0,由於其實就是#define NULL 0,有些時候是((void *) 0)emmm,不說那些廢話了。因此這些就會遇到一個問題,c++

例以下面這兩個函數正則表達式

1 int fun(char* ch){}
2 int fun(int num){}

當char ch = NULL,這樣一個值去代入函數時,編譯會調用下面那個函數,而不是第一個。而nullptr類型是nullptr_t,就是專門爲了區別空指針和0,因此之後寫代碼nullptr代替NULL就能行了。算法

2、constexpr

constexpr變量必須是一個常量,必須用常量表達式來初始化,例以下面代碼安全

1 const int a = 10;
2 int b = 10;
3 constexpr int d = 10;  //正確
4 constexpr int c = a + 10;  //正確
5 constexpr int e = b;  //錯誤

 值得一提的是若是成員函數標記爲constexpr,則默認其是內聯函數,若是變量聲明爲constexpr,則默認其是const,這個會在構造函數中用到。多線程

3、類型推導

auto

其實之前的c++中就有auto,可是emmm,廢話不說,直接說內容吧,有了auto就很方便了。好比下面這個代碼閉包

1 map<int,int>::iterator it = mp.begin();
2 auto it = mp.begin();

誰方便一眼便知道了吧,auto能夠把自動推導成變量或者函數。dom

好比函數

1 int fun(int i){
2     cout << i << endl;   
3 }
4 
5 function<int(int)> f;
6 auto a = fun;

這裏auto其實就和自動推導成了function<int(int)>類型。值得注意的時,定義auto類型變量必須賦初值,否則則會編譯錯誤。還有就是函數的返回值不能直接用auto代替,例如

auto fun(int i){
}

這是會報錯的,可是不是函數的返回值就不能是auto類型了呢,實際上是能夠的,但只是不能直接這樣定義,下面咱們會講到如何把函數返回值定義爲auto。

decltype

顧名思義,就是推導一個變量的類型是啥,和sizeof用法相似,它的出現就是爲了彌補auto的缺陷。

    auto a = 1,b = 2;
    decltype(a+b) z;

 

拖尾返回類型

上面不是說怎麼把函數值弄爲auto嗎,其實在之前得c++中也能夠用typename,好比

 

1 template<typename R, typename T, typename U>
2 R add(T x, U y) {
3     return x+y
4 }

 當有多個這種函數的時候,就會顯得代碼很冗長,很不人性化qwq,因此就出現了拖尾返回類型,例以下面這個代碼

1 auto fun(int i) -> bool{
2     return i&1;
3 }

這個代碼編譯是沒有問題的,後面的->bool就是說明函數的返回類型是布爾類型,因此就可使用auto做爲函數類型,固然也能夠和decltype連用寫出下面的代碼。

1 template<typename T>
2 auto fun(T i) -> decltype(i*i){
3     return i*i;
4 }

簡潔易懂,並且看着特別舒服有木有。

4、區間迭代

在其餘語言裏面常常能夠看到for(a : b)這樣的循環用法,而在c++裏面,你要是想對一個容器進行區間迭代,你得這樣寫

1 for(vector<int>::iterator iter = vec.begin(); iter != vec.end(); iter++)

雖然看上去很整潔,但也太不人性化了吧,因此11就引用了:進行區間迭代,如今你能夠直接用auto和:寫出下面的代碼

1 for(auto &iter : vec)

這真的很coool

5、尖括號'>'

當多個容器疊加的時候,咱們都是用空格將>>區分開,由於在之前版本的c++中>>是會當作右移操做符號的,可是11更新後,你徹底不用擔憂這個問題,例以下面代碼,在11中編譯是沒有任何問題的

1 vector<vector<int>> q;

 

6、初始值

在之前的c++版本中,當咱們須要對一個結構體或者類賦初值,咱們得寫一個函數,例如

1 struct node{
2     int a,b;
3     node(int _a,int _b){this->a = _a,this->b = _b;}
4     node(int _a,int _b):a(_a),b(_b){}
5 };

但如今,c++11有五種不一樣的方式對各類變量初始化,你能夠直接用大括號賦值,下面提供了幾種不一樣的結構體賦值方法。

1 struct node{
2     int a,b;
3 };
4 
5 node asd{1,2};
6 node as = {1,2};

對於其餘變量和容器也適用。

7、類型別名以及默認別名

類型別名

typedef你們確定使用過,通常將一個類或者結構體作別名,但對於容器,好比下面用法

1 template<typename T>
2 typedef vector<T> qwq;

是不合法的,因此c++11引入了using,和typedef功能相同。

1 template<typename T>
2 using asd = vector<T>;

當須要使用vector時候,就直接調用便可。

1 asd<long long> vec;

默認別名

對於某個函數

1 template<typename T1,typename T2>
2 auto fun(T1 a,T2 b)->bool{
3     return a < b;
4 }

可是若是你想制定默認模板參數類型怎麼辦,c++11提供了一種便利,能夠直接指定默認參數

1 template<typename T1 = int,typename T2 = int>
2 auto fun(T1 a,T2 b)->bool{
3     return a < b;
4 };

 

8、lambda表達式

Lambda表達式是用來建立匿名函數的。什麼叫作匿名函數,就是沒有名字qwq,那麼爲何要用到匿名函數呢,舉個例子,好比sort的第三個參數,謂詞函數,通常咱們會寫一個比較函數,但這樣的後果就是,若是有多個sort你就得,每一個函數去找他的比較函數,實屬麻煩。各位看下面這個例子

1 sort(a.begin(),a.end(),[](int a,int b)->bool{return a < b;});

就算你不懂什麼是lambda表達式,但你也能猜到這個sort就是按照a<b比較吧。那麼lambda表達式是什麼樣子的呢。大體就是

1 [capture](parameters)opt->return-type{body}

emmm 後面那一截和普通函數沒什麼區別,參數,返回值類型,函數體。和函數最大的不一樣就是匿名函數是臨時函數,沒有函數名,及拿及用,固然你也能夠用function保存一個匿名函數。

至於opt是個什麼東西= =下面說,這個通常不會用上。

其中:

1.返回值類型->return-type能夠省略,由語言自動推導,但只限於lambda表達式語句簡單。

2.引入lambda表達式的前導符是一對方括號,叫作lambda引入符。lambda表達式可使用與其相同範圍內的變量,一共有下面這麼幾種方式捕獲變量

[]//不捕獲任何外部變量

[=]//以值形式捕獲全部外部變量

[&]//以引用方式捕獲全部外部變量

[x,&y]//x以值形式捕獲,y以引用形式捕獲

[=,&x]//x以引用形式捕獲,其他變量以值捕獲

[&,x]//x以值形式捕獲,其他變量以引用形式捕獲

[this]捕獲當前類的指針。捕獲this的目的是能夠在lambda中使用當前類的成員函數和成員變量。

對於[=]和[&],lambda能夠直接使用this指針,可是對於[]的形式,若是要使用this指針,必須顯式傳入

3.opt函數選項

能夠選填mutable,exception,attribute。

mutable說明lambda表達式體內的代碼能夠修改被捕獲的變量,而且能夠訪問被捕獲的對象的non-const方法。

exception說明lambda表達式是否拋出異常以及何種異常

attribute用來聲明屬性

4.lambda表達式不能直接被賦值,閉包類型禁用了賦值操做符號,可是能夠用lambda表達式去初始化另外一個lambda表達式,例如

1 auto a = []{cout <<1 << endl;};
2 auto b = a;  

也能夠吧lambda表達式賦值給相對應的函數指針,例如

1 function<int(int)> fun = [](int a){return a;};

那麼就能夠很方便的利用lambda表達式填充各類謂詞函數了,例以下面即是一個斐波那契數列

1     array<int,10> a;
2     auto f0 = 0, f1 = 1;
3     generate(a.begin(),a.end(),[&f0,&f1]()->int{int v = f1;f1 += f0, f0 = v;return v;});
4     for_each(a.begin(),a.end(),[](int v){cout << v << " ";});
5     cout << endl;;

代碼一眼看上去就能知道意思,無需定義額外函數。大部分的STL算法,均可以搭配lambda表達式來實現想要的效果。

9、右值引用和move

什麼是右值引用,先看一個例子。

 1 string a(x);
 2 string b(x+y);
 3 string c(fun());
 4 
 5 //若是使用如下拷貝構造函數
 6 string(const string& str){
 7     size_t size = strlen(str.data)+1;
 8     data = new char[size];
 9     memcpy(data,str.data,size);
10 }

則只有第一行的x深度拷貝有必要,由於其餘地方還可能會用到x,x就是一個左值。但第二行和第三行的參數則是右值,由於表達式產生的匿名string對象,以後無法再用。

c++11引入了一種機制「右值引用」,用&&來表示右值引用,以便咱們經過重載直接使用右值參數,例以下面這個構造函數:

1 string(string&& that){
2     data = that.data;
3     that.data = 0;
4 }

咱們沒有深度拷貝堆內存中的數據,而是僅僅複製了指針,並把源對象的指針置空。事實上,咱們「偷取」了屬於源對象的內存數據。因爲源對象是一個右值,不會再被使用,所以客戶並不會覺察到源對象被改變了。在這裏,咱們並無真正的複製,因此咱們把這個構造函數叫作「轉移構造函數」,他的工做就是把資源從一個對象轉移到另外一個對象,而不是複製他們。

那麼賦值操做符就能夠寫成

1 string& operator=(string that){
2     std::swap(data, that.data);
3     return *this;
4 }

注意到咱們是直接對參數that傳值,因此that會像其餘任何對象同樣被初始化,那麼確切的說,that是怎樣被初始化的呢?對於C++ 98,答案是複製構造函數,可是對於C++ 11,編譯器會依據參數是左值仍是右值在複製構造函數和轉移構造函數間進行選擇

若是是a=b,這樣就會調用複製構造函數來初始化that(由於b是左值),賦值操做符會與新建立的對象交換數據,深度拷貝。這就是copy and swap 慣用法的定義:構造一個副本,與副本交換數據,並讓副本在做用域內自動銷燬。這裏也同樣。

若是是a = x + y,這樣就會調用轉移構造函數來初始化that(由於x+y是右值),因此這裏沒有深度拷貝,只有高效的數據轉移。相對於參數,that依然是一個獨立的對象,可是他的構造函數是無用的(trivial),所以堆中的數據沒有必要複製,而僅僅是轉移。沒有必要複製他,由於x+y是右值,再次,從右值指向的對象中轉移是沒有問題的。

轉移左值是十分危險的,可是轉移右值倒是很安全的。若是C++能從語言級別支持區分左值和右值參數,我就能夠徹底杜絕對左值轉移,或者把轉移左值在調用的時候暴露出來,以使咱們不會不經意的轉移左值。

複製構造函數執行的是深度拷貝,由於源對象自己必須不能被改變。而轉移構造函數卻能夠複製指針,把源對象的指針置空,這種形式下,這是安全的,由於用戶不可能再使用這個對象了

 

有時候,咱們可能想轉移左值,也就是說,有時候咱們想讓編譯器把左值看成右值對待,以便能使用轉移構造函數,即使這有點不安全。出於這個目的,C++ 11在標準庫的頭文件< utility >中提供了一個模板函數std::move。實際上,std::move僅僅是簡單地將左值轉換爲右值,它自己並無轉移任何東西。它僅僅是讓對象能夠轉移。

1 unique_ptr<Shape> a(new Triangle);
2 unique_ptr<Shape> b(a);              //false
3 unique_ptr<Shape> c(move(a));   //true

請注意,第三行以後,a再也不擁有Triangle對象。不過這沒有關係,由於經過明確的寫出move(a),咱們很清楚咱們的意圖:親愛的轉移構造函數,你能夠對a作任何想要作的事情來初始化c;我再也不須要a了,對於a,您請自便。

固然,若是你在使用了mova(a)以後,還繼續使用a,那無疑是搬起石頭砸本身的腳,仍是會致使嚴重的運行錯誤。

總之,move(val)將左值轉換爲右值(能夠理解爲一種類型轉換),使接下來的轉移成爲可能。

 

10、正則表達式

正則表達式描述了一種字符串匹配的模式。通常使用正則表達式主要是實現下面三個需求:
1) 檢查一個串是否包含某種形式的子串;
2) 將匹配的子串替換;
3) 從某個串中取出符合條件的子串。

C++11 提供的正則表達式庫操做 string 對象,對模式 std::regex (本質是 basic_regex)進行初始化,經過 std::regex_match 進行匹配,從而產生 smatch (本質是 match_results 對象)。

咱們經過一個簡單的例子來簡單介紹這個庫的使用。考慮下面的正則表達式:

[a-z]+.txt: 在這個正則表達式中, [a-z] 表示匹配一個小寫字母, + 可使前面的表達式匹配屢次,所以 [a-z]+ 可以匹配一個及以上小寫字母組成的字符串。在正則表達式中一個 . 表示匹配任意字符,而 . 轉義後則表示匹配字符 . ,最後的 txt 表示嚴格匹配 txt 這三個字母。所以這個正則表達式的所要匹配的內容就是文件名爲純小寫字母的文本文件。
regex_match 用於匹配字符串和正則表達式,有不少不一樣的重載形式。最簡單的一個形式就是傳入string 以及一個 regex 進行匹配,當匹配成功時,會返回 true,不然返回 false。例如:

1 string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};//`\` 會被做爲字符串內的轉義符,須要對 `\` 進行二次轉義,從而有 `\\.`
2 regex txt_regex("[a-z]+\\.txt");
3 for (const auto &fname: fnames)
4     cout << fname << ": " << regex_match(fname, txt_regex) << endl;

另外一種經常使用的形式就是依次傳入 string/smatch/regex 三個參數,其中 smatch 的本質實際上是 match_results,在標準庫中, smatch 被定義爲了 match_results,也就是一個子串迭代器類型的 match_results。使用 smatch 能夠方便的對匹配的結果進行獲取,例如:

 1 regex base_regex("([a-z]+)\\.txt");
 2 smatch base_match;
 3 for(const auto &fname: fnames) {
 4     if (regex_match(fname, base_match, base_regex)) {
 5         // sub_match 的第一個元素匹配整個字符串
 6         // sub_match 的第二個元素匹配了第一個括號表達式
 7         if (base_match.size() == 2) {
 8             string base = base_match[1].str();
 9             cout << "sub-match[0]: " << base_match[0].str() << endl;
10             cout << fname << " sub-match[1]: " << base << endl;
11         }
12     }
13 }

代碼運行結果爲

foo.txt: 1
bar.txt: 1
test: 0
a0.txt: 0
AAA.txt: 0
sub-match[0]: foo.txt
foo.txt sub-match[1]: foo
sub-match[0]: bar.txt
bar.txt sub-match[1]: bar

 

11、構造函數&繼承&修飾符

C++11 引入了委託構造的概念,這使得構造函數能夠在同一個類中一個構造函數調用另外一個構造函數,從而達到簡化代碼的目的:

委託構造

 1 class Base {
 2 public:
 3     int value1;
 4     int value2;
 5     Base() {
 6         value1 = 1;
 7     }
 8     Base(int value) : Base() {  // 委託 Base() 構造函數
 9         value2 = 2;
10     }
11 };

 

繼承構造

在繼承體系中,若是派生類想要使用基類的構造函數,須要在構造函數中顯式聲明。
倘若基類擁有爲數衆多的不一樣版本的構造函數,這樣,在派生類中得寫不少對應的「透傳」構造函數。以下:

 1 struct A
 2 {
 3   A(int i) {}
 4   A(double d,int i){}
 5   A(float f,int i,const char* c){}
 6   //...等等系列的構造函數版本
 7 };
 8 struct B:A
 9 {
10   B(int i):A(i){}
11   B(double d,int i):A(d,i){}
12   B(folat f,int i,const char* c):A(f,i,e){}
13   //......等等好多個和基類構造函數對應的構造函數
14 };

 

constexpr構造

若是想要使得函數擁有編譯時計算的能力,則使用關鍵字constexpr

 1 class Square {
 2 public:
 3     constexpr Square(int e) : edge(e){};
 4     constexpr int getArea() {return edge * edge;}
 5 private:
 6     int edge;
 7 };
 8  
 9 int main() {
10     Square s(10);
11     cout << s.getArea() << endl;
12     return 0;
13 }

 

12、刪除的函數以及新增的函數

字符串和數值類型轉換

itoa等等字符串和數值類型的轉換成爲歷史。

提供了to_string和stox方法,將字符串和數值自由轉換;

 1 //數值轉字符串
 2 std::string to_string(int value);
 3 std::string to_string(long int value);
 4 std::string to_string(long long int value);
 5 std::string to_string(unsigned int value);
 6 std::string to_string(unsigned long long int value);
 7 std::string to_string(float value);
 8 std::string to_string(double value);
 9 std::wstring to_wstring(int value);
10 std::wstring to_wstring(long int value);
11 std::wstring to_wstring(long long int value);
12 std::wstring to_wstring(unsigned int value);
13 std::wstring to_wstring(unsigned long long int value);
14 std::wstring to_wstring(float value);
15 std::wstring to_wstring(double value);
16 
17 //字符串轉數值
18 std::string str = "1000";
19 int val = std::stoi(str);
20 long val = std::stol(str);
21 float val = std::stof(str);

c++11還提供了字符串(char*)轉換爲整數和浮點類型的方法:

1 atoi: 將字符串轉換爲 int
2 atol: 將字符串轉換爲long
3 atoll:將字符串轉換爲 long long
4 atof: 將字符串轉換爲浮點數

隨機數函數

生成隨機數,免去了之前須要自行調用srand初始化種子的步驟,由於有時候忘記初始化致使結果錯誤。

std::random_device rd;

int randint = rd();

std::chrono

獲取時間函數,比之前方便許多。

 1 std::chrono::duration<double> duration //時間間隔
 2 
 3 std::this_thread::sleep_for(duration); //sleep
 4 
 5 LOG(INFO) << "duration is " << duration.count() << std::endl;
 6 
 7 std::chrono::microseconds  //微秒
 8 
 9 std::chrono::seconds //
10 
11 end = std::chrono::system_clock::now(); //獲取當前時間

all_of(),any_of(),none_of(),copy_n(),iota()

 1 #include<algorithm>
 2 #include<numeric>
 3 
 4 all_of(first,first+n,ispositive());//false
 5 
 6 any_of(first,first+n,ispositive());//true
 7 
 8 none_of(first,first+n,ispositive());//false
 9 
10 int source[5]={0,12,34,50,80};
11 int target[5];
12 //從source拷貝5個元素到target
13 copy_n(source,5,target);
14 
15 //iota()算法能夠用來建立遞增序列,它先把初值賦值給 *first,而後用前置 ++ 操做符增加初值並賦值到給下一個迭代器指向的元素,以下:
16 
17 inta[5]={0};
18 charc[3]={0};
19 iota(a,a+5,10);//{10,11,12,13,14}
20 iota(c,c+3,'a');//{'a','b','c'}

 

原子變量和正則表達式

std::atomic<XXX>

用於多線程資源互斥操做,屬c++11重大提高,多線程原子操做簡單了許多。

C正則(regex.h)和boost成爲歷史

hash進入STL

新增基於hash的無序容器。

對於容器的emplace

做用於容器,區別於push、insert等,如push_back是在容器尾部追加一個容器類型對象,emplace_back是構造1個新對象並追加在容器尾部

對於標準類型沒有變化,如std:;vector<int>,push_back和emplace_back效果同樣

如自定義類型class A,A的構造函數接收一個int型參數,

那麼對於push_back須要是:

std::vector<A> vec;
A a(10);
vec.push_back(a);

對於emplace_back則是:

std::vector<A> vec;
vec.emplace_back(10);

避免無用臨時變量。好比上面例子中的那個a變量。

對於容器的shrink_to_fit

這個改進仍是有點意義的,平常程序應該能減小很多無心義的內存空間佔用

push、insert這類操做會觸發容器的capacity,即預留內存的擴大,實際開發時每每這些擴大的區域並無用途

    std::vector<int> v{1, 2, 3, 4, 5};
    v.push_back(1);
    std::cout << "before shrink_to_fit: " << v.capacity() << std::endl;
    v.shrink_to_fit();
    std::cout << "after shrink_to_fit: " << v.capacity() << std::endl;

能夠試一試,減小了不少。

十3、動態指針&智能指針

C++98標準庫中提供了一種惟一擁有性的智能指針std::auto_ptr,該類型在C++11中已被廢棄,由於其「複製」行爲是危險的。

auto_ptr 的危險之處在於看上去應該是複製,但實際上確是轉移。調用被轉移過的auto_ptr 的成員函數將會致使不可預知的後果。因此你必須很是謹慎的使用auto_ptr ,若是他被轉移過。

C++ 11中,std::auto_ptr< T >已經被std::unique_ptr< T >所取代,後者就是利用的右值引用。

十4、會使用的庫

<utility>

<unordered_map>

<unordered_set>

<random>

<tuple>

<array>

<numeric>

 


 

須要深刻了解的話就本身去找資料了吧qwq,我以爲用的最多的可能仍是lambda表達式和區間迭代了吧,愈來愈像python了,qwq但願有所幫助。

相關文章
相關標籤/搜索