C++11新特性總結 (一)

薰衣草的旋律發表於2016-07-04

1. 概述

最近在看C++ Primer5 剛好看到一半,總結一下C++11裡面確實加了很多新東西,如果沒有任何瞭解,別說自己寫了,看別人寫的程式碼估計都會有些吃力。C++ Primer5是學習C++11的比較好的書籍。這篇文章僅總結關於C++11中的新東西,老的東西不再贅述。本文的所有程式碼僅僅值列出關鍵程式碼,並且所有特性都已經用編譯器驗證過,我的編譯環境 gcc 5.3.1  g++ 5.3.1 ,據說 4.7以上的版本已經支援大部分C++11的特性,VS系列的編譯器對C++11的支援情況不甚瞭解,如果沒有合適的編譯器,可以點選這裡 C++shell 這是一個線上的C++編譯系統,裡面有多個選項可以選擇C++98,C++11,C++14等,可以在這裡面驗證C++11的正確性。

2. long long 型別

long long 型別實際上沒有在C++ 98中存在,而之後被C99標準收錄,其實現在市面上大多數編譯器是支援 long long 的,但是這個型別正式成為C++的標準型別是在C++11中。標準要求long long至少是64位也就是8個位元組。一個字面常量使用LL字尾表示long long型別,使用ULL字尾表示unsigned long long 型別。

3. 列表初始化

C++11中全面加入了列表初始化的功能,包括對vector,map,值型別,struct等等都可以使用列表初始化,還可以在函式中返回一個花括號括起來的列表,而在這之前我們只能對陣列進行列表初始化:

//陣列列表初始化
int xx[5]={1,2,3,4,5};
int yy[]={6,7,8,9,0};

//值型別進行初始化
int a{10}; 
int b={10};
int c={10.123}; // 編譯器報錯,g++ 5.3.1當列表初始化用於值型別的時候,如果有精度損失,編譯器會報錯。

//列表初始化還可以用結構體
typedef struct Str{
   int x;
   int y;
}Str;
Str s = {10,20};

//列表初始化類,必須是public成員,如果含有私有成員會失敗
class Cls{
public:
   int x;
   int y;
};
Cls c  = {10,20};

//vector不僅可以使用列表初始化,還可以使用列表進行賦值,陣列不能用列表賦值
vector<int>v1={1,2,3,4,5,6,7,8,9}; // 初始化
vector<int>v2;
v2={3,4,5,6,7}; //賦值

//map列表初始化
map<string ,int> m = {
      {"x",1},
      {"y",2},
      {"z",3}
};

//用函式返回初始化列表只展示關鍵程式碼,相關標頭檔案自行新增
//同理結構體,類,map的返回也可以使用初始化列表返回
vector<int> getVector()
{
  return {1,2,3,4,5};
}

int main()
{
  vector<int> v = getVector();
  cout<<v[0]<<v[1]<<v.size()<<endl;
  return 0 ;
}

4. nullptr 空指標

C++11中新加入的字面值表示不指向任何物件的空指標,以前我們常常用一個預定義的巨集NULL來表示空指標,實際上NULL的值是0,新標準推薦使用nullptr而不是NULL

5. constexpr變數

我們在定義常量的時候一般使用const來定義,一個常量必須在定義的時候進行初始化,並且之後不可更改。一個常量必須使用一個常量表示式進行初始化,並且在編譯期間就可以得到常量的值,但是如何確定一個表示式就是常量表示式呢,這個通常是由程式設計師自己確定的,例如:

const int a =20; 
//20是一個字面值,當然也是一個常量表示式,所以用20來為a賦值是沒有問題的
//然而下面的程式碼也可以通過編譯,g++ 5.3.1
int a = 20 ;
const int x =  a; 
int b[x]={0};

為常量x賦值的是一個變數a,這樣做應該是不合理的,但是編譯器沒有報告任何錯誤,當然這種錯誤是顯而易見的,但是在複雜的系統中如何判斷一個表示式是否是常量表示式是很困難的,例如這裡的a我們一眼就可以判斷其並不是一個常量表示式。為此C++11提供了一個新的關鍵字constexpr,使用該關鍵字定義的常量,由編譯器檢查為其賦值的表示式是否是常量表示式,例如上面的程式碼改成:

int a = 20 ;
constexpr int x =  a; 

編譯器編譯的時候就會報錯說a並不是常量。顯然constexpr關鍵字將常量表示式的檢查轉交給編譯器處理,而不是程式設計師自己,所以使用constexpr定義常量要比const安全。

6. constexpr函式

普通的函式一般是不能用來為constexpr常量賦值的,但是C++11允許定義一種constexpr的函式,這種函式在編譯期間就可以計算出結果,這樣的函式是可以用來為constexpr賦值的。定義constexpr函式需要遵守一些約定,函式的返回型別以及所有形參的型別都應該是字面值,一般情況下函式體中必須有且只有一條return語句。

constexpr int size()
{
	return 42;
}

constexpr int si = size();

執行初始化的時候編譯器將函式的呼叫替換成結果值,constexpr函式體中也可以出現除了return之外的其他語句,但是這些語句在執行時不應該執行任何操作,例如空語句,using宣告等。constexpr函式允許其返回值並非是一個字面值,例如:

