从C++开始的编程生活(24)——C++11标准Ⅰ

前言

本系列文章承接C++基础的学习,需要有**++C语言的基础++** 才能学会哦~

第24篇主要讲的是有关于C++的++C++11标准的第一部分++ 。
C++才起步,都很简单!

列表初始化 { }

C++11,让一切对象皆可用{ }初始化。

cpp 复制代码
int main()
{
	//C++98支持的初始化
	int a1[] = { 1,2,3,4,5 };
	int a2[5] = { 1 };
	point p = { 3,4 };

	//C++11支持的初始化
	//内置类型也支持
	int x1 = { 2 };

	//并没有使用拷贝构造,底层是直接构造
	//本质都是构造函数支持的隐式类型转换
	string str = "1111111111";
	Date d0 = 2020;
	Date d1 = { 2025,1,1 };//可省略括号,只有{}初始化才可以省略
	//Date d1{ 2025, 1, 1};

	const Date& r1 = 2020;
	const Date& r2 = { 2025,1,1 };

	PushBack(d1);
	PushBack({ 2025,1,1 });
	PushBack(2020);

	return 0;
}

{ }初始化时,可以省略 = 。

initializer_list

初始化列表

cpp 复制代码
auto il = {1,2,3,4,5,6};
cout << typeid(il).name() << endl;

cout << il.begin() << endl;
int i = 0, j = 1;
cout << &i << endl;
cout << &j << endl;

输出的地址都是临近的地址,初始化列表的创建也是在栈上完成的。

像vector、list、map等容器的构造,都可以使用初始化列表。

cpp 复制代码
vector<int> v1 = {1, 2, 34, 5, 56, 6, 7, 78, 8};
list<int> l1 = {1, 23, 4, 5, 43, 6, 36, 432};
//此处的map,由内部的{}构造pair对象,和外部的{}构造的initializer_list
map<int, int> m1 = {{3, 6}, {5, 1}};

但是像Date类的构造使用的就不是初始化列表

cpp 复制代码
Date d1 = {2025,1,2};//不是初始化列表

因为这里需要传入对应数量的参数,走的时Date的构造函数。

而initializer_list对个数是没有限制的。

右值引用和移动语义

C++11之前的引用都是左值引用,无论左值引用还是右值引用,都是取别名。

左值与右值

左值

一个数据表达式(变量、解引用的指针等),

可以在 = 的左边和右边,

,持久存于内存中,可以获取它的地址。

右值

一个数据表达式(字面量常量、临时对象等),

不可以在 = 的左边,

不能被取地址。

cpp 复制代码
//左值可以取地址
//p,b,c,*p,s,s[0]就是常见的左值
int* p = new int(0);
int b = 1;
const int c = b;
*p = 10;
string s("111111");
s[0] = 'x';
cout << &c << endl;
cout << &s[0] << endl;
//右值不可以取地址;
//10,x+y,fmin(x,y),string("111")都是右值
double x = 1.1, y = 2.2;

10;
x + y;
fmin(x, y);
string("1111");

左值引用与右值引用

cpp 复制代码
//左值引用,一个&
Type& r1 = x;
//右值引用,两个&
Type&& rr1 = y;

右值引用示例:

cpp 复制代码
int&& rr1 = 10;
double&& rr2 = x + y;
string&& rr3 = string("1111");

要点:

①左值引用不能直接引用右值,但是const左值引用可以引用右值

cpp 复制代码
const double& r2 = 10.0;//不报错
double& r2 = 10.0;//报错

②右值引用不能直接引用左值,但是右值引用可以引用move(左值)。move在底层是一个进行类型转换的函数模板。

cpp 复制代码
int&& rr4 = move(b);//不报错
int&& rr4 = b;//报错

③右值引用和左值引用本身都属于是左值。

cpp 复制代码
double&& rr2 = x + y;
int&& rr4 = move(b);

cout << &rr4 << endl;
cout << &r2 << endl;

引用延长生命周期

右值引用可以延长临时对象生命周期,const左值引用也可以延长生命周期。

cpp 复制代码
class AA
{
public:
	AA(int a1, int a2)
		:_a1(a1)
		,_a2(a2)
	{ }
	~AA()
	{
		cout << "~AA()" << endl;
	}

private:
	int _a1 = 1;
	int _a2 = 1;
};

int main()
{
	AA aa1(1, 1);
	AA(2, 2);

	cout << "_________________________" << endl;

	return 0;
}

