C++进阶(9)C++11

个人主页:仍有未知等待探索-CSDN博客

专题分栏:C++

目录

一、统一列表初始化

二、变量类型推导

1、auto

2、decltype

3、typeid

三、左值/右值

1、左值引用/右值引用(粗)

2、右值

3、右值引用(细)

1.移动构造、移动赋值

2.对于默认生成移动构造、移动赋值的条件  

四、lambda表达式

五、新的类功能 

六、模板的可变参数

1、计算形参对象包的数据个数?

2、打印出形参对象包里面的数据内容? 

3、容器中emplace系列

 七、包装器

1、function --- 类模板

2、bind --- 函数模板


一、统一列表初始化

一切可以用列表初始化。

注意:列表初始化和初始化列表是两个概念。

列表初始化是一个动作,而初始化列表是一种数据结构。

1、int arr[] = {1, 2, 3, 4, 5};
2、int x = {2}; // 正常的定义是: int x = 2; 但是为了统一列表初始化,上述也能支持
3、单参数的隐式类型转化
4、多参数的隐式类型转换 const A& aa1 = {2, 2};
5、vector<int> v = {1, 2, 3, 4, 5, 6};
6、构造:vector<int> v({1,2,3})
x自定义类型 = y类型 -> 隐式类型转换 x(y e)需要x支持y类型为参数的构造函数

二、变量类型推导

1、auto

自动推导变量类型

注意:

1、auto修饰的变量必须初始化。

2、auto类型不能用于函数参数。

3、auto类型不能用于定义数组。

4、auto无法推导模板参数。

5、auto不可以用于非静态成员变量

推导类型规则

// 当auto没有声明引用或者指针类型,auto类型推导则不带原变量的const和引用属性
// 输出
// 10
// 11
    const int a = 10;
    auto c = a;
    c++;
    cout << a << std::endl << c;


// 当auto类型声明了引用类型或者指针类型,auto类型推导带原变量的const和引用属性
	const int a = 10;
   	auto& c = a;
	c++; // 这句报错
	cout << a << std::endl << c; 

2、decltype

将变量的类型声明为表达式指定的类型

和auto的区别:

这个推导出来的类型可以定义变量。

list<int>::iterator it;
decltype(it) it2;

3、typeid

用于获取一个表达式或类型的运行时类型信息

list<int>::iterator it;
cout << typeid(it).name() << endl;

三、左值/右值

左值和右值不仅仅是一个值,也可能是有返回值的表达式

左值和右值的根本区别

1、能不能被取地址。

2、左值能被取地址。

3、右值不能被取地址。(匿名对象,字面常量,临时对象)

1、左值引用/右值引用(粗)

引用的意义:提高效率,减少拷贝。

左值引用:给左值起别名。

右值引用:给右值起别名。

注意:

  • 左值引用不能给右值起别名,但是const左值引用可以。
  • 右值引用不能给左值起别名,但是可以给move之后的左值起别名。

2、右值

右值包括:匿名对象,字面常量,临时对象。

右值分为两种

1、纯右值。(内置类型)

2、将亡值。(自定义类型)

class A
{};
// 纯右值
10、'e'、3.14
// 将亡值
A()

3、右值引用(细)

注意:

右值引用本身是左值。

只有右值引用本身处理成左值,才能实现移动构造和移动复制,转移资源。

1.移动构造、移动赋值

移动构造和移动赋值的本质就是转移资源

  • 右值引用的属性如果是右值,那么移动构造和移动复制,要转移资源的语法逻辑是矛盾的,右值是不能被改变的(右值带有const属性)。
  • 右值引用本身是左值。只有右值引用本身处理成左值,才能实现移动构造和移动复制,转移资源。
  • 深拷贝的类才有转移资源的移动系列函数。

说白了转移资源就是把将亡值的数据直接和类内成员变量进行交换。

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

class A
{
private:
	string _a;
public:
	// 强制生成A()
	A() = default;
	// 类A的拷贝构造 --- 深拷贝
	A(const string& a)
		:_a(a)
	{}
	// 类A的赋值重载
	A& operator=(const string& a)
	{
		_a = a;
		return *this;
	}
	// 移动构造
	A(string&& a)
	{
		swap(_a, a);
	}
	// 移动赋值
	A& operator=(string&& a)
	{
		swap(_a, a);
	}
};
int main()
{
	// 拷贝构造、赋值
	A a;
	string b = "abcd";
	a = b;
	A c(b);

	// 移动构造、移动赋值
	A d("11111");
	A e = string("11111");

	return 0;
}

2.对于默认生成移动构造、移动赋值的条件  

  • 如果你没有实现移动构造函数,且没有实现析构函数、拷贝构造,拷贝复制重载中的任意一个,那么编译器会默认生成一个默认的移动构造。对于内置类型成员会执行逐成员按字节拷贝;对于自定义类型成员,如果该自定义类型实现了移动构造函数就执行,刚没有实现就调用拷贝构造。
  • 如果你没有实现移动赋值重载,且没有实现析构函数、拷贝构造,拷贝复制重载中的任意一个,那么编译器会默认生成一个默认的移动赋值重载。对于内置类型成员会执行逐成员按字节拷贝;对于自定义类型成员,如果该自定义类型实现了移动赋值重载就执行,刚没有实现就调用拷贝赋值。
  • 如果提供了移动构造函数或者移动赋值,编译器则不会自动提供拷贝构造和拷贝赋值

