C++11
列表初始化
C++11里扩展了大括号 {} 的初始化,基本所有类型都可以使用它来初始化。
class Person {
public:
Person(const string& name, int age) :_name(name), _age(age) {}
private:
string _name;
int _age;
};
int main()
{
int i = { 1 };
vector<int> v = { 1,2,3 };
vector<int> v2{ 1,2,3 };
vector<int>{1, 2, 3};
Person* p_Arr = new Person[2]{ {"张三",31},{"李四",22} };
}
自定义类型调用{}初始化,本质是调用对应的构造函数。自定义类型对象可以使用 {} 初始化,必须要要有对应参数类型和个数的构造函数。STL容器支持{}初始化,因为STL容器支持一个initializer_list作为参数的构造函数。
initializer_list<int> l1{ 1,2,3 };
initializer_list<string> l2{ "1","2","3" };
如果需要自定义类型支持花括号初始化,需要支持这样的构造函数。
template <class T>
class my_vector
{
my_vector(initializer_list<T> li) {
for (const T& x : li) {
this->push_back(x);
}
}
private:
};
int main()
{
my_vector<int> v = { 1,2,3,4 };
}
范围for
范围for本质还是由迭代器实现。需要注意,取出的元素是值传递,如不需要修改最好加上const &
int main()
{
vector<string> v = { "1","6" ,"54" ,"3" };
for (const string& i : v) {
cout << i;
}
}
decltype
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
vector<decltype(T1 * T2)> v;
decltype<T1* T2> var;
}
STL中的新容器
array
一个存在栈上的静态数组,除了内部对下标访问做了检查,防止越界好像就没什么用。。
forward_list
底层是单链表,比list 少了尾插和尾插。唯一的优点就是少了一个存储前一个节点的指针节省了几字节。。
unordered_map/set
非常好用的容器,底层为hash表,但为什么到C++11 才支持。。
右值引用 移动语义
C++11 之前就有引用,应该称为左值引用,而C++11中新增了的右值引用语法特性,无论左值引用还是右值引用,都是给对象取别名。
什么是左值、右值
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
这些都是左值
int i = 1;
const int i2 = 1;
int* pa = new int[2]{ 1,2 };
左值的共同点:它们都可以被取地址,也基本可以修改(除了被const修饰)
右值可以是一个表达式,如:字面常量、表达式返回值,传值返回函数的返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。
这些都是右值
string func2()
{
string s1 = "12";
return s1;
}
int main()
{
int x, y;
x + y;
10;
1 + 2;
func2();
}
右值的共同点:它们都不能出现在赋值运算符的右边和被取地址,也不能被修改
右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&&rr1去引用,这个特性不重要。
int&& rr1 = 123;
rr1 = 2;
左值引用
左值引用已经很熟悉了,很大程度的减少了深拷贝,比如使用在构造函数里,防止深拷贝。
左值引用小结:
左值引用只能引用左值,不能引用右值。
但是const左值引用既可引用左值,也可引用右值,例如:
const int i = 1;
在没有右值引用之前这种场景非常常用: 当模板类型为int、double 这种内置类型,就相当引用了右值
template <class T>
class my_vector
{
void push_back(const T& x) {
}
}
左值引用的短板
当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如: 标准库中的to_sting函数
to_string 只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
右值引用
移动构造
右值引用解决了上面左值引用的缺点,本质也是为了减少拷贝。
只有拷贝构造情况下,依旧会产生一次深拷贝。
但如果提供了移动构造,就不会产生深拷贝,同时有拷贝构造和移动构造的情况下,优先选择移动构造。
func函数中的临时对象出了作用域就即将销毁了,有些地方叫将亡值,使用移动构造相当于废物利用,直接将临时对象中的数据交换到新对象上来,减少了深拷贝,提高效率,这是右值引用的一个使用场景。
移动赋值
一般在重载赋值运算符函数内部都要构造一个临时对象,用来初始化。
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
vector<T> tmp(v);
swap(tmp);
}
return *this;
}
这种情况下还是会产生一次深拷贝。
但如果提供了移动赋值版本的赋值运算符重载就只会发生一次移动构造和移动赋值,不会产生任何深拷贝。
右值引用,并不是直接使用右值引用去减少拷贝,提高效率。而是支持深拷贝的类,提供移动构造和移动赋值,这时这些类的对象进行传值返回或者是参数为右值时,则可以用移动构造和移动赋值,转移资源,避免深拷贝,提高效率。
一些特殊场景
正常情况下,右值引用只能引用右值,当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于头文件中,该函数唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。
int main()
{
string s1("123");
string s2(s1);
string s3(std::move(s1));
return 0;
}
需要注意:s1在move之后内部数据已经被交换了,不能再使用。
右值引用的其他使用场景
右值引用,还可以使用在容器插入接口函数中,如果实参是右值,则可以转移它的资源,减少拷贝
完美转发
模板的万能引用
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。上面说过,右值引用相等于在内存特定位置存储了右值,例如 int&& rr1 = 10; 此时rr1是个左值,可以被取地址和修改。
我们希望能够在传递过程中保持它的左值或者右值的属性,就需要用到完美转发
使用完美转发
|