模版别名Alias Template
template <typename T>//模版参数
using Vec=std::vector<T,MyAlloc<T>>;
Vec<int> coll;//调用
用抽象类型作为模版参数,但Container会报错,因此用模板模版参数,能够在一个模板类中接受一个模板参数,在模板类中取出这个模板参数
模板模板参数
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>
会报错,因为vector模板容器需要两个模板参数(类型和分配器)
因此使用using:
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;
XCIs<Mystring,Vec> c1;//能够正常调用
Variadic Temples可变参数
Template <typename T,typename… Types>
Void printX(const T& firstArg,const Types&… args){
Cout<<firstarg<<endl;
Printf(args…);//递归调用
}
print(7.5,"hello",biset<16>(337),42); // 可以这么调用
cout <<sizeof...(args)<< endl; // 想知道参数包中有几个参数,可以用sizeof...(args)
例子1: print()参数类型与个数均不同
例子2:max()
参数Type都相同,max()的实现方式
参数type不同,max()的实现方式
例子3:对于第一个元素和最后一个元素处理的方式不同
例子4:Tuple:递归继承
例子5:递归复合
类型别名Type Alias(类似typedef)
typedef void (*func)(int,int);//11之前
using func=void(*)(int,int);//11之后
using value_type=T;
关键字
Noexcep
异常一定要被处理,如果一个函数声明为noexcept就会把异常交给上一层调用。
最好是告诉C++(特别是vector):移动构造函数和移动赋值构造函数是noexcept的。因为vector要增长空间,增长空间有构造函数调用过程,如果不显示声明移动构造时noexcept的,编译器不敢调用。
Override(复写)
只应用到虚函数上面:帮助编译器检测子类重写父类的虚函数有没有写错。否则,编译器只会将错误的函数当成子类自己的函数.
Final(是最后一个类/函数,不可以再被继承/重写)
修饰类,表示被修饰的类不能够继承;
修饰虚函数,表示虚函数不能被重写
Decltype
主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。有点类似typeof
用法:
(1)用来申明一个返回类型
auto add(T1 x,T2 y) -> decltype(x+y);//编译器会自动找出一个表达式的类型
(2) 元编程
可以根据具体对象得到其对象类型
map<string, float> coll;
decltype(coll)::value_type elem; // C++11
map<string, float> coll;
map<string, float>::value_type elem; // C++11之前
(3) 得到lambda表达式的类型
auto add = [](int x, int y){
return x + y;
}
cout << decltype(add) << endl; //假设可以输出
explicit
struct complex{
int real, imag;
complex(int re, int im = 0) : real(re), imag(im){}
explicit complex operator+(const complex& x){
return complex((real + x.real), (imag + x.imag));
}
}
complex c1(2,3);
complex c2=c1+5;//将int型转化为complex型
11之前,用于一个参数的构造函数。有上面的代码,那么在执行complex c1(12, 5); complex c2 = c1 + 5;的时候,构造函数不用explicit修饰,则会把5变成一个复数类,但是如果用explicit修饰了,这两句代码就会报错。也就是,让编译器知道,用户希望只在显式调用构造函数的时候才会调用,其他时候不允许调用。
11之后,explicit可用在接受一个以上参数的构造函数。突破了一个参数的限制,阻止任意多个参数的构造函数的隐式调用。
=default, =delete
如果自行定义了一个构造函数,那么编译器就不会给你一个默认的构造函数;
如果你强行加一个 =default
,就可以重新获得并使用编译器给出的默认构造函数。
=default
只可以用于构造函数,=delete
可用于任何函数,给编译器说明禁止使用这个函数。
class Zoo{
public:
Zoo(int i1,int i2):d1(i1),d2(i2){}
Zoo()=default;//构造函数可以有多个
Zoo(const Zoo& x):d1(x.d1),d2(x.d2){}
//!Zoo(const Zoo&)=default;//拷贝构造函数只能有一个
Zoo(Zoo&&)=default;
Zoo& operator=(const Zoo& x){d1=x.d1;d2=x.d2;return *this;}
//!Zoo& operator=(const Zoo&)=default;
//!Zoo& operator=(const Zoo&&)=delete;
//!void func1()=default;//default用于特殊函数之外的函数无意义,编译报错
void func2()=delete;//编译不会报错,表示不想编译这个函数
~Zoo(){}=default;
private:
int d1,d2;
};
用法:无拷贝构造/私人拷贝构造--》单件模式
Lambdas表达式
[x,&x,…] (const T &x,…) [mutable throwSpec -> retType] {...}
[] | []开头的就是lambdas表达式,叫做introducer,用于捕获lambdas表达式外的变量用到具体方法内,可以指定传值方法。如果是=,则方法内可以以值传递的方式捕获lambdas外的所有变量 |
() | ()放函数参数 |
mutable | mutable关系到[]中的内容是否可被改写,用在传值上(写上mutable说明[]中的值可以在{}中进行修改),可选 |
throwSpec | throwSpec指定是否抛出异常,可选 |
retType | retType指定返回类型,可选 |
{} | {}中放具体的方法实现 |
调用 | 定义出来的是一个对象,需要()来调用 |
例子:
auto I=[]{
std::cout<<”hello”<<std::endl;
}
I();//调用
C++11引入lambdas表达式,允许定义一个匿名函数,这个匿名函数是inline的。可以定义在一些状态或者表达式中。下图中的auto就是一个functor的匿名类。lambdas无默认构造函数(所以在容器中指定sort方法的时候,需要调用该容器对应的lambda构造函数)。
Rvalue reference右值引用
左值:可以出现在operator=左侧
右值:只能出现在operator=右侧
右值引用可用于减少非必要的拷贝,右边是一个右值,左边能偷到右值资源,而不用拷贝动作
临时对象是一个右值。拷贝构造是个深拷贝。
当右值出现于operator=的右侧时,我们认为其对资源进行搬移而非拷贝是可以的,合理的,那么:
- 必须要有语法让我们在调用端告诉编译器,这里是个右值(move)
- 必须有语法让我们在被调用端写出一个专门处理右值的move assignment函数(移动构造)
初始化列表initializer_list
如果类中提供一个以initializer_list为参数的构造函数,那么使用{}来进行初始化的时候就直接调用他;如果不提供,那么就会将{}中的内容进行拆解,拆解为一个一个的参数,然后调用和{}中元素个数相同参数个数的构造函数。