摘要:不過由于主要是對標準中的漏洞進行修復,語言的核心部分則沒有改動,因此人們習慣性的把兩個標準合并稱為標準。從到,標準年磨一劍,第二個真正意義上的標準珊珊來遲。
在2003年C++標準委員會曾經提交了一份技術勘誤表(簡稱TC1),使得C++03這個名字已經取代了C++98稱為C++11之前的最新C++標準名稱。不過由于TC1主要是對C++98標準中的漏洞進行修復,語言的核心部分則沒有改動,因此人們習慣性的把兩個標準合并稱為C++98/03標準。從C++0x到C++11,C++標準10年磨一劍,第二個真正意義上的標準珊珊來遲。相比于C++98/03,C++11則帶來了數量可觀的變化,其中包含了約140個新特性,以及對C++03標準中約600個缺陷的修正,這使得C++11更像是從C++98/03中孕育出的一種新語言。相比較而言,C++11能更好地用于系統開發和庫開發、語法更加泛華和簡單化、更加穩定和安全,不僅功能更強大,而且能提升程序員的開發效率。
在C++98中,標準允許使用花括號{}對數組元素進行統一的列表初始值設定。比如:
</>復制代碼
int array1[] = {1,2,3,4,5};int array2[5] = {0};
對于一些自定義的類型,卻無法使用這樣的初始化。比如:
vector v{1,2,3,4,5};
就無法通過編譯,導致每次定義vector時,都需要先把vector定義出來,然后使用循環對其賦初始值,非常不方便。C++11擴大了用大括號括起的列表(初始化列表)的使用范圍,使其可用于所有的內置類型和用戶自定義的類型,使用初始化列表時,可添加等號(=),也可不添加。
</>復制代碼
int main(){
// 內置類型變量
int x1 = { 10 };
int x2{ 10 };
int x3 = 1 + 2;
int x4 = { 1 + 2 };
int x5{ 1 + 2 };
// 數組
int arr1[5] {1, 2, 3, 4, 5};
int arr2[]{1, 2, 3, 4, 5};
// 動態數組,在C++98中不支持
int* arr3 = new int[5]{1, 2, 3, 4, 5};
// 標準容器
vector<int> v{ 1, 2, 3, 4, 5 };
map<int, int> m{ { 1, 1 }, { 2, 2, }, { 3, 3 }, { 4, 4 } };
return 0;}
注意:列表初始化可以在{}之前使用等號,其效果與不使用=沒有什么區別。
標準庫支持單個對象的列表初始化。
</>復制代碼
class Point{public:
Point(int x = 0, int y = 0) : _x(x), _y(y)
{}private:
int _x;
int _y;};int main(){
Pointer p{ 1, 2 };
return 0;}
多個對象的列表初始化:
多個對象想要支持列表初始化,需給該類(模板類)添加一個帶有initializer_list類型參數的構造函數即可。注意:initializer_list是系統自定義的類模板,該類模板中主要有三個方法:begin()、end()迭代器以及獲取區間中元素個數的方法size()。
</>復制代碼
#include template<class T>class Vector {public:
// ...
Vector(initializer_list<T> l) : _capacity(l.size()), _size(0)
{
_array = new T[_capacity];
for (auto e : l)
_array[_size++] = e;
}
Vector<T>& operator=(initializer_list<T> l) {
delete[] _array;
size_t i = 0;
for (auto e : l)
_array[i++] = e;
return *this;
}
// ...private:
T* _array;
size_t _capacity;
size_t _size;};
在定義變量時,必須先給出變量的實際類型,編譯器才允許定義,但有些情況下可能不知道需要實際類型怎么給,或者類型寫起來特別復雜,比如:
</>復制代碼
#include #include int main(){
short a = 32670;
short b = 32670;
// c如果給成short,會造成數據丟失,如果能夠讓編譯器根據a+b的結果推導c的實際類型,就不會存在問題
short c = a + b;
std::map<std::string, std::string> m{ { "apple", "蘋果" }, { "banana", "香蕉" } };
// 使用迭代器遍歷容器, 迭代器類型太繁瑣
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << " " << it->second << endl;
++it;
}
return 0;}
C++11中,可以使用auto來根據變量初始化表達式類型推導變量的實際類型,可以給程序的書寫提供許多方便。將程序中c與it的類型換成auto,程序可以通過編譯,而且更加簡潔。關于auto的詳細介紹可以參考前面的博客。
auto使用的前提是:必須要對auto聲明的類型進行初始化,否則編譯器無法推導出auto的實際類型。但有時候可能需要根據表達式運行完成之后結果的類型進行推導,因為編譯期間,代碼不會運行,此時auto也就無能為力。
</>復制代碼
template<class T1, class T2>T1 Add(const T1& left, const T2& right){
return left + right;}
如果能用加完之后結果的實際類型作為函數的返回值類型就不會出錯,但這需要程序運行完才能知道結果的實際類型,即RTTI(Run-Time Type Identification 運行時類型識別)。
C++98中確實已經支持RTTI:
運行時類型識別的缺陷是降低程序運行的效率。
decltype是根據表達式的實際類型推演出定義變量時所用的類型,比如:
</>復制代碼
int main(){
int a = 10;
int b = 20;
// 用decltype推演a+b的實際類型,作為定義c的類型
decltype(a + b) c;
cout << typeid(c).name() << endl;
return 0;}
</>復制代碼
void* GetMemory(size_t size){
return malloc(size);}int main(){
// 如果沒有帶參數,推導函數的類型
cout << typeid(decltype(GetMemory)).name() << endl;
// 如果帶參數列表,推導的是函數返回值的類型,注意:此處只是推演,不會執行函數
cout << typeid(decltype(GetMemory(0))).name() << endl;
return 0;}
參考前面博客:C++入門
后續文章講解。
在C++中對于空類編譯器會生成一些默認的成員函數,比如:構造函數、拷貝構造函數、運算符重載、析構函數和&和const&的重載、移動構造、移動拷貝構造等函數。如果在類中顯式定義了,編譯器將不會重新生成默認版本。有時候這樣的規則可能被忘記,最常見的是聲明了帶參數的構造函數,必要時則需要定義不帶參數的版本以實例化無參的對象。而且有時編譯器會生成,有時又不生成,容易造成混亂,于是C++11讓程序員可以控制是否需要編譯器生成。
在C++11中,可以在默認函數定義或者聲明時加上=default,從而顯式的指示編譯器生成該函數的默認版本,用=default修飾的函數稱為顯式缺省函數。
</>復制代碼
class A{public:
A(int a) : _a(a)
{}
// 顯式缺省構造函數,由編譯器生成,一定生成
A() = default;
// 在類中聲明,在類外定義時讓編譯器生成默認賦值運算符重載
A& operator=(const A& a);private:
int _a;};A& A::operator=(const A& a) = default;int main(){
A a1(10);
A a2;
a2 = a1;
return 0;}
如果能想要限制某些默認函數的生成,在C++98中,是該函數設置成private,并且不給定義,這樣只要其他人想要調用就會報錯。在C++11中更簡單,只需在該函數聲明加上=delete即可,該語法指示編譯器不生成對應函數的默認版本,稱=delete修飾的函數為刪除函數。
</>復制代碼
class A{public:
A(int a) : _a(a)
{}
// 禁止編譯器生成默認的拷貝構造函數以及賦值運算符重載
A(const A&) = delete;
A& operator(const A&) = delete;private:
int _a;};int main(){
A a1(10);
// 編譯失敗,因為該類沒有拷貝構造函數
//A a2(a1);
// 編譯失敗,因為該類沒有賦值運算符重載
A a3(20);
a3 = a2;
return 0;}
C++98中提出了引用的概念,引用即別名,引用變量與其引用實體公共同一塊內存空間,而引用的底層是通過指針來實現的,因此使用引用,可以提高程序的可讀性。
</>復制代碼
#define _CRT_SECURE_NO_WARNINGS 1#include using namespace std;void Swap(int& left, int & right){
int tmp = left;
left = right;
right = tmp;}
為了提高程序運行效率,C++11中引入了右值引用,右值引用也是別名,但其只能對右值引用。
</>復制代碼
int add(int a, int b){
return a + b;}int main(){
int a = 10;
int b = 20;
Swap(a, b);
const int&& ra = 10;
// 引用函數返回值,返回值是一個臨時變量,為右值
int&& rRet = add(10, 20);
return 0;}
為了與C++98中的引用進行區分,C++11將該種方式稱之為右值引用。
左值與右值是C語言中的概念,但C標準并沒有給出嚴格的區分方式,一般認為:可以放在=左邊的,或者能夠取地址的稱為左值,只能放在=右邊的,或者不能取地址的稱為右值,但是也不一定完全正確。
</>復制代碼
#define _CRT_SECURE_NO_WARNINGS 1#include using namespace std;int main(){
int x = 1;
int y = 2;
//左值引用的定義
int a = 0;
int &b = a;
//左值引用不能引用右值,但const左值引用可以
//int& e = 10;
//int& f = x + y;
const int& e = 10;
const int& f = x + y;
//右值引用的定義
int&& c = 10;
int&& d = x + y;
//右值引用不能引用左值,但是可以引用move后左值
//int&& m = a;
int&& m = move(a);
return 0;}
在C++98中的普通引用與const引用在引用實體上的區別:
</>復制代碼
int main(){
// 普通類型引用只能引用左值,不能引用右值
int a = 10;
int& ra1 = a; // ra為a的別名
//int& ra2 = 10; // 編譯失敗,因為10是右值
const int& ra3 = 10;
const int& ra4 = a;
return 0;}
注意: 普通引用只能引用左值,不能引用右值,const引用既可引用左值,也可引用右值。
C++11中右值引用:只能引用右值,一般情況不能直接引用左值。
</>復制代碼
int main(){
// 10純右值,本來只是一個符號,沒有具體的空間,
// 右值引用變量r1在定義過程中,編譯器產生了一個臨時變量,r1實際引用的是臨時變量
int&& r1 = 10;
r1 = 100;
int a = 10;
int&& r2 = a; // 編譯失敗:右值引用不能引用左值
return 0;}
問題:既然C++98中的const類型引用左值和右值都可以引用,那為什么C++11還要復雜的提出右值引用呢?
如果一個類中涉及到資源管理,用戶必須顯式提供拷貝構造、賦值運算符重載以及析構函數,否則編譯器將會自動生成一個默認的,如果遇到拷貝對象或者對象之間相互賦值,就會出錯,比如:
</>復制代碼
#define _CRT_SECURE_NO_WARNINGS 1#include #include namespace yyw{
class string
{
string(const char* str = "")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
//s2(s1)
string(const string& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
//s2=s1
string& operator=(const string& s)
{
if (this != &s)
{
char* _Pstr = new char[strlen(s._str) + 1];
strcpy(_Pstr, s._str);
delete[] _str;
_str = _Pstr;
}
return *this;
}
~string()
{
delete[]_str;
_str=nullptr;
}
private:
char* _str;
};}
上述代碼看起來沒有什么問題,但是有一個不太盡人意的地方:
在operator+中:strRet在按照值返回時,必須創建一個臨時對象,臨時對象創建好之后,strRet就被銷毀了,最后使用返回的臨時對象構造s3,s3構造好之后,臨時對象就被銷毀了。仔細觀察會發現:strRet、臨時對象、s3每個對象創建后,都有自己獨立的空間,而空間中存放內容也都相同,相當于創建了三個內容完全相同的對象,對于空間是一種浪費,程序的效率也會降低,而且臨時對象確實作用不是很大,那能否對該種情況進行優化呢。
C++11提出了移動語義概念,即:將一個對象中資源移動到另一個對象中的方式,可以有效緩解該問題。
在C++11中如果需要實現移動語義,必須使用右值引用。上述String類增加移動構造:
因為strRet對象的生命周期在創建好臨時對象后就結束了,即將亡值,C++11認為其為右值,在用strRet構造臨時對象時,就會采用移動構造,即將strRet中資源轉移到臨時對象中。而臨時對象也是右值,因此在用臨時對象構造s3時,也采用移動構造,將臨時對象中資源轉移到s3中,整個過程,只需要創建一塊堆內存即可,既省了空間,又大大提高程序運行的效率。
注意:
按照語法,右值引用只能引用右值,但右值引用一定不能引用左值嗎?因為:有些場景下,可能真的需要用右值去引用左值實現移動語義。當需要用右值引用引用一個左值時,可以通過move函數將左值轉化為右值。C++11中,std::move()函數位于 頭文件中,該函數名字具有迷惑性,它并不搬移任何東西,唯一的功能就是將一個左值強制轉化為右值引用,然后實現移動語義。
</>復制代碼
int main(){
String s1("hello world");
String s2(move(s1));
String s3(s2);
return 0;}
注意:以上代碼是move函數的經典的誤用,因為move將s1轉化為右值后,在實現s2的拷貝時就會使用移動構造,此時s1的資源就被轉移到s2中,s1就成為了無效的字符串。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/118811.html
這篇文章主要介紹如何在springboot中如何創建含有多個module的工程,栗子中含有兩個 module,一個作為libarary. 工程,另外一個是主工程,調用libary .其中libary jar有一個服務,main工程調用這個服務。 創建根工程 創建一個maven 工程,其pom文件為: 4.0.0 com.forezp springboot-multi-...
摘要:十開放模式識別項目開放模式識別項目,致力于開發出一套包含圖像處理計算機視覺自然語言處理模式識別機器學習和相關領域算法的函數庫。 一、開源生物特征識別庫 OpenBROpenBR 是一個用來從照片中識別人臉的工具。還支持推算性別與年齡。使用方法:$ br -algorithm FaceRecognition -compare me.jpg you.jpg二、計算機視覺庫 OpenCVOpenC...
摘要:本題目的考察點在于函數的格式輸出規則。方法改變隨機數生成器的種子,可以在調用其他隨機模塊函數之前調用此函數。參數改變隨機數生成器的種子。返回一個至區間包含和的整數。 ...
摘要:三結對編程排位賽四個人為一組,由隊長帶隊刷題,每周根據這周四個人的刷題總數進行隊伍間排名。萬人千題結對編程排位賽如果想參加的第二期的同學,可以先聯系作者加群,看看第一期的同袍是如何奮斗的。 ...
閱讀 3048·2021-10-13 09:39
閱讀 1890·2021-09-02 15:15
閱讀 2452·2019-08-30 15:54
閱讀 1814·2019-08-30 14:01
閱讀 2613·2019-08-29 14:13
閱讀 1426·2019-08-29 13:10
閱讀 2740·2019-08-28 18:15
閱讀 3899·2019-08-26 10:20