ubuntu 16.04 自帶gcc 5.4 支持c 11
ubuntu 18.04 自帶gcc 7.3 支持c 14
查看編譯器支持:
c 11
c 14
c 17
c 11 feature
- nullptr/constexpr
- enum class
- auto/decltype
- for iteration
- initialize_list
- lamda
- template
- rvalue/move
nullptr
以前的編譯器實(shí)現(xiàn),可能會(huì)把NULL定義為0.所以,當(dāng)你有兩個(gè)同名函數(shù)foo(int),foo(char*)時(shí),foo(NULL)你的本意可能是調(diào)用后者,但實(shí)際調(diào)用的是前者.nullptr的引入就是為了解決這個(gè)問(wèn)題.
void foo(char *ch)
{
std::cout << "call foo(char*)" << std::endl;
}
void foo(int i)
{
std::cout << "call foo(int)" << std::endl;
}
void test_nullptr()
{
if (NULL == (void *)0)
std::cout << "NULL == 0" << std::endl;
else
std::cout << "NULL != 0" << std::endl;
foo(0);
//foo(NULL); // 編譯無(wú)法通過(guò)
foo(nullptr);
}
constexpr
常量表達(dá)式的引入是為了提高性能,將運(yùn)行期間的行為放到編譯期間去完成.如下代碼
constexpr long int fib(int n)
{
return (n <= 1)? n : fib(n-1) fib(n-2);
}
void test_constexpr()
{
auto start = std::chrono::system_clock::now();
const long int res = fib(30);
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
cout << "elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
long int res2 = fib(30);
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "elapsed_seconds:"<<elapsed_seconds.count()<<endl;
由于傳給fib(int n)的參數(shù)是30,是個(gè)固定的值.所以可以在編譯期間就求出來(lái).當(dāng)我們用const long和long聲明返回值時(shí),對(duì)前者會(huì)在編譯期就做計(jì)算優(yōu)化.如下圖,可以看到二者運(yùn)行時(shí)間有數(shù)量級(jí)上的差異.

enum class
c 11中把枚舉認(rèn)為是一個(gè)類,以前的標(biāo)準(zhǔn)中枚舉值就是一個(gè)整數(shù)而已.看如下代碼
void test_enum()
{
enum color {black,white};
//auto white = true; //redeclared
enum class Color{r,g,b};
auto r = 1;
}
以前的標(biāo)準(zhǔn)中enum color {black,white};相當(dāng)于定義了兩個(gè)int變量black,white.所以//auto white = true;編譯期會(huì)報(bào)錯(cuò).引入了enum class則不再有這個(gè)問(wèn)題.
auto/decltype
這個(gè)是c 11中非常重要的一點(diǎn)特性,極大地簡(jiǎn)化了編碼的復(fù)雜.編譯期自動(dòng)去推導(dǎo)變量的類型.再也不需要我們操心了.
auto做變量類型推導(dǎo),decltype做表達(dá)式類型推導(dǎo).
void test_auto()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
for (std::vector<int>::iterator it = v.begin(); it != v.end(); it )
{
cout << *it << endl;
}
for (auto it = v.begin(); it != v.end(); it )
{
cout << *it << endl;
}
for (auto &i : v)
{
cout << i << endl;
i = 100; //修改掉v中的元素值
}
for (auto i : v)
{
cout << i << endl; //輸出100
i = 200; //不會(huì)修改v中的元素值
}
for (auto i : v)
{
cout << i << endl; //輸出為100
}
}
用法如上述代碼所示.比如遍歷vector,寫法由for (std::vector::iterator it = v.begin(); it != v.end(); it )簡(jiǎn)化到for (auto it = v.begin(); it != v.end(); it ),如果配合上for迭代,則進(jìn)一步簡(jiǎn)化到for (auto &i : v).
注意c 11中,auto變量自動(dòng)推導(dǎo)有2個(gè)例外
- //int add(auto x,auto y); //c 14才支持函數(shù)參數(shù)為auto
- //auto arr[10] = {0}; //編譯錯(cuò)誤 auto不能用于數(shù)組類型的推導(dǎo)
decltype做表達(dá)式類型推導(dǎo).假設(shè)我們要寫一個(gè)加法的模板函數(shù),比如
template<typename R, typename T, typename U>
R add(T x, U y)
{
return x y;
}
對(duì)于返回值類型R的話,我們必須在模板的參數(shù)列表中手動(dòng)指明.調(diào)用的時(shí)候形式則為add<R,T,U>(x,y).而很可能我們并不知道返回類型是什么,比如我使用一個(gè)第三方庫(kù),我只是想對(duì)x,y做一個(gè)add操作,后續(xù)我可能會(huì)從x,y取一些數(shù)據(jù)做后續(xù)處理,此時(shí)我并不關(guān)心add操作返回值類型是什么.
c 11中用decltype自動(dòng)推導(dǎo)表達(dá)式類型解決這個(gè)問(wèn)題.
template<typename T, typename U>
auto add_cxx11(T x, U y) -> decltype(x y)
{
return x y;
}
用decltype(x y)聲明返回值類型,讓編譯器自動(dòng)推導(dǎo)就好了.
在c 14中,有了更好的支持,已經(jīng)不再需要顯示地聲明返回值類型了.
//c 14支持
/*
template<typename T, typename U>
auto add_cxx14(T x, U y)
{
return x y;
}
*/
for迭代
基于范圍的for迭代,非常類似與python中的用法了.代碼在前面auto/decltype一節(jié)已經(jīng)展示.需要注意的是for (auto i : v)拿出的i是副本,不會(huì)修改v中的元素的值.for (auto &i : v)拿到的是引用,會(huì)修改掉v中的值.
初始化列表
c 11之前對(duì)象的初始化并不具有統(tǒng)一的表達(dá)形式,比如
int a[3] = {4,5,6}
/*
以前類的初始化只能通過(guò)拷貝構(gòu)造函數(shù)或者()
比如 */
class A
{
A(int x,int y,int z)
}
A b;
A a(b);
A a2(1,2,3)
c 11提供了統(tǒng)一的語(yǔ)法來(lái)初始化任意對(duì)象.
比如
class XX
{
public:
XX(std::initializer_list<int> v):v_int(v)
{
}
vector<int> v_int = {3,4,5};
};
XX xxxxxx = {6,7,8,9,10};
或者
struct A
{
int a_;
int b_;
};
A a {1,2};
此外,初始化列表還可以作為函數(shù)的入?yún)ⅲ?/p>
void f_take_initialize(initializer_list<int> list)
{
int sum = 0;
for (auto l : list)
{
sum = l;
}
cout << sum << endl;
}
void test_initialize()
{
f_take_initialize({1, 2, 3});
}
using關(guān)鍵字
using并不是c 11才有的,但是c 11中提升了這個(gè)關(guān)鍵字的功能,用于取代typedef,提供更加統(tǒng)一的表達(dá)形式.
template <typename T, typename U>
class SuckType;
typedef SuckType<std::vector<int>, std::string> NewType;
using NewType = SuckType<std::vector<int>, std::string>;
typedef int(*mycallback)(void*);
using mycallback = int(*)(void*);
lamda表達(dá)式
即匿名函數(shù),這也是c 11中一個(gè)相當(dāng)重要的特性.有的時(shí)候,我們可能需要使用某個(gè)功能,但這個(gè)功能可能只在某一個(gè)函數(shù)內(nèi)部有用,那么我們則沒(méi)有必要去寫一個(gè)全局函數(shù)或者類的成員函數(shù)去抽象這個(gè)功能.這時(shí)候就可以實(shí)現(xiàn)一個(gè)匿名函數(shù).
[捕獲列表](參數(shù)列表) -> 返回類型
{
// 函數(shù)體
}
匿名函數(shù)的形式如上所示.參數(shù)列表,返回類型都很好理解.默認(rèn)情況下,匿名函數(shù)是不可以使用匿名函數(shù)外部的變量的,捕獲列表就起到一個(gè)傳遞外部參數(shù)的作用
捕獲列表有下述幾種情況
- 值捕獲
- 引用捕獲
- 隱式捕獲
- 表達(dá)式捕獲 //c 14支持
值得注意的是,值捕獲的話,值是在匿名函數(shù)定義好的時(shí)候就做傳遞,而不是調(diào)用的時(shí)候做傳遞.
如下代碼
void test_lamda()
{
auto add_func = [](int x,int y) -> int{return x y;};
auto sum = add_func(3,4);
cout<<"sum="<<sum<<endl;
int a = 1;
auto copy_a = [a]{return a;};
a = 100;
auto a_i_get = copy_a();
printf("a_i_get=%d,a=%d\n",a_i_get,a);
auto copy_a_refrence = [&a]{a=50;return a;};
auto a_refrence_i_get = copy_a_refrence();
printf("a_i_get=%d,a=%d\n",a_refrence_i_get,a);
int b = 1;
auto f = [&](){return b;} ;
}

