前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++进阶】C++11的认识与学习

【C++进阶】C++11的认识与学习

作者头像
aosei
发布2024-01-23 15:26:04
1360
发布2024-01-23 15:26:04
举报
文章被收录于专栏:csdn-nagiYcsdn-nagiY

一.列表初始化

这个要和构造函数的初始化列表区分开。

在C++11中,新增加了列表初始化,即可以用(=){},给所有的内置类型和自定义类型初始化(等号可有可无)。

在C++98中,new 出来的一个int指针可以直接初始化,但是当有多个对象时,就只能用循环初始化,C++11的列表初始化就解决了这个问题,可以用{},给多个对象初始化。

代码语言:javascript
复制
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 = { 1 };  //列表初始化内置类型,加 = 号
	int b{ 5 };  //不加 = 号
	int* p1 = new int(2);  //初始化单个new的对象
	int* p2 = new int[5] {1, 2, 3, 4, 5};  //初始化多个new的对象
	Date d1 = { 2023,1,27 };  //列表初始化自定义类型,加 = 号
	Date d2{ 2023,2,10 };   //列表初始化自定义类型,不加 = 号
	cout << *p1 << endl;
	for (int i = 0; i < 5; i++)
		cout << p2[i] << " ";
	cout << endl;
	return 0;
}

std::initializer_list

那列表初始化是怎么实现的呢?

我们来看看它的类型,可以用下面的代码查看

代码语言:javascript
复制
int main()
{
    // the type of il is an initializer_list
    auto il = { 10, 20, 30 };
    cout << typeid(il).name() << endl;
    return 0;
}

可以发现它是 initializer_list 类型,下面是关于该类型的一些介绍

 initializer_list 的底层其实有一个 start 指针和一个 finish 指针,分别指向数据的开始和末尾的下一个位置,其实在使用 {} 列表初始化时,就是在调用 initializer_list 的构造函数,C++11为STL容器都添加了有 initializer_list 的构造函数。

例如vector

二.auto,decltype和nullptr声明

auto

auto之前都用过,它可以自己推导变量的类型,但是它必须要初始化,否则无法推导。

auto仅仅只是占位符,编译阶段编译器根据初始化表达式推演出实际类型之后会替换auto。

decltype

decltype也可以用来声明,和auto不同的是,它可以不初始化。

使用方法:decltype() 变量

nullptr

C++中 NULL 直接被定义成了0 ,没有了指针属性,为了补上这个漏洞,定义 nullptr 为空指针


 三.右值引用和移动语义

什么是左值?什么是右值?

  • 左值:可以取地址,一般情况下,值可以被修改,左值可以出现在赋值符号的左右边;例如变量名,解引用的指针
  • 右值:不可以取地址,右值只能出现在赋值符号的右边;例如字面常量,表达式,函数返回值

是否能取地址是左值和右值最大的区别。

左值引用和右值引用

  • 左值引用:引用的是左值就是左值引用,左值引用可以做返回值,减少拷贝,提高效率,但是局部变量不可以左值引用返回;左值引用是一个 & 
  • 右值引用:引用的是右值就是右值引用;右值引用是两个 &&

注意:

  • const 左值引用可以引用右值
  • 右值引用可以引用 move过的左值(move是一个库函数,可以把左值转换为右值)
  • 所以并不是左值引用只能引用左值,右值引用只能引用右值
  • 右值引用变量的属性仍会被编译器识别为左值
代码语言:javascript
复制
int fun(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 10, b = 20;
	int& r1 = a;  //左值引用
	const int& r2 = 10;  //const 左值引用引用右值
	int&& rr1 = fun(a , b);  //右值引用
	int&& rr2 = move(a);  //右值引用引用move过的左值(注意,a仍然是左值,只不过是move返回的是一个右值)
	//注意 rr1 rr2 的属性仍为左值 
	return 0;
}

右值引用的应用

场景1

自定义类型中深拷贝的类中,必须传值返回的场景。

        我们知道,当函数是传值返回时,会先创建一个临时对象(临时对象具有常属性),将数据拷贝一份给临时对象,然后临时对象返回,原来的那个对象销毁,这样要连续拷贝,如果是一个大的对象,那么将极大地影响效率,所以 vs 对这块做了优化,把连续拷贝直接合为一个拷贝,这样做确实会有效率的提升,但还是要拷贝,而且换成另一个平台可能并没有这个优化,所以右值引用就出来了,极大地改善了这方面的问题。

        对于生命周期即将结束的值,我们称为将亡值

右值引用和移动语义。

 移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己,这极大地提高了效率。

代码语言:javascript
复制
namespace bit
{    
        // 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动语义" << endl;
			swap(s);
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动语义" << endl;
			swap(s);
			return *this;
		}

}
bit::string func()
{
	bit::string str;
	return str;
}

int main()
{
	bit::string ret;
	ret = func();

	return 0;
}

        上面代码会打印   "string& operator=(string&& s) -- 移动语义"  ,但是 func  里的 str 是一个左值,并不是右值,为什么会调用移动赋值呢?

C++11中,这一块底层其实都调用了 move ,把左值属性变成右值属性。

场景2

  容器的插入接口,如果插入对象是右值,可以利用移动构造转移资源给数据结构中的对象,也可以减少拷贝。

代码语言:javascript
复制
void func(string&& str)
{
	cout << "func(bit::string&& str)" << endl;
}

void func(string& str)
{
	cout << "func(bit::string& str)" << endl;
}

int main()
{
	string s1;
	func(s1);
	func(move(s1));

	return 0;
}

所以在C++11中,给每个容器中都增加了 以右值引用作为参数的接口。


四.完美转发

 模板中的&& 万能引用

当 && 写在模板中,它就是万能引用,既可以是左值引用,也可以是右值引用。

