C++11新特性——总结
1. 列表初始化的使用
在C++98中,標準允許使用花括號{}對數組或者結構體元素進行統一的列表初始值設定。比如:
struct Point {int _x;int _y; }; int main() {int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0; }C++11擴大了用大括號括起的列表(初始化列表)的使用范圍,使其可用于所有的內置類型和用戶自定義類型,使用初始化列表時,可添加等號(=),也可不添加。
struct Point {int _x;int _y; }; int main() {int x1 = 1;int x2{ 2 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C++11中列表初始化也可以適用于new表達式中int* pa = new int[4]{ 0 };return 0; }創建對象時也可以使用列表初始化方式調用構造函數初始化。
class Date { public:Date(int year, int month, int day):_year(year),_month(month),_day(day){cout << "Date(int year, int month, int day)" << endl;} private:int _year;int _month;int _day; }; int main() {Date d1(2022, 1, 1); // old style// C++11支持的列表初始化,這里會調用構造函數初始化Date d2{ 2022, 1, 2 };Date d3 = { 2022, 1, 3 };return 0; }注意:初始化列表語法可防止縮窄,即禁止將數值賦值給無法存儲它的數值變量,即將值存儲到比它窄的變量中。
2.C++11新聲明
1. auto
C++11中將auto用于實現自動類型推斷。要求必須進行顯示初始化,讓編譯器將定義對象的類型設置為初始化值的類型。通過auto自動類型推斷獲得的變量可以使用typeid( )函數獲得實際類型。
int main() {int i = 10;auto p = &i;float m = 10.01;auto pf = m;cout << typeid(p).name() << endl;//int *cout << typeid(pf).name() << endl;//floatmap<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0; }當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量。
auto a = 1, b = 2;auto c = 3, d = 4.0; // 該行代碼會編譯失敗,因為c和d的初始化表達式類型不同auto不能作為函數的參數,不能直接用來聲明數組:
// 此處代碼編譯失敗,auto不能作為形參類型,因為編譯器無法對a的實際類型進行推導 void TestAuto(auto a){} // 此處代碼編譯失敗,auto不能推導數組 auto b[] = {4,5,6};2. decltype
關鍵字decltype將變量的類型聲明為表達式指定的類型。
int main() {const int x = 1;double y = 2.2;int z = 0;decltype(x * y) ret; // ret的類型是doubledecltype(&x) p; // p的類型是int*decltype(z) tmp;// z的類型是intcout << typeid(ret).name() << endl;//doublecout << typeid(p).name() << endl;//int *cout << typeid(tmp).name() << endl;//intreturn 0; }在定義模板的時候特別好用,因為只有等到模板被實例化時才能確定類型。
template<class T, class U> void func(T t, U u) {...decltype(T*U) tu;... }3.返回類型后置
C++新增的一種函數聲明語法:在函數名和參數列表后面(而不是前面)指定返回類型。讓我們能夠使用 decltype 來指定模板函數的返回類型。
template<class T, class U> -> decltype(T*U) auto func(T t, U u) {...decltype(T*U) tu;... }這里解決的問題是,在編譯器遇到func的參數列表前, T和U還不在作用域內,因此必須在參數列表后使用decltype。這種新語法使得能夠這樣做。
4.模板別名:using =
新語法可用于模板部分具體化,但是typedef不能
typedef array<double, 12> arrd; typedef array<int, 12> arri; typedef array<std::string, 12> arrstr;arrd gallons;// gallons is type array<double, 12> arri days; // days is type array<int, 12> arrst months;// months is type array<std::string, 12>/*新語法*/ template<class T> using arrtype = array<T, 12>;//template to create multiple aliasesarrtype<double> gallons; //gallons is type array<double, 12> arrtype<int> days; //days is type array<int, 12> arrtype<std::string> months; //months is type array<std::string, 12>5. nullptr
由于C++中NULL被定義成字面量0,這樣就可能回帶來一些問題,因為0既能指針常量,又能表示整形常量。所以出于清晰和安全的角度考慮,C++11中新增了nullptr,用于表示空指針。nullptr 可以被隱式轉換成任意的指針類型。
為向后兼容,C++11仍允許使用0來表示空指針,因此表達式nullptr==0為true。
6. explicit
C++11引入了關鍵字explicit,以禁止單參數構造函數導致的自動轉換:
class Plebe {...Plebe (int);explicit Plebe(double);... };intm main() {Plebe a, b;a= 5; //allowedb = 0.5; //unallowedb = Plebe (0.5);//顯式轉換,allowed }7.類成員初始化
在類聲明中,可使用等號或大括號版本的初始化,但不能使用圓括號版本的初始化。
8. 管理虛方法 override 和 final
class A {int a; public:Action(int i = 0) : a(i) {}int val() const {return a;}virtual void show(char ch) const { cout << val() << ch << endl;} };class B: public A { public: B(int i = 0) : A (i) {}virtual void show(char * ch) const {cout << val() << ch << "!" << endl; } };由于類B定義的是show(char * ch)而不是show(char ch),將對B對象隱藏show(char ch),因此下面代碼報錯:
B b(10); b.show('b');//faild在C++11中,可使用虛說明符override指出您要覆蓋一個虛函數:將其放在參數列表后面。如果聲明與基類方法不匹配,編譯器將視為錯誤。下面的函數重寫將生成一條編譯錯誤:
virtual void show(char * ch) const override {cout << val() << ch << "!" << endl; }如果想禁止派生類覆蓋特定的虛方法,為此可在參數列表后面加上final。例如,下面的代碼禁止A的派生類重新定義函數show(char ch):
virtual void show(char ch) const final{ cout << val() << ch << endl;}3.基于范圍的for循環
對于一個有范圍的集合而言,由程序員來說明循環的范圍是多余的,有時候還會容易犯錯誤。因此C++11中引入了基于范圍的for循環。for循環后的括號由冒號“ :”分為兩部分:第一部分是范圍內用于迭代的變量,第二部分則表示被迭代的范圍。
void TestFor() {int array[] = { 1, 2, 3, 4, 5 };for(auto& e : array)//e為引用類型,用于修改數組元素e *= 2;for(auto e : array)cout << e << " ";//2,4,6,8,10return 0; }?注意:與普通循環類似,可以用continue來結束本次循環,也可以用break來跳出整個循環。for循環迭代的范圍必須是確定的。
4.右值引用
1、概念理解
- 左值:表示可尋址。是保存在內存中,具有確切地址,并能取地址,進行訪問,修改等操作的表達式(變量)。
- 右值:表示可讀不可尋址。是保存在內存中,或者寄存器中,不知道也無法獲得其確切地址,在得到計算表達式結果后就銷毀的臨時表達式。包括字面常量、諸如x+y表達式以及函數返回值。
- 右值引用:表示即將過期但還沒過期的值。
右值是能夠賦值給左值,但是左值不能賦值給右值。
右值引用主要作用是解決:(1)大對象在作為函數返回值返回時的深度拷貝問題,(2)智能指針時將其他unique_ptr通過move()移動后可以賦值給unique_ptr,(3)大對象之間的快速復制。
??? int &a = 1;//錯誤,1是右值,a是左值引用,無法直接初始化,需要用左值初始化;
??? int a = 1; //正確,a是左值不是左值引用,可以被賦值;
??? int const &a = 1;//正確,a是常量左值引用,右值可以綁定到常左值引用;
??? int &&a = 1;//正確,這是C++11中的右值引用,a為左值;
2、函數的返回值一般是右值,也可能是左值:
- 當返回值為函數內部定義的局部變量時,函數返回值為右值,因為函數內部該變量已被銷毀。
- 當返回值為指針類型或引用類型的參數時,或者全局變量時,函數返回值為左值。
3、移動語義std::move()
- move作用是可以將一個左值轉換成右值引用,從而可以調用C++11的拷貝構造函數。由于在C++11中的移動構造函數的入參是右值引用,因此當傳入左值時,無法調用該移動構造函數,需要借助move將左值轉換成右值引用。
- 使用move后,原來的指針unique_ptr指針轉讓所有權變成空指針,讓你能夠將一個unique_ptr賦給另一個unique_ptr。
4、完美轉發std::forward()
完美轉發是指在函數模板中,完全依照模板的參數類型,將參數傳遞給當前函數模板中的另外一個函數。
因此,為了實現完美轉發,除了使用萬能引用之外,我們還要用到std::forward(C++11),它在傳參的過程中保留對象的原生類型屬性。這樣右值引用在傳遞過程中就能夠保持右值的屬性。
運行結果如下:
5.新增容器
用橘色圈起來是C++11中的幾個新容器,但是實際最有用的是unordered_map和unordered_set。
6.類的新功能
1、類默認生成函數
任何一個類,在什么都不寫的時候,都會默認生成6個成員函數:
默認構造函數、析構函數、拷貝構造函數、賦值重載函數、普通對象取地址、const對象取地址。
C++11中類新增了2個默認成員函數,使其變為了8個默認成員函數。新增的這兩個成員函數就是移動構造函數和移動賦值函數。
如果我們沒有自己實現沒有實現析構函數 、拷貝構造、拷貝賦值重載中的任意一個,那么編譯器才自動生成默認移動賦值和移動賦值函數。如果我們自己實現了移動構造函數或移動賦值運算符,編譯器將不會自動提供復制構造函數和復制賦值運算符。
class Person { public:Person(const char* name="", int age = 0){ ... };Person (const Person& p) ;//復制構造函數Person(Person&& p);//移動構造函數 private:char* _name;int _age; }//復制構造函數 Person::Person(const Person& p) {_name = new char[20];strcpy(_name, p._name);_age = p._age; }//移動構造函數 //移動構造函數/移動賦值運算符的參數不能是const引用,因為這個方法修改了源對象 Person::Person(Person&& p) {_name = p._name;p._name = nullptr;//修改了原對象_age = p._age; }通過提供一個使用左值引用的構造函數和一個使用右值引用的構造函數,將初始化分成了兩組。使用左值對象初始化對象時,將使用復制構造函數,而使用右值對象初始化對象時,將使用移動構造函數。
2、defult? / delete
- delete關鍵字除了釋放被new開辟的空間外,delete關鍵字還可以修飾默認生成的成員函數,讓其禁止默認生成。
- default關鍵字,假設要使用某個默認的函數,但是因為一些原因這個函數沒有默認生成。比如:我們提供了拷貝構造,就不會生成移動構造了,在類成員函數的后面提供defult,會讓該成員函數強制默認生成。
7.Lambda表達式
Lambda 表達式是一個匿名函數,即沒有函數名的函數。
1、Lambda表達式的語法規則
[capture-list] (parameters) mutable -> return-type { statement}
- ?[capture-list] : 捕捉列表,捕捉列表能夠捕捉上下文中的變量供lambda函數使用。
- [var]:表示值傳遞方式捕捉變量var
- [=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
- [&var]:表示引用傳遞捕捉變量var
- [&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
- [this]:表示值傳遞方式捕捉當前的this指針
- (parameters):參數列表,如果不需要參數傳遞,則可以連同()一起省略。
- mutable:默認情況下,lambda函數總是一個const函數,mutable可以取消其常量性。使用該修飾符時,參數列表不可省略(即使參數為空)。
- ->returntype:返回值類型,聲明函數的返回值類型,沒有返回值時此部分可省略。也可省略,由編譯器對返回類型進行推導。
- {statement}:函數體,在函數體內,除了可以使用其參數外,還可以使用所有捕獲到的變量。
【注意】在lambda函數定義中,參數列表和返回值類型都是可選部分,而捕捉列表和函數體可以為空。因此C++11中最簡單的lambda函數為:[]{}; 該lambda函數不能做任何事情。
int main() {// 最簡單的lambda表達式, 該lambda表達式沒有任何意義[] {};// 省略參數列表和返回值類型,返回值類型由編譯器推導為intint a = 3, b = 4;[=] {return a + 3; };// 省略了返回值類型,無返回值類型auto fun1 = [&](int c) {b = a + c; };fun1(10);cout << a << " " << b << endl;// 各部分都很完善的lambda函數auto fun2 = [=, &b](int c)->int {return b += a + c; };cout << fun2(10) << endl;// 復制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0; }lambda表達式實際上可以理解為無名函數,該函數無法直接調用,如果想要直接調用,可借助auto將其賦值給一個變量。
2、為什么引入Lambda表達式?
如果想要對一個數據集合中的元素進行排序,可以使用std::sort方法。
int main() {int array[] = { 4,1,8,5,3,7,0,9,2,6 };// 默認按照小于比較,排出來結果是升序std::sort(array, array + sizeof(array) / sizeof(array[0]));// 如果需要降序,需要改變元素的比較規則std::sort(array, array + sizeof(array) / sizeof(array[0]), std::greater<int>());return 0; }但是如果待排序的元素為自定義類型,則需要用戶自定義排序時的比較規則:
struct Goods {string _name; // 名字double _price; // 價格int _evaluate; // 評價Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){} }; struct ComparePriceLess//按照水果價格降序排序 {bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;} }; struct ComparePriceGreater//按照水果價格生序排序 {bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;} }; int main() {vector<Goods> v = { { "蘋果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠蘿", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());//降序排序sort(v.begin(), v.end(), ComparePriceGreater());//升序排序 }引入Lambda表達式后,上述排序代碼可以寫成如下形式:
vector<Goods> v = { { "蘋果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠蘿", 1.5, 4 } }; //降序排序 sort(v.begin(), v.end(), [](Goods gl, Goods gr)->bool {return gl._price<gr._price}); //升序排序 sort(v.begin(), v.end(), [](Goods gl, Goods gr)->bool {return gl._price>gr._price});3、Lambda表達式原理
函數對象,又稱為仿函數,即可以像函數一樣使用的對象,就是在類中重載了operator()運算符的類對象。
class Rate { public:Rate(double rate):_rate(rate){}double operator()(double money, int year){return money * _rate * year;} private:double _rate; }; int main() {// 函數對象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lambdaauto r2 = [=](double monty, int year)->double {return monty * rate * year;};r2(10000, 2);return 0; }從使用方式上來看,函數對象與lambda表達式完全一樣。函數對象將rate作為其成員變量,在定義對象時給出初始值即可,lambda表達式通過捕獲列表可以直接將該變量捕獲到。
實際在底層編譯器對于lambda表達式的處理方式,完全就是按照函數對象的方式處理的,即:如果定義了一個lambda表達式,編譯器會自動生成一個類,在該類中重載了operator()。
8.包裝器
function包裝器 也叫作適配器。C++中的function本質是一個類模板,也是一個包裝器。那我們為什么需要包裝器呢?先看例子:ret = func(x) ;
上面的代碼中func可以是仿函數對象,可以是函數指針,也可以是lambda表達式. . .這些對象都可以調用,在實現模板時就會對一個模板進行多次的實例化,導致了模板的效率底下。
template<class F, class T> T useF(F f, T x) {static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x); } double f(double i)//普通函數 {return i / 2; } struct Functor//仿函數 {double operator()(double d){return d / 3;} }; void test20() {// 函數名cout << useF(f, 11.11) << endl;// 仿函數cout << useF(Functor(), 11.11) << endl;// lamber表達式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl; }由代碼的執行結果我們可以看到,count有三份,說明我們實現了3個不同的useF函數模板,主要是由于程序中定義的幾種模式的函數,都滿足函數模板中調用的函數參數與變量參數之間的使用關系,因此在實際運行模板函數時,分別調用幾種不同的函數。
? 但是如果我們使用包裝器將三個函數進行包裝,對應的模板只會實現一份。包裝格式如下:
function<返回類型(參數類型)> 包裝器名稱=包裝函數
#include<iostream> #include<functional> using namespace std;template<class F, class T> T useF(F f, T x) {static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x); } int f(int a) {//普通函數return a + 0; } struct Functor {//仿函數 public:int operator() (int a) {return a + 1;} }; class Plus//類成員函數 { public:static int plusi(int a) {return a + 2;}double plusd(double a) {return a + 3;} }; void test21() {// 函數名(函數指針)std::function<int(int)> func1 = f;cout << useF(func1, 11) << endl;// 函數對象std::function<int(int)> func2 = Functor();cout << useF(func2, 11) << endl;// lamber表達式std::function<int(int)> func3 = [](const int a) {return a + 5; };cout << useF(func3, 11) << endl; }在使用了包裝器后,模板參數就不再出現實例化多份的情況了。
9.智能指針
C++11——智能指針_oywLearning的博客-CSDN博客
10.原子操作
C++并發編程 | 原子操作std::atomic_oywLearning的博客-CSDN博客_std::atomic
11.線程庫std::thread
C++多線程編程之thread類詳解_oywLearning的博客-CSDN博客_c++ thread
12.可變參數模板
可變參數模板 使得編程者能夠創建這樣的 模板函數 和 模板類 ,即可 接受可變數量的參數 。 例如要編寫一個函數,它可接受任意數量的參數,參數的類型只需是 cout 能顯示的即可,并將 參數顯示為用逗號分隔的列表,其函數模板如下: //為多個參數的情況定義的函數 template<typename T,typename... Arg> void show_list(T value,Arg... arg) {cout<<value<<",";show_list(arg...);//遞歸調用多個變量的show_list版本,最后調用單個變量的show_list版本 } //為一個參數的情況定義的 template<typename T> void show_list(T value) {cout<<value<<endl; } //為沒有參數的情況定義的 void show_list(){} 要創建可變參數模板,需要理解幾個要點:- 模板參數包
- 函數參數包
- 展開參數包
- 遞歸
總結
以上是生活随笔為你收集整理的C++11新特性——总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022年全球市场三维测量产品总体规模、
- 下一篇: 【C++】-- C++11基础常用知识点