sql2008做查询网站,做网站的公司广州,江西网站做的好的企业,企业创建网站的途径都有啥参考资料#xff1a;
《C Primer》第5版《C Primer 习题集》第5版
14.8 函数调用运算符#xff08;P506#xff09;
如果类重载了函数调用运算符#xff0c;则我们可以像使用函数一样使用该类的对象。这样的类同时也能存储状态#xff0c;所以它们比普通函数更加灵活。…参考资料
《C Primer》第5版《C Primer 习题集》第5版
14.8 函数调用运算符P506
如果类重载了函数调用运算符则我们可以像使用函数一样使用该类的对象。这样的类同时也能存储状态所以它们比普通函数更加灵活。我们先考虑这样一个简单的类
struct absInt {int operator()(int val) const {return val 0 ? -val : val;}
};int i -42;
absInt absObj;
int ui absObj(i); // ui的值为42调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符相互之间在参数数量或类型上应该有所区别。
如果类定义了调用运算符则该类的对象称作函数对象function object。
含有状态的函数对象类
函数对象类通常含有一些数据成员这些成员被用于定制调用运算符中的操作
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(vs.begin(), vs.end(), PrintString(cerr, \n));14.8.1 lambda是函数对象P507
当我们编写一个 lambda 后编译器将该表达式翻译成一个未命名类的未命名对象该类中有一个重载的函数调用运算符。例如我们之前传递给 stable_sort 的 lambda 表达式
stable_sort(words.begin(), words.end(),[](const string a, const string b){return a.size()b.size();});其行为类似于下面这个类的未命名对象
class ShorterString {
public:bool operator()(const string a, const string b) const {return a.size() b.size();}
};lambda 产生的类有一个函数调用运算符成员其返回值类型、形参列表、函数体均与 lambda 表达式完全一样。默认情况下 lambda 不能改变它捕获的变量因此 lambda 产生的类中的函数调用运算符是一个 const 成员函数除非 lambda 被声明成可变的。
我们用上面的类替代 lambda 表达式
stable_sort(words.begin(), words.end(), ShorterString()); // 创建一个ShorterString对象表示lambda及相应捕获行为的类
前面提到过如果 lambda 引用捕获变量时程序负责保证 lambda 执行时所引用的对象确实存在所以编译器可以直接使用该引用而无需在 lambda 类中将其存储为数据成员。
相反通过值捕获的变量将被拷贝到 lambda 中。因此此种 lambda 产生的类必须为每个值捕获的变量建立数据成员同时创建构造函数。例如我们有这样一个 lambda
auto wc find_if(words.begin(), words.end(),[sz](const string s){return a.size() sz; });它产生的类形如
class SizeComp{
public:SizeComp(size_t n): sz(n) { } // 值捕获对应变量bool operator()(const string s) const // 返回类型、形参、函数体均与lambda一致{return s.size() sz; }
private:size_t sz;
}14.8.2 标准库定义的函数对象P509
标准库定义了一组表示算术运算符、关系运算符、逻辑运算符的类每个类分别定义了一个执行命名操作的调用运算符。这些类定义在头文件 functional plusint intAdd;
negateint intNegate;
int sum intAdd(10, 20);
sum intNegate(intAdd(10, 20));在算法中使用标准库函数对象
如果想要执行降序排列我们可以向 sort 传入一个 greater 类型的对象
sort(svec.begin(), svec.end(), greaterstring());我们之前提到过比较两个无关的指针是未定义的行为。但有时候我们希望根据内存地址对指针 vector 进行排序我们可以使用 less 类型的对象
vectorstring* nameTable;
sort(nameTable.begin(), nameTable.end(),[](string *a, string *b) {return a b;}); // 错误是未定义行为
sort(nameTable.begin(), nameTable.end(), lessstring*()); // 正确14.8.3 可调用对象与functionP511
C 中有集中可调用的对象函数、函数指针、lambda 表达式、bind 创建的对象、重载了调用运算符的类。可调用对象也有类型每个 lambda 有自己独有的类型函数和函数指针的类型由返回值类型和参数类型决定。
然而两个不同类型的调用对象却可能共享同一种调用形式call signature。调用形式指明了调用的返回类型及调用所需的参数类型一种调用形式对应一种函数类型
int(int, int);不同类型可能具有相同的调用形式
对于不同类型可调用对象共享同一种调用形式的情况我们有时希望把它们看作相同类型
// 普通函数
int add(int i, int j) { return i j; }
// lambda表达式
auto mod [](int i, int j) {return i % j; };
// 函数对象类
struct divide {int operator()(int denominator, int divisor) {return denominator / divisor;}
};虽然上述可调用对象的类型各不相同但是共享同一种调用形式 int(int, int) 。我们可能想使用上述可调用对象构建一个简单的计算器。为了达成这一目的我们需要定义一个函数表用于存储这些可调用对象的指针。函数表可以通过 map 实现将表示运算符号的 string 作为关键字。我们会遇到这样的问题
mapstring, int(*)(int, int) binops;
binops.insert({, add}); // 正确
binops.insert({%, mod}); // 错误mod不是一个函数指针//然而vs2022和devc都不报错且能正常使用
binops.insert({., divide}); // 错误原因同上标准库function类型
头文件 functional 头文件中定义了名为 function 新标准库类型可以解决上面提到的问题 function 是一个模板在创建一个具体的 function 类型时需要对象的调用形式
functionint(int, int) f1 add;
functionint(int, int) f2 divide();
functionint(int, int) f3 mod;使用 function 我们可以重新定义 map
mapstring, functionint(int, int) binops {{, add},{-, std::minusint()},{/, divide()},{*, [](int i, int j) {return i * j; }},{%, mod}
};重载的函数与function
直接将重载函数的名字存入 funtion 类型的对象中将产生二义性错误
int add(int i, int j) { return i j; }
double add(double i, double j) { return i j; }
mapstring, functionint(int, int) binops({ , add }); // 错误不知道是哪个add解决上述二义性问题的一条途径是存储函数指针
int (*fp)(int, int) add;
binops.insert({ , add });14.9 重载、类型转换与运算符P514
转换构造函数和类型转换运算符共同定义了类类型转换class-type conversions。
14.9.1 类型转换运算符P514
类型转换运算符conversion operator是类的一种特殊成员函数负责将一个类类型的值转换为其他类型
operator type() const;其中type 可以是除 void 外的任意一种类型只要该类型能作为函数的返回类型。类型转换运算符没有显式的返回类型也没有形参而且必须定义成类的成员函数。类型转换运算符不应改变转换对象的内容因此类型转换运算符一般被定义成 const 成员
定义含有类型转换运算符的类
我们定义一个简单的类用来表示 0~255 之间的一个整数
class SmallInt {
public:SmallInt(int i 0) :val(i) {if (i0 || i255) {throw out_of_range(Bad SmallInt value);}}operator int() const { return val; }
private:unsigned val;
};SmallInt 类既定义了向类类型的转换也定义了从类类型向替他类型的转换
SmallInt si;
si 4;
si 3; // si被隐式转换成int然后执行整型加法编译器一次只能执行一个用户定义的类型转换但隐式的用户定义类型转换可以置于标准类型转换的之前或之后并与其一起使用。因此我们可以将任何算术类型传递给 SmallInt 的构造函数也能将 SmallInt 对象转换成 int 再将 int 转换成任何算术类型对象
SmallInt si 3.14;
si 3.14;由于类型转换运算符是隐式执行的所以无法给其传递参数当然也就不能再类型转换运算符的定义中使用任何形参。 避免过度使用类型转换函数如果类类型和转换的目标类型之间不存在明显的映射关系则这样的类型转换可能存在误导性。 类型转换运算符可能产生意外结果
在实践中类很少提供类型转换运算符一个例外情况是人们常常会定义向 bool 类型的转换。
然而如果一个类想定义一个向 bool 的类型转换常常会遇到一个问题由于 bool 是一种算术类型所以类类型转换成 bool 可能被转换成其他算术类型从而引发意想不到的结果。
显式的类型转换说明符
为了防止上述异常状况的发生C11 新标准引入了显式的类型转换运算符explicit conversion operator
class SmallInt {
public:explicit operator int() const { return val; }
};SmallInt si 3;
si 3; // 错误此处需要隐式的类型转换但类型转换运算符是显式的
static_castint(si) 3; // 正确显式请求类型转换编译器不会将一个显式的类型转换运算符用于隐式类型转换唯一的例外是如果表达式被用作条件显式的类型转换必须有转换成 bool 的运算符将被隐式执行。
转换为bool
向 bool 的类型通常用在条件部分operator bool 一般被定义成 explicit 。
14.9.2 避免有二义性的类型转换P517
如果类中包含类型转换必须确保类类型和目标类型之间只存在唯一一种转换方式。两种情况可能产生多重转换路径
两个类提供了相同的类型转换类 A 定义了接受一个 类 B 对象的转换构造函数类 B 又定义了一个转换目标是类 A 的类型转换运算符。类定义了多个转换规则而这些转换涉及的类型又可以通过其他类型转换联系在一起。最典型的例子是算术类型一个类最好只定义一个与算术类型有关的转换。
实参匹配和相同的类型转换
struct B;
struct A {A() default;A(const B );
};
struct B {operator A() const;
};
A f(const A );
B b;
A a f(b); // 错误是f(B::operator A())还是f(A::A(const B))如果我们一定要执行上述函数调用我们可以显式调用转换构造函数或类型转换运算符
A a f(b.operator A());
A a f(A(b));二义性与转换目标为内置类型的多重类型转换
struct A {A(int 0) { cout int - A; }A(double) {cout double-A; }operator int() const { cout A - int; return val; };operator double() const { cout A - double; return val; }int val;
};
void f(long double);A a;
f(a); // 错误是f(a.operator int())还是f(a.operator double())
long lg;
A a2(lg); // 错误是A(int)还是A(double)
short s 0;
A a3(s); // 正确short被提升成int然后执行A(int)除了显式地向 bool 类型的转换之外我们应尽量避免定义转换目标是内置类型的转换。 重载函数与转换构造函数
struct C {C(int);
};
struct D {D(int);
};
void manip(const C );
void manip(const D );manip(10); // 错误是manip(C(10))还是manip(D(10))重载函数与用户定义的类型转换
当调用重载函数时如果多个用户定义的类型转换都提供了可行匹配则编译器认为这些类型转换一样好且编译器不会考虑任何可能出现的标准类型转换的级别
struct C {C(int);
};
struct E {E(double);
};
void manip(const C );
void manip(const E );manip(10); // 错误是manip(C(10))还是manip(E(double(10)))只有当重载函数能通过同一个类型转换得到匹配时编译器才会考虑其中出现的标准类型转换
struct F{operator int();
};
void manip(int);
void manip(double);F f;
manip(f); // 正确调用manip(int)14.9.3 函数匹配与重载运算符P521
重载的运算符也是重载的函数当运算符函数出现在表达式中时候选函数集的范围可能比调用普通重载函数更大。如果运算符的左侧运算对象是类类型则候选函数将同时包含内置运算符以及该运算符的非成员版本和成员版本。而对于一般的函数调用即使成员函数和非成员函数重名但它们的调用方式是不一样的。
class SmallInt {
public:SmallInt(int i 0) :val(i) { }SmallInt operator(const SmallInt a) {cout member plus endl;return SmallInt(a.val val);}friend SmallInt operator(const SmallInt , const SmallInt );operator int() { return val; }
private:unsigned val;
};1 s1; // common plus
s1 1; // member plus居然没有二义性错误吗
s1 s2; // member plus如果在上面的 SmallInt 中加入 operator int() { return val; } 成员那么 1 s1 和 s1 1 将出现二义性错误因为无法确定调用重载 还是标准 。