如上,aa1在程序结束后才析构,临时对象刚创建完就析构了。

cpp 复制代码
int main()
{
	AA aa1(1, 1);
	const AA& r1 = AA(2, 2);//左值引用
	AA&& r2 = AA(2, 6);//右值引用

	cout << "_________________________" << endl;

	return 0;
}

如上,我们用引用之后,就可以延长临时对象的生命周期

左值和右值的参数匹配

cpp 复制代码
void f(int& x)
{
	cout << "左值引用重载 f(" << x << ")" << endl;
}
void f(const int& x)
{
	cout << "const的左值引用重载 f(" << x << ")" << endl;
}
void f(int&& x)
{
	cout << "const的右值引用重载 f(" << x << ")" << endl;
}

int main()
{
	int i = 1;
	const int ci = 2;
	f(i);//调用f(int&)
	f(ci);//调用f(const int&)
	f(3);//调用f(int&&),如果没有,则会调用f(const int&)
	f(move(i));//调用f(int&&)
}

哪个合适匹配哪个重载。

左值 -> 左值引用重载

const左值 -> const左值引用重载

右值 -> 右值引用重载

右值引用和移动语义的使用场景

和左值引用一样,右值引用的目的是为了减少拷贝,提高效率。

移动构造和移动赋值

移动构造函数,类似拷贝构造函数

cpp 复制代码
//string的移动构造
//代码示意:

string(string&& s)
{
    cout << "string(string&& s) -- 移动构造" << endl;
    swap(s);
}

若传值为右值,构造就会调用移动构造。

cpp 复制代码
int main()
{
    //构造
    string s1("xxxxx");
    //拷贝构造
    string s2 = s1;
    //移动构造,因为用来一个临时对象------也就是右值------来赋值。
    stirng s3 = sting("111");
    //移动构造,将s1的资源交给s4接管,s4会变为空
    string s4 = move(s1);
    
    return 0;
}

为什么叫做移动构造?

因为移动构造函数是①将临时对象(匿名对象)的资源给左值 " 接管 " ,然后②再销毁临时对象(匿名对象) 。这个操作只是移动了内部资源,不需要再进行额外的资源分配和拷贝的步骤,效率有所提高。也可用靠move()强制类型转换非临时对象为右值引用后再移动。

如将string("111")创建的临时对象的资源,给到s3,临时对象的资源全置空,然后销毁临时对象。

实际编译器会把string s3 = string("111")++进行优化++ ,++使用直接构造++,相当于string s3("111")。

而且,如果存在多次连续的移动构造,编译器还会直接优化为一次直接构造,大大增加效率。过程中,对象就不再拷贝和移动,而是直接构造在main函数的栈帧构造最后的对象,中间代码中的对象变量按照最终对象的别名处理。

如果存在多次连续的移动和拷贝构造,编译器会优化为多次移动构造,大大增加效率。

不过这些优化方式不是C++标准规定的,只限于较新的编译器,是编译器开发人员自行实现的。

类型分类(了解即可)

泛左值:求值可确定某个对象或者函数的标识的表达式。

纯右值:字面值常量、相当于字面量常量的求值结果、不具名的临时对象。

将亡值:返回右值引用的函数的调用表达式、转换为右值引用的转换函数的调用表达(move(x)、static_cast<x&&>(x) )。

左值:并非将亡值的泛左值。

右值:纯右值或将亡值

引用折叠

C++不可以直接定义引用的引用

cpp 复制代码
int&&& r = 1;

但可以通过模板或typedef间接定义引用的引用。

typedef场景:

cpp 复制代码
typedef int& lref;
using rref = int&&;//相当于typedef int&& rref;

//引用折叠
lref& r1 = n;//r1的类型是int&
lref& r2 = n;//r2的类型是int&
rref& r3 = n;//r3的类型是int&
rref&& r4 = n;//r4的类型是int&&

模板场景:

cpp 复制代码
template<class T>
void f1(T& x)
{ };

int n = 0;

//没有折叠
f<int>(n);//不报错
f<int>(0);//报错

//折叠,int& & -> int&
f<int&>(n);//不报错
f<int&>(0);//报错

//折叠,int&& & -> int&
f<int&&>(n);//不报错
f<int&&>(0);//报错

C++11的标准是:右值引用的右值引用折叠为右值引用,其他的组合均为左值引用。

万能引用

C++11引入了万能引用的概念,模板+&&则为万能引用。

