C++11详解(四) -- 新的类功能和包装器
文章目录
- 1.新的类功能
- 1.1 默认的移动构造和移动赋值
- 1.2 成员变量声明的时候给缺省值
- 1.3 default和delete
- 1.4 final和override
- 2.STL中⼀些变化
- 3.包装器
- 3.1function
- 3.2例题逆波兰表达式求值(用了function)
- 题目解析
- 代码
- 3.3 bind(绑定)
1.新的类功能
1.1 默认的移动构造和移动赋值
1. 原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重载/const 取地址重载,最后重要的是前4个,后两个用处不大,默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。
2. 如果你没有自己实现移动构造函数且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
3. 默认移动赋值跟上面的移动构造完全类似
4. 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
private:
string _name;
int _age;
};
int main()
{
Person s1;// 构造
Person s2 = s1;// 拷贝构造
Person s3 = std::move(s1);// 移动构造
Person s4;
s4 = std::move(s2);
return 0;
}
1.2 成员变量声明的时候给缺省值
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
private:
bit::string _name;
int _age = 1;
// 声明的时候给缺省值
// 显示地给了age就不会用缺省值了
1.3 default和delete
1. C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
default为强制生成
class Person
{
public:
Person(const char* name = "张三", int age = 1)
:_name(name)
, _age(age)
{}
Person(const Person& p) = default;
Person(Person&& p) = default;
// 写了析构就无法生成移动构造,所以用default强制生成移动构造
~Person()
{}
private:
string _name;
int _age;
};
void func(ostream& out)
{}
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
Person s4;
//s4 = std::move(s2);
func(cout);
return 0;
}
2. istream和ostream不能进行拷贝构造,必须用引用。
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错,声明为私有,别人在类外实现就会报错。
在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
void func(ostream& out)
{}
int main()
{
func(cout);
return 0;
}
1.4 final和override
1. final被修饰的父类不能被继承,如果我们不想在子类重写这个虚函数,用final修饰,不能被重写
2. override放在子类重写的虚函数后面去检查是否完成了重写,没有完成重写就会报错
3. 这些在继承和多态中有详解讲解
2.STL中⼀些变化
1. 产生了新容器和新接口,比如unordered_map和unordered_set
2. STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push/insert/emplace系列接口和移动构造和移动赋值,还有initializer_list版本的构造等
3.包装器
3.1function
1. std::function 是一个类模板,也是一个包装器。 std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标,则称它为空。调用空std::function 的目标导致抛出 std::bad_function_call 异常。
#include<functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
// 类
class Plus
{
public:
Plus(int n = 10)
:_n(n)
{}
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
1.
// 包装各种可调用对象
// 返回类型和参数的类型要对应
// int int int
function<int(int, int)> f1 = f;// 函数指针
function<int(int, int)> f2 = Functor();// 仿函数
function<int(int, int)> f3 = [](int a, int b) {return a + b; };// lambda
2.
// 包装类的静态成员函数
// 成员函数要指定类域并且前面加&才能获取地址
// 静态成员函数可以不加&
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
3.
// 包装普通成员函数,还有一个this指针
可以用指针调用 &pd
function<double(Plus*,double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 2.2) << endl;
可以用对象调用 pd
function<double(Plus, double, double)> f6 = &Plus::plusd;
cout << f6(pd, 1.1, 1.1) << endl;
cout << f6(Plus(),1.1,1.1) << endl;
可以用右值引用
function<double(Plus&&, double, double)> f7 = &Plus::plusd;
cout << f7(move(pd), 1.1, 1.1) << endl;
cout << f7(Plus(), 1.1, 1.1) << endl;
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
return 0;
}
对于第三点,可以传对象的指针,也可以传对象是为什么?
底层是用.*的运算符,可以用对象直接调用,也可以用对象的指针调用
对象点函数调用
3.2例题逆波兰表达式求值(用了function)
题目链接
题目解析
遇到数字插入栈,遇到符号出栈,先出的是右操作数,再出的是左操作数,再把结果入栈,如此循环
代码
class Solution
{
public:
int evalRPN(vector<string>& tokens)
{
stack<int> st;
// function可调用对象的类型
map<string,function<int(int,int)>> offunMap =
{
{"+",[](int x,int y){return x + y;}},
{"-",[](int x,int y){return x - y;}},
{"*",[](int x,int y){return x * y;}},
{"/",[](int x,int y){return x / y;}}
};
for(auto& str :tokens)
{
// 操作符
if(offunMap.count(str))
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
int ret = offunMap[str](left,right);
st.push(ret);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
3.3 bind(绑定)
1. bind 是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收的fn可调用对象进行处理后返回一个可调用对象。 bind 可以用来调整参数个数和参数顺序。bind 也在这个头文件中。
2. arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置:
_1为newCallable的第一个参数,_2为第二个参数,以此类推。_1/_2/_3…这些占位符放到placeholders的一个命名空间中。
3.根据_1,_2,_3这些调整适配参数
bind参数中第一个传的是可调用对象
可调用对象:lambda,仿函数,函数指针
调整参数顺序
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{
return (a - b) * 10;
}
int main()
{
// 函数指针
// _1->a,_2->b
// sub1中的第一个位置的参数对应_1,
// 第二个位置的参数对应_2
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl;
// bind 本质返回的一个仿函数对象
// 调整参数顺序(不常用)
// _1代表第一个实参
// _2代表第二个实参
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl;
return 0;
}
调整参数个数
int Sub(int a, int b)
{
return (a - b) * 10;
}
// 调整参数个数 (常⽤)
// a始终是100,绑定了第一个参数
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl;
// b始终是100,绑定了第二个参数
auto sub4 = bind(Sub, _1, 100);
cout << sub4(5) << endl;
分别绑定第1,2,3位置的参数
int SubX(int a, int b, int c)
{
return (a - b - c) * 10;
}
// 分别绑死第123个参数
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl;
auto sub6 = bind(SubX, _1, 100, _2);
cout << sub6(5, 1) << endl;
auto sub7 = bind(SubX, _1, _2, 100);
cout << sub7(5, 1) << endl;
成员函数对象进行绑死,就不需要每次都传递了
bind返回的是一个仿函数对象,仿函数对象可以用function进行包装
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;
// bind⼀般⽤于绑死⼀些固定参数
// 绑定了匿名对象的传递
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
计算复利的lambda
int main()
{
// 计算复利的lambda
// 复利前一年的利息变成第二年本金
// (10000*0.02 + 10000)*0.02 + 10000*0.02 + 10000
// 利率 本金 年限
// 得到的是利息,总共得到的钱减去本金
auto func1 = [](double rate, double money, int year)->double
{
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
//cout << func1(0.05, 10000000, 30) << endl;
// 绑死一些参数,实现出支持不同年利率,不同金额和不同年份计算出复利的结算利息
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_1_5 = bind(func1, 0.015, _1, 10);
function<double(double)> func3_2_5 = bind(func1, 0.025, _1, 3);
function<double(double)> func5_2_5 = bind(func1, 0.025, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_1_5(1000000) << endl;
cout << func3_2_5(1000000) << endl;
cout << func5_2_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
return 0;
}