秒懂C++之C11新特性

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

 

统一的列表初始化

initializer_list

decltype

右值引用

万能引用

新的默认成员函数

关键字default/delete

可变参数模板

emplace

 lambda表达式

function包装器

bind

结语


统一的列表初始化

C++11 扩大了用大括号括起的列表 ( 初始化列表 ) 的使用范围,使其可用于所有的内置类型和用户自
定义的类型, 使用初始化列表时,可添加等号 (=) ,也可不添加
struct Point
{
	int _x;
	int _y;
};

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()
{
	int a = 0;
	//不推荐
	int b = { 1 };
	int c{ 1 };

	int array1[]{ 1, 2, 3, 4, 5 };
	Point p{ 1,2 };

	// 多参数
	// C++98  构造
	Date d1(2024, 3, 23);

	// C++11 
	// 先构造 + 再拷贝构造 -> 优化,直接构造
	// 多参数的隐式类型转换
	Date d2 = {2024, 3, 23};
	Date d3 { 2024, 3, 23 };

	// 单参数
	// 构造
	string s1("1111");
	// 先构造 + 再拷贝构造 -> 优化,直接构造
	string s2  = "1111";

	


	Date* darr1 = new Date[3]{d1,d2,d3};
	Date* darr2 = new Date[3]{ { 2024, 3, 23 } ,{ 2024, 3, 23 } ,{ 2024, 3, 23 } };

	Date* darr3 = new Date(2023,3,34);
	Date* darr4 = new Date{ 2023, 3, 34 };

	return 0;
}

开辟空间这种初始化写法可以参考一下,其他的认识即可~不过这种初始化一般不用于内置类型中~因为看着怪怪的~

initializer_list

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

其实initializer_list本质就是两个指针,一个指向{}头,一个指向{}尾~

int main()
{
	
	Date d1 = { 2024,9,3 };
	vector<int> v1 = { 1,2,3,4,5,6,7,8,9};
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	
}

二者虽然同为先构造再拷贝构造,但vector是走的特殊的构造,在这里可以不限制参数个数~

我们再来深入了解其他场景下的应用~

int main()
{

	pair<string, string> kv1("sort", "排序");
	pair<string, string> kv2("string", "字符串");
	//可以识别为pair类型的initializer_list
	map<string, string> dict1 = { kv1, kv2 };
	//那么这个又是怎么识别的呢
	map<string, string> dict2 = { {"sort", "排序"}, {"string", "字符串"} };

	for (auto& kv : dict2)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	
}

decltype

关键字decltype将变量的类型声明为表达式指定的类型。
// 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;
}

ps:顶层const是指修饰自身的const,底层const是指修饰器指向的内容,而在查看变量类型时会默认去掉顶层const~

右值引用

左值是一个表示数据的表达式 ( 如变量名或解引用的指针 ) 我们可以获取它的地址 + 可以对它赋
值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边 。定义时 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;
}
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值 ( 这个不能是左值引
用返回 ) 等等, 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址 右值引用就是对右值的引用,给右值取别名。

int main()
{
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;
return 0;
}

从语法层面上看,引用都是作为其别名,不开空间~左值引用是给左值取别名,右值引用是给右值取别名~

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

特殊情况:

int func1()
{
	static int x = 0;
	return x;
}
int main()
{
	// 左值引用能否给右值取别名 不能
	// 但是const左值引用可以
	const int& r1 = func1();
	const int& r2 = 10;

	// 右值引用能否给左值取别名 不能
	// 但是右值引用可以给move以后的左值可以
	int x = 0;
	int&& rr1 = move(x);

	return 0;
}

我们来分析以下场景:

当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,
只能传值返回。例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,
传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

简要概括后我们再来深入理解右值引用出现的意义~

引用最开始被设计的意义是什么?

为了减少拷贝~

那么右值引用出现的意义是什么?

是为了解决左值引用不能解决的问题~

左值引用:

可以解决传递参数时的拷贝问题~例如 void func(const T& x)

可以解决部分返回对象拷贝的问题~(出了作用域后返回对象还在,那么就可以用左值返回来减少一次拷贝)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值