注意,第一個(gè)輸出的a_i_get=1而不是100.盡管a是100.這是因?yàn)樵赾opy_a定義的時(shí)候,a的值是1. copy_a_refrence的捕獲列表是引用,函數(shù)體內(nèi)修改a=50,所以輸出的是50
當(dāng)要捕獲的變量非常多的時(shí)候,一個(gè)個(gè)寫是非常麻煩的,所以可以直接在捕獲列表里用=和&表示傳值和傳引用
[](){} //傳空
[=](){} //傳值
[&](){} //傳引用
變長(zhǎng)模板
template<typename... Ts> class Magic;
c 11之前,模板的參數(shù)是固定個(gè)數(shù)的.c 11之后支持不定長(zhǎng)參數(shù)的模板.用...表示不定長(zhǎng).
c 11標(biāo)準(zhǔn)庫(kù)新引入的數(shù)據(jù)結(jié)構(gòu)tuple就是用了這個(gè)特性實(shí)現(xiàn)的.
move語(yǔ)義和右值引用.
這也是c 11中引入的非常重要的一個(gè)特性.主要作用在于性能的提升.
通俗地講,一個(gè)可以取地址的變量,即為左值,不可以取地址的即為右值.
以之前的vector.push_back()為例,插入的是數(shù)據(jù)的一份拷貝.當(dāng)要插入的數(shù)據(jù)結(jié)構(gòu)本身內(nèi)存特別大的時(shí)候,這種拷貝帶來(lái)的性能消耗是非常大的.
move的引入即用于解決此類問(wèn)題,move()將一個(gè)值轉(zhuǎn)換為右值. 標(biāo)準(zhǔn)庫(kù)的數(shù)據(jù)結(jié)構(gòu)里實(shí)現(xiàn)了void push_back( T&& value );的版本.
move()名字叫move,但他本身并不做任何move的操作,move更類似于淺拷貝,即拷貝待拷貝內(nèi)容的地址過(guò)去.但是淺拷貝并不會(huì)使得之前的對(duì)象失去所有權(quán),而move會(huì),所以move所做的事情就是資源的所有權(quán)的轉(zhuǎn)移

