C++11——新特性(一)

1.列表初始化

  在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。 
struct Point
{
	int _x;
	int _y;
};
int main()
{
	int array[] = { 0,1,2,3,4,5 };
	int array[5] = { 0 };
	Point p = { 1,2 };
	return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型使用初始化列表时,可添加等号(=),也可不添加
struct Point
{
	int _x;
	int _y;
};
int main()
{
	int a = 1;
	int b{ 2 };
	int array1[]{ 1, 2, 3, 4, 5 };
	int array2[5]{ 0 };
	Point p{ 1, 2 };
	// C++11中列表初始化也可以适用于new表达式中
	int* pa = new int[4] {0};

	return 0;
}
创建对象时也可以使用列表初始化方式调用构造函数初始化
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;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 4, 1);
	Date d2 = { 2024,4,2 };
	Date d3{ 2024,4,3 };
	Date* dp1 = new Date[3]{ d1,d2,d3 };
	Date* dp2 = new Date[3]{ {2024,4,1},{2024,4,2},{2024,4,3} };


	return 0;
}

2.std::initializer_list

initializer_list是一个特殊的容器(不实际存储数据)

此类型用于访问C++初始化列表中的值,该列表是const T类型的元素列表。编译器根据初始化列表声明自动构建此类型的对象,初始化列表声明是一个逗号分隔的元素列表,用大括号括起来

嗯...以实例来看比较好理解,这里我们用vector为例

std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加
std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=
的参数,这样就可以用大括号赋值

3.变量类型推导 

3.1auto

感觉auto没什么好讲的,很少用到,就是单存的适配所有类型,当然auto声明的类型必须要进行初始化,而且不要作为函数的参数和用来接收函数返回值的变量类型


int main()
{
	int i = 10;
	auto p = &i;
	cout << typeid(p).name() << endl;
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
	//map<string, string>::iterator it = dict.begin();
    //嫌迭代器太长、麻烦,用auto做类型(感觉已经挺过分了)
	auto it = dict.begin();
	return 0;
}

3.2decltype

关键字decltype可以根据表达式的实际类型推演出定义变量时所用的类型,且可以用推导出来的类型进行变量声明,而typeid().name不行
int func1()
{
	return 10;
}

int main()
{
	const int x = 1;
	double y = 2.2;

	cout << typeid(x).name() << endl;
	cout << typeid(string).name() << endl;

	decltype(x) z = 1;
	cout << typeid(z).name() << endl;

	const int* p1 = &x;
	cout << typeid(p1).name() << endl;
	decltype(p1) p2 = nullptr;
	cout << typeid(p2).name() << endl;

	auto ret = func1();
	// 假设要用vector存func1的数据
	vector<decltype(ret)> v;

	return 0;
}

4.STL中一些变化

新容器
用橘色圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和
unordered_set。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可。

array是静态数组,forward_list是单链表 

5.右值引用和移动语义 

5.1左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们
之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名
什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋
值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左
值,不能给他赋值,但是可以取它的地址(右值不能)。左值引用就是给左值取别名;右值引用就是给右值取别名
常见的左值及左值引用
int main()
{
    // 以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    // 以下几个是对上面左值的左值引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
    return 0;
}

常见的右值及右子引用

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地
址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,
这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

总结一下:

语法上,引用都是别名,不开空间,左值引用是给左值取别名,右值引用是给右子取别名

就底层来说,引用是用指针实现的。左值引用是存当前左值的地址;右值引用,是把当前右值拷贝到栈上的一个临时空间,存储这个临时空间的地址

 5.2左值引用和右值引用比较

左值引用:

左值引用只能引用左值,不能引用右值

但是const左值既可以引用左值,也可以引用右值


int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra = a;
	//int& ra2 = 10;   // 编译失败,因为10是右值
	// const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;
	return 0;
}

右值引用:

右值引用只能给右值取别名,不能给左值取别名

但是move后的左值可以被右值引用取别名


int main()
{
    int x = 0;//左值
    int&& rr1 = move(x);
	return 0;
}

 注意:

const左值引用既可以给左值取别名,也可以给右值取别名

右值引用可以给move(左值)取别名

6.右值引用使用场景和意义

6.1移动构造和移动赋值

左值引用做参数和返回值可以提高效率->解决了什么问题

1.传参的拷贝全解决了

2.传返回值的问题解决了一部分

另外一部分

局部对象(出了作用域就销毁对象)返回的拷贝问题没有解决

由右值引用来解决

右值引用:提高移动构造/移动赋值等深拷贝场景的效率

namespace xxx
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		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;

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

		// 移动构造 -- 右值(将亡值)
		string(string&& s)
		{
			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()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		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';
		}

		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};


	xxx::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

		xxx::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;

			str += ('0' + x);
		}

		if (flag == false)
		{
			str += '-';
		}

		reverse(str.begin(), str.end());
		return str;
	}
}
int main()
{
	xxx::string ret = xxx::to_string(-1234);
}

string构造是string(const string& s),const左值既能引用左值又能引用右值,所以要重载一个右值版本的构造,也就是移动构造

		// 移动构造 -- 右值(将亡值)
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动拷贝" << endl;
			swap(s);
		}
左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,
传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

有了移动构造后呢

C++11对右值概念的解释(细分,方便理解)

1.纯右值(内置类型的右值)如:10、a+b

2.将亡值(自定义类型的右值)如:匿名对象、传值返回函数 

string s2(tmp)

如果tmp对象将亡值,深拷贝是一种极度的资源浪费,因为深拷贝后,tmp马上又销毁 

那么可以用移动构造 

C++提供右值引用,本质是为了参数匹配时区分左右值

注意:不是所有的类都要移动构造(浅拷贝的类不需要,深拷贝的才要)

移动赋值结合以下场景分析

int main()
{
	/*xxx::string ret = xxx::to_string(-1234);*/
	xxx::string ret;
	ret = xxx::to_string(-1234);
}

 

考虑下赋值重载

s2 = tmp

如果tmp是左值那么只能深拷贝

如果tmp是右值(将亡值)

 

移动将亡值的资源,并将不要的空间给将亡值,让将亡值帮忙释放 

		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动拷贝" << endl;
			swap(s);

			return *this;
		}

 

 总结:左值引用没有解决的问题,右值引用解决了。深拷贝对象传值返回只需要移动该资源,代价很低。C++11后所以容器都增加了移动构造和移动赋值

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值