自己一直用的是C++98規范來編程,對於C++11只聞其名卻沒用過其特性。近期因為工作的需要,需要掌握C++11的一些特性,所以查閱了一些C++11資料。因為自己有C++98的基礎,所以從C++98過渡到C++11並不算特別吃力,讀了一些書籍后,算是對C++11有了個比較基礎的理解,感覺程序員還是要經常保持新語言新特性的更新,現在 C++ 標准都出到C++17了!這篇文章就是對C++11一些常用新特性的一些總結,以C++98和 C++11在語法上的差異來突出C++11新特性的非凡優勢。
一、新語法
1.自動類型推導auto
auto的自動推導,用於從初始化表達式中推斷出變量的數據類型。
//C++98
int a = 10;
string s = "abc";
float b = 10.0;
vector<int> c;
vector<vector<int> > d;
map<int, string> m;
m[1] = "aaa";
map<int, string>::iterator it = m.begin();
//C++11
auto a1 = 10; //a1為int
auto s1 = "abc"; //s1為string
auto b1 = b;
auto c1 = c;
auto d1 = d;
auto e1 = 'a';
int* x = &a1;
auto d1 = x;
auto m1 = m.begin();
auto x=1,y=2; //ok
auto i=1.j=3.14; //compile error
double a2 = 3.144;
const auto a3 = a2; //const double
auto a4 = a2; //double
volatile int c2 = 3;
auto c3 = c2; //int
2.萃取類型decltype
decltype可以通過一個變量或表達式得到類型。
#include <iostream>
#include <vector>
using namespace std;
int add(int a)
{
return ++a;
}
void fun(int a)
{
cout << "call function: [int]\n" << endl;
}
void fun(int *a)
{
cout << "call function: [int*]\n" << endl;
}
int main()
{
//C++11
int aa = 10;
decltype(aa) bb = 11;
string ss = "hello intel";
decltype(ss) ss1 = "hello";
const vector<int> vec(1);
decltype(vec[0]) cc = 1;
decltype(0) dd = vec[0]; //dd是int類型
decltype(add(1)) ee; //int
int a[5];
decltype(a) ff; //int[5]
//decltype(fun) gg; 無法通過編譯,是個重載函數
return 0;
}
3.nullptr
空指針標識符nullptr是一個表示空指針的標識,他不是一個整數,這是與我們常用的NULL宏的區別。NULL只是一個定義為常整數0的宏,而nullptr是C++11的一個關鍵字,一個內建的標識符。
#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()
{
//C++11
fun(NULL); //call function: [int]
fun(nullptr); //call function: [int*]
int* p = NULL;
fun(p); //call function: [int*]
return 0;
}
4.區間迭代range for
C++98和C++11在使用語法上的差異如下:
#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)
{
cout << d << endl;
}
return 0;
}
值得指出的是,是否能夠使用基於范圍的for循環,必須依賴一些條件。首先,就是for循環迭代的范圍是可確定的。對於類來說,如果該類有begin和end函數,那么for_each之間就是for循環迭代的范圍。對於數組而言,就是數組的第一個和最后一個元素間的范圍。其次,基於范圍的for循環還要求迭代的對象實現+ + 和==等操作符。對於STL中的容器,如string、array、map等使用起來是不會有問題的。下面是C++11操作vector和數組的實踐:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vec(8, 1);
//C++11
cout << "C++11 value range for:" << endl;
/*d非引用,修改d不會影響vector里的值*/
for (auto d : vec) //d中存儲的是vec中的值
{
d = 2;
}
for (auto d : vec)
{
cout << d << endl;
}
cout << "C++11 reference range for:" << endl;
/*當迭代變量d為引用時,vector里的值可以被修改*/
for (auto &d : vec)
{
d = 2;
}
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;
}
5.返回類型后置語法
先看下面這個例子,編譯器在推導decltype(t1+t2)時表達式中t1和t2都未聲明,所以編譯失敗。
#include <iostream>
#include <vector>
using namespace std;
template<class T1, class T2>
decltype(t1 + t2) sum(T1 t1, T2 t2)
{
return t1 + t2;
}
int main()
{
auto total = sum(1, 2);
cout << total << endl;
return 0;
}
所以C++11引入新語法,即把函數的返回值移至參數聲明之后,復合符號->decltype(t1+t2)被稱為追蹤返回類型。而原本的函數返回值由auto占據。
#include <iostream>
#include <vector>
using namespace std;
template<class T1, class T2>
auto sum(T1 t1, T2 t2) ->decltype(t1+t2)
{
return t1 + t2;
}
int main()
{
auto total = sum(1, 2);
cout << total << endl;
return 0;
}
6.final和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函數
};
struct D2 : public B
{
void f1(int) const final; //不許后續的其他類覆蓋
};
struct D3 :public D2
{
void f2();
void f1(int) const; //error,final函數不可覆蓋
};
final還可以用於防止繼承的發生
class NoDerived final
{
};
class Bad :NoDerived //NoDerived不可做基類
{
};
class Base
{
};
class Last final :Base
{
};
class Bad2 :Last //Last不可做基類
{
};
7.=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);
};
8.lambda表達式
簡單來說,Lambda函數也就是一個函數(匿名函數),它的語法定義如下:
[capture](parameters) mutable ->return-type{statement}
- [=,&a,&b]表示以引用傳遞的方式捕捉變量a和b,以值傳遞方式捕捉其它所有變量;
- [&,a,this]表示以值傳遞的方式捕捉變量a和類的this指針,引用傳遞方式捕捉其它所有變量。
#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表達式在C++下的應用,排序
#include <stdio.h>
#include <algorithm>
#include <vector>
using namespace std;
void print(char arr[], int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
bool cmp(char a, char b)
{
if (a > b)
return true;
else
return false;
}
int main()
{
//c++98
char arr1[] = { 2,5,2,1,5,89,36,22,89 };
int len = sizeof(arr1) / sizeof(char);
sort(arr1, arr1 + len, cmp);
print(arr1, len);
//c++11
char arr2[] = { 2,5,2,1,5,89,36,22,89 };
int len2 = sizeof(arr2) / sizeof(char);
sort(arr2, arr2 + len2, [](char a, char b)->bool {return a > b; });
print(arr2, len2);
return 0;
}
9.std::move
std::move是為性能而生,通過std::move,可以避免不必要的拷貝操作。std::move是將對象的狀態或者所有權從一個對象轉移到另一個對象,只是轉移,沒有內存的搬遷或者內存拷貝。
#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.std::array
-
使用 std::array保存在棧內存中,相比堆內存中的 std::vector,我們就能夠靈活的訪問這里面的元素,從而獲得更高的性能;同時正式由於其堆內存存儲的特性,有些時候我們還需要自己負責釋放這些資源。
-
使用std::array能夠讓代碼變得更加現代,且封裝了一些操作函數,同時還能夠友好的使用標准庫中的容器算法等等,比如 std::sort。
std::array 會在編譯時創建一個固定大小的數組,std::array 不能夠被隱式的轉換成指針,使用 std::array 很簡單,只需指定其類型和大小即可:
#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.std::forward_list
std::forward_list 使用單向鏈表進行實現,提供了 O(1) 復雜度的元素插入,不支持快速隨機訪問(這也是鏈表的特點),也是標准庫容器中唯一一個不提供 size() 方法的容器。當不需要雙向迭代時,具有比 std::list 更高的空間利用率。
#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.std::unordered_map和std::unordered_set
無序容器中的元素是不進行排序的,內部通過 Hash 表實現,插入和搜索元素的平均復雜度為 O(constant),在不關心容器內部元素順序時,能夠獲得顯著的性能提升。
C++11 引入了兩組無序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。
下面給出unordered_map和unordered_set的使用方法。
#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. std::shared_ptr
shared_ptr使用引用計數,每一個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減為0時,刪除所指向的堆內存。shared_ptr內部的引用計數是安全的,但是對象的讀取需要加鎖。
#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;
}
3 2. std::unique_ptr
std::unique_ptr 是一種獨占的智能指針,它禁止其他智能指針與其共享同一個對象,從而保證代碼的安全:
#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. std::weak_ptr
先觀察下面的代碼,如果我們在類father中使用的是shared_ptr
father !
son !
以上問題就是shared_ptr的環形引用問題。為了避免shared_ptr的環形引用問題,需要引入一個弱引用weak_ptr, weak_ptr是為了配合shared_ptr而引入的一種智能指針,弱引用不會引起引用計數增加,它更像是shared_ptr的一個助手而不是智能指針,因為它不具有普通指針的行為,沒有重載operator*和->,它的最大作用在於協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況.
#include <iostream>
#include <memory>
using namespace std;
class father;
class son;
class father {
public:
father() {
cout << "father !" << endl;
}
~father() {
cout << "~~~~~father !" << endl;
}
void setSon(shared_ptr<son> s) {
son = s;
}
private:
//shared_ptr<son> son;
weak_ptr<son> son; // 用weak_ptr來替換
};
class son {
public:
son() {
cout << "son !" << endl;
}
~son() {
cout << "~~~~~~son !" << endl;
}
void setFather(shared_ptr<father> f) {
father = f;
}
private:
shared_ptr<father> father;
};
void test() {
shared_ptr<father> f(new father());
shared_ptr<son> s(new son());
f->setSon(s);
s->setFather(f);
}
int main()
{
test();
return 0;
}
輸出:
father !
son !
~~~~~~son !
~~~~~father !