下面来看这一段代码

代码语言:javascript
复制
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

template<class T>
void PerfectForward(T&& t)
{
	Fun(t);
}
int main()
{
	PerfectForward(10);    //右值
	int a;
	PerfectForward(a);     //左值
	PerfectForward(move(a)); // 右值
	const int b = 8;
	PerfectForward(b);     //const 左值
	PerfectForward(move(b)); // const 右值
	return 0;
}

打印结果是什么?

答案出乎意料地全是左值引用。

前面已经说过,不管是左值引用变量还是右值引用变量,它们的属性都会被编译器识别为左值,所以全部打印出了左值引用 。

那要如何保留右值属性呢?

std::forward 完美转发在传参的过程中保留对象原生类型属性

代码语言:javascript
复制
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<class T>
void PerfectForward(T&& t)
{
    Fun(forward<T>(t));
}

再来看看打印结果


 五.final 和 override

final

  • 只能修饰类和虚函数
  • 修饰类时,表示该类不可以被继承
  • 修饰虚函数时,表示该虚函数不可以被重写

override

  • 作用发生在编译时
  • override只能修饰子类的虚函数
  • 用于显式地表明派生类中的函数是重写基类中的虚函数。这可以确保派生类中的函数正确地重写了基类中的虚函数,并且可以避免一些程序错误。如果派生类中的函数没有正确地重写基类中的虚函数,则编译器会发出警告

六.lambda表达式

语法:[capture-list] (parameters) mutable -> return-type { statement}

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda 函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以 连同()一起省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量 性。使用该修饰符时,参数列表不可省略(即使参数为空)。其实这个用的很少。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推 导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获 到的变量。

下面就是一个简单的lambda表达式

代码语言:javascript
复制
auto swap1 = [](int x, int y) {return x + y; };
  • 注意 lambda 表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量;
  • 参数列表和返回值类型都可以省略,但是参数列表 [] ,和函数体 {} ,不可以省略,所以最简单的  lambda 表达式为 []{}

捕捉列表说明

  • [val],表示值传递方式捕获某个变量
  • [=],表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var],表示引用传递捕捉变量var
  • [&],表示引用传递捕捉所有父作用域中的变量(包括this)
  • 以上捕捉方法都可以混合使用
代码语言:javascript
复制
int main()
{
	int a = 10, b = 20;
	int c = 30, d = 40;
	auto func1 = [a, b] {cout << a << " " << b << endl; };   //部分值传递
	auto func2 = [=] {cout << a << " " << b << " " << c << " " << d << endl; };  //值传递捕获父域所有变量
	auto func3 = [&a, &b] {a = 20, b = 10; };   //部分引用传递
	auto func4 = [&] {a = 1, b = 2, c = 3, d = 4; };  //引用传递捕获父域所有变量

	return 0;

}

注意:

  • 父作用域指包含lambda函数的语句块
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误
  • 在块作用域以外的lambda函数捕捉列表必须为空
  • lambda表达式之间不能相互赋值

其实,lambda表达式的底层是仿函数,即使两个lambda表达式看起来是一样的,但它们在底层仍然不同,不属于同一个类型

七.新的类功能

C++11之前,类有6个默认成员函数:

  • 构造函数
  • 拷贝构造
  • 赋值重载
  • 析构函数
  • 取地址重载
  • const 取地址重载

C++11后又新增加了两个默认成员函数:

  • 移动构造函数
  • 移动赋值运算符重载

一些注意点:

  • 如果没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个,那么编译器会自动生成一个默认移动构造。
  • 如果没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。
  • 如果提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

八.delete 的新作用 

C++11中,delete除了用来释放掉动态申请的资源外,还可以使编译器不自动生成默认的成员函数。

代码语言:javascript
复制
class A
{
public:
	A(const A& a) = delete;   //禁止生成拷贝构造
	A operator=(const A& a) = delete;  //禁止生成赋值重载
};

九.包装器

 迄今为止,学过的可调用对象有3种:

  • 函数指针
  • 仿函数
  • lambda表达式

但是怎么把这三种对象存到一个对象里呢?

function包装器

包含在头文件  <functional>  中

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

Ret 是返回值类型,Args...是一个可变参数包,也就是可调用对象的参数类型

十.bind 绑定

 std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。使用方法如上图所示。第一个参数是一个可调用对象,后面的参数是一个参数包。

它可以通过绑定一些参数,来调整参数的顺序。

C++的库中定义了一个命名空间:placeholders  ,其中的_1 _2表示的是第几个参数,比如_1表示的就是函数的第一个参数......

注意如果可调用对象是类的对象,那么要取地址。 

如果可变参数写的是常数的话,那么就相当于缺省参数。

代码语言:javascript
复制
int sub(int a, int b)
{
	return a - b;
}

int main()
{
    //把sub函数的参数顺序调换
	function<int(int, int)> s = bind(sub, placeholders::_2, placeholders::_1);  
	cout<<s(1, 2)<<endl;
	cout << sub(1, 2) << endl;

	return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.列表初始化
    • std::initializer_list
    • 二.auto,decltype和nullptr声明
      • auto
        • decltype
          • nullptr
          •  三.右值引用和移动语义
            • 什么是左值?什么是右值?
              • 左值引用和右值引用
                • 右值引用的应用
                • 四.完美转发
                  •  模板中的&& 万能引用
                    • std::forward 完美转发在传参的过程中保留对象原生类型属性
                    •  五.final 和 override
                      • final
                        • override
                        • 六.lambda表达式
                        • 七.新的类功能
                        • 八.delete 的新作用 
                        • 九.包装器
                          • function包装器
                          • 十.bind 绑定
                          相关产品与服务
                          容器服务
                          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档