cpp 复制代码
template<class T>
void Funtion(T&& t)
{
    int a = 0;
    T x = a;
    cout << &a << endl;
    cout << &x << endl;
}

如上代码,T&&即为万能引用(const T&&不可以构成万能引用)。

  • 若实参是左值 ,则 T 会被推导为左值引用类型
  • 若实参是右值 ,则 T 会被推导为非引用类型

程序会根据你传入的实参,自动推导T的类型。

注意:如果传入的是const int ,T推导出的是const int&。

cpp 复制代码
//传入右值,T推导为int,T&&折叠为int&&
Funtion(10);

//传入左值a,T推导为int&,T&&折叠为int&
int a = 0;
Funtion(a);

//传入右值std::move(a),T推导为int,T&&折叠为int&&
Funtion(std::move(a));

//传入左值const int b = 0,T推导为const int&,T&&折叠为const int&
const int b = 0;
Funtion(b);

lambda表达式语法

本质上是匿名函数对象,可以在函数内定义。

语法格式:

cpp 复制代码
[捕捉列表](参数列表) -> 返回值类型
{
    函数体
}

简单lambda表达式:

cpp 复制代码
auto add1 = [](int x, int y) -> int { return x + y; }
//调用
cout << add1(1, 2) << endl;

要点:

①捕捉列表不能省略

②参数列表可以省略

③返回值可以省略,可以靠返回对象自动推导

④函数体不能省略

捕捉列表

①传值捕捉的变量不能修改(如a),传引用捕捉的变量可以修改(如b)。

cpp 复制代码
int main()
{
    int a = 0, b = 1, c = 2, d = 3;
    auto func1 = [a, &b]()
    {
        //a++,报错
        b++;
        int ret = a + b;
        return ret;
    };
    cout << func1() << endl;
    
    return 0;
}

②隐式捕捉,用了什么变量,就捕捉什么变量。

隐式引用捕捉,则在捕捉列表写一个&;

隐式值捕捉, 则在捕捉列表写一个=;

cpp 复制代码
int main()
{
    int a = 0, b = 1, c = 2, d = 3;
    auto func2 = [=]()
    {
        int ret = a + b + c;
        return ret;
    };

    cout << func2() << endl;

    return 0;
}

③混合捕捉,指定的值进行传值(传引用)捕捉,其他值隐式捕捉。

cpp 复制代码
int main()
{
    int a = 0, b = 1, c = 2, d = 3;
    auto func3 = [&, b, c]()
    {
        int ret = a + b + c;
        return ret;
    };

    auto func4 = [=, &b, &c]()
    {
        int ret = a + b + c;
        return ret;
    };

    cout << func3() << endl;
    cout << func4() << endl;

    return 0;
}

注意,隐式值捕捉只能和传引用进行混合捕捉;隐式引用捕捉只能和传值进行混合捕捉,从而避免代码冗余。

④当lambda是在全局或静态时,不可以进行捕捉,也不需要捕捉。

⑤传值捕捉本质上是拷贝,然后再用const修饰。用mutable修饰之后,传值捕捉的变量可以修改,但是不会影响原变量。

cpp 复制代码
int main()
{
    int a = 0, b = 1, c = 2, d = 3;
    auto func5 = [a]()mutable
    {
        a++
        cout << a << endl;
        return ret;
    };
    cout << a << endl;
    
    return 0;
}

C++11标准之后,lambda表达式在底层使用仿函数实现的,捕捉列表就变为它的成员变量。

❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤

相关推荐
mjhcsp2 小时前
AT_arc205_c [ARC205C] No Collision Moves 题解
开发语言·c++·算法·题解
MLGDOU2 小时前
【Qt开发】信号与槽
开发语言·数据库·qt
风萧萧19992 小时前
Milvus Java 快速入门
java·开发语言·milvus
wanderist.2 小时前
高维矩阵的压维存储和高维差分
c++·算法·蓝桥杯
2301_810154553 小时前
CVE-2019-6341 漏洞复现
java·开发语言
王璐WL3 小时前
【C++】经典且易错的题
c++
feasibility.3 小时前
OpenCV图像滤波算法应用:常见滤波器的原理与效果对比(含c++/python代码与中文显示)
c++·opencv·算法
老虎06273 小时前
数据结构09(Java)-- 二分查找模板
java·开发语言·数据结构
蓝天星空3 小时前
C#中for循环和foreach循环的区别
开发语言·c#