这一章重载运算符和类型转换比较简单,直接看书理解就行。
但也比较容易出现错误,仔细看书就行。
重载运算符和类型转换是为了更好的使用类型,即使没有不使用这个技术也不影响。
下面说一下函数调用运算符:()
如果类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象。
因为这样的类同时也能存储状态,所以与普通函数相比它们更灵活。例如:
struct absInt
{
int operator()(int val) const //返回绝对值
{
return val<0 ? -val : val;
}
};
int i = -3;
absInt obj;
int ui = obj(i); //等价于:obj.operator()(i); 就是函数调用
我们可以调用obj,即使obj只是一个对象而非函数。
***调用对象实际上是在运行重载的调用运算符:本例中,该运算符接受一个int值并返回其绝对值。
函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。
如果类定义了调用运算符,则该类的对象称作函数对象。因为可以调用这种对象,所以说这些对象的行为像函数一样。
含有状态的函数对象类
函数对象类也可以包含其他成员,函数对象类通常含有一些数据成员,这些成员被用于定制调用运算符中的操作。例如:
class PrintString //打印字符串,并在末尾添加指定字符
{
public:
PrintString(ostream &o = cout, char c = ' '):os(o),sep(c) {}
void operator()(const string &s) const {os<<s<<sep;}
private:
ostream &os;
char sep;
};
PrintString printer;
printer(s); //在cout打印s,后面跟一个空格
PrintString errors(cerr,'\n');
errors(s); //在cerr中打印s,后面跟一个换行符
函数对象常常作为泛型算法的实参。例如,可以使用标准库for_each算法和我们自己的PrintString类来打印容器的内容:
vector<string> vec{ "abc","aoe","qwer" };
for_each(vec.begin(), vec.end(), PrintString(cerr,'\n'));
等价于:第三个参数传递一个函数对象,上面是通过构造函数直接构造,下面是先构造,再传给形参
PrintString print(cerr,'\n');
for_each(vec.begin(), vec.end(), print);
lambda是函数对象
当我们编写一个lambda后,编译器将该表达式翻译成一个未命名类的未命名对象。
在lambda表达式产生的类中含有一个重载的函数调用运算符:
如上面的例子用lambda重写:
for_each(vec.begin(),vec.end(),[](const string& s){cout<<s<<endl;});
这个lambda生成的未命名的类类似下面:
class NoName
{
public:
void operator()(const string& s) const
{cout<<s<<endl;};
};
默认情况下,lambda不能改变捕获的变量,由lambda产生的类当中的函数调用运算符是一个const成员函数,
如果lambda被声明为可变的,则调用运算符就不是const的了
我们也可以用上面的类重写:
for_each(vec.begin(),vec.end(),NoName());
*****表示lambda及相应捕获行为的类*****
当一个lambda表达式通过引用捕获变量时,将由程序负责确保lambda执行时引用所引的对象确实存在。编译器可以直接使用该引用
而无须在lambda产生的类中将其存储为数据成员。
通过值捕获的变量被拷贝到lambda中,这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令
其使用捕获的变量的值来初始化数据成员。例如:下面的lambda,作用是找到第一个长度不小于给定值的string:
auto wc = find_if(words.begin(),words.end(),[size](const string& a) { return a.size() >= size; });
该lambda表达式产生的类将形如:
class SizeComp
{
public:
SizeComp(size_t n):size(n){} //对应的捕获的变量
bool operator()(const string& s) const //该调用运算符的返回类型、形参和函数体斗鱼lambda一致
{ return s.size() >= size; }
private:
size_t size; //对应的捕获的变量
};
lambda表达式产生的类不含默认构造函数、赋值运算符及默认析构函数;它是否含有默认的拷贝/移动构造函数则通常要视捕获的
数据成员类型而定。
标准库定义的函数对象
在算法中使用标准库函数对象
重点:可调用对象与function
不同类型可能具有相同的调用形式
学过C#的同学知道,委托和这个功能类似。
自己简单实现类似C#委托的功能:比较简陋,只是提供一个思路
class Action
{
typedef function<void(int)> Fun; //函数类型是无返回值,接受一个int参数
public:
void operator()(int value) //重载函数调用运算符
{
for (auto fun : vec)
{
fun(value);
}
}
void operator+=(Fun fun) //重载+=运算符
{
/*auto iter = find(vec.begin(), vec.end(), fun); //这里不注释会报错
if (iter == vec.end())*/
vec.push_back(fun);
}
//这里不注释会报错,
/*void operator-=(Fun fun) //重载-=运算符
{
auto iter = find(vec.begin(), vec.end(), fun);
if (iter != vec.end())
vec.erase(iter);
}*/
private:
vector<Fun> vec;
};
void Test(int i) //一般函数
{
cout << "Test" << i << endl;
}
struct Test2
{
void operator()(int i) //函数对象
{
cout << "Test2" << i << endl;
}
};
void main()
{
Action myAction;
myAction += [] (int a) { cout << "lambda" << a << endl; }; //lambda表达式
myAction += Test;
myAction += Test2();
myAction(666); //利用函数对象执行调用
}
上面会报错的代码的原因:
find会进行==比较两个函数原型void(int),因为没有重载找不到,所以报错。
可以自己重载==操作,但现在还不知道怎么判断两个相同函数原型的函数的相等性