四、lambda表达式

匿名函数对象

        

#include <iostream>
using namespace std;

int main()
{
	int c = 2, d = 1;
	// 捕捉列表,捕捉变量
	auto add = [c, d](int a, int b)->int {return a + b + c + d; };
	cout << add(1, 2) << endl;
	cout << "-------------------------------------------------------" << endl;

	// 默认的捕捉列表里面的变量具有const性,且是传值
	// 这么写会报错:表达式必须是可修改的左值
	auto swap = [c, d]()->void
		{
			int t = c;
			d = c;
			c = t;
		};
	cout << "-------------------------------------------------------" << endl;

	// 这样就没有什么问题,因为mutable取消了const性
	// 但是c、d仍无法交换
	auto swap = [c, d]()mutable->void
		{
			int t = c;
			c = d;
			d = t;
		};
	cout << c << " " << d << endl;
	swap();
	cout << c << " " << d << endl;
	cout << "-------------------------------------------------------" << endl;

	// 这么写就能正常的交换
	auto swap = [&c, &d]()mutable->void
		{
			int t = c;
			c = d;
			d = t;
		};
	cout << c << " " << d << endl;
	swap();
	cout << c << " " << d << endl;

	return 0;
}

五、新的类功能 

  • 强制自动生成默认函数 --- default
    class A
    {
    private:
    	int _a;
    public:
    	A() = default; // 强制生成无参构造函数
    	A(int a)
    		:_a(a)
    	{}
    };

  • 禁止生成函数 --- delete

    class A
    {
    private:
    	int _a;
    public:
    	A() = default; // 强制生成无参构造函数
    	A(int a)
    		:_a(a)
    	{}
    	A& operator=(int a) = delete; // 禁止生成默认函数
    };

六、模板的可变参数

1、计算形参对象包的数据个数?

sizeof...(args)

2、打印出形参对象包里面的数据内容? 

 模板的可变参数应该是在编译的时候进行解析的,而不是在运行时进行解析

如下是错误示例:

template <class ...Args>
void print(Args... args)
{
	// 错误做法读取args
	// error C3520: “args”: 必须在此上下文中扩展参数包
	// 不支持、不匹配
	// 参数包是在编译时逻辑
	// 代码是运行时逻辑
	for (int i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << " ";
	}
}

如下是正确示范:

#include <iostream>
using namespace std;

// 用于判断参数包是否已经读完
// 不能用数据个数来判断是否已经结束,因为sizeof是运行的时候才进行替换
// 而这个打印参数包内容是在编译时就已经展开完毕
void cpp_print()
{
	cout << endl;
}
// 打印主体
// 第一个参数用一个模板进行接收,从而获取到参数包内容
template <class T, class ...Args>
void cpp_print(T val, Args... args)
{
	cout << val << endl;
	cpp_print(args...);
}

// 外壳函数
template <class ...Args>
void print(Args... args)
{
	// 调用主体函数,传参的时候要传参数包
	// 虽然调用的函数模板参数有两个,但是可以传一个参数包,自动把参数包第一个参数匹配到val中,剩下的还传到args参数包中
	cpp_print(args...);
}

int main()
{
	print(1, "22", 3.333);

	return 0;
}

3、容器中emplace系列

容器中emplace系列的参数都是传的参数包。

所以当你要传入多个参数的时候,建议使用emplace系列函数。

list<pair<string, int>> it;

it.push_back({ "xxxxxx" , 1});
it.emplace_back("bbbbbbb", 2);

 七、包装器

包装可调用对象。

下列的头文件都在<functional>。

1、function --- 类模板

// 包装类包装类内成员函数
#include <iostream>
#include <functional>

using namespace std;

class Plus
{
public:
    static int func1(int a, int b)
    {
        return a + b;
    }
    double func2(double a, double b)
    {
        return a + b;
    }

};
int main()
{
    // 包装静态的成员函数
    function<int(int, int)> f1 = &Plus::func1;
    cout << f1(1, 2) << endl;
    // 包装非静态的成员函数
    // 第一种方式
    function<double(Plus*, double, double)> f2 = &Plus::func2;
    Plus p2;
    cout << f2(&p2, 1.1, 2.2) << endl;
    // 第二种方式
    function<double(Plus, double, double)> f3 = &Plus::func2;
    cout << f3(Plus(), 1.1, 2.2) << endl;

    return 0;
}

2、bind --- 函数模板

包装可调用对象和参数包。

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


class Sub
{
public:
    int sub(int a, int b)
    {
        return a - b;
    }
};

int main()
{
    auto f1 = bind(&Sub::sub, placeholders::_1, placeholders::_2, placeholders::_3);
    cout << f1(Sub(),  10, 5) << endl;

    // bind可以调整可调用对象的参数个数和顺序
    auto f2 = bind(&Sub::sub, placeholders::_1, placeholders::_3, placeholders::_2);
    cout << f2(Sub(), 10, 5) << endl;

    // 调整对调用对象的参数个数
    auto f4 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
    cout << f4(10, 5) << endl;
    Sub sub;
    auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);
    cout << f5(10, 5) << endl;

    return 0;
}

谢谢大家!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

仍有未知等待探索

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

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

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

打赏作者

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

抵扣说明:

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

余额充值