【C++11】新的类功能、lambda

📝前言:

这篇文章我们来讲讲C++11——新的类功能、lambda、包装器

🎬个人简介:努力学习ing
📋个人专栏:C++学习笔记
🎀CSDN主页 愚润求学
🌄其他专栏:C语言入门基础python入门基础python刷题专栏Linux


一,新的类功能

(1)默认构造和默认赋值

在学习类和对象的时候,我们已经了解过了类的6个默认成员函数。
而C++11引入右值引用以后,又多增加了:移动构造函数移动赋值运算符重载函数这两个默认成员函数。两者类似。

默认移动构造

  • 生成条件:1,当没有显式实现移动构造;2,且没有实现析构函数、拷贝构造、拷贝赋值中任意一个。编译器就会生成默认移动构造
  • 行为:对于内置类型:按字节浅拷贝。对于自定义类型:调用对应类型的成员的移动构造,如果没有实现移动构造,则调用它的拷贝构造

默认的移动赋值重载函数

  • 生成条件:1,没有显式的实现移动赋值重载函数;2,且没有实现析构函数、拷贝函数,拷贝赋值中的任意一个。编译器就会生成默认的移动赋值重载函数。
  • 行为:对于内置类型:按字节浅拷贝。对于内置类型:调用成员的移动赋值重载,如果没有,就调用它的拷贝赋值重载

移动构造/赋值和拷贝构造/赋值的关系:
如果提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。即:移动和拷贝两者的默认生成前提都是另一个没有实现。

示例1:
没有移动构造和移动赋值,但是有拷贝构造和拷贝赋值的时候(不会生成默认的):

int main()
{

	tr::string s1 = "hello world";
	tr::string s3 = tr::string("tr");
	s1 = tr::string("fighting");
	return 0;
}

运行结果:
在这里插入图片描述

  • 第一个:构造是s1
  • 第二个:本来应该是:构造 + 移动构造(但是没有移动构造,且没有默认生成),变成了构造 + 拷贝构造,然后被优化成一个构造
  • 第三、四个:构造 + 移动赋值 变成 构造 + 拷贝赋值

示例2:
没有析构、拷贝构造、拷贝赋值时,使用生成的默认的:

int main()
{
	tr::string s1 = "hello world";
	tr::string s3 = tr::string("tr");
	s1 = move(s1);
	return 0;
}

运行结果:
在这里插入图片描述

这里打印不出来啊,因为调的是系统默认生成的。但是我们可以看变量信息:
在这里插入图片描述
因为tr::string的成员函数都是内置类型,所以在移动赋值的时候都用了浅拷贝。

如果自己实现了移动构造和移动赋值:
在这里插入图片描述
可见确实和上面的默认的效果一样。(前面构造是:编译器把构造 + 移动构造优化了)

default和delete

  • default :用于显式地要求编译器生成默认版本的特殊成员函数(即使自己已经实现了)
  • delete:禁用成员函数,或要求编译器自动生成特定的默认成员函数
    (注意这个delete可不是和new对应那个)

示例:

string(const string& s) = delete; // 要求不自动生成拷贝构造
void reserve(size_t n) = delete; // 禁止使用reserve成员函数
string(string&& s) = default; // 要求编译器生成默认的移动构造

注意:delete的函数,就代表禁用,函数不能再实现,不然是重定义,default也是。
即:一个函数(不构成重载)不能既被delete / default,又被实现

final与override

final

  • 用于类:表示类是最终类,不能被继承
  • 用于虚函数:表示函数是最终函数,不能被重写

示例:

class Base final { // 用于类写法
    // 类的成员
};

class Base{
    virtual void func() final { // 用于函数写法
    // 函数实现
    }
};

override:帮助用户检查虚函数是否真正重写(书写正确不会报错,书写错误会报错)

示例:

class Shape {
public:
    virtual void draw() {
        std::cout << "Drawing a generic shape." << std::endl;
    }
};

// 重写正确(如:基类中确实有这个虚函数,函数名没问题...)
class Circle final : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle." << std::endl;
    }
};

二,lambda

lambda基础语法

这里我只记录C++中lambda的语法,以及一些细节知识点。不对lambda的意义做过多讲解。

  • lambda表达式的本质是一个⼀个匿名函数对象
  • lambda语法层面而言没有类型,如果要接收可以用auto对象(当然也可以不接收)

lambda表达式基本语法:

[捕抓列表] (参数列表) -> 返回类型 {函数体}
  • 当参数列表 / 返回类型为空时,这两部分可以省略,()->也可以省。返回类型不为空可以省略,编译器自动推导
  • 函数体 / 捕捉列表就算为空:[ ]{ }也不能省

基本示例:

auto lambda1 = [](int x, int y)->int {return x + y; };
int main()
{
	cout << lambda1(1, 1) << endl; // 输出 2 
	return 0;
}

捕捉列表

下面详细讲讲捕捉列表
lambda表达式默认只能使用,lambda函数体和参数中的变量,如果想使用外层作用域的变量就需要捕捉

捕捉有三种方法:

  • 显式捕捉:值捕捉和引用捕捉:[x,y, &z]值捕捉了xy,引用捕捉了z。【值捕捉的值不能修改,相当于const修饰,引用捕捉的值可以修改,且会改外面的(因为是别名)】
  • 隐式自动捕捉:=表示隐式值捕捉,&表示隐式引用捕捉。我们在lambda中用了什么变量,编译器会自动帮我们去捕捉。但不能[=, &]
  • 混合捕捉:即上面的两种方法可以混合,但是第一个参数必须为隐式捕捉,如:[=, &x, &y]

其他细节:

  • lambda 表达式如果在函数局部域中,它可以捕捉 lambda 位置之前定义的变量,但不能捕捉静态局部变量和全局变量(静态局部变量和全局变量也不需要捕捉,可以直接用)。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空
  • 在参数列表后面加mutable可以取消其值捕捉参数的常性,这样我们就可以修改它,但是这时候的修改不会改到外部的实参。

使用示例:

int x = 0;
// 捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = []() {x++; };

int main()
{
	// 只能用当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;
	auto func1 = [a, &b]
		{
			//a++; 值捕捉的变量不能修改(有常性
			b++; // 引用捕捉的变量可以修改
			int ret = a + b;
			return ret;
		};
	cout << func1() << endl;

	// 隐式值捕捉
	// 用了哪些变量就捕捉哪些变量
	auto func2 = [=]
		{
			int ret = a + b + c;
			return ret;
		};
	cout << func2() << endl;

	// 混合捕捉1
	auto func4 = [&, a, b]
		{
			c++;
			d++;
			return a + b + c + d;
		};
	cout << func4() << endl;
	cout << a << " " << b << " " << c << " " << d << endl;

	// 局部的静态和全局变量不能捕捉,也不需要捕捉
	static int m = 0;
	auto func5 = []
		{
			int ret = x + m;
			return ret;
		};
	cout << func5() << endl;
	// 传值捕捉本质是⼀种拷贝,并且被const修饰了
	// mutable相当于去掉const属性,可以修改了
	// 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷贝
	auto func6 = [=]()mutable
		{
			a++;
			b++;
			c++;
			d++;
			return a + b + c + d;
		};
	cout << func6() << endl;
	cout << a << " " << b << " " << c << " " << d << endl;
	return 0;
}

运行结果:
在这里插入图片描述

lambda 的原理

从编译后的汇编指令层看,lambda就是一个编译器生成的对应的仿函数的类。

  • 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同
  • lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体
  • lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参

🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

愚润泽

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

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

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

打赏作者

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

抵扣说明:

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

余额充值