国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值

這篇具有很好參考價值的文章主要介紹了從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

1.?列表初始化initializer_list

2. 前面提到的一些知識點

2.1 小語法

2.2 STL中的一些變化

3. 右值和右值引用

3.1?右值和右值引用概念

3.2?右值引用類型的左值屬性

3.3?左值引用與右值引用比較

3.4?右值引用的使用場景

3.4.1 左值引用的功能和短板

3.4.2?移動構造

3.4.3?移動賦值

3.4.4?插入右值時減少深拷貝

4. 完美轉發(fā)

4.1?萬能引用(引用折疊)

4.2 完美轉發(fā)forward

5. 新的類功能

5.1 默認生成的移動構造/賦值

5.2 類里新的關鍵字

本篇完。


????????在2003年C++標準委員會曾經(jīng)提交了一份技術勘誤表(簡稱TC1),使得C++03這個名字已經(jīng)取代了C++98稱為C++11之前的最新C++標準名稱。不過由于C++03(TC1)主要是對C++98標準中的漏洞進行修復,語言的核心部分則沒有改動,因此人們習慣性的把兩個標準合并稱為C++98 / 03標準。從C++0x到C++11,C++標準10年磨一劍,第二個真正意義上的標準珊珊來遲。

????????相比于C++98 / 03,C++11則帶來了數(shù)量可觀的變化,其中包含了約140個新特性,以及對C++03標準中約600個缺陷的修正,這使得C++11更像是從C++98 / 03中孕育出的一種新語言。相比較而言,C++11能更好地用于系統(tǒng)開發(fā)和庫開發(fā)、語法更加泛華和簡單化、更加穩(wěn)定和安全,不僅功能更強大,而且能提升程序員的開發(fā)效率,公司實際項目開發(fā)中也用得比較多,所以我們要作為一個重點去學習。C++11增加的語法特性非常篇幅非常多,這里沒辦法壹壹講解,所以本博客主要講解實際中比較實用的語法。

1.?列表初始化initializer_list

  • 列表:花括號:{ }就被叫做列表。

之前可以使用列表來初始化數(shù)組,初始化結構體變量,初始化元素類型為結構體變量的數(shù)組等等。

  • C++11擴大了用大括號括起的列表(初始化列表)的使用范圍,使其可用于所有的內置類型和用戶自定義的類型,使用初始化列表時,可添加等號(=),也可不添加。
#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

protected:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	int x1 = 1;

	int x2 = { 2 }; // 要能看懂,但是不建議使用
	int x3{ 2 };

	Date d1(2023, 1, 1); // 都是在調用構造函數(shù)

	Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建議使用
	Date d3{ 2023, 3, 3 };

	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

可以不加等號進行初始化,如上圖代碼所示,但是強烈不建議使用。

????????這其實很雞肋,沒有什么價值,繼續(xù)使用C++98中的方式就挺好的,而且容易理解,C++11中的方式反而不太好理解了。C++中這種雞肋的語法被很多人吐槽,理性看待。

列表初始化真正有意義的地方是用于初始化STL中的容器:

????????之前提到:vector和list以及map等STL中的容器也可以像普通數(shù)組一樣使用初始化列表來初始化了。這是因為列表初始化本身就是一個類模板:

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????如上圖所示,這是C++11才有的一個類型,該類型叫做列表初始化,而且還有自己的成員函數(shù),包括構造函數(shù),計算列表大小的接口,獲取列表迭代器位置。(但幾乎都不用)

????????C++11為這些容器提供了新的構造函數(shù),該構造函數(shù)是使用列表來初始化對象的,它的形參就是initializer_list,所以列表初始化才可以初始化STL中的容器。

賦值運算符重載函數(shù)也有一個列表的重載版本:

#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

protected:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	int x1 = 1;

	int x2 = { 2 }; // 要能看懂,但是不建議使用
	int x3{ 2 };

	Date d1(2023, 1, 1); // 都是在調用構造函數(shù)

	Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建議使用
	Date d3{ 2023, 3, 3 };

	// 調用支持list (initializer_list<value_type> il)類似這樣的構造函數(shù)
	vector<int> v1 = { 1, 2, 3, 4, 5, 6 };
	vector<int> v2 { 1, 2, 3, 4, 5, 6 };

	list<int> lt1 = { 1, 2, 3, 4, 5, 6 };
	list<int> lt2{ 1, 2, 3, 4, 5, 6 };

	auto x = { 1, 2, 3, 4, 5, 6 };
	cout << typeid(x).name() << endl; // 打印初始化列表的類型

	vector<Date> v3 = {d1, d2, d3};
	vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };

	string s1 = "11111";

	map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } }; // 構造

	initializer_list<pair<const string, string>> kvil = { { "left", "左邊" }, { "right", "右邊" } }; // 賦值重載
	dict = kvil; // 上面的類型就不能用auto推導,編譯器不知道那里是一個pair

	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言


