初始化列表
在C语言中,对于数组的初始化,我们可以这样进行
int arr[] = { 1, 2, 3, 4, 5 };
允许数组使用花括号{}
的方式进行初始化,但是在C++98中的vector
数组以及一些自定义的类型中,这样初始化还是不被允许的,而C++11对此做出了修正。
他允许verctor
使用花括号{}
的方式对数组进行初始化.
vector<int> arr2 { 1, 2, 3, 4, 5, 6, 7 };
vector<int> arr3 = { 1, 2, 3, 4, 5, 6, 7 };
再进行初始化的过程中,数组名和花括号中可以不添加=
。
支持花括号进行初始化的原因:
如果想要使用多个对象对列表进行初始化,需给该类(模板类)添加一个带有initializer_list类型
参数的构造函数 和 =运算符的重载。
initializer_list
是系统自定义的类模板,该类模板中主要有三个方法:begin(),end()迭代器,以及获取区间中元素个数的方法size()#include <initializer_list>
,在使用之前需要加上该模板的头文件
template<class T>
class vector
{
public:
//初始化列表
vector(initializer_list<T> tmpArr)
: _capacity(tmpArr.size())
, _size(0)
{
_arr = new T[_capacity];
for (auto num : tmpArr)
_arr[_size++] = num;
}
vector<T>& operator=(initializer_list<T> tmpArr) {
delete[] _arr;
_capacity = tmpArr.size();
_arr = new T[_capacity];
_size = 0;
for (auto num : tmpArr)
_arr[_size++] = num;
return *this;
}
size_t size()
{
return _size;
}
T operator[](size_t idx)
{
return _arr[idx];
}
private:
T* _arr;
size_t _size;
size_t _capacity;
};
默认成员函数的控制
在C++中,当我们创建一个类,这个类中会默认有六个成员函数。如果在一些特殊的场合下,我们不想让这些默认的成员函数发生作用,我们在C++98中可以这样来:
class Date
{
private:
Date();
~Date();
private:
int _year;
int _month;
int _day;
};
我们可以把不想存在的默认构造函数变成私有成员,并只对默认函数进行声明不进行实现,这样在类外就不能进行访问,就可以去掉这个默认的构造函数了。
但是在C++11中,新增了一个delete关键字,他可以取消掉默认构造函数的作用。就是在该函数声明加上=delete
即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Date
{
public:
Date() = delete;
~Date() = delete;
private:
int _year;
int _month;
int _day;
};
对应的还有一个显示缺省函数。在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版本,用=default
修饰的函数称为显式缺省函数。
class Date
{
public:
Date() = default;
~Date() = default;
private:
int _year;
int _month;
int _day;
};
右值引用
C++相比C语言,提出了引用的概念,但是引用只能对一个已经声明的对象进行操作,给他一个别名,引用的变量和引用的实体共用一块内存空间,就相当于指针,只是我们在使用的时候不用像使用指针那样(*p),写法简单。
但是对于那些临时变量,这时使用引用是不可以的,因为他们本身就没有名字,是一个匿名对象。
为了解决这一问题,C++11加入了右值引用的概念,他也是一个别名,只是他只能对右值进行使用。
int&& num = 10;
左值与右值
左值,一般就是可以放在 =
左边,并且可以取地址的变量
- 普通的变量,他们有名字,那么就可以取地址,就可以作为左值
- const 修饰的常量,虽然不可以修改,但是可以取地址,所以说也是左值
- 函数的返回值是引用类型,那么也可以认为是左值
- 变量的引用类型,也是左值
右值,一般认为只能放在=
右边,或者不能取地址的变量
- 函数的返回值是一个临时变量,那么就认为这是一个右值
- 变量本身就是一个临时变量,或者匿名对象,则为右值
int a = 10;
const int& ra1 = 10;
const int& ar2 = a;
而对于const引用而言,他可以引用右值,也可以引用左值。而普通的引用只可以引用左值。
而右值引用只能引用右值,不能引用左值。
emplace_back 与 push_back
在C++11
中,对于拷贝构造的时候,如果所传的参数是一个临时变量,那么就会使用右值引用的方式进行操作,而emplace_back函数
就是一个对于临时变量操作的接口,他可以完成对临时对象的尾插操作,避免多余的拷贝。
用基本类型(int,char)来看的话效果不是那么明显,使用string类型来看一下关于右值引用
我们可以看到这个过程一共调用了两次拷贝构造,一次是参数的拷贝构造,一次是插入元素时的拷贝构造,这就造成了数据的利用率比较低。
这是因为const 引用既可以引用左值,也可以引用右值,所以对于左值和右值不容易区分。在C++11中使用右值引用,再加一个函数重载就可以了。
那么对于右值引用,只需要将他的参数(一个临时对象),进行swap交换一下就可以了,避免了赋值的时候再调用一次拷贝构造。
vector(vector<T>&& tmpArr)
{
std::swap(_arr, tmpArr._arr);
std::swap(_capacity, tmpArr._capacity);
std::swap(_size, tmpArr._size);
}
vector<T>& operator=(vector<T>&& tmpArr)
{
std::swap(_arr, tmpArr._arr);
std::swap(_capacity, tmpArr._capacity);
std::swap(_size, tmpArr._size);
return *this;
}
lambda表达式
有时候在做题的时候,经常会看到一些大佬的程序中有这样的代码
sort(arr.begin(), arr.end(),
[](const int& a, const int& b)->bool
{
return a > b;
});
而自己在写的时候是这样的,调用自己在函数外部写的仿函数,然后进行排序
bool (const int& a, const int& b)
{
return a > b;
}
sort(arr.begin(), arr.end(), cmp);
这就是C++11中的lambda表达式,他可以说是一个匿名函数,在函数的内部声明 + 使用。
他的格式如下:
[capture-list] (parameters) mutable -> return-type { statement }
[capture-list]
: 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。(parameters)
:参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略mutable
:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。->returntype
:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。{statement}
:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
关于捕捉列表,他其实就是为了让我们在使用lambda表达式的时候,如果想要使用当前作用域内的变量,但是又不想让他变成参数,那么就可以使用捕捉列表对想要使用的变量进行捕捉
内容 | 意义 |
---|---|
[var] | 表示值传递方式捕捉变量var |
[=] | 表示值传递方式捕获所有父作用域中的变量(包括this) |
[&var] | 表示引用传递捕捉变量var |
[&] | 表示引用传递捕捉所有父作用域中的变量(包括this) |
[this] | 表示值传递方式捕捉当前的this指针 |
lambda表达式与普通函数在汇编中的调用方式
可以看到两种函数的调用的方式是相同的,都是call指令
,只是lambda表达式在调用的时候,会自己给函数起一个lambda的名字,然后使用call进行调用。