C++11(上)

目录

1:列表初始化

2:std::initializer_list

3:变量类型推导

3.1:auto推导类型

3.2:decltype

3.3:nullptr

4:范围for

5:STL新增容器和容器新增接口

5.1:array

6:左值引用和右值引用

6.1:左值

6.2:右值

6.3:左值引用

6.4:右值引用

6.5:左值引用的场景和短板

 6.5.1:编译器关于构造的优化场景回顾

6.6:右值引用场景一:移动构造

6.7:右值引用场景二:移动赋值



1:列表初始化

C++11扩大了列表初始化的范围,旨在一切类型皆可用

在C语言中,列表初始化可以用于结构体的初始化。

struct date
{
	int month;
	int day;
};
int main()
{
	date d1 = { 8,4 };
	date d2[2] = { {1,1},{2,2} };
}

对于内置类型,我们可以通过花括号这样初始化

	int x1 = 1;
	int x2 = { 1 };
	int x3{ 1 };

但是这样有点鸡肋,感觉这个语法在c++11里面就是多余的成分。再看下面

可以在new对象,new变量的时候使用列表初始化。

	int* p1 = new int[4];//原本定义方式
	int* p2 = new int[4]{ 0 };//给4个0初始化
	int* p3 = new int[4]{ 0,1,2,3 };//用0123初始化

但是还是感觉没啥意义,再看下面,关于创建类对象的时候。

