这个要和构造函数的初始化列表区分开。
在C++11中,新增加了列表初始化,即可以用(=){},给所有的内置类型和自定义类型初始化(等号可有可无)。
在C++98中,new 出来的一个int指针可以直接初始化,但是当有多个对象时,就只能用循环初始化,C++11的列表初始化就解决了这个问题,可以用{},给多个对象初始化。
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;
}
那列表初始化是怎么实现的呢?
我们来看看它的类型,可以用下面的代码查看
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之前都用过,它可以自己推导变量的类型,但是它必须要初始化,否则无法推导。
auto仅仅只是占位符,编译阶段编译器根据初始化表达式推演出实际类型之后会替换auto。
decltype也可以用来声明,和auto不同的是,它可以不初始化。
使用方法:decltype() 变量
C++中 NULL 直接被定义成了0 ,没有了指针属性,为了补上这个漏洞,定义 nullptr 为空指针
是否能取地址是左值和右值最大的区别。
注意:
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 对这块做了优化,把连续拷贝直接合为一个拷贝,这样做确实会有效率的提升,但还是要拷贝,而且换成另一个平台可能并没有这个优化,所以右值引用就出来了,极大地改善了这方面的问题。
对于生命周期即将结束的值,我们称为将亡值
右值引用和移动语义。
移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己,这极大地提高了效率。
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
容器的插入接口,如果插入对象是右值,可以利用移动构造转移资源给数据结构中的对象,也可以减少拷贝。
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中,给每个容器中都增加了 以右值引用作为参数的接口。
当 && 写在模板中,它就是万能引用,既可以是左值引用,也可以是右值引用。
下面来看这一段代码
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<T>(t)在传参的过程中保持了t的原生类型属性。
template<class T>
void PerfectForward(T&& t)
{
Fun(forward<T>(t));
}
再来看看打印结果
语法:[capture-list] (parameters) mutable -> return-type { statement}
下面就是一个简单的lambda表达式
auto swap1 = [](int x, int y) {return x + y; };
捕捉列表说明
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表达式看起来是一样的,但它们在底层仍然不同,不属于同一个类型
C++11之前,类有6个默认成员函数:
C++11后又新增加了两个默认成员函数:
一些注意点:
C++11中,delete除了用来释放掉动态申请的资源外,还可以使编译器不自动生成默认的成员函数。
class A
{
public:
A(const A& a) = delete; //禁止生成拷贝构造
A operator=(const A& a) = delete; //禁止生成赋值重载
};
迄今为止,学过的可调用对象有3种:
但是怎么把这三种对象存到一个对象里呢?
包含在头文件 <functional> 中
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
Ret 是返回值类型,Args...是一个可变参数包,也就是可调用对象的参数类型
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。使用方法如上图所示。第一个参数是一个可调用对象,后面的参数是一个参数包。
它可以通过绑定一些参数,来调整参数的顺序。
C++的库中定义了一个命名空间:placeholders ,其中的_1 _2表示的是第几个参数,比如_1表示的就是函数的第一个参数......
注意如果可调用对象是类的对象,那么要取地址。
如果可变参数写的是常数的话,那么就相当于缺省参数。
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;
}