1. 程式人生 > >C++11新特性(一)

C++11新特性(一)

auto關鍵字

C語言中其實就有auto關鍵字,修飾可變化的量,但是由於平時我們直接使用int a = 10;也是宣告變數,編譯器已經自動幫我們加上了auto關鍵字,是C語言中應用最廣泛的一種型別,也就是說,省去型別說明符auto的都是自動變數! 隨著時代進步,Java10中有一個新特性,就是使用var來定義變數,當然前提是型別可推導,語言總是在演化,C++11也是支援了這個新特性,不過在C++11中是auto關鍵字:使用auto的時候,編譯器根據上下文情況,確定auto變數的真正型別! 接下來演示一下auto的使用:

int main() {
	auto a = 10;
	auto b = 20;

	list<
string> s; list<string>::iterator be = s.begin(); list<string>::iterator en = s.end(); auto be2 = s.begin(); //很顯然使用auto可以減少很多不必要的程式碼 auto en2 = s.end(); return 0; }

很顯然使用auto可以減少很多不必要的程式碼,但是:

  • auto不能作為函式引數
  • auto不能直接用來宣告陣列
  • auto不能定義類的非靜態成員變數
  • 例項化模板時不能使用auto作為模板引數
  • auto作為函式返回值時,只能用於定義函式,不能用於宣告函式
  • 為了避免與C++98中的auto發生混淆,C++11只保留了auto作為型別指示符的用法

for-each

新式風格的for迴圈,在Java中叫做增強for迴圈,這個特性從JDK1.5開始被支援,隨後C++11也支援了這種for迴圈

int arr[] = { 1,3,5,7,9,11 };
for(int i:arr){
	cout << i << " ";
}

for迴圈迭代的範圍必須是確定的:對於陣列而言,就是陣列中第一個元素和最後一個元素的範圍;對於類而言,應該提供begin和end的方法,begin和end就是for迴圈迭代的範圍 不能對引數中的陣列進行for-each,因為長度不確定

指標空值nullptr

在良好的C/C++程式設計習慣中,宣告一個變數時最好給該變數一個合適的初始值,否則可能會出現不可預料的錯誤,比如未初始化的指標。 NULL實際是一個巨集,在傳統的C標頭檔案(stddef.h)中

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

可以看到,NULL可能被定義為字面常量0,或者被定義為無型別指標(void*)的常量。不論採取何種定義,在使用空值的指標時,都不可避免的會遇到一些麻煩:

void f(int){
	cout<<"f(int)"<<endl;
}
void f(int*){
	cout<<"f(int*)"<<endl;
}
int main(){
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

程式本意是想通過f(NULL)呼叫指標版本的f(int*)函式,但是由於NULL被定義成0,因此與程式的初衷相悖。 在C++98中,字面常量0既可以是一個整形數字,也可以是無型別的指標(void*)常量,但是編譯器預設情況下將其看成是一個整形常量,如果要將其按照指標方式來使用,必須對其進行強轉(void *)0。 為了考慮相容性,C++11並沒有消除常量0的二義性,C++11給出了全新nullptr表示空值指標。C++11為什麼不在NULL的基礎上進行擴充套件,這是因為NULL以前就是一個巨集,而且不同的編譯器廠商對於NULL的實現可能不太相同,而且直接擴充套件NULL,可能會影響以前舊的程式。因此:為了避免混淆,C++11提供了 nullptr,即:nullptr代表一個指標空值常量。nullptr是有型別的,其型別為nullptr_t,僅僅可以被隱式轉化為指標型別,nullptr_t被定義在標頭檔案中:

typedef decltype(nullptr) nullptr_t;

注意:

  1. 在使用nullptr表示指標空值時,不需要包含標頭檔案,因為nullptr是C++11作為新關鍵字引入的。
  2. 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所佔的位元組數相同。
  3. 為了提高程式碼的健壯性,在後續表示指標空值時建議最好使用nullptr。

long long 型別

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

constexpr

定義常量的時候一般使用const來定義,一個常量必須在定義的時候進行初始化,並且之後不可更改。一個常量必須使用一個常量表達式進行初始化,並且在編譯期間就可以得到常量的值,但是如何確定一個表示式就是常量表達式呢,這個通常是由程式設計師自己確定的,所以C++11提供了一個新的關鍵字constexpr,使用該關鍵字定義的常量,由編譯器檢查為其賦值的表示式是否是常量表達式:

int a = 10;
const int i = a;//OK
constexpr int i2 = a;//error

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

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

int fun(){	//error
	return 0;
}
constexpr int fun(){	//OK
	return 0;
}
int main(){
	constexpr int ret = fun();
	return 0;
}

執行初始化的時候編譯器將函式的呼叫替換成結果值,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的好處!

using類型別名

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

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

INT a = 20;
INT2 b = 30;

列表初始化

//列表初始化還可以用結構體
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 ;
}

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如果作用於一個引用型別,其得到的還是一個引用型別 如果一個表示式是一個解指標引用的操作,decltype得到的也是一個引用型別:

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)當作是一個表示式處理,而變數是一種可以作為賦值語句左值的表示式,所以會解釋成引用型別。

尾置返回型別

看看下面這個函式宣告:

int (*func(char x))[10];

很顯然,func函式的引數是一個char型別的x,返回值是一個指向10個int型別的指標陣列的指標(就是一個10個元素的陣列,每個元素分別指向一個int型別,返回的就是陣列首元素地址—>陣列指標),這樣的定義實在是晦澀難懂,於是C++11新特性中出現了尾置返回型別:

auto func(char x) -> int(*) [10];

這種形式將函式的返回型別寫在函式宣告的最後面,並且在函式形參列表後面加上 -> 符號,然後緊接著是函式需要返回的型別,由於函式的返回型別被放在了形參列表之後,所以在函式名前面使用一個 auto替代。

=default 生成預設建構函式

在C++的類中,如果我們沒有定義建構函式,編譯器會為我們合成預設的無參建構函式,如果我們定義了建構函式,則編譯器就不生成預設構造函數了,但是如果我們定義建構函式同時也希望編譯器生成預設建構函式呢? C++11中可以通過在建構函式的宣告中直接 =default的方式要求編譯器生成建構函式。

class ClassName{
    public:
        ClassName(int x);
        ClassName()=default; // 顯示要求編譯器生成建構函式
};

類物件成員的類內初始化

class ClassName{
public:
	int x = 10; //C++11 之前是不允許的
};

lambda表示式

int main()
{
	auto add = [](int a, int b)->int {
         return a + b;
    };
    int ret = add(1, 2);
	std::cout << "ret:" << ret << std::endl;
    return 0;
}

預設引數<不在C++11新特性內>

這個只是為了我做一些筆記,這個並不屬於C++11性特性

  • 半預設引數必須從右往左依次來給出,不能間隔著給,站在編譯器的角度考慮這個問題
  • 預設引數不能在函式宣告和定義中同時出現
  • 預設引數在實現與標頭檔案分離的時候預設引數定義只能出現在標頭檔案中,如果標頭檔案未定義預設引數,那麼即使在實現的時候定義預設引數也是無法編譯的