class date2
{
public:
	date2(int year,int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

	date2 dd1(2023, 4, 11);
    date2 dd2 = {2023,4,11};
	date2 dd3{2023,4,11 };

这里就是调用了构造函数进行初始化。

初始化列表的原理是什么呢?

2:std::initializer_list

C++11会自动识别花括号里面的内容为initializer_list的内容,举个例子:

	auto il = { 1,2,3,4 };
	cout << typeid(il).name() << endl;

 就像 这样。

其实initializer_list就是相当于保存常量的一个容器,经常用作构造函数的参数,也可以作为operator=的参数,可以用于花括号赋值。关于该容器,有这样几个接口:

用vector举个例子,vector可以这样初始化。

vector<int> v{ 1,2,3,4 };

从底层来说就是先用initializer_list保存花括号里面的常量,然后再遍历initializer_list,push_back到vector中。接下来模拟实现该原理。

namespace bit
{
	template<class T>
	class vector {
	public:
		typedef T* iterator;
		vector(initializer_list<T> l)
		{
			_start = new T[l.size()];
			_finish = _start + l.size();
			_endofstorage = _start + l.size();
			iterator vit = _start;
			typename initializer_list<T>::iterator lit = l.begin();
			while (lit != l.end())
			{
				*vit++ = *lit++;
			}
			//for (auto e : l)
			//   *vit++ = e;
		}
		vector<T>& operator=(initializer_list<T> l) {
			vector<T> tmp(l);
			std::swap(_start, tmp._start);
			std::swap(_finish, tmp._finish);
			std::swap(_endofstorage, tmp._endofstorage);
			return *this;
		}
	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

同样的,list也能这样初始化。

3:变量类型推导

3.1:auto推导类型

	vector<int> v(10, 0);
	vector<int>::iterator it1 = v.begin();
	auto it2 = v.begin();
	map<string, string> m;
	map<string, string>::iterator it1 = m.begin();
	auto it2 = m.begin();

 auto可以实现自动类型判断,但也并非万能。

3.2:decltype

该关键字可以将变量声明为表达式指定的类型。如:

template<class T1,class T2>
void Func(T1 x, T2 y)
{
	decltype(x * y) ret = x * y;
	cout << ret << endl;
}

T1和T2可能是两个不同的非类型模板参数,比如int和double相乘,如果用T1或者T2去声明ret,就会有结果误差。因此decltype可以精准的推导出x*y的类型。

void Func(T1 x, T2 y)
{
	decltype(x * y) ret = x * y;
	auto ret2 = x * y;
	cout << ret <<" "<< ret2 << endl;
}
	Func(1, 2.2);
	Func(1, 3);

 这里auto一样可以推导类型,所以decltype在auto不能起作用的地方起作用,实际上意义不大,但是要认识这个关键字。

3.3:nullptr

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

c++中有人将NULL定义为0,所以在某些地方判断条件的时候用NULL会有bug,因此如果想判断某个指针为空,需要使用nullptr。

4:范围for

一个比较实用的语法糖,

有3个条件

  1. 迭代范围需确定
  2. 需要写出迭代器的begin和end接口,左闭右开
  3. 迭代器需要支持++和==

void TestFor(int array[])
{
    for(auto& e : array)
        cout<< e <<endl;
}

这个就不能迭代,因为array的范围不确定。

5:STL新增容器和容器新增接口

 最下面2个应该不陌生,是比较方便查找的哈希表。

5.1:array

array是一个静态数组,相比较于c语言中的数组,多了一个越界写可以检查出来的机制。

	int arr[10];
	arr[10];
	arr[20] = 1;

c语言中的数组越界读没问题,而越界写则是随机检查。

	int arr[10];
	arr[10];
	arr[20] = 1;
	arr[10] = 1;

 当对10下标的元素进行写的时候,报错,对20下标不报错。因此C语言的数组只能随机对写进行检查。对读不检查。

而array既能对读检查,也能对写检查。

	array<int,10> a;
	a[10];

 读报错。

	array<int,10> a;
	a[10] = 1;

 写报错。

但是其实这样不如使用我们的vector。

6:左值引用和右值引用

6.1:左值

左值:是一个数据的表达式(如变量名或者指针解引用),可以在该语句之后还能获取到他的地址,能对他进行取地址和赋值操作,左值既可以在赋值符号左边,也可以在右边。

	// 以下的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;

6.2:右值

右值:也是一个数据表达式,通常比如:字面常量,传值返回的函数的返回值,表达式返回值。

double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;

 fmin的函数返回值不能是左值引用,否则该返回值就不是右值。

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

6.3:左值引用

  1. 左值引用只能引用左值。
  2. const左值引用既可以引用左值,也可以引用右值。
int a = 10;
const int& x1 = a;
const int& x2 = 10;

6.4:右值引用

  1. 右值引用只能引用右值。
  2. 右值可以引用左值move之后的值。
	int a = 10;
	int&& x1 = 10;
	int&& x2 = std::move(a);

6.5:左值引用的场景和短板

用于函数引用传参,和传引用返回,可以减少拷贝。

namespace wjw
{
	const string& to_string(const string& x)
	{
		return x;
	}
}

	wjw::to_string("abcd");

x是一个静态变量,可以直接返回x,且传引用返回没问题,减少值拷贝。但是如果碰到下面的情况呢?

	const string& to_string(const string& x)
	{
		string str;
		return str;
	}

这样写就有很大的问题,str是一个局部变量,出了函数作用域就会销毁,这里用左值引用返回就是返回一个已经销毁的变量,很明显不行。

所以需要使用传值返回,但是如果是传值返回,我们在函数栈帧学过,返回临时变量的时候,会创建一个临时变量,再把str拷贝构造给临时变量,这样就多了一次拷贝,这个时候就需要使用右值引用返回。

这里分为2步:

  •  创建临时对象,str拷贝构造给临时对象,临时对象拷贝构造给ret2,因为这个时候ret2是正在创建的,不是已经创建好的(所以ret2=这一步是拷贝构造,不是赋值重载)这里是一个拷贝构造+拷贝构造,编译器会自动优化成一次拷贝构造,就是将ret直接拷贝构造给ret2。

 6.5.1:编译器关于构造的优化场景回顾

void f1(A aa)
{}
A f2()
{
 A aa;
 return aa;
}
int main()
{
 // 传值传参
 A aa1;
 f1(aa1);
 cout << endl;
 // 传值返回
 f2();
 cout << endl;
 // 隐式类型,连续构造+拷贝构造->优化为直接构造
 f1(1);
 // 一个表达式中,连续构造+拷贝构造->优化为一个构造
 f1(A(2));
 cout << endl;
 // 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
 A aa2 = f2();
 cout << endl;
 // 一个表达式中,连续拷贝构造+赋值重载->无法优化
 aa1 = f2();
 cout << endl;

6.6:右值引用场景一:移动构造

 前面说过,连续的拷贝构造编译器会优化成一个拷贝构造,因为to_string是传值返回,返回值是一个右值,用这个右值去拷贝构造ret2,因为ret2的拷贝构造函数的参数是一个const左值引用,所以没有编译错误,这里就是调用了一个深拷贝。

拷贝的代价太大了如何解决?

str反正是一个要销毁的变量了,我们何妨不直接把他的资源偷窃给ret2呢?这里需要用到右值引用.。

string(string&& s)
   :_str(nullptr)
   ,_size(0)
   ,_capacity(0)
{
cout<<"string(string&& s)"<<"移动语义"<<endl;
swap(s);
}

c++中的右值有2种

 顾名思义,str是一个即将销毁的变量,我们直接对他进行引用,把他的资源偷窃给_str,就无需使用拷贝构造了。

6.7:右值引用场景二:移动赋值

 ret1是已经创建好的变量,之前说过拷贝构造+赋值重载不能进行优化,所以这里会发生2个步骤,先创建一个临时对象,编译器在这里会很聪明的把str的返回值识别成一个右值,这样的话临时对象就不会调用拷贝构造str,而是直接移动构造str,然后再把临时对象作为to_string的返回值赋值给ret1。所以结果会是一个移动构造+一个移动赋值

这里总结:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不熬夜不抽烟不喝酒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值