Visual C++2010的c++語言四大新特性
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
微軟即將在2010年4月12日釋出VS2010的正式版,對於c++語言做了修改,使之更加符合c++標準。
下面對於微軟對於c++語言的修改做一下分析!
Lambda表示式
很多程式設計程式語言都支援匿名函式(anonymous function)。所謂匿名函式,就是這個函式只有函式體,而沒有函式名。Lambda表示式就是實現匿名函式的一種程式設計技巧,它為編寫匿名函式提供了簡明的函式式的句法。同樣是Visual Studio中的開發語言,Visual Basic和Visual C#早就實現了對Lambda表示式的支援,終於Visual C++這次也不甘落後,在Visual Studio 2010中添加了對Lambda表示式的支援。
Lambda表示式使得函式可以在使用的地方定義,並且可以在Lambda函式中使用Lambda函式之外的資料。這就為針對集合操作帶來了很大的便利。在作用上,Lambda表示式類似於函式指標和函式物件,Lambda表示式很好地兼顧了函式指標和函式物件的優點,卻沒有它們的缺點。相對於函式指標或是函式物件複雜的語法形式,Lambda表示式使用非常簡單的語法就可以實現同樣的功能,降低了Lambda表示式的學習難度,避免了使用複雜的函式物件或是函式指標所帶來的錯誤。我們可以看一個實際的例子:
- #include "stdafx.h"
- #include <algorithm>
- #include <iostream>
- #include <ostream>
- #include <vector>
- using namespace std;
- int _tmain(int argc, _TCHAR* argv[])
- {
- vector<int> v;
- for (int i = 0; i < 10; ++i) {
- v.push_back(i);
- }
- for_each(v.begin(), v.end(), [] (int n) {
- cout << n;
- if (n % 2 == 0) {
- cout << " even ";
- } else {
- cout << " odd ";
- }
- });
- cout << endl;
- return 0;
- }
這段程式碼迴圈遍歷輸出vector中的每一個數,並判斷這個數是奇數還是偶數。我們可以隨時修改Lambda表示式而改變這個匿名函式的實現,修改對集合的操作。在這段程式碼中,C++使用一對中括號“[]”來表示Lambda表示式的開始,其後的”(int n)”表示Lambda表示式的引數。這些引數將在Lambda表示式中使用到。為了體會Lambda表示式的簡潔,我們來看看同樣的功能,如何使用函式物件實現:
- #include "stdafx.h"
- #include <algorithm>
- #include <iostream>
- #include <ostream>
- #include <vector>
- using namespace std;
- struct LambdaFunctor {
- void operator()(int n) const {
- cout << n << " ";
- if (n % 2 == 0) {
- cout << " even ";
- } else {
- cout << " odd ";
- }
- }
- };
- int _tmain(int argc, _TCHAR* argv[])
- {
- vector<int> v;
- for (int i = 0; i < 10; ++i) {
- v.push_back(i);
- }
- for_each(v.begin(), v.end(), LambdaFunctor());
- cout << endl;
- return 0;
- }
通過比較我們就可以發現,Lambda表示式的語法更加簡潔,使用起來更加簡單高效
靜態斷言static_assert
在之前的C++標準C++03中,我們可以使用兩種斷言:
• 使用預處理中的條件編譯和#error指令,可以在預處理階段檢查一些編譯條件
• 可以使用巨集assert來進行執行時檢查,以確保程式邏輯的正確性
但使用#error方法是非常煩瑣的,並且不能夠對模板引數進行檢查,因為模板例項化是在編譯時進行,而#error方法是在預處理階段進行的。而assert巨集是在執行時進行檢查。不難發現,我們缺少了一樣東西,那就是可用於在編譯時檢查的工具。於是,靜態斷言應運而生。
在新的C++標準C++0x中,加入了對靜態斷言的支援,引入了新的關鍵字static_assert來表示靜態斷言。使用靜態斷言,我們可以在程式的編譯時期檢測一些條件是否成立,這個特性在除錯模板函式的模板引數時特別有用。在編譯的時候,模板函式例項化,這時我們就可以使用靜態斷言去測試模板函式的引數是否按照我們的設計擁有合適的值。例如下面這段程式碼:
[cpp] view plain copy print ?- template <int N> struct Kitten {
- static_assert(N < 2, "Kitten<N> requires N < 2.");
- };
- int main() {
- Kitten<1> peppermint;
- Kitten<3> jazz;
- return 0;
- }
當我們在主函式中使用“1”去例項化Kitten這個結構體時,在編譯的時候,靜態斷言static_assert會測試引數N的值,當N的值小於2時就會產生一個斷言錯誤,並將相應的除錯幫助資訊輸出到“Error List”視窗中,這樣程式設計師就可以對問題快速定位,解決問題就更加方便了。
圖2 static_assert斷言及其輸出
另外,靜態斷言還帶來很多其他的優勢。例如靜態斷言在編譯時進行處理,不會產生任何執行時刻空間和時間上的開銷,這就使得它比assert巨集具有更好的效率。另外比較重要的一個特性是如果斷言失敗,它會產生有意義且充分的診斷資訊,幫助程式設計師快速解決問題。
auto關鍵字
在C++0x中,auto關鍵字的意義發生了改變。從Visual C++ 2010開始,auto關鍵字將用於指引編譯器根據變數的初始值來決定變數的資料型別。換句話說,我們可以把auto當成一種新的資料型別,它可以“從初始化器(initialize)中推匯出所代表的變數的真正型別”。這種對auto關鍵字的使用方式可以大大消除當前替代方式所導致的冗長和易出錯的程式碼。我們看一個實際的例子:
[cpp] view plain copy print ?- #include <iostream>
- #include <map>
- #include <ostream>
- #include <regex>
- #include <string>
- using namespace std;
- using namespace std::tr1;
- int main() {
- map<string, string> m;
- const regex r("(//w+) (//w+)");
- for (string s; getline(cin, s); ) {
- smatch results;
- if (regex_match(s, results, r)) {
- m[results[1]] = results[2];
- }
- }
- for (auto i = m.begin(); i != m.end(); ++i) {
- cout << i->second << " are " << i->first << endl;
- }
- return 0;
- }
在這段程式碼中,我們使用auto關鍵字來代替了真正的資料型別map<string, string>::iterator,這使得整個程式碼自然而簡潔。
另外,跟其他資料型別一樣,我們也可以對auto關鍵字進行修飾,例如新增const,指標(*),左值引用(&),右值引用(&&)等等,編譯器會根據auto型別所代表的真正的資料來決定這些修飾的具體含義。
為了相容一些舊有的C++程式碼,我們可以使用/Zc:auto這個編譯器選項,來告訴編譯器是採用auto關鍵字的原有定義還是在新標準C++0x中的定義。
右值引用
作為最重要的一項語言特性,右值引用(rvalue references)被引入到 C++0x中。我們可以通過操作符“&&”來宣告一個右值引用,原先在C++中使用“&”操作符宣告的引用現在被稱為左值引用。
int a;
int& a_lvref = a; // 左值引用
int b;
int&& b_rvref = b; // 右值應用
左值引用和右值引用的表現行為基本一致,它們唯一的差別就是右值引用可以繫結到一個臨時物件(右值)上,而左值引用不可以。例如:
int& a_lvref = int(); // error C2440: 'initializing' : cannot convert from 'int' to 'int &'
int&& b_rvref = int(); // OK!
在第一行程式碼中,我們將一個臨時物件int()繫結到一個左值引用,將產生一個編譯錯誤。而在第二行中,我們將臨時物件繫結到右值引用,就可以順利通過編譯。
右值是無名的資料,例如函式的返回值一般說來就是右值。當對右值進行操作的時候,右值本身往往沒有必要保留,因此在某些情況下可以直接“移動”之。通過右值引用,程式可以明確的區分出傳入的引數是否為右值,從而避免了不必要的拷貝,程式的效率也就得到了提高。我們考慮一個簡單的資料交換的小程式,從中來體會右值引用所帶來的效率提升。我們可以寫一個函式swap來實現兩個變數值的交換:
[cpp] view plain copy print ?- template <class T> swap(T& a, T& b)
- {
- T tmp(a); // tmp物件建立後,我們就擁有了a的兩份拷貝
- a = b; // 現在我們擁有b的兩份拷貝
- b = tmp; // 現在我們擁有a的兩份拷貝
- }
在這段程式碼中,雖然我們只是為了進行簡單的資料交換,但是卻執行了多次物件拷貝。這些物件的拷貝操作,特別是當這些物件比較大的時候,無疑會影響程式的效率。
那麼,如果使用右值引用如何實現呢?
[cpp] view plain copy print ?- #include "stdafx.h"
- template <class T>
- T&& move(T&& a)
- {
- return a;
- }
- template <class T> void swap(T& a, T& b)
- {
- T tmp(move(a)); // 物件a被移動到物件tmp,a被清空
- a = move(b); // 物件b被移動到物件a,b被清空
- b = move(tmp); // 物件tmp被移動到物件b
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- int a = 1;
- int b = 2;
- swap(a, b);
- return 0;
- }
在這段重新實現的程式碼中,我們使用了一個move()函式來代替物件的賦值操作符“=”,move()只是簡單地接受一個右值引用或者左值引用作為引數,然後直接返回相應物件的右值引用。這一過程不會產生拷貝(Copy)操作,而只會將源物件移動(Move)到目標物件。
正是拷貝(Copy)和移動(Move)的差別,使得右值引用成為C++0x中最激動人心的新特性之一。從實踐角度講,它能夠完美是解決C++中長久以來為人所詬病的臨時物件的效率問題。從語言本身講,它健全了C++中的引用型別在左值右值方面的缺陷。從庫設計者的角度講,它給庫設計者又帶來了一把利器。而對於廣大的庫使用者而言,不動一兵一卒便能夠獲得“免費的”效率提升。
在Visual Studio 2010中,因為有了對這些C++0x新特性的支援,重新點燃了程式設計師們對C++的熱情。C++重振雄風,指日可待!
本文作者專著《Visual C++2010開發權威指南》即將推出,敬請關注,Visual C++2010最近技術,Windows7開發最新技術!