IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++11特性:大括号初始化、右值引用和移动语义 -> 正文阅读

[C++知识库]C++11特性:大括号初始化、右值引用和移动语义

目录
1. 统一的列表初始化
2. 右值引用和移动语义
2.1右值引用的详细讲解
2.2右值引用中move的使用
2.3完美转发

统一的列表初始化

在C++98标准下,和C语言一样允许对数组和结构体用{}进行初始化。

struct A
{
	int _a;
	int _b;
};

int main()
{
	int arr[] = { 3,5,6,23,2,5 };
	A a = { 1,2 };
	A aa[] = { {3,2},{4,1},{4,6} };
	return 0;
}

上述初始化都是语法允许的,对于class 类的成员变量默认是私有的用大括号初始化,就必须实现公共的构造函数,struct中的成员变量默认是公共的所以不用实现构造函数即可

class A
{
private:
	int _a;
	int _b;
};

int main()
{
	A a = { 2,2 };   //报错
	A aa[] = { {1,3},{2,4} };  //报错
	return 0;
}

在这里插入图片描述
下面这样就不会报错

class A
{
public:
	A(int a,int b)   //实现一个构造函数,就可以用大括号初始化对象
		:_a(a)
		,_b(b)
	{}
private:
	int _a;
	int _b;
};

int main()
{
	A a = { 2,2 };
	A aa[] = { {1,3},{2,4} };
	return 0;
}

C++98对于stl中的容器是不支持用大括号来初始化的,比如vector,我们创建一个vector的对象,要么就是用默认构造创建,容器中没有存储元素,或者是创建对象时指定n个特定值,或者拷贝构造一个其他vector的副本,还可以使用resize成员方法。C++11实现了stl中的容器同样可以用大括号来初始化。

int main()
{
	vector<int>v = { 2,4,6,3,6 };
	map<int, string >m= { {2, "sort"}, { 3,"left" }, { 4,"right" }};
	list<int>ls{ 5,3,6,7 };
	auto e = { 4,3,5 };
	cout << typeid(e).name() << endl;
	return 0;
}

运行上述代码会打印大括号的类型
在这里插入图片描述
我们再看看这些容器的构造函数看看它们为什么能支持大括号初始化
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以发现stl的容器都实现了一个用initializer_list的对象来构造的函数,我们前面也打印了大括号类型的数据就是initializer_list。下面我们来验证是不是因为有这个类型的构造函数才支持的大括号初始化,我们写一个简单的vector,看看不写initializer_list版本的构造函数,和写了initializer_list版本的构造函数的情况是什么样的。

template<class T>
class myvector
{
public:
	myvector()
		:_a(nullptr)
		,index(0)
		,capacity(0)
	{}
	void push_back(const T& val)
	{
		if (index == capacity)
		{
			int capa = capacity == 0 ? 10 : 2 * capacity;
			expain(capa);
		}
		_a[index++]=val;
	}
	~myvector()
	{
		if (_a)
		{
			delete[]_a;
			index = capacity = 0;
		}
	}
private:
	void expain(size_t capa)
	{
		if (capa > capacity)
		{
			T* tem = new T[capa];
			for (int i = 0; i < index; ++i)
				tem[i] = _a[i];
			delete[]_a;
			_a = tem;
			capacity = capa;
		}	
	}
	T* _a;
	size_t index;
	size_t capacity;
};
int main()
{
	myvector<int>v1;
	for (int i = 1; i <= 9; ++i)
		v1.push_back(i);
	myvector<int>v2 = { 2,4,5 }; //报错
	return 0;
}

在这里插入图片描述
实现一个initializer_list版本的构造函数后的完整代码如下

