前言
本系列文章承接C++基础的学习,需要有**++C语言的基础++** 才能学会哦~
第25篇主要讲的是有关于C++的++C++11标准的第二部分++ 。
C++才起步,都很简单!
可变参数模板
即支持可变数量参数的函数模板和类模板,可变数目的参数称为参数包。
参数包分为:模板参数包 ,表示0或多个模板参数;函数参数包,表示0或多个函数参数。
cpp
// 模板参数包 函数参数包
template<class ...Args> void Func(Args... args) { }
template<class ...Args> void Func(Args&... args) { } //传左值引用
template<class ...Args> void Func(Args&&... args) { } //传万能引用
代码示例:
cpp
template<class ...Args>
void Print(Args&&... args)
{
cout << sizeof...(args) << endl;//计算可变参数包中参数的个数
}
int main()
{
double x = 2.2;
Print();//包中无参数
Print(1);//包中有1个参数
Print(1, string("xxxxx"));//包中有2个参数
Print(1.1, string("xxxx"), x);//包中有3个参数
return 0;
}
有可变参数的函数模板,**不仅可以传任意类型的参数,而且可以传任意数量的参数。**不仅再类型上有泛型,还有了数量的变化,使得代码更加灵活。
包扩展
扩展一个包,就是将他分解为构成他的元素。
代码示例:
cpp
//编译时递归推导的包扩展
void ShowList()
{
//编译器时递归的终止条件,参数包是0个时,直接匹配这个函数
cout << endl;
}
template<class T, class ...Args>
void ShowList(T x, Args... args)
{
//结束条件不可以是运行时判断逻辑,会报错
//if(sizeof...(args)==0)
// return;
cout << x << " ";
//参数包第一个传给了x,剩下的N-1个给第二个参数包args
ShowList(args...);
}
//编译时递归推演解析参数
template<class ...Args>
void Print(Args... args)
{
showList(args...);
}
在编译时的推演步骤可如下理解:

