目录
一、类的新功能
1.default
在继承和多态中,我们介绍了final与override,这里我们介绍两个新的使用的方法。
class Person
{
public:
Person(string &s, int &a)
:_name(s)
,_age(a)
{}
Person(Person& p)
{
_name =p._name;
_age =p._age;
}
private:
string _name;
int _age;
};
int main()
{
Person p;
return 0;
}
当没有默认构造函数的时候,Person p会发生报错。如果我们希望编译器在我们实现了构造函数的基础上再自己实现一个默认构造函数的话,就需要使用default关键字了,在类中加上:
Person() = default;
此时就可以构造成功了,default的作用是通知编译器自动生成一个默认构造函数。
同理,拷贝构造函数和析构函数也可以使用default让编译器自己实现。
2.delete
delete一般在单例模式中使用,单例模式中不允许使用拷贝构造函数,在C++98中,可以将拷贝构造函数设为私有。
但是并不能保证类内不去调用拷贝构造函数,因此C++11引入了delete。
class Person
{
public:
Person(string s=" ", int a=0)
:_name(s)
, _age(a)
{}
Person(Person& p) = delete;
private:
string _name;
int _age;
};
int main()
{
Person p;
return 0;
}
此时拷贝构造函数就无法调用了,编译器也无法自动生成。
二、可变参数模板
1.参数包
在STL容器中,插入函数不只有push_back等,还有一种插入函数:emplace_back()
它的参数是Args&&...,它表示的就是一个可变参数列表,注意&&是一个万能引用。Args是一个参数包,可以包含很多个函数:
2.参数宝的插入与解析
(1)参数包的个数
template<class ...Args>
void ShowArgs(Args...args)
{
cout << sizeof...(Args) << endl;//注意这里打印的是args的个数
}
int main()
{
int a = 2;
ShowArgs(1);
ShowArgs(1,"aaa");
ShowArgs(1,a,"dddd");
}
通常配合着模板一起使用,我们其中Args为一个可变参数包类型,args为参数包,我们可以向其中传入多个不同类型的参数。
Args
是模板参数包,它定义了可以接受哪些类型的参数。args
是函数参数包,它在函数体内引用了实际传递给模板函数的参数。
要打印参数个数,使用的是sizeof…这一奇怪的结构,sizeof...(Args)这么用
语法比较简单,那么函数内部该如何换取各个参数呢?这就需要对参数包进行解析。
(2)添加参数解析
可以添加一个参数模板后使用递归来进行解析:
template<class T>
void ShowArgs(const T& val)//重载函数,用于终止
{
cout << typeid(val).name() << endl;
}
template<class T,class ...Args>//添加一个模板参数T,每一次向它传入剩下的可变参数列表
void ShowArgs(T val, Args... args)
{
cout << typeid(val).name() << ":" << val << endl;
ShowArgs(args...);
}
int main()
{
int a = 2;
ShowArgs(1);
cout << endl;
ShowArgs(1, "aaa");
cout << endl;
ShowArgs(1, a,"dddd");
}
(3)逗号表达式展开
template<class T>
void ShowArgs(const T& val)
{
cout << typeid(val).name()<<":"<<val << endl;
}
template<class ...Args>
void PrintArgs(Args... args)
{
int arr[] = { (ShowArgs(args),0)... };
cout << endl;
}
int main()
{
PrintArgs(1, 'a', 2.2);
}
expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表, 通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…} 将会展开成 ((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ) ,最终会创建一个元素值都为0的数组int arr[sizeof…(Args)] 。
由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
(4)emplace_back
emplace_back对于push_back的优势就在于支持可变参数列表,显得更加灵活一些:
list<pair<int, char>> mylist;
mylist.push_back(make_pair(1, 'a'));
mylist.emplace_back(make_pair(2, 'b'));
//mylist.push_back(1,'v');//不能自动构建pair
mylist.emplace_back(3, 'o');//可以自动构建pair
三、包装器
包装器的本质是一个类模板。
1.function(封装)
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
template<class F,class T>
T useF(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
int main()
{
cout << useF(f, 11.1) << endl;
cout << useF(Functor(), 1) << endl;
cout << ([](double d)->double {return d / 4; }, 11.1);
}
看这样一段代码,有一个模板函数,当同时向它的F传入函数,仿函数,以及lambda表达式的时候会实例化三个模板。
我们希望它只实例化出一个模板,需要使用function,它被包含在functional这个头文件中。
发现count的值都是0,地址各不相同,说明实例化了三份模板。
此时就需要使用func函数:
function<double(double)> func1 = f;
cout << useF(func1, 11.1) << endl;
function<double(double)> func2 = Functor();
cout << useF(func2, 1.1) << endl;
function<double(double)> func3 = [](int d)->int {return d / 4; };
cout << useF(func3, 12.1) << endl;
注意,包装器只有在类型相同,并且传入参数类型也相同的时候,才只会初始化一份。其他情况还是初始化多份。
2.bind(绑定)
绑定函数的作用有两个,一个是改变参数顺序,还有就是可以将参数写死到函数中。
int sub(int a, int b)
{
return a - b;
}
int main()
{
function<int(int, int)> f1 = sub;
cout << f1(10, 3) << endl;
function<int(int, int)> f2 = bind(sub, placeholders::_2, placeholders::_1);
cout << f2(10, 3) << endl;
}
通过bind函数中的placeholders参数,可以调换参数位置:
bind还可以改变参数个数:
#include <functional>
int sum(int a, int b, int c)
{
return a + b + c;
}
int main()
{
auto f6 = bind(sum, placeholders::_1, placeholders::_2, 3);
int a = f6(1, 2);
cout << a << endl;
}
此时第三个参数为3是被写死了的。