constexpr int size(int s)
{
	return s*4;
}

int a = 20;
const int b = 30;
constexpr int c = 40;
constexpr int si = size(a);  //error a是一個變數所以函式返回的是一個可變的值
constexpr int si1 = size(20); //ok 函式返回的實際上是一個常量
constexpr int si2 = size(b);  //ok
constexpr int si3 = size(c);  //ok

由上可知constexpr函式並不一定返回常量,如果應用於函式的引數是一個常量表示式則返回常量,否則返回變數,而該函式呼叫到底是一個常量表示式還是非常量表示式則由編譯器來判斷。這就是constexpr的好處。

7. using型別別名

型別別名其實早在C語言中就有了,一般情況下我們使用關鍵字typedef來宣告一個型別的別名,在C++11中增加了另一種宣告型別別名的方法就是使用using關鍵字,using關鍵字在C++11以前一般用來引用名稱空間。

typedef int INT;  // 右側符號代表左側
using INT2 = int; // 左側符號代表右側

INT a = 20;
INT2 b = 30;

8. auto型別指示符

我們定義一個變數的時候首先必須確定該變數的型別,而很多時候並不是我們先需要一個變數然後為該變數賦值合適的資料,而是我們有一個值但是我們卻不知道該用什麼型別的變數儲存它,特別是C++的模版使用的非常廣泛,有時候要定義一個變數,其型別是很複雜的會帶有模版的型別引數,例如一個最常見的例子:

map<string ,int> m ; 
map<string,int>::iterator it = m.begin();

上面的例子中我們定義了一個map<string,int>::iterator型別的變數來存放 m.begin()的值,這個例子相對來說還不算困難但是我在開始使用map容器的時候也曾經被搞暈過,如果map是一個 map<string,double> 型別則需要定義一個  map<string.double>::iterator it 來存放了。特別是如果map和vector之間互相巢狀的情況就更容易弄錯了。定義這種型別變數的另一個缺點就是一個型別的名字往往會很長,試想一下程式程式碼中通篇都是這種變數宣告,恐怕不會有幾個人看著舒服吧。不過沒關係C++11為我們定義了一個新的關鍵字 auto 用來定義變數,而變數的型別由編譯器自動根據賦值的表示式推匯出來,不需要我們顯示定義了。因為auto定義的變數的型別由編譯器根據賦值的表示式推導,所以auto定義的變數必須有初始值,否則編譯器沒法確定該變數的型別。

auto x = 20; // x 是int
auto y = 3.14; // y 是double
map<string ,int> m ; 
auto it = m.begin(); //it 是map<string,int>::iterator

這樣是不是方便了不少,而且程式看起來更加簡潔了
auto可以在一條語句中宣告多個變數,但是要保證語句中的基礎資料型別只有一個,例如:

auto i=10,*p=&i; // OK i是int,p是int*
auto a=10,b=3.14; // Error 型別是int還是double ?

9. decltype型別指示符

有時候會有這樣的需求,我們需要知道一個表示式的型別,並使用該型別去定義一個變數,例如:

int a = 10;
int b = 20;
auto c = a + b; // OK a+b的型別是int,此時c的型別是int,並且c的值是 a+b

auto可以解決部分問題,例如我們定義的變數的型別就是表示式 a+b 的型別,但是如果我們僅僅需要定義一個與表示式 a+b 的型別相同的變數,但是我們又不希望將表示式a+b的值賦值給剛剛定義的變數,我們希望賦另外一個值或者是僅僅定義變數而不賦值呢。 這就需要用到C++11 提供的另一個型別說明符 decltype了。decltype作用於一個表示式,並且返回該表示式的型別,在此過程中編譯器分析表示式的型別,並不會計算表示式的值。例如

int a = 10;
int b = 20;
decltype(a+b) c = 50; // OK c的型別就是 a+b 的型別int

對於引用型別decltype有一些特別的地方:

int a = 20 ;
int &b = a;
decltype(b) c ;  // Error c是引用型別必須賦值
decltype(b) d = a; // OK  d是引用型別,指向a

可以看到decltype如果作用於一個引用型別,其得到的還是一個引用型別。我們知道一個引用型別在使用的時候一般會當作其關聯的那個變數的同義詞處理,例如如果使用 cout<<b<<endl; 其中b實際上相當於a,但是decltype作用於引用型別的時候會保留引用性質。

如果一個表示式是一個解指標引用的操作,decltype得到的也是一個引用型別:

int a = 20 ;
int *p = &a;
decltype(*p) c = a;  // c的型別是int&
c = 50;
cout<<a<<endl;  // 輸出50

當decltype作用於一個變數的時候,變數加不加括號是有區別的,例如:

int a = 20;
decltype(a) b = 30; //ok b的型別是 int
decltype((a)) c = a ; // ok c的型別是int& 其關聯變數 a

加上括號之後編譯器會把(a)當作是一個表示式處理,而變數是一種可以作為賦值語句左值的表示式,所以會解釋成引用型別。

C++11新特性(二)

如果您覺得這篇文章對您有幫助,需要您的【贊】,讓更多的人也能看見哦

相關文章