前言
本系列文章承接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表达式在底层使用仿函数实现的,捕捉列表就变为它的成员变量。
❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