{
template<class T>
class myvector
{
public:
	myvector()
		:_a(nullptr)
		,index(0)
		,capacity(0)
	{}
	myvector(initializer_list<T>li)
		:_a(nullptr)
		, index(0)
		, capacity(0)
	{
		for (auto e : li)
			push_back(e);
	}
	size_t size()
	{
		return index;
	}
	T& operator[](size_t i)
	{
		assert(i < index);
		return _a[i];
	}
	void push_back(const T& val)
	{
		if (index == capacity)
		{
			int capa = capacity == 0 ? 10 : 2 * capacity;
			expain(capa);
		}
		_a[index++]=val;
	}
	~myvector()
	{
		if (_a)
		{
			delete[]_a;
			index = capacity = 0;
		}
	}
private:
	void expain(size_t capa)
	{
		if (capa > capacity)
		{
			T* tem = new T[capa];
			for (int i = 0; i < index; ++i)
				tem[i] = _a[i];
			delete[]_a;
			_a = tem;
			capacity = capa;
		}	
	}
	T* _a;
	size_t index;
	size_t capacity;
};
int main()
{
	myvector<int>v1;
	for (int i = 1; i <= 9; ++i)
		v1.push_back(i);
	myvector<int>v2 = { 2,4,5 };
	for (int i = 0; i < v2.size(); ++i)
		cout << v2[i] << " ";
	cout << endl;
	return 0;
}

实现了initializer_list版本就可以用大括号来初始化了
在这里插入图片描述

右值引用和移动语义

我们知道引用就是给变量取别名,操作同一块内存空间,右值引用就是给右值取别名,先谈一谈什么是左值和右值:

左值通常就是可以放在等号左边的值,我们在栈上和堆上定义的变量,解引用的指针,我们可以给左值赋值,取左值的地址,左值也可以放在等号右边。

右值是只能放在等号右边,右值也是一个表示数据的表达式,如临时变量、表达式的返回值和函数返回值,这些都是右值,右值不能取地址,但是可以给右值取别名,取别名后,就有了一块稳定的空间存放右值数据,可以对被引用后的别名取地址。能不能取地址是区分左右值的关键。

int main()
{
	int x = 2, y = 3;
	int a = 5   //a ,x,y都是左值
		10     //  10   x+y    add(x,y)都是右值
		const int& f = 10;   //const 的左值引用可以引用右值 , 右值引用只能引用右值,不能引用左值
		x + y;
		x + y = a;     //右值是不能被赋值的,不能放在等号左边
		add(x, y) = a;
		10 = a;
	add(x, y);
	int&& b = 10;          //右值引用
	int&& c = x + y;
	int&& d = add(x + y);
	return 0;
}

移动语义

C++11提供右值引用如果只是单纯想给右值取别名,那用处是不大的。这就要谈到左值引用的短板了,当在函数中的一个对象出了作用域就要销毁,如果要用这个对象去函数外构建一个新的对象,我们要用值返回的方式返回这个对象,要返回的对象会拷贝构造一个临时对象,然后再用这个临时对象去拷贝构造一个新的对象,当对象中都涉及资源管理时,两次拷贝都只是为了创建一个拥有返回对象资源的对象,两次深拷贝,开空间,释放空间,再去开空间又释放空间,这样的场景效率有很大的提升空间。右值引用的移动语义就是当我要用一个即将销毁的对象去构造新的对象时,实现一个右值引用该类对象的构造函数即移动构造,来构造新对象,函数返回函数内创建的对象,临时对象,都是将亡值,出了作用域就销毁和执行下一步就销毁的对象,编译器认为其是右值,所以用它们构造对象时会走移动构造,在移动构造里,我们只要简单的交换资源就行了,还有移动赋值,没有深拷贝的开销从而提高了效率。

实现一个string类来说明移动语义的作用

class mystring
{
public:
	mystring(const char* str = "")
		:sz(strlen(str))
		,capacity(sz)
	{
		_str = new char[capacity + 1];
		strcpy(_str, str);
	}
	mystring(const mystring& s)
		:sz(strlen(s._str))
		,capacity(sz)
	{
		_str = new char[capacity + 1];
		strcpy(_str, s._str);
		cout << "拷贝构造" << endl;
	}
	void swap(mystring& s)
	{
	std::swap(_str, s._str);
	std::swap(sz, s.sz);
	std::swap(capacity, s.capacity);
	}
	mystring(mystring&& s) 
		:_str(nullptr)
		,sz(0)
		,capacity(0)
	{
		this->swap(s);
		cout << "移动构造,资源转移" << endl;
	}
	mystring& operator=(const mystring& s)
	{
		mystring tem(s);
		this->swap(tem);
		cout << "拷贝赋值" << endl;
		return *this;
	}
	mystring& operator=(mystring&& s)
	{
		this->swap(s);
		cout << "移动赋值" << endl;
		return *this;
		
	}
	char* c_str()const
	{
		return _str;
	}
	void push_back(char ch)
	{
		if (sz == capacity)
		{
			int newpan = capacity == 0 ? 10 : 2 * capacity;
			reserve(newpan);
		}
		_str[sz++] = ch;
	}
	mystring operator+(const char* str)
	{
		int len = strlen(str);
		mystring tem(_str);
		if (sz + len == capacity)
		{
			tem.reserve(2 * (sz + len));
		}
		strcpy(tem.c_str()+sz, str);
		tem.sz += len;
		return tem;
	}
	mystring operator+(const mystring& s)
	{
		mystring tem(_str);
		if (sz + s.sz == capacity)
		{
			tem.reserve(2 * (sz + s.sz));
		}
		strcpy(tem.c_str() + sz, s.c_str());
		tem.sz += s.sz;
		return tem;
	}
	size_t size()
	{
		return sz;
	}
	char& operator[](size_t t)
	{
		assert(t < sz);
		return _str[t];
	}
	void reserve(size_t newpan)
	{
		if (newpan > capacity)
		{
			char* tem = new char[newpan + 1];
			strcpy(tem, _str);
			delete[]_str;
			_str = tem;
			capacity = newpan;
		}
	}
private:
	char* _str;
	size_t sz;
	size_t capacity;
};
mystring func1()
{
	mystring tem("hello world");
	return  tem;
}
int main()
{
	mystring s1("hello");
	mystring s2("world");
	mystring s3; 
	s3= s1 + s2;   //这里值返回方式返回operator+中的局部对象会移动构造一个临时对象再用临时对象移动赋值给s3
	mystring s5; 
	s5=(mystring("vs2019")); //移动赋值
	s5 = func1();//也是先移动构造临时对象,临时对象再移动赋值给s5
	mystring s4 = func1(); //这里本来是移动构造临时对象,临时对象再移动构造s4,编译器优化只有一次移动构造
	return 0;
}

在这里插入图片描述

右值引用中move的使用

const 左值引用可以引用右值,但是不能右值引用左值,如果非要去引用一个左值的话,必须将一个左值强制转化为一个右值,可以通过move函数将一个左值转化为一个右值,传入该右值去移动构造和移动赋值,完成移动构造或移动赋值后,原先的左值资源就被转移走了,要谨慎使用move。

mystring s1("hello");
	mystring s2("world");
	mystring s3(move(s2));   //s1和s2都是左值,使用move后移动构造s3,再用s1移动赋值s3, 此后s2和s3中的资源都被转移走了。
	s3 = move(s1);

在这里插入图片描述

完美转发

在一些场景中外层函数接受一下参数,再用这些参数来调用内层函数时,如果原先接受了一个右值,到函数中也会被认为是左值,完美转发就是要保持参数原先的属性然后去调用其他函数。下面实验一下没有完美转发和有完美转发的效果。

void Func(int& a) { cout << "lvalue" << endl; }
void Func(int&& a) { cout << "rvalue" << endl; }
void Func(const int& a) { cout << "const lvalue" << endl; }
void Func(const int&& a) { cout << "const rvalue" << endl; }
template<class T>
void perfectfunc(T&& a)// 函数模板中的&&是万能引用,传入的是左值就是左值引用传入右值就是右值引用 
{
	Func(a);  
}
int main()
{
	perfectfunc(10);
	int a = 10;
	const int b = a;
	perfectfunc(a);
	perfectfunc(b);
	perfectfunc(move(b));
	return 0;
}

没有完美转发,传给Func的参数都被认为是左值。
在这里插入图片描述
将函数模板加上完美转发的语法修饰

void perfectfunc(T&& a)
{
	Func(std::forward<T>(a));  //加上forward<T>原来传入右值给外函数也会保持其右值属性传给目标函数,是左值就保持左值属性
}

现在的效果
在这里插入图片描述
达到了保持传入参数属性的效果。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:32:27  更:2022-07-03 10:35:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/12 6:13:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码