根据参数包一步步递归解析出参数。
这里只是讲述其中一个扩展原理,在实际应用中,我们一般不这样使用包扩展。
也可以这样不递归扩展
cpp
#include <iostream>
using namespace std;
template<typename... Args>
void print(Args... args)
{
// 逗号表达式 + 初始化列表,强制展开参数包
initializer_list<int>
{
(cout << args << " ", 0)... // ... 展开整个括号
};
cout << endl;
}
int main() {
print(1, 3.14, "test");
return 0;
}
在编译时,逗号表达式会展开为三段代码:
cpp
(cout << 1 << " ", 0),
(cout << 3.14 << " ", 0),
(cout << "test" << " ", 0)
完美转发 + 可变参数
配合 C++11 完美转发 std::forward ,可以无损传递任意参数,这是工厂模式、封装函数的核心用法。
cpp
#include <iostream>
#include <utility> // forward
using namespace std;
template<typename T, typename... Args>
T* createObject(Args&&... args) {
// 完美转发:保持参数的左值/右值属性
return new T(std::forward<Args>(args)...);
}
// 测试类
class Test {
public:
Test(int a, double b) {
cout << a << " " << b << endl;
}
};
int main() {
Test* t = createObject<Test>(10, 20.5);
delete t;
return 0;
}
可以避免参数包传递时丢失左值或者右值属性导致的增效(如预想是直接构造,但是运行后是拷贝构造)失败。
... 符号
...在参数包前面,++表声明++;
...在参数包后面,++表扩展++。
cpp
template<class ...Args>//声明为类型,...放在类型Args前面
void Print(Args... args)//声明为参数,...放在参数args前面
{
ShowList(args...)//扩展参数包,...放在args后面
}
注意事项
①参数包可以为空。
②一个模板只能有一个参数包。
③参数包要放在参数列表的最后。
新的类功能
默认的移动构造和移动赋值
原本C++类中有6个默认成员函数,构造、析构、拷贝构造、拷贝复制重载、取地址重载和const取地址重载,前四个比较重要。
C++11新增了两个默认成员函数,移动构造函数和移动复制函数。
当你没有自行实现移动构造以及没有实现析构、拷贝、拷贝赋值重载中的其中一个,那么编译器会自动生成一个默认移动构造。
默认生成的移动构造函数,
对内置类型成员会执行逐成员按字节拷贝;
对自定义类型成员,则需要看这个成员,是否实现了移动构造,若实现了就调用移动构造,反之就会调用拷贝构造。
当你没有自行实现移动赋值重载函数,且没有实现析构、拷贝、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值重载函数,
对内置类型成员会执行逐成员按字节拷贝;
对自定义类型成员,则需要看这个成员,是否实现了移动赋值,若实现了就调用移动赋值,反之就会调用拷贝赋值(同上类似)。
当你提供了移动构造和移动赋值,编译器就不会自动提供拷贝构造和拷贝赋值。
成员变量声明时给缺省值
成员变量声明时给的缺省值是初始化列表要用的,如果初始化列表中没有显示初始化,那么就会用这个缺省值初始化,这个在前面的文章讲过,欢迎到我的主页观看我的文章~
default和delete
default
用于控制想要使用的默认函数。
如上,若我们提供了拷贝构造,就不会自动生成移动构造了,那么我们就可以用default,显式指定移动构造生成。
cpp
#include <string>
using namespace std;
class Person {
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {}
//手动实现了拷贝构造
Person(const Person& other)
: name(other.name), age(other.age)
{
//...
}
//此时编译器不再生成移动构造/移动赋值
//显式写 = default ,恢复想要编译器自动生成的函数
Person(Person&&) = default; // 要移动构造
Person& operator=(Person&&) = default; // 要移动赋值
};
此时,就算手动实现了拷贝构造,编译器还是会自动生成移动构造和移动赋值。
delete
用于禁止编译器自动生成不需要的默认函数。
cpp
class NoCopy {
public:
NoCopy() = default;
// 禁用拷贝构造,不再会默认生成
NoCopy(const NoCopy&) = delete;
// 禁用赋值运算符,不再会默认生成
NoCopy& operator=(const NoCopy&) = delete;
};
int main() {
NoCopy a;
NoCopy b = a; // 报错!被 delete 了
NoCopy c;
c = a; // 报错!被 delete 了
}
此时,NoCopy类因为被禁止自动生成拷贝构造,所以无法进行拷贝,强行拷贝会报错。
final与override
在继承和多态的文章中有讲到,欢迎到我的主页看相关文章~
STL的变化
①新增了unordered_map与unordered_set。之前的文章也就讲过~
②增加了新的接口,如push/insert/emplace的右值引用和移动语义的接口,还有初始化列表的构造
③新增了范围for
包装器
function
是一个类模板,也是一个包装器,可用于包装存储其他的可调用对象,如函数指针、仿函数、lambda、类成员函数(要配合bind)。
语法格式:
cpp
function<[返回类型](参数类型,参数类型,···) [包装变量名] = [包装对象的标识符]
function<int(int, int)> f1 = f;
包装普通函数:
cpp
int add(int a, int b) { return a + b; }
function<int(int, int)> f = add;
cout << f(1, 2) << endl;
包装lambda表达式:
cpp
function<int(int, int)> f = [](int a, int b) {
return a * b;
};
cout << f(2, 3) << endl;
包装仿函数:
cpp
struct Sub {
int operator()(int a, int b) {
return a - b;
}
};
function<int(int, int)> f = Sub();
cout << f(5, 3) << endl;
包装成员函数:
cpp
struct Calc {
int add(int a, int b) { return a + b; }
};
Calc obj;
function<int(int, int)> f = bind(&Calc::add, &obj, placeholders::_1, placeholders::_2);
cout << f(10, 20) << endl;
bind的作用,是把对象和它的成员函数绑定成普通函数。这里是把Calc类的add成员函数和Calc类对象obj绑定,并用两个占位符表示参数数量和位置。
bind
也是一个包装器,它的作用是绑定参数。
语法格式:
cpp
bind([要绑定的函数], [参数1], [参数2], [占位符...]);
其中占位符是
cpp
placeholder::_1
placeholder::_2
placeholder::_3
//可用using简写
using placeholder;
_1
_2
_3
用例1:
固定部分函数参数
cpp
#include <functional>
using namespace std;
using namespace placeholders;
int add(int a, int b) {
return a + b;
}
int main()
{
add(2, 3);//相当于f(3)
//绑定之后add的第一个参数绑死为 2不变
auto f = bind(add, 2, _1);
//因为第一个参数已经固定为 2,只需传第二个参数即可
f(3);
return 0;
}
用例2:
调换参数顺序
cpp
//占位符是有顺序的
auto f = bind(add, _2, _1);
f(2, 3);
//实际上执行
add(3, 2);
❤~~本文完结!!感谢观看!!接下来更精彩!!欢迎来我博客做客~~❤