C++ Primer第五版笔记——重载运算符(二)
函数调用运算符:
如果类重载了函数调用运算符“()”,则可以像调用函数一样使用该类的对象,因为类同时还能存储状态,所以普通函数相比之下更加灵活。
比如:
struct absInt{
int operator()(int v) const{
return v > 0? v : -v;
}
};
int main(){
int i = -99;
absInt s;
int x = s(i);
cout<<x<<endl;
}
该类接受int类型的实参,返回它的绝对值。调用的方式也很想函数的调用过程。
函数调用运算符必须是成员函数,这个类的对象也被称为“函数对象”,一个类可以定义多种不同版本的调用运算符,像函数重载一样应该在参数数量或类型上有所区别。
含有状态的函数对象类:
和其他类一样,函数对象类也有自己的状态,其也可有自己的数据成员,这些成员通常用于定制调用运算符函数。例如:
class PrintStr{
public:
PrintStr(ostream& o = cout,char cc = '\n'):os(o),c(cc){}
void operator()(const string s){
os<<s<<c;
}
private:
ostream& os;
char c;
};
int main(void){
PrintStr p;
string s = "this is a stence";
p(s); //会打印s和换行
}
函数对象常常作为泛型算法的实参,例如for_each函数中可以用自己的类来打印内容:
for_each(v.cbegin(),v.cend(),PrintStr());
这里的第三个参数是一个临时对象,这里默认初始化了该对象,当调用for_each时,将会把v中的每个元素依次打印到标准输出,以换行符分割。
lambda是函数对象:
刚才将PrintStr作为for_each实参的用法和使用lambda表达式的方式类似。当我们编写一个lambda表达式后,编译器将其翻译为一个未命名类的未命名对象,在这个类中有一个重载的函数调用运算符,例如:
//根据单词长度排序
stable_sort(words.begin(),words.end(),
[](const string& a,const string& b)
{return a.size() < b.size();});
//其中的lambda表达式翻译成类似下面的类
class ShorterString{
public:
bool operator()(const string& a,const string& b) const {
return a.size() < b.size();
}
};
//替换后的函数
stable_sort(words.begin(),words.end(),ShorterString());
表达lambda及捕获对象的类:
当一个lambda表达式通过引用捕获变量时,由程序来确保lambda执行时引用所引的对象确实存在,因此,编译器可以直接使用该引用而不需再lambda表达式产生的类中将其存储为数据成员。(我理解的意思是编译器在翻译lambda表达式为类时,将传给lambda表达式的参数自动翻译为该类的数据成员)。例如:
//获得字符长度大于给定值的字符
auto wc = find_if(words.begin(),words.end(),
[sz](const string& a)
{return a.size() > sz;});
//lambda将翻译为类似下面的类
class SizeComp{
public:
SizeComp(size_t n):sz(n){}
bool operator()(const string& a) const {
return a.size() > sz;
}
private:
size_t sz;
};
//替换后的函数
auto wc = find_if(words.begin(),words.end(),SizeComp(sz));
标准库定义的函数对象:
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符,比如plus类表示+,equal_to表示==。
这些类都被定义为模板的形式,比如plus<string>表示加法运算符作用于string对象。这些类型都被定义在functional头文件中:
这样的化,根据字符串长度排序的函数又可以改写为:
//按降序排列
sort(words.begin(),words.end(),greater<string>());
第三个参数是一个greater<string>类型的未命名对象。
需要注意的是,标准库规定其函数对象对于指针同样适用,但是比较两个无关指针将产生未定义的行为,因此,当做希望通过比较指针地址来排序之类的操作时,可以使用标准库函数对象。
vector<string*> nameTalbe;
//错误,nameTable中的指针彼此之间没有关系,调用"<"将产生未定义行为
sort(nameTable.begin(),nameTable.end(),
[](const NameTable& a,const NameTable& b)
{return a < b;});
//正确标准库中的less定义时良好的
sort(nameTable.begin(),nameTable.end(),less<string*>());
可调用对象和function
C++语言中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了的函数调用运算符。
可调用对象也有自己的类型,比如lambda表达式有自己的(未定义)类类型。但可能共享一种调用形式(call signature),调用形式指明调用返回的类型以及传递给调用的实参类型。例如:int(int,int)是一个函数类型,它接受两个int,返回一个int。
不同类型可能具有相同的调用形式:
对于几个可调用对象共享同一种调用形式的情况,有时我们希望把它们看成具有相同的调用形式,例如:
//普通函数
int add(int a,int b){
return a+b;
}
//lambda
auto mod = [](int a,int b){
return a % b;
}
//函数对象类
struct divide{
int operator()(int a,int b){
return a/b;
}
};
上面的可调用对象类型不同,但是共享的一种调用形式:int(int,int).
假设希望通过这些对象来构建啊一个计算器,为了实现这个目的,需要定义一个函数表用于存储指向这些对象的“指针”。当需要执行某个特定操作时,从表中查找对应对象。
在C++语言中,可以使用map来实现函数表,对应这里的map可以如下定义:
//构建从运算符到函数指针的映射关系
map<string,int(*)(int ,int)> binops;
//添加add
binops.insert({"+",add});
但是不能将后两种操作存入到binops中,因为lambda表达式和函数对象类都不能作为函数指针传递到binops中的值中。
我们可以使用一个名为function的新标准库类型解决这个问题,function定义在functional头文件中。
function是一个模板,尖括号中的内容是能够表示的对象的调用形式,例如:
function<int(int,int)>
//可以用这个声明表示以上任意一种类型
function<int(int,int)> f1 = add;
function<int(int,int)> f2 = divide();
function<int(int,int)> f3 = [](int i,int j){return i*j;};
//使用这个function重新定义map,并添加成员
map<string,function<int(int,int)>> binops = {
{"+",add},
{"-",std::minus<int>()},
{"*",[](int i,int j){return i*j;}},
{"%",divide()},
{"%",mod}
};
//使用
binops["+"](10,5); //调用add(10,5)
重载的函数与function:
不能(直接)将重载函数的名字存入function对象中,这可能为产生二义性:
int add(int a,int b);
Sales_date add(const Sales_date& a,const Sales_date& b);
map<string,function<int(int,int)>> binops;
binops.insert({"+",add}); //错误,不知道是哪个add
解决办法之一可以通过存储函数指针而不是存储函数的名字:
int (*fp)(int,int) = add; //表示指针所指的函数接受两个int参数
binops.insert({"+",fp}); //正确
同样也可以使用lambda来消除二义性:
binops.insert({"+",[](int a,int b){return a + b;}});
上一篇: 爱普生清零破解软件使用方法 好用
下一篇: 关于高考那点事