【C++11新特性】类的新功能,可变模板参数,包装器
文章目录
一、类的新功能
1.default
在继承与多态中,我们介绍了final与override,这里我们介绍两个新的使用的方法。
class Person
{
private:
string _name;
int _age;
public:
Person(string& s, int& a):_name(s),_age(a)
{}
Person(Person& p)
{
_name = p._name;
_age = p._age;
}
};
int main()
{
Person p;
}
当没有默认构造函数的时候,Person p会发生报错。如果我们希望编译器在我们实现了构造函数的基础上再自己实现一个默认构造函数的话,就需要使用default关键字了,在类中加上:
Person() = default;
此时就可以构造成功了,default的作用是通知编译器自动生成一个默认构造函数。
同理,拷贝构造函数和析构函数也可以使用default让编译器自己实现。
2.delete
delete一般在单例模式中使用,单例模式中不允许使用拷贝构造函数,在C++98中,可以将拷贝构造函数设为私有。
但是并不能保证类内不去调用拷贝构造函数,因此C++11引入了delete。
class Person
{
private:
string _name;
int _age;
public:
Person(string& s, int&& a):_name(s),_age(a)
{}
Person(Person& p) = delete;
};
int main()
{
string s("aaa");
Person p(s,12);
Person p1(p);
}
此时拷贝构造函数就无法调用了,编译器也无法自动生成。
二、可变参数模板
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为参数包,我们可以向其中传入多个不同类型的参数。
要打印参数个数,使用的是sizeof…()这一奇怪的结构。
语法比较简单,那么函数内部该如何换取各个参数呢?这就需要对参数包进行解析。
(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...);
//cout << sizeof...(args) << endl;//注意这里打印的是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() << 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
emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象,那么除了用法上,和push_back没什么太大的区别。传左值对比 – push_back没有区别;传右值对比 – push_back是构造+移动构造 emplace_back是直接构造形态有区别,如果push_back/emplace_back的参数对象及其成员都实现了移动构造,本质区别不大, 因为构造出来+移动构造,和直接构造成本差不多, 但是如果push_back/emplace_back的参数对象及其成员没有实现移动构造,那么emplace_back还是直接构造,push_back则是构造+拷贝构造(没有移动构造编译器会调用拷贝构造),因此代价就大了.
稳妥一点呢用emplace_back更好,因为它可以不依赖参数对象是否提供移动构造。
三、包装器
包装器的本质是一个类模板。
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;
注意,包装器只有在类型相同,并且传入参数类型也相同的时候,才只会初始化一份。其他情况还是初始化多份。
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
function<int(int, int)> f1 = f;
cout << f1(1, 2) << endl;
function<int(int, int)> f2 = Functor();
cout << f2(1, 2) << endl;
function<int(int, int)> f3 = &Plus::plusi;
cout << f3(1, 2) << endl;
function<double(Plus, double, double)> f4 = &Plus::plusd;
cout << f4(Plus(),1, 2) << endl;
}
在封装类中的函数时,对于静态函数来说,需要指明类域,最后的取地址可以加也可以不加。
对于普通函数来说,需要在参数中加一个类类型,用于接收一个对象(因为需要对象来调用函数),通常传入一个匿名对象。
需要使用取地址符,为了便于记忆,通常静态函数和动态函数都加取地址符。
2.bind(绑定)
绑定函数的作用有两个,一个是改变参数顺序,还有就是改变参数个数。
int sub(int a, int b)
{
return a - b;
}
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还可以改变参数个数:
function<int(int, int)> f5 = bind(&Sub::add,s, placeholders::_1, placeholders::_2);//即使用add来进行占位
cout << f5(10, 3) << endl;
此时第一个参数为add是被写死了的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!