explicit关键字
struct complex{
int real, imag;
// explicit
complex(int re, int im = 0) : real(re), imag(im){}
complex operator+(const complex& x){
return complex((real + x.real), (imag + x.imag));
}
}
11之前,用于一个参数的构造函数。有上面的代码,那么在执行complex c1(12, 5); complex c2 = c1 + 5;的时候,构造函数不用explicit修饰,则会把5变成一个复数类,但是如果用explicit修饰了,这两句代码就会报错。也就是,让编译器知道,用户希望只在显式调用构造函数的时候才会调用,其他时候不允许调用。
11之后,explicit可用在接受一个以上参数的构造函数。突破了一个参数的限制,阻止任意多个参数的构造函数的隐式调用。
class p {
public:
p(int a, int b){
cout << "p(int a, int b)" << endl;
}
explicit p(int a, int b, int c){
cout << "expilict p(int a, int b, int c)" << endl;
}
}
p p1(1, 2); //p(int a, int b)
p p2 = {1, 2, 3}; // 报错,因为initializer想要去调用用explicit修饰的构造函数,被编译器阻止
Automatic Type Deduction with auto
auto关键字:当类型复杂或者很长的时候,让编译器来进行参数的类型。不建议经常使用这个,自己最好完全知道变量的类型,不用这个屠龙宝刀,尽量只在类型超级长的时候懒得打时才使用auto。
注意:
1.使用auto类型 必须初始化
2.auto不能在函数参数中使用
3.auto不能声明数组
Uniform Initialization
decltype类型推导
和auto一样,都是在编译时期进行自动类型推导
有点类似typeof
统一初始化
统一用大括号初始化
=deault,=delete
如果自行定义了一个构造函数,那么编译器就不会给你一个默认的构造函数;
如果你强行加一个 =default
,就可以重新获得并使用编译器给出的默认构造函数。
=default
只可以用于构造函数,=delete
可用于任何函数,给编译器说明禁止使用这个函数。
一个空类真的是空的吗?显然不是。因为编译器会给一个空类添加上默认的构造函数、析构函数。
Alias Template 别名
alias template 能接受参数
别以为它只是让一个类换个名字,方便书写,它后面会引发一个大事情。
比如下面这个例子:
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;
Vec<int> coll;
那我们可以用#define
或是typedef
来达到同样的效果吗?
#define Vec<T> template<typename T> std::vector<T, MyAlloc<T>>;
Vec<int> coll; //------> template<typename int> std::vector<int, MyAlloc<int>>; //这不是我们想要的
//如果使用typedef,因为typedef是不能够接受参数的,最多写成下面这样
typedef std::vector<int, MyAlloc<int>> Vec;
所以,using的优势就是能够指定参数。
那这样做,只是为了减少代码量吗?如果有一个场景,需要以一个类型作为参数,而不是以一个类的对象作为参数呢?比如说下面这种情况:
Container是一种类型,所以上面的语句就会报错,找不到Container定义。有一种解决方案是牺牲方法的通用性,将Container和T进行组合变成一个参数传入,这种实现方法也不赖。那有没有更优雅的实现方案呢?程序员嘛,都是将就优雅的!
所以要进行下面的思考:有没有模板语法,能够在一个模板类中接受一个模板参数,在模板类中取出这个模板参数呢?有!模板模板参数!
模板模板参数
template<typename T, template<class> class Container>
class XCIs{
private:
Container<T> c;
public:
XCIs(){
for(long i = 0; i < size; ++i)
c.insert(c.end(), T());
...(上图中的实现)
}
}
上述就是模板模板参数的使用。在引入模板模板参数之后,就可以这么使用:XCIs<Mystring, vector> c1;
完成我们的任务。但是!这一句又会报错,因为vector模板容器需要两个模板参数(类型和分配器)!这,不就需要using了吗?
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;
XCIs<Mystring, Vec> c1;
类型别名:Type Alias
类似于typedef,不过是借助using来实现:
typedef void(*func)(int, int); // 函数指针
|
|
v
using func = void(*)(int, int);
和typedef的唯一区别是using多了一个模板别名(带参数)的功能。
using的所有用法
- using namespace std; //打开一个命名空间
using std::count; //打开一个命名空间的一个组件- using Base::_M_Prime; //打开一个类的一个成员
- 模板别名、类型别名
noexcept 关键字
让一个函数不会发生异常
void foo() noexcept;
void foo() noexcept(predicate); // 在条件predicate为真时,不抛出异常
异常一定要被处理,如果一个函数声明为noexcept就会把异常交给上一层调用。
你最好是告诉C++(特别是vector):你的移动构造函数和移动赋值构造函数是noexcept的。因为vector要增长空间,增长空间需要保证构造函数不能发生异常(让别人调用的时候很安心),如果不显示声明移动构造是noexcept的,编译器不敢调用!
会增长的容器有vector、deque、红黑树。
override
让编译器检测自己重写父类的虚函数有没有写错,防止自己粗心。我觉得还有一个作用就是,在回看源码的时候,能够清楚知道一个函数是在重写父类的虚函数。
decltype
为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。有点类似typeof
// C++11
map<string, float> coll;
decltype(coll)::value_type elem;
// C++11之前
map<string, float> coll;
map<string, float>::value_type elem;
应用:
- 用于声明返回类型
// 希望如此
templat<typename T1, typename T2>
decltype(x+y) add(T1 x, T2 y);
// 这么实现
templat<typename T1, typename T2>
auto add(T1 x, T2 y) -> decltype(x+y); //和lambdas表达式相似
2.元编程:
3.lambadas表达式
lambdas表达式
[] 存放外部变量或者引用
mutable表示中括号[]里的东西是否能改动,
throwSpec表示抛出的异常
retType表示返回类型
如果这仨有一个存在,参数列表就要出现
上边这个表示接受任何参数
[]开头的就是lambdas表达式,叫做introducer,用于捕获lambdas表达式外的变量用到具体方法内,可以指定传值方法。如果是=,则方法内可以以值传递的方式捕获lambdas外的所有变量
()放函数参数
mutable关系到[]中的内容是否可被改写,用在传值上(写上mutable说明[]中的值可以在{}中进行修改),可选
throwSpec指定是否抛出异常,可选
retType指定返回类型,可选
{}中放具体的方法实现
定义出来的是一个对象,需要()来调用
vector<int> vec {1,30,69,20,40,195,124};
int x = 20;
int y = 100;
vec.erase(
remove_if(vec.begin(), vec.end(), [x, y](int n){return x < n && n < y;}),
vec.end()
); // 之前需要使用仿函数适配器才能完成这个任务
可变参数:Variadic Templates
初级语法
允许一个函数的参数可以是任意类型,任意个数。
函数在执行的时候,会将参数包解析为一个参数和剩余参数组成的参数包两个部分。
需要一个空函数用于处理在参数包解析到不包含参数的情况。
tuple 头尾处理方式不同
Rvalue references 右值引用
对容器的效率有大幅度改善,避免不必要的copy(当拷贝的来源端是一个右值,则接受端可以去偷(steal)这个右值的资源)而不执行separate allocation。perfect forward
左值:可以出现在operator=左侧
右值:只能出现在operator=右侧
临时对象是一个右值。拷贝构造是个深拷贝。
函数返回值是右值。
为什么放在标准库这一块谈呢?因为它和标准库密切相关。
右值不能取地址或引用!
当右值出现于operator=的右侧时,我们认为其对资源进行搬移而非拷贝是可以的,合理的,那么:
- 必须要有语法让我们在调用端告诉编译器,这里是个右值(move)
- 必须有语法让我们在被调用端写出一个专门处理右值的move assignment函数(移动构造)
进行移动构造的时候,把新指针指向旧指针指向的地方,旧指针的指向会被取消掉,旧指针不能够再使用!所以,右值和移动构造配合得很好,因为右值以后也不会再使用了!如果本来是一个左值,可以把这个左值传入move函数作为参数,就是显式调用移动构造函数(move把左值变成了右值)。