先看一下這段代碼
void test_move()
{
std::string str = "Hello world.";
std::vector<std::string> v;
// 將使用 push_back(const T&), 即產(chǎn)生拷貝行為
v.push_back(str);
// 將輸出 "str: Hello world."
std::cout << "str: " << str << std::endl;
// 將使用 push_back(const T&&), 不會(huì)出現(xiàn)拷貝行為
// 而整個(gè)字符串會(huì)被移動(dòng)到 vector 中,所以有時(shí)候 std::move 會(huì)用來(lái)減少拷貝出現(xiàn)的開(kāi)銷
// 這步操作后, str 中的值會(huì)變?yōu)榭? v.push_back(std::move(str));
// 將輸出 "str: "
std::cout << "str: " << str << std::endl;
}
即我們前面提到的"hello world"這些內(nèi)容的所有權(quán)的轉(zhuǎn)移.move之后,原先的str已經(jīng)失去了所有權(quán),打印為空.
再來(lái)看一段代碼.
void test_move_efficience()
{
vector<int> v_i;
for(auto i = 0;i<10000000;i )
{
v_i.push_back(i);
}
auto f = [&](vector<int> v)
{
return v;
};
auto g = [&](vector<int>& v)
{
return v;
};
auto start = std::chrono::system_clock::now();
f(v_i);
auto end = std::chrono::system_clock::now();
std::chrono::duration<double,std::milli> elapsed_seconds = end-start;
cout << "f(v_i) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
f(move(v_i));
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "f(move(v_i)) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
g(v_i);
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "g(v_i) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
list<int> li;
for(auto i = 0;i<10000000;i )
{
li.push_front(i);
li.push_back(i 1);
}
vector<list<int>> vl;
start = std::chrono::system_clock::now();
vl.push_back(li);
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "vl.push_back(li) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
start = std::chrono::system_clock::now();
vl.push_back(move(li));
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
cout << "vl.push_back(move(li)) elapsed_seconds:"<<elapsed_seconds.count()<<endl;
}
先貼輸出

我們知道,函數(shù)傳參的時(shí)候,值傳遞的話,實(shí)際上是做了一份參數(shù)的拷貝.所以f(v_i)的開(kāi)銷是最高的,達(dá)15ms,f(move(v_i))的話,沒(méi)有了拷貝的操作,耗時(shí)1ms.對(duì)于g(v_i)的話,參數(shù)為引用,沒(méi)有拷貝操作,耗時(shí)只有0.000594ms. 因?yàn)椴还苁莊(),g(),函數(shù)內(nèi)部都是非常簡(jiǎn)單的,沒(méi)有任何操作,而move()本身會(huì)帶來(lái)一定消耗,所以f(move(v_i))耗時(shí)比g(v_i)更高.
而move語(yǔ)義更常見(jiàn)的使用在于標(biāo)準(zhǔn)庫(kù)里的封裝.比如上述代碼,vl.push_back(li)和vl.push_back(move(li))調(diào)用了不同的push_back(). 其性能是有著數(shù)量級(jí)的差異. 以我們的例子為例,其耗時(shí)分別為1207ms和0.001548ms.
所以如果我們自己的數(shù)據(jù)類型,內(nèi)部含有大量的數(shù)據(jù),我們應(yīng)當(dāng)自己去實(shí)現(xiàn)move構(gòu)造函數(shù).這樣使用標(biāo)準(zhǔn)庫(kù)時(shí)才可以更好的提高性能.
來(lái)源:https://www./content-1-442901.html
|