函数调用运算符
函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。
如果类定义了调用运算符,则该类的对象称为函数对象。
含有状态的函数对象类
函数对象除了operator()之外也可以包含其他成员。函数对象类通常含有一些数据成员,这些成员被用于定制调用运算符中的操作。
定义一个打印string实参内容的类。默认情况下,我们的类会将内容写入到cout中,每个string之间以空格隔开。同时也允许类的用户提供其他可写入的流及其他分隔符。
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; //用于将不同输出隔开的字符
};
练习14.33:一个重载的函数调用运算符应该接受几个运算对象?
0个或者多个
练习14.34:定义一个函数对象类,令其执行if-then-else的操作:该类的调用运算符接受三个形参,它首先检查第一个形参,如果成功返回第二个形参的值,如果不成功返回第三个形参的值。
class myclass {
public:
myclass(){}
myclass(int i1, int i2, int i3): i1(i1), i2(i2), i3(i3){}
int operator()(int i1, int i2, int i3) {
return i1 ? i2 : i3;
}
private:
int i1, i2, i3;
};
练习14.35:编写一个类似于PrintString的类,令其从istream中读取一行输入,然后返回一个表示我们所读内容的string。如果读取失败,返回空string。
class ReadString {
public:
ReadString(istream &is = cin): is(is){}
string operator()() {
string line;
if (!getline(is, line)) {
line == " ";
}
return line;
}
private:
istream& is;
};
练习14.36:使用前一个练习定义的类读取标准输入,将每一行保存为vector的一个元素。
void TestReadString() {
ReadString rs;
vector<string> vec;
while (true) {
string line = rs();
if (!line.empty())
vec.push_back(line);
else
break;
}
}
练习14.37:编写一个类令其检查两个值是否相等。使用该对象及标准库算法编写程序,令其替换某个序列中具有给定值的所有实例。
class IntCompare {
public:
IntCompare(int v):val(v){}
bool operator()(int v) { return val == v; }
private:
int val;
};
int main() {
vector<int> vec = { 1,2,3,2,1 };
const int oldvalue = 2;
const int newvalue = 200;
IntCompare icmp(oldvalue);
std::replace_if(vec.begin(), vec.end(), icmp, newvalue);
return 0;
}
lambda是函数对象
当我们编写一个lambda后,编译器将该表达式翻译成一个未命名对象。在lambda表达式产生的类中含有一个重载的函数调用运算符,例如:对于我们传递给stable_sort作为其最后一个实参的lambda表达式来说:
//根据单词的长度对其进行排序,对于长度相同的单词按字母表顺序排序
stable_sort(words.begin(), words.end(), [](const string& a, const stirng& b) {return a.size() < b.size(); });
//行为类似于下面这个类的一个未命名对象
class ShorterString {
public:
bool operator()(const string& s1, const string& s2)const {
return s1.size() < s2.size();
}
};
默认情况下lambda不能改变它捕获的变量。因此在默认情况下,由lambda产生的类当中的函数调用运算符是一个const成员函数。如果lambda被声明为可变的,则调用运算符就不是const的了。
用这个类代替lambda表达式后:
stable_sort(words.begin(), words.end(), ShorterString());
表示lambda及相应捕获行为的类
当一个lambda表达式通过引用捕获变量时,将由程序确保lambda执行时引用所引的对象确实存在。因此,编译器可以直接使用该引用而无需在lambda产生的类中将其存储为数据成员。
相反,通过值捕获的变量被拷贝到lambda中。因此,这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员。
//获得第一个指向满足条件元素的迭代器,该元素满足size() is >= sz
auto wc = find_if(words.begin(), words.end(), [sz](const string& a) {return a.size() >= sz; });
//该lambda表达式产生的类将形如:
class SizeComp {
SizeComp(size_t n):sz(n){} //该形参对应捕获的变量
//该调用运算符的返回类型、形参和函数体都与lambda一致
bool operator()(const string& s) const {
return s.size() >= sz;
}
private:
size_t sz;
};
和ShorterString类不同,上面这个类含有一个数据成员以及一个用于初始化该成员的构造函数。这个合成的类不含有默认构造函数,因此要想使用这个类必须提供一个实参.
auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
练习14.38:编写一个类令其检查某个给定的string对象的长度是否与一个阈值相等。使用该对象编写程序,统计并报告在输入的文件中长度为1的单词有多少个、长度为2的单词有多少个、……、长度为10的单词有多少个。
#include
#include
#include
#include
using namespace std;
class StrLenIs {
public:
StrLenIs(int len): len(len){ }
bool operator()(const string& str) { return str.length() == len; }
private:
int len;
};
void ReadStr(istream& is, vector<string>& vec) {
string word;
while (is >> word) {
vec.push_back(word);
}
}
int main() {
vector<string> vec;
ReadStr(cin, vec);
const int MinLen = 1;
const int MaxLen = 10;
for (int i = MinLen; i <= MaxLen; i++) {
StrLenIs Is(i);
cout << "len: " << i << ",cnt: " << count_if(vec.begin(), vec.end(), Is) << endl;
}
return 0;
}
练习14.39:修改上一题的程序令其报告长度在1至9之间的单词有多少个、长度在10以上的单词又有多少个。
class StrLenBetween {
public:
StrLenBetween(int MinLen, int MaxLen) : MinLen(MinLen), MaxLen(MaxLen) { }
bool operator()(const string& str) {
return str.length() >= MinLen && str.length() <= MaxLen;
}
private:
int MinLen;
int MaxLen;
};
class StrNotShorterThan {
public:
StrNotShorterThan(int MinLen) : MinLen(MinLen) { }
bool operator()(const string& str) {
return str.length() >= MinLen;
}
private:
int MinLen;
};
//extern ReadStr(istream& is, vector& vec);
int main() {
vector<string> vec;
ReadStr(cin, vec);
StrLenBetween slb(1, 9);
StrNotShorterThan snst(10);
cout << "len 1~9: " << count_if(vec.begin(), vec.end(), slb) << endl;
cout << "len >= 10: " << count_if(vec.begin(), vec.end(), snst) << endl;
return 0;
}
练习14.40:重新编写10.3.2节的biggies函数,使用函数对象类替换其中的lambda表达式。
class IsShorter {
public:
bool operator()(const string& s1, const string& s2) {
return s1.size() < s2.size();
}
};
class StrNotShorterThan {
public:
StrNotShorterThan(int MinLen) : MinLen(MinLen) { }
bool operator()(const string& str) {
return str.length() >= MinLen;
}
private:
int MinLen;
};
class PrintString {
public:
void operator()(const string& str) {
cout << str << " ";
}
};
void biggies(vector<string>& words, vector<string>::size_type sz) {
elimDups(words);
IsShorter is;
stable_sort(words.begin(), words.end(), is);
StrNotShorterThan snst(sz);
auto wc = find_if(words.begin(), words.end(), snst);
auto count = words.size() - wc;
cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer " << endl;
PrintString ps;
for_each(wc, words.end(), ps);
cout << endl;
}
练习14.41:你认为C++11新标准为什么要增加lambda
在C++11中,lambda是通过匿名的函数对象来实现的,因此我们可以把lambda看作是对函数对象在使用方式上的简化。
标准库定义的函数对象
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。这些类都被定义成模板的形式,我们可以为其指定具体的应用类型,这里的类型即调用运算符的形参类型。例如,plus
在算法中使用标准库函数对象
//传入一个临时的函数对象用于执行两个string对象的>比较运算
sort(svec.begin(), svec.end(), greater<string>());
需要特别注意的是,标准库规定其函数对象对于指针同样适用。比较两个无关指针将产生未定义的行为,然而可以通过比较指针的内存地址来sort指针的vector。直接这样做会产生未定义行为,因此我们可以使用一个标准函数对象来实现该目的:
vector<string*> nameTable; //指针的vector
//错误:nameTable中的指针彼此之间没有关系,所以<将产生未定义的行为
sort(nameTable.begin(), nameTable.end(), [](string* a, string* b) {return a < b; });
//正确:标准库规定指针的less是定义良好的
sort(nameTable.begin(), nameTable.end(), less<string*>());
练习14.42:使用标准库函数对象及适配器定义一条表达式,令其
(a)统计大于1024的值有多少个
(b)找到第一个不等于pooh的字符串
(c)将所有值乘以2
count_if(vec.begin(), vec.end(), bind(greater<int>(), 1024));
find_if(vec.begin(), vec.end(), bind(not_equal_to<string>(), "pooh"));
transform(vec.begin(), vec.end(), vec.begin(), bind(multiplies<int>(), 2));
练习14.43:使用标准库函数对象判断一个给定的int值是否能被int容器中的所有元素整除
bool divide(vector<int> &ivec, int dividend){
return count_if(ivec.begin(), ivec.end(), bindlst(modulus<int>, dividend)) == 0;
}
练习14.44:编写一个简单的桌面计算器使其能处理二元运算
#include
#include
#include
#include
using namespace std;
map<string, function<int(int, int)>> binops = {
{"+", plus<int>()},
{"-", minus<int>()},
{"/", divides<int>()},
{"%", modulus<int>()}
};
int main() {
int a, b;
string op;
cin >> a >> op >> b;
cout << binops[op](a, b) << endl;
return 0;
}