2. 前面提到的一些知識點

2.1 小語法

C++11提供了一些新的小語法,很多我們都接觸過甚至是使用過,這里系統(tǒng)講講。


c++11提供了多種簡化聲明的方式,尤其是在使用模板時。這里講autodecltype

auto:這個關鍵字我們已經(jīng)使用過很多了

????????在C++98中auto是一個存儲類型的說明符,表明變量是局部自動存儲類型,但是局部域中定義局部的變量默認就是自動存儲類型,所以auto就沒什么價值了。C++11中廢棄auto原來的用法,將其用于實現(xiàn)自動類型推斷這樣要求必須進行顯示初始化,讓編譯器將定義對象的類型設置為初始化值的類型。

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
#include <map>
using namespace std;

int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcpy;
	cout << typeid(p).name() << endl;
	cout << typeid(pf).name() << endl;
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();

	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言


decltype:

關鍵字decltype將變量的類型聲明為表達式指定的類型。

使用typeid().name()只能打印出類型的名稱,并不能用這個名稱繼續(xù)創(chuàng)建變量,而decltype可以:

template<class T1, class T2>
void F(T1 t1, T2 t2)
{
	decltype(t1 * t2) ret;
	cout << typeid(ret).name() << endl;
}

int main()
{
	const int x = 1;
	double y = 2.2;
	decltype(x * y) ret; // ret的類型是double
	decltype(&x) p; // p的類型是int*
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;
	F(1, 'a');
	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????使用decltype可以自動推演類型,并且可以用推演出的結果繼續(xù)創(chuàng)建變量,如上圖所示,對于一些不同類型直接的運算結果,decltype有奇效。


nullptr:

????????由于C++中NULL被定義成字面量0,這樣就可能回帶來一些問題,因為0既能指針常量,又能表示整形常量。所以出于清晰和安全的角度考慮,C++11中新增了nullptr,用于表示空指針。

在C++中存在條件編譯:(以后用nullptr就行了)這算是修復了一個bug

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

范圍for循環(huán)

????????范圍for我們也一直都在使用,這是C++11提供的語法糖,使用起來非常方便,它的底層就是迭代器,只是編譯器給自動替換了,這里就不再詳細講解了。一般是這么用的:

#include<iostream>
using namespace std;
int main()
{
    int arr[] = { 1, 2, 3, 4, 5 };
    for (auto& e : arr)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言


2.2 STL中的一些變化

新容器:

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????紅色框中的是C++11增加的新容器,基本只有unordered_map和unordered_set有用,其他很雞肋。容器array對標的是靜態(tài)數(shù)組,array也是一個靜態(tài)的,也就是在棧區(qū)上的,大小是通過一個非類型模板參數(shù)確定的。容器forward_list是一個單鏈表,也很雞肋,因為絕大部分場景雙鏈表都可以滿足要求,而且更加方便,唯一使用到單鏈表的地方就是哈希桶中。前面都提到過。

????????至于unordered_map和unordered_set,這兩個容器的底層是哈希桶,雖然不能實現(xiàn)排序,但是可以降重。而且在查找時具有其他容器無法比擬的效率。這兩個容器是非常實用的,而且也是我們經(jīng)常使用的。

容器中的使用新方法:

1. 使用列表構造
在前面就講解過了,幾乎每個容器都增加了新的接口,使用std::initializer_list類型來構造。

2. 移動構造和移動賦值
在下面講解了右值引用就可以明白了。

3. emplace_xxx插入接口或者右值引用版本的插入接口。
同樣在后面才能學習到。


3. 右值和右值引用

????????傳統(tǒng)的C++語法中就有引用的語法,而C++11中新增了的右值引用語法特性, 之前學習的引用就叫做左值引用。無論左值引用還是右值引用,都是給對象取別名。

3.1?右值和右值引用概念

什么是左值?什么是右值?

  • 左值:一個表示數(shù)據(jù)的表達式,如變量名或者指針解引用。
  • 特點:可以對左值取地址 + 可以對左值賦值。

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

?上圖代碼中所示的變量都屬于左值,要牢記左值可以取地址這一個特性。

  • ?定義時const修飾符后的左值,不能給它賦值,但是可以取它的地址。
  • ?左值可以出現(xiàn)在賦值符號的左邊,也可以出現(xiàn)在賦值符號的右邊。

  • 右值:也是一個表示數(shù)據(jù)的表達式。如:字面常量,表達式返回值,函數(shù)返回值,類型轉換時的臨時變量等等。
  • 特點:右值不可以取地址,不可以賦值。

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

要牢記右值特性:不能取地址不能賦值。

  • ?右值可以出現(xiàn)在賦值符號的右邊,但是不能出現(xiàn)出現(xiàn)在賦值符號的左邊。

什么是右值引用?

左值引用是給左值取別名,右值引用顯而易見就是給右值取別名。

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

  • 右值引用使用兩個&符號。

?上圖代碼中的rr1,rr2,rr3就是三個右值的別名,也就是右值引用。


3.2?右值引用類型的左值屬性

  • 右值是不能取地址的,但是給右值取別名后,會導致右值被存儲到特定位置,且可以取到該位置的地址。

????????對于內置類型的右值,如字面常量,一旦右值引用以后,就會被存儲到特定的位置,并且可以取到該地址,而且還可以修改。

int main()
{
	int&& rr1 = 10;
	cout << rr1 << endl;

	rr1 = 5;
	cout << rr1 << endl;

	const double&& rr2 = (1.1 + 2.2);
	//rr2 = 5.5; // 不能修改

	return 0;
}

????????字面常量10原本是不可以被修改的,但是右值引用以后,在特定的位置開辟了變量來存放10,所以就可以被修改了。

????????表達式或者函數(shù)的返回值,會有一個臨時變量來存放返回值,我們知道這樣的臨時變量具有常性,也是右值。對于這種右值引用,編譯器會修改它的屬性,將常性修改,并且存儲在特定位置。注意const類型的右值,即便開辟了變量存放該右值也是不可以被修改的,因為被const修飾了。


內置類型的右值被稱為純右值。

自定義類型的右值被稱為將亡值。

????????對于自定義類型的右值,如容器的臨時變量,它確確實實會被銷毀,而不會被存放。自定義類型的右值才能體現(xiàn)出右值存在的意義,后面會詳細講解。

  • 右值引用是右值的別名,它所指向的右值是不可以被修改的。
  • 但是右值引用本身也是一種類型,并且它的屬性是左值,可以取地址,可以賦值。

3.3?左值引用與右值引用比較

思考:左值引用可以引用右值嗎?

????????要知道,右值引用是C++11才出來的,右值傳參給函數(shù)還是右值,那我們以前寫的函數(shù)都用不了右值傳參了?

template<class T>
void Func(const T& x)
{}

????????這里去掉const肯定是不能傳參的,為了給右值傳參(當然還有其它原因),所以const的左值引用可以引用右值。總結:普通的左值引用不可以引用右值,const的左值引用可以引用右值。

思考:右值引用可以引用左值嗎?

????????右值引用不可以引用普通的左值,可以引用move以后的左值:(move這個語法先記?。┳笾到?jīng)過move以后就變成了右值,如:

int main()
{
	// 左值引用可以引用右值嗎? const的左值引用可以
	double x = 1.1, y = 2.2;
	//double& r1 = x + y;
	const double& r1 = x + y;

	// 右值引用可以引用左值嗎?可以引用move以后的左值
	int b = 7;
	//int&& rr5 = b;
	int&& rr5 = move(b);

	return 0;
}

成功編譯:

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

VS的提示已經(jīng)很智能了:

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言


3.4?右值引用的使用場景

namespace rtx
{
	class string
	{
	public:
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		string(const string& s) // 拷貝構造
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 拷貝構造(深拷貝)" << endl;

			//string tmp(s._str);
			//swap(s);

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		string& operator=(const string& s) // 拷貝賦值
		{
			cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

	protected:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

????????先自己實現(xiàn)一個string,只有拷貝構造函數(shù),賦值運算符重載函數(shù),析構函數(shù),以及一個普通的構造函數(shù)。無論是拷貝構造還是賦值運算符重載,都會進行深拷貝,采用現(xiàn)代寫法來實現(xiàn):

namespace rtx
{
	class string
	{
	public:
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		const char* c_str() const
		{
			return _str;
		}

		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		string(const string& s) // 拷貝構造
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 拷貝構造(深拷貝)" << endl;
			string tmp(s._str);
			swap(tmp);
		}	

		string& operator=(const string& s) // 拷貝賦值
		{
			cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

	protected:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

左值引用的場景:

使用普通傳值調用,存在一次深拷貝:

void Func(rtx::string s)
{}

int main()
{
	rtx::string s("hello world");
	Func(s);

	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言


使用傳拷貝引用時,不存在深拷貝,F(xiàn)unc函數(shù)直接使用main函數(shù)中的s1對象:

void Func(rtx::string& s)
{}

int main()
{
	rtx::string s("hello world");
	Func(s);

	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

函數(shù)返回參數(shù)和上面一樣,傳引用返回有時確實能提高效率。


3.4.1 左值引用的功能和短板

左值引用的功能:
做參數(shù)。

1. 減少拷貝,提高效率。

2. 做輸出型參數(shù)。


做返回值。

1. 減少拷貝,提高效率。

2. 引用返回,可以修改返回對象(比如: operator[ ])。

但是左值引用做返回值只解決了70%的問題,在類似 to_string 函數(shù)中:

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

  • 傳值返回時,存在一次深拷貝。
  • rtx::string to_string(int value)

要知道深拷貝的代價是比較大的,深拷貝次數(shù)減少可以很大程度上提高代碼的效率。

  • 傳左值引用返回時,不存在深拷貝。(可以嗎?)
  • rtx::string& to_string(int value)

????????但是敢傳引用返回嗎?我們把int value 轉換成string,此時的 string 是一個形參。出了函數(shù)就銷毀了。外面拿到的就是被銷毀了的棧幀。

所以左值引用存在的短板:

????????前面我們在調用 to_string 函數(shù)的時候,我們把int value 轉換成string,此時的 string 是一個形參。所以只能傳值返回,此時mian函數(shù)中拿到 to_string 中的 string 對象要進行兩次深拷貝。

????????第一次深拷貝,to_string函數(shù)返回時,會將string對象放在一個臨時變量中,此時發(fā)生的深拷貝。函數(shù)返回時,如果是內置類型等幾個字節(jié)的變量,會將函數(shù)中的臨時變量放在寄存器中返回,如果是自定義類型所占空間比較大,就會放在臨時變量中壓棧到上一級棧幀中。

第二次深拷貝,main函數(shù)中,ret接收函數(shù)返回了的string對象時會再發(fā)生一次深拷貝。

????????但是編譯器會進行優(yōu)化,將兩次深拷貝優(yōu)化成一次。雖然只有一次,但有些情況代價還是很大的。

????????C++98是如何解決上面的問題?那就是輸出型參數(shù):rtx::string to_string(int value)變成rtx::void to_string(int value,string& s)。但是這樣不太符合使用習慣。

  • 有沒有辦法讓它符合使用習慣,并且一次深拷貝都沒有?那就要用到下面的C++11新增的移動構造和移動賦值了

3.4.2?移動構造

此時用右值引用就可以解決這個問題。

右值引用的價值之一:補齊臨時對象不能傳引用返回這個短板?

前面的深拷貝是拷貝構造產(chǎn)生的:string(const string& s) // 拷貝構造(形參是左值引用)


演示在string類中增加一個移動構造函數(shù):

前面提到過:內置類型的右值被稱為純右值。

自定義類型的右值被稱為將亡值。(這里的傳右值就是將亡值

基于拷貝構造:無論是左值還是右值都老老實實地開空間:

		string(const string& s) // 拷貝構造
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 拷貝構造(深拷貝)" << endl;
			string tmp(s._str);
			swap(tmp);
		}	

????????左值因為還要使用,肯定要開空間的,這里的右值是將亡值,沒用了,所以也不用開空間了,因為不用開空間了,所以深拷貝也沒了,而是資源轉移(直接swap):

		string(string&& s) // 移動構造
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移動構造(資源轉移)" << endl;
			swap(s);
		}
  • 移動構造的形參是右值引用。

從to_string中返回的string對象是一個臨時變量,具有常性,也就是我們所說的右值。

  • 用右值來構造string對象時,會自定匹配移動構造函數(shù)。(以前沒有移動構造時,右值傳參會走拷貝構造,因為const 的左值引用可以接收右值,但是這不是最優(yōu)方案,現(xiàn)在寫了移動構造,右值傳參就會走移動構造)

3.4.3?移動賦值

拷貝賦值移動賦值和拷貝構造移動構造類似:

		string& operator=(const string& s) // 拷貝賦值
		{
			cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		string& operator=(string&& s) // 移動賦值
		{
			cout << "string& operator=(string s) -- 移動賦值(資源移動)" << endl;
			swap(s);

			return *this;
		}

總結:右值引用和左值引用減少拷貝的原理不太一樣。

  • 左值引用是別名,直接在原本的對象上起作用。
  • 右值引用是間接起作用,通過右值引用識別到右值,然后在移動構造和移動賦值中進行資源轉移。

????????使用移動構造和移動賦值時,被轉移資源的對象必須是個將亡值(像to_string的使用一樣),因為會被銷毀。C++11的STL標準庫中也提供了移動構造和移動賦值函數(shù)。


3.4.4?插入右值時減少深拷貝

C++11在STL庫容器中的所有插入接口都提供了右值版本,push_back,insert等。

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言在我們寫的string恢復這兩個接口:

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

然后分別像庫里的 list 插入左值和右值

int main()
{
	list<rtx::string> lt;
	rtx::string s1("hello"); // 左值
	lt.push_back(s1);  // 插入左值

	cout << "----------------------------------" << endl;

	lt.push_back(rtx::string("world")); // 插入右值
	//lt.push_back("world");

	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

如果沒有移動構造那么下面的也是深拷貝了。


4. 完美轉發(fā)

4.1?萬能引用(引用折疊)

寫多個重載函數(shù),根據(jù)實參類型調用不同函數(shù)。

  • ?形參類型分別是左值引用,const左值引用,右值引用,const右值引用:
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 萬能引用(引用折疊):t既能引用左值,也能引用右值
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t); // 此時t變成了左值/const左值
}

int main()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

????????代碼中的perfectForward函數(shù)模板被叫做萬能引用模板,無論調用該函數(shù)時傳的是什么類型,它都能推演出來:

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????在函數(shù)模板推演的過程中會發(fā)生引用折疊:模板參數(shù)T&&中的兩個&符號折疊成一個。當傳入的實參是左值時,就會發(fā)生引用折疊,是右值時就不會發(fā)生引用折疊。

  • 無論傳的實參是什么,都不用改變模板參數(shù)T&&,編譯器都能夠自己推演。
  • 這就是萬能引用,只需要一個模板就可以搞定,不需要分類去寫。

????????上面萬能模板中,雖然推演出來了各自實參類型,但是由于右值引用本身是左值屬性,所以需要使用move改變屬性后才能調用對應的重載函數(shù)。

????????有沒有辦法不用move改變左值屬性,讓模板函數(shù)中的t保持它推演出來的類型。答案是有的,完美轉發(fā)就能夠保持形參的屬性不變。


4.2 完美轉發(fā)forward

完美轉發(fā)同樣是C++11提供的,它也是一個模板:

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????完美轉發(fā):完美轉發(fā)在傳參的過程中保留對象原生類型屬性。實參傳遞過來后,推演出的形參是什么類型就保持什么類型繼續(xù)使用。

這里會語法就行:

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }


// 萬能引用(引用折疊):t既能引用左值,也能引用右值
template<typename T>
void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t)); // 完美轉發(fā):保持t引用對象屬性
}

int main()
{
	PerfectForward(10);           // 右值

	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值

	const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值

	return 0;
}

????????此時再使用萬能引用的時候,在函數(shù)模板中調用重載函數(shù)時只需要使用完美轉發(fā)就可以保持推演出來的屬性不變,右值引用仍然是右值,const右值引用也仍然是右值。

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

需要注意的是:

????????雖然右值不可以被修改,但是右值引用以后具有了左值屬性,才能被轉移,一旦被const修飾以后就無法轉移了。所以在使用右值引用的時候,不要使用const來修飾。


5. 新的類功能

在原來的C++類中,有6大默認成員函數(shù):

1. 構造函數(shù) 2. 析構函數(shù) 3. 拷貝構造函數(shù) 4. 拷貝賦值重載 5. 取地址重載 6. const 取地址重載

????????重要的是前4個,后兩個用處不大。默認成員函數(shù)就是我們不寫編譯器會生成一個默認的,而且完全符號我們使用的需求。

5.1 默認生成的移動構造/賦值

C++11中新增了兩個:移動構造和移動賦值運算符重載,此時C++11一共有8個默認成員函數(shù)了。

這兩個成員函數(shù)在前面已經(jīng)介紹過了,這里站在默認成員函數(shù)的角度繼續(xù)談談。

滿足下列條件,編譯器會自定生成移動構造函數(shù):

  • 沒有自己顯示定義移動構造函數(shù),且沒有實現(xiàn)析構函數(shù),拷貝構造函數(shù),拷貝賦值重載中的任何一個。

此時編譯器會自定生成一個默認的移動構造函數(shù)。功能:

  • 默認生成的移動構造函數(shù),對于內置類型會逐字節(jié)進行拷貝。
  • 對于自定義類型,如果實現(xiàn)了移動構造就調用移動構造,沒有實現(xiàn)就調用拷貝構造。

滿足下列條件,編譯器會自動生成移動賦值重載函數(shù)

  • 自己沒有顯示定義移動賦值重載函數(shù)。且沒有實現(xiàn)析構函數(shù),拷貝構造函數(shù),拷貝賦值重載中的任何一個。

此時編譯器會自動生成一個默認移動賦值函數(shù)。功能:

  • 對于內置類型會按字節(jié)拷貝。
  • 對于自定義類型,如果實現(xiàn)了移動賦值就調用移動賦值,如果沒有實現(xiàn)就調用拷貝賦值。

????????創(chuàng)建一個類,屏蔽掉拷貝構造,拷貝賦值,以及析構函數(shù),成員變量有一個是我們自己實現(xiàn)的string,里面有移動構造和移動賦值。

namespace rtx
{
	class string
	{
	public:
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		string(const string& s) // 拷貝構造
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 拷貝構造(深拷貝)" << endl;
			string tmp(s._str);
			swap(tmp);
		}

		string(string&& s) // 移動構造
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移動構造(資源轉移)" << endl;
			swap(s);
		}

		string& operator=(const string& s) // 拷貝賦值
		{
			cout << "string& operator=(string s) -- 拷貝賦值(深拷貝)" << endl;
			string tmp(s);
			swap(tmp);

			return *this;
		}

		string& operator=(string&& s) // 移動賦值
		{
			cout << "string& operator=(string s) -- 移動賦值(資源移動)" << endl;
			swap(s);

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

	protected:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

class Person
{
public:
	//Person(const char* name = "", int age = 0)
	//	:_name(name)
	//	, _age(age)
	//{}
	//Person(const Person& p) // 拷貝構造
	//	:_name(p._name)
	//	, _age(p._age)
	//{}
	//Person& operator=(const Person& p) // 拷貝賦值
	//{
	//	if (this != &p)
	//	{
	//		_name = p._name;
	//		_age = p._age;
	//	}
	//	return *this;
	//}
	//~Person()
	//{}

protected:
	rtx::string _name;
	int _age;
};

int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????此時Person就自動生成了移動構造函數(shù),并且調用了string中的移動構造和移動賦值函數(shù)來構造string對象。

? ? ? ? 將Person中的拷貝構造,拷貝賦值,析構函數(shù)任意放出一個來。(這里只放出了析構)使用右值構建string對象時,都會調用string的拷貝構造和拷貝賦值函數(shù)。

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

  • 編譯器默認生成的移動賦值和移動構造類型。
  • 如果符合條件就生成,內置類型按字節(jié)處理,自定義類型調用自定義類型的移動賦值或者移動構造,如果沒有的化就調用它們的拷貝賦值或者拷貝構造。
  • 如果不符合條件,就直接調用自定義類型的拷貝復制或者拷貝構造。

5.2 類里新的關鍵字

強制生成默認函數(shù)的關鍵字default:

這個default并不是switch中的default,而是C++11的新用法。

  • 假設類中的某個默認成員函數(shù)沒有自動生成,但是我們需要它,就可以用default,強制讓編譯器自動生成默認函數(shù)。

????????5.1里的代碼:將Person中的拷貝構造,拷貝復制,析構函數(shù)都顯示定義,此時就破壞了自動生成移動構造的條件。把Person里的注釋放開,使用default強制生成默認的移動構造函數(shù)

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

?從結果中可以看到,仍然調用了string中的移動構造函數(shù),而不是調用的拷貝構造(深拷貝)。

  • ?說明Person中仍然生成了默認的移動構造函數(shù)。

禁止生成默認成員函數(shù)的關鍵字delete:

如果能想要限制某些默認函數(shù)的生成,在C++98中,是該函數(shù)設置成private,并且只聲明補丁 已,這樣只要其他人想要調用就會報錯。在C++11中更簡單,只需在該函數(shù)聲明加上=delete即 可,該語法指示編譯器不生成對應函數(shù)的默認版本,稱=delete修飾的函數(shù)為刪除函數(shù)。

C++98不生成默認成員函數(shù)的方法:直接一個分號(要放到保護或者私有里,這里就不放了)

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????在Person類中不顯示定義拷貝構造函數(shù),拷貝復制函數(shù),析構函數(shù),此時符合自動生成默認移動構造的條件。?聲明移動構造函數(shù),但是沒有定義(要放到保護或者私有里,防止類外實現(xiàn),這里就不放了)。此時在編譯的時候就會報錯,這是C++98中的方式利用鏈接時找不到函數(shù)的定義報錯。C++11就新增delete關鍵字使其在編譯階段就報錯:

  • C++11中,使用delete同樣可以實現(xiàn)不讓自動生成默認成員函數(shù)。

從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值,④從C語言到C++,c++,c++11,知識點,右值引用,開發(fā)語言

????????同樣在編譯時報錯了。編譯器會自動生成移動構造函數(shù),但是此時使用了delete,編譯器就會報錯,告訴我們這里生成了移動構造。這是為了在編譯階段就報錯,而不是運行時再報錯。

以前提到的一道題:

// 要求delete關鍵字實現(xiàn),一個類,只能在堆上創(chuàng)建對象
class HeapOnly
{
public:
?? ?HeapOnly()
?? ?{
?? ??? ?_str = new char[10];
?? ?}

?? ?~HeapOnly() = delete;

?? ?//void Destroy() // 如果要銷毀只能這樣
?? ?//{
?? ?//?? ?delete[] _str;

?? ?//?? ?operator delete(this);
?? ?//}

private:
?? ?char* _str;
?? ?//...
};

繼承和多態(tài)中的final與override關鍵字

這兩個關鍵字在繼承和多態(tài)部分詳細講解過,這里不再詳細講解。

final

  • 在繼承中,被final修飾的類叫做最終類,是無法繼承的。
  • 在多態(tài)中,被final修飾的虛函數(shù)是無法進行重寫的。

override

  • 在多態(tài)中,用來檢查虛函數(shù)是否完成了重寫。

本篇完。

????????C++11中的很多東西雖然讓C++越來越不像C++,比如列表初始化等內容,但是還是有一些非常有用的東西的:比如今天講到的右值引用,和下一篇學的lambda表達式。

下一篇:從C語言到C++_34(C++11_下)可變參數(shù)+ lambda+function+bind+筆試題。

穿越回來復習順便貼個下篇鏈接:從C語言到C++_34(C++11_下)可變參數(shù)+ lambda+function+bind+筆試題-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-657012.html

到了這里,關于從C語言到C++_33(C++11_上)initializer_list+右值引用+完美轉發(fā)+移動構造/賦值的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【C++進階知識】04 - 函數(shù)默認實參、默認初始化、initializer_list

    默認實參需要注意以下幾點: (1)函數(shù)默認實參的賦值應從右往左,否則編譯報錯,因為參數(shù)入棧應該從右往左。 (2)類外的默認實參會使類的非默認構造函數(shù)變成默認構造函數(shù)。 (3)如果在類中添加了該函數(shù)的該參數(shù)的默認實參,那么在類外再次定義該參數(shù)的默認實參

    2024年02月14日
    瀏覽(17)
  • 【C++11】左值引用和右值引用

    【C++11】左值引用和右值引用

    需要云服務器等云產(chǎn)品來學習Linux的同學可以移步/--騰訊云--/--阿里云--/--華為云--/官網(wǎng),輕量型云服務器低至112元/年,新用戶首次下單享超低折扣。 ??? 目錄 一、新的類功能 1、新的默認成員函數(shù) 2、類成員變量初始化 3、強制生成默認函數(shù)的default 4、禁止生成默認函

    2023年04月17日
    瀏覽(24)
  • 【送書】【C++11】左值引用和右值引用

    【送書】【C++11】左值引用和右值引用

    需要云服務器等云產(chǎn)品來學習Linux的同學可以移步/--騰訊云--/--阿里云--/--華為云--/官網(wǎng),輕量型云服務器低至112元/年,新用戶首次下單享超低折扣。 ??? 目錄 一、新的類功能 1、新的默認成員函數(shù) 2、類成員變量初始化 3、強制生成默認函數(shù)的default 4、禁止生成默認函

    2023年04月09日
    瀏覽(18)
  • 【C++】C++11右值引用

    【C++】C++11右值引用

    ?? 樊梓慕: 個人主頁 ??? 個人專欄: 《C語言》 《數(shù)據(jù)結構》 《藍橋杯試題》 《LeetCode刷題筆記》 《實訓項目》 《C++》 《Linux》 《算法》 ?? 每一個不曾起舞的日子,都是對生命的辜負 目錄 前言 1.什么是左值什么是右值 左值 右值 2.什么是左值引用什么是右值引用 左

    2024年04月22日
    瀏覽(29)
  • [開發(fā)語言][c++]:左值、右值、左值引用、右值引用和std::move()

    寫在前面: 如果你也被 左值、右值、左值引用、右值引用和std::move 搞得焦頭爛額,相關概念和理解不夠深入,或者認識模棱兩可,那么這篇文章將非常的適合你,耐心閱讀,相信一定會有所收獲~~ 左值: 可以取地址、位于等號左邊 – 表達式結束后依然存在的持久對象

    2024年02月02日
    瀏覽(14)
  • 【C++學習】C++11——新特性 | 右值引用 | 完美轉發(fā)

    【C++學習】C++11——新特性 | 右值引用 | 完美轉發(fā)

    ??作者:一只大喵咪1201 ??專欄:《C++學習》 ??格言: 你只管努力,剩下的交給時間! C++的發(fā)展截至到目前為止,雖然版本有很多,但是C++11則帶來了數(shù)量可觀的變化,其中包含了約140個新特性,以及對C++03標準中約600個缺陷的修正,這使得C++11更像是從C++98/03中孕育出的一

    2024年02月06日
    瀏覽(19)
  • 【C++】—— C++11新特性之 “右值引用和移動語義”

    【C++】—— C++11新特性之 “右值引用和移動語義”

    前言: 本期,我們將要的介紹有關 C++右值引用 的相關知識。對于本期知識內容,大家是必須要能夠掌握的,在面試中是屬于重點考察對象。 目錄 (一)左值引用和右值引用 1、什么是左值?什么是左值引用? 2、什么是右值?什么是右值引用? (二)左值引用與右值引用比

    2024年02月11日
    瀏覽(19)
  • C++11『右值引用 ‖ 完美轉發(fā) ‖ 新增類功能 ‖ 可變參數(shù)模板』

    C++11『右值引用 ‖ 完美轉發(fā) ‖ 新增類功能 ‖ 可變參數(shù)模板』

    ?個人主頁: 北 海 ??所屬專欄: C++修行之路 ??操作環(huán)境: Visual Studio 2022 版本 17.6.5 自從C++98以來,C++11無疑是一個相當成功的版本更新。它引入了許多重要的語言特性和標準庫增強,為C++編程帶來了重大的改進和便利。C++11的發(fā)布標志著C++語言的現(xiàn)代化和進步,為程序員

    2024年02月05日
    瀏覽(25)
  • 【C++干貨鋪】C++11新特性——右值引用、移動構造、完美轉發(fā)

    【C++干貨鋪】C++11新特性——右值引用、移動構造、完美轉發(fā)

    ========================================================================= 個人主頁點擊直達:小白不是程序媛 C++系列專欄:C++干貨鋪 代碼倉庫:Gitee ========================================================================= 目錄 左值與左值引用 右值與右值引用 左值引用和右值引用的比較 ?左值引用總結:

    2024年01月25日
    瀏覽(19)
  • 【C++】 C++11(右值引用,移動語義,bind,包裝器,lambda,線程庫)

    【C++】 C++11(右值引用,移動語義,bind,包裝器,lambda,線程庫)

    C++11是C++語言的一次重大更新版本,于2011年發(fā)布,它包含了一些非常有用的新特性,為開發(fā)人員提供了更好的編程工具和更好的編程體驗,使得編寫高效、可維護、安全的代碼更加容易。 一些C++11的新增特性包括: 強制類型枚舉,使得枚舉類型的通常行為更加可靠和容易控制

    2024年02月10日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包