C++11新特性總結
一、語法
1、auto
自動推導變量數據類型,用於從初始化表達式中推斷出變量的數據類型。類型確定後,不能重複初始化。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <map>
#include <list>
#include <vector>
using namespace std;
struct testStruct
{
int a;
int b;
};
int main()
{
// auto
cout << "Hello World!\n";
auto a = 5; //int類型
//a = "ding"; //錯誤,不能重複初始化,a已推到出int類型
auto b = "testing"; //不是string類型
string c = "testing";
//auto d = b.c_str();
auto e = c; //string類型
cout << e.c_str() << endl;
char* f = new char[5];
auto g = f; //char*類型
f[5] = 0;
g[5] = 0;
f = NULL;
g = NULL;
auto* h = new testStruct; //自定義testStruct類型
h->a = 5;
vector<int> i;
vector<vector<int>> j;
map<int, string> k;
k[1] = "aaa";
map<int, string>::iterator it = k.begin();
auto l = i; //vector<int>類型
auto m = j; //vector<vector<int>>類型
auto n = k; //map<int, string>
//auto ll = i, mm = j, nn = k; //錯誤寫法,編譯失敗
auto ll = 5, mm = 6, nn = 7; //正確寫法,在聲明符列表中,auto必須始終推到爲同一類型
auto iter = k.begin(); //map<int, string>::iterator類型
double a2 = 3.144;
const auto a3 = a2; //const double
auto a4 = a2; //double
volatile int c2 = 3;
auto c3 = c2; //int
return 0;
}
for循環迭代差異:
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <vector>
using namespace std;
int main()
{
//C++98
vector<int> vec(8, 1);
cout << "C++98 range for:" << endl;
for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++)
{
cout << *it << endl;
}
//C++11
cout << "C++11 range for:" << endl;
for (auto d : vec) //d中存儲的是vec中的值
{
d = 2; //不會改變vector中的值,d只是一個臨時變量;
}
for (auto d : vec) //d中存儲的是vec中的值
{
cout << d << endl;
}
for (auto &d : vec)
{
d = 6; //可以改變vector中的值
}
for (auto d : vec)
{
cout << d << endl;
}
//數組for_each
char arr[] = {'a','b','c','d'};
for (auto &d : arr)
{
d -= 32;
}
for (auto d : arr)
{
cout << d << endl;
}
//遍歷二維數組,注意迭代變量row必須爲引用。如果你想用 range for 的方法,來遍歷更高維的數組 (dim > 2),那麼你只需要:除了最內層循環之外,其他所有外層循環都加入 '&' 即可。
int array2[5][5] = {0};
for (auto &row : array2)
for (auto col : row)
cout << col << endl;
return 0;
}
2、decltype
通過一個變量或表達式得到變量的數據類型,並不會真正的執行表達式;
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <map>
#include <list>
#include <vector>
using namespace std;
int func(int a)
{
cout << "this is func, int type param" << endl;
return a;
}
string func(string b)
{
cout << "this is func, string type param" << endl;
return "this is func, string type param";
}
void func()
{
cout << "this is func, without return value" << endl;
}
int main()
{
// decltype
map<int, int> aa;
aa[5] = 6;
decltype(aa) bb = aa; //map<int, int>類型
bb[1] = 2;
map<int, int>::iterator cc = aa.begin();
decltype(cc) dd = cc; //map<int, int>::iterator類型
decltype(func(5)) ee = 5; //int類型,並不會執行func(5)
decltype(func("5")) ff = "5"; //string類型,並不會執行func("5")
// decltype(func()) gg = 6; //編譯錯誤,非法使用void類型
return 0;
}
3、nullptr
nullptr是一個表示空指針的標識。NULL只是一個定義爲常整數0的宏,而nullptr是C++11的一個關鍵字,一個內建的標識符。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <vector>
using namespace std;
void fun(int a)
{
cout << "call function: [int]\n" << endl;
}
void fun(int *a)
{
cout << "call function: [int*]\n" << endl;
}
int main()
{
fun(NULL); //調用void fun(int a)
fun(nullptr); //調用void fun(int *a)
int* p = NULL;
fun(p); //調用void fun(int *a)
return 0;
}
4、final
final使用方法有以下三種:
- 修飾變量
final修飾基本數據類型的變量時,必須賦予初始值且不能被改變,修飾引用變量時,該引用變量不能再指向其他對象。 - 修飾方法
代表這個方法不可以被子類的方法重寫。如果你認爲一個方法的功能已經足夠完整了,子類中不需要改變的話,你可以聲明此方法爲final。 - 修飾類
final類通常功能是完整的,它們不能被繼承。
C++11中的final不能修飾變量,修飾方法和類與java中final功能相同。
class Base final //final修飾類
{
public:
int a;
//final int b; //final不能修飾變量
virtual void func() final; //final修改函數,不能被子類重寫
};
class Last final : Base //Base不能被繼承
{
};
5、override
允許派生類顯示的註明它將使用那個成員函數改寫基類的虛函數,必須是virtual修飾的虛函數。
與java中的@override功能類似,標識重寫(覆蓋)。
struct B
{
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1 : public B
{
void f1(int) const override; //ok
void f2(int) override; //error,B中沒有形如f2(int)的函數
void f3() override; //error,f3不是虛函數
void f4() override; //error,B中無f4函數
};
6、=default和=delete
對於 C++ 的類,如果程序員沒有爲其定義特殊成員函數,那麼在需要用到某個特殊成員函數的時候,編譯器會隱式的自動生成一個默認的特殊成員函數,比如拷貝構造函數,或者拷貝賦值操作符。
C++11允許我們使用=default來要求編譯器生成一個默認構造函數,也允許我們使用=delete來告訴編譯器不要爲我們生成某個默認函數
class B
{
B() = default; //顯示聲明使用默認構造函數
B(const B&) = delete; //禁止使用類對象之間的拷貝
~B() = default; //顯示聲明使用默認析構函數
B& operator=(const B&) = delete; //禁止使用類對象之間的賦值
B(int a);
};
7、lambda表達式
lambda的基礎語法定義如下:
[capture](parameters) mutable ->return-type{statement}
-
[capture]:捕獲列表。它總是出現在lambda函數的開始位置。在編譯器看來[]是lambda的引出符號,編譯器正式通過它來判斷接下來的代碼是否是lambda函數。捕獲列表能夠捕捉當前上下文中的變量供給lambda函數使用。具體的capture列表中的語法,下面還會詳細講述。
-
(parameters):參數列表。它跟一般函數的參數列表一樣,使用規則也相同。在lambda中,如果不需要傳入參數,可以省略。
-
mutable:修飾符。默認情況下,lambda函數總是一個const函數,mutable可以取消它的常量屬性。顯示指定mutable修飾符的時候,參數列表不能省略。
-
->return-type:返回值類型。->這個同C++11新引入的追蹤返回值類型的聲明是一致的,語法也是一致的。不同的是,處於方便,lambda函數在沒有返回值的情況下,可以省略掉(在某些編譯器可以推導出返回值類型的情況亦可省略)。
-
{statement}:函數體。與一般函數的函數體一致,額外可以使用捕獲列表中捕獲的變量。
直觀感受下lambda的使用:
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
using namespace std;
int main()
{
auto f = []() {cout << "hello world!" << endl; };
f(); //hello world!
int a = 123;
auto f1 = [a] { cout << a << endl; };
f1(); //123
auto f2 = [&a] {cout << a << endl; };
a = 789;
f2(); //789
//隱式捕獲:讓編譯器根據函數體中的代碼來推斷需要捕獲哪些變量
auto f3 = [=] {cout << a << endl; };
f3(); //789
auto f4 = [&] {cout << a << endl; };
a = 990;
f4(); //990
auto f5 = [](int a, int b)->int {return a + b; };
printf("%d\n", f5(1, 2)); //3
return 0;
}
lambda函數相較普通函數調用最便捷之處就是其捕獲列表,它可以通過值傳遞捕獲或者引用傳遞方式捕獲,直接在函數體內訪問到上下文(一個代碼塊內)的變量。
如果是普通函數的話,這些都要以參數形式傳遞進去,使代碼十分冗長。那麼捕獲列表的具體語法可以歸納如下:
-
[a]表示值傳遞捕獲變量a(多個參數可以用逗號分隔)
-
[=]表示值傳遞捕獲上下文所有變量
-
[&a]表示引用傳遞捕獲變量a
-
[&]表示引用傳遞捕獲上下文所有變量
-
[this]表示值傳遞捕獲當前的this指針
-
[=, &a, &b]表示值傳遞捕獲上下文所有變量,但是a、b變量以引用傳遞方式捕獲。
-
[&, a, this]表示引用傳遞捕獲上下文所有變量,但是a和this指針以值傳遞方式捕獲
char c = 'a';
float d = 1.11f;
foo([=](int param)->void
{
std::cout << "lambda param is :"<< param <<std::endl;
std::cout << "lambda cap is :" << c << std::endl;
std::cout << "lambda cap2 is :" << d << std::endl;
}, 123);
8、move
是將對象的狀態或者所有權從一個對象轉移到另一個對象,只是轉移,沒有內存的搬遷或者內存拷貝。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{
std::string str = "Hello";
std::vector<std::string> v;
//調用常規的拷貝構造函數,新建字符數組,拷貝數據
v.push_back(str);
std::cout << "After copy, str is \"" << str << "\"\n"; //After move, str is "Hello"
//調用移動構造函數,掏空str,掏空後,最好不要使用str
v.push_back(std::move(str));
std::cout << "After move, str is \"" << str << "\"\n"; //After move, str is ""
std::cout << "The contents of the vector are \"" << v[0]
<< "\", \"" << v[1] << "\"\n"; //The contents of the vector are "Hello", "Hello"
}
二、STL新內容
1、array
-
使用 std::array保存在棧內存中,相比堆內存中的 std::vector,我們就能夠靈活的訪問這裏面的元素,從而獲得更高的性能;同時正式由於其堆內存存儲的特性,有些時候我們還需要自己負責釋放這些資源。
-
使用std::array能夠讓代碼變得更加現代,且封裝了一些操作函數,同時還能夠友好的使用標準庫中的容器算法等等,比如 std::sort。
std::array 會在編譯時創建一個固定大小的數組,std::array 不能夠被隱式的轉換成指針,使用 std::array 很簡單,只需指定其類型和大小即可:
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <algorithm>
#include <array>
void foo(int* p)
{
}
int main()
{
std::array<int, 4> arr = {4,3,1,2};
foo(&arr[0]); //OK
foo(arr.data()); //OK
//foo(arr); //wrong
std::sort(arr.begin(), arr.end()); //排序
return 0;
}
2、forward_list
std::forward_list 使用單向鏈表進行實現,提供了 O(1) 複雜度的元素插入,不支持快速隨機訪問(這也是鏈表的特點),也是標準庫容器中唯一一個不提供 size() 方法的容器。當不需要雙向迭代時,具有比 std::list 更高的空間利用率。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <forward_list>
int main()
{
std::forward_list<int> list1 = { 1, 2, 3, 4 };
//從前面向foo1容器中添加數據,注意不支持push_back
list1.pop_front(); //刪除鏈表第一個元素
list1.remove(3); //刪除鏈表值爲3的節點
list1.push_front(2);
list1.push_front(1);
list1.push_front(14);
list1.push_front(17);
list1.sort();
for (auto &n : list1)
{
if (n == 17)
n = 19;
}
for (const auto &n : list1)
{
std::cout << n << std::endl; //1 2 2 4 14 19
}
return 0;
}
3、unordered_map和unordered_set
無序容器中的元素是不進行排序的,內部通過 Hash 表實現,插入和搜索元素的平均複雜度爲 O(constant),在不關心容器內部元素順序時,能夠獲得顯著的性能提升。
C++11 引入了兩組無序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。
下面給出unordered_map和unordered_set的使用方法。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
void foo(int* p)
{
}
int main()
{
//unordered_map usage
std::unordered_map<std::string, int> um = { {"2",2},{"1",1},{"3",3} };
//遍歷
for (const auto &n : um)
{
std::cout << "key:" << n.first << " value:" << n.second << std::endl;
}
std::cout << "value:" << um["1"] << std::endl;
//unordered_set usage
std::unordered_set<int> us = { 2,3,4,1};
//遍歷
for (const auto &n : us)
{
std::cout << "value:" << n << std::endl;
}
std::cout << "value:" << us.count(9) << std::endl; //判斷一個數是否在集合內,1存在0不存在
std::cout << "value:" << *us.find(1) << std::endl; //查找一個特定的數是否在集合內,找到就返回該數的迭代器位置
return 0;
}
三、智能指針
1、shared_ptr
shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,刪除所指向的堆內存。賦值非原子操作,多線程使用需加鎖。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <memory>
#include <iostream>
int main()
{
//auto ptr = std::make_shared<int>(10);
std::shared_ptr<int> ptr(new int(10));
std::shared_ptr<int> ptrC(ptr);
auto ptr2 = ptr;
{
auto ptr3 = ptr2;
std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl; //4
std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl; //4
}
std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl; //3
std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl; //3
int *p = ptr.get(); //獲取原始指針
std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl; //3
std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl; //3
return 0;
}
2、unique_ptr
一種獨佔的智能指針,它禁止其他智能指針與其共享同一個對象,從而保證代碼的安全。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <stdio.h>
#include <memory>
#include <iostream>
int main()
{
std::unique_ptr<int> ptr(new int(10));
//auto ptr2 = ptr; //非法
//雖說unique_ptr是不可複製的,但我們可以使用std::move將其獨佔權轉移到其他的unique_ptr
auto ptr2(std::move(ptr));
std::cout << *ptr2 << std::endl;
return 0;
}
3、weak_ptr
weak_ptr是爲了配合shared_ptr而引入的一種智能指針,因爲它不具有普通指針的行爲,沒有重載operator*和->,它的最大作用在於協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。
// testC++11.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
#include "pch.h"
#include <iostream>
#include <map>
#include <list>
#include <vector>
#include <forward_list>
using namespace std;
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
A() {
cout << "A !" << endl;
}
~A() {
cout << "~~~~~A !" << endl;
}
void setBB(shared_ptr<B> val) {
bb = val;
}
private:
//shared_ptr<B> bb;
weak_ptr<B> bb; //用循環引用的指針之一爲weak_ptr,來解決循環引用
};
class B {
public:
B() {
cout << "B !" << endl;
}
~B() {
cout << "~~~~~~B !" << endl;
}
void setAA(shared_ptr<A> val) {
aa = val;
}
private:
shared_ptr<A> aa;
};
int main()
{
shared_ptr<A> aa(new A());
shared_ptr<B> bb(new B());
aa->setBB(bb);
bb->setAA(aa);
return 0;
}
參考鏈接:
https://www.cnblogs.com/skyfsm/p/9038814.html
https://www.cnblogs.com/lenmom/p/9198126.html
https://www.jianshu.com/p/5ea6e6feeaf3
https://www.jianshu.com/p/234b818f289a