第十四章:运算符重载Ⅱ
1、函数调用运算符:
-
类重载了函数调用运算符,可以像使用函数一样使用该类的对象。
-
struct absInt{ int operator()(int val) const{ return val < 0 ? -val : val; } };
-
函数调用运算符必须是成员函数,一个类可以定义不同版本的调用运算符,相互之间需要在参数数量或类型上有所区别。
-
类定义了调用运算符,则类的对象称为函数对象,其行为像函数一样。
-
函数对象常常作为泛型算法的实参。
2、lambda表达式:
-
可调用对象:对一个对象或表达式,若可以对其使用调用运算符,则称其为可调用的。
-
lambda表达式:表示一个可调用的代码单元,可将其理解为一个未命名的内联函数。
-
具有一个返回类型、一个参数列表、一个函数体形式为:
-
[capture list](parameter list) -> return type {function body}
-
capture list :是lambda所在函数中定义的 局部变量 的列表(通常是空),lambda表达式必须使用尾置返回指定返回类型。
-
可以忽略参数列表和返回类型但是必须包含捕获列表和函数体。
-
若lambda函数体包含单一return语句之外的内容,且未指定返回类型,则返回void。
-
-
lambda不能有默认参数,其实参数目永远与形参数目相等。
-
lambda通过将局部变量包含在其捕获列表中指出将会使用哪些变量。也只有在其捕获列表中捕获其所在函数中的局部变量,才能在函数体中使用该变量。
-
捕获列表只作用域局部非static变量,lambda可以直接使用局部static变量和所在函数之外声明的名字。
-
lambda捕获的变量的值是在lambda 创建时拷贝, 而不是调用时拷贝。在值捕获时,捕获的是拷贝值,在引用捕获时,捕获的是引用所绑定的对象。
-
void fcn1(){ size_t v1 = 42; auto f = [v1] {return v1;} v1 = 0; auto j = f(); // j是42,f保存的是创建v1时的拷贝 } void fcn2(){ size_t v1 = 42; // 引用捕获 auto f = [&v1] {return v1;} v1 = 0; auto j = f(); // j是0,f保存的是v1的引用而非拷贝 } void fcn3(){ size_t v1 = 42; // 在参数列表首加上关键字mutable可以改变被捕获的变量的值。 auto f = [v1] mutable {return ++v1;} v1 = 0; auto j = f(); // j是43,f保存的是创建v1时的拷贝 } void fcn4(){ size_t v1 = 42; // 一个引用捕获的变量是否可以修改,依赖于此引用指向的是一个const类型还是一个非const类型。 auto f = [&v1] {return ++v1;} v1 = 0; auto j = f(); // j是1,f保存的是创建v1时的拷贝 } void fcn5() { size_t v1 = 42; // p is a const pointer to v1 size_t* const p = &v1; // increments v1, the objet to which p points auto f = [p]() { return ++*p; }; auto j = f(); // returns incremented value of *p cout << v1 << " " << j << endl; // prints 43 43 v1 = 0; j = f(); // returns incremented value of *p cout << v1 << " " << j << endl; // prints 1 1 }
-
在使用引用捕获时,就必须确保被引用对象在lambda执行的适合是存在的。有些对象比如ostream对象不能被拷贝,所以捕获os的唯一方法是捕获其引用。如果函数返回一个lambda则不能包含引用捕获。**在以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。**应尽量避免捕获指针或引用。
-
可以通过在捕获列表中写一个 & 或 ===== 告诉编译器采用什么样的捕获方式,一般 & 采用捕获引用的方式,=====采用值捕获。还可以混合使用隐式捕获和显示捕获但是必须保证显示捕获的变量必须使用与隐式捕获不同的方式。
-
for_each(words.begin(), words.end(),[=,&os](const string& s) {os << s << c;});
-
默认情况下,若lambda函数体包含单一return语句之外的内容,且未指定返回类型,则返回void。当指定一个lambda返回类型时,必须使用尾置返回类型
-
//单一的return语句,返回一个表达式的结果,无需指定返回类型,可以通过条件类型推断出来。 transform(vi.begin(),vi.end(),vi.begin(),[](int i) {return i < 0? : -i : i;}); // 错误,会产生编译错误,不能推断lambda的返回类型。 transform(vi.begin(),vi.end(),vi.begin(),[](int i) {if(i < 0) return -i; else return i;}); // 必须使用尾置返回类型。 transform(vi.begin(),vi.end(),vi.begin(),[](int i)->int {if(i < 0) return -i; else return i;});
-
-
对一个lambda表达式,编译器将其翻译为一个未命名的未命名对象,lambda表达式产生的类中含有一个重载的函数调用运算符。默认情况下,lambda产生的类中的函数调用运算符是一个const成员函数,只有当lambda被声明为可变的(mutable),那么调用运算符就不是const的了。
-
// 调用lambda表达式对每一个元素进行排序。 stable_sort(words.begin(),words.end(),[](const string& a, const string& b){return a.size() < b.size();}); // 等价于未命名类创建的重载了函数调用运算符的未命名对象。 class ShortString{ public: // lambda产生的类中的函数调用运算符是一个const成员函数 bool operator()(const string& s1, const string& s2) const {return s1.size() < s2.size();} }; // 等价的stable_sort,实参ShortString新创建ShorterString对象,调用函数调用运算符。 stable_sort(words.begin(), words.end(),ShortString());
-
-
表示lambda及相应捕获行为的类:
-
当使用lambda表达式通过引用捕获变量时,编译器可以直接使用引用的变量而无需在lambda产生的类中将其存储为数据成员。
-
当使用lambda表达式通过值捕获变量时,变量被拷贝到lambda中,产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值初始化数据成员。
-
// 获得第一个指向满足 size() >= 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){} bool operator()(const string& s) const {return s.size() >= sz;} // 数据成员对应通过值捕获的变量。 private: size_t sz; }; // 合成的类不具有默认构造函数,所以使用时需要显示的提供一个实参。 auto wc = find_if(words.begin(), words.end(),SizeComp(sz));
-
-
3、参数绑定:
-
标准库bind函数:定义在头文件functional中,接受一个可调用对象,生成一个新的可调用对象来“适配”原对象的参数列表。
-
auto newCallable = bind(callbale, arg_list);
-
newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当调用newCallable时,newCallable调用callable,并传递给arg_list的参数。参数列表arg_list中的参数包含的 _n 表示占位符,表示newCallable的参数,数值 n 表示生成的可调用对象中参数的位置。
-
-
// 二元谓词,为真的满足s.size() >= sz; bool check_size(const std ::string& s, std::string::size_type sz){ return s.size() >= sz; } //使用lambda表达式找出单词长度大于sz的迭代器位置。 void biggies(vector<string> & words, vector<string>::size_type sz){ auto wc = find_if(words.begin(), words.end(),[sz](const string& str){return str.size() >= sz;}); cout << *wc << endl; } void biggies(vector<string> & words, vector<string>::size_type sz){ // 函数 find_if()是一元谓词,第三个参数是一个一元谓词。 //error: too few arguments to function auto wc = find_if(words.begin(), words.end(),check_size); cout << *wc << endl; } // 使用bind函数将check_size重新适配,使得check_size接受第一个参数s作为check6的一元谓词参数,bind函数的第二个参数绑定到sz的值。 void biggies(vector<string> & words, vector<string>::size_type sz){ auto check6 = bind(check_size,_1,sz); auto wc = find_if(words.begin(), words.end(),check6); cout << *wc << endl; }
-
使用placeholders名字:
- _n 定义在对每个占位符名字,提供一个对应的using声明,
using std::placeholders ::_1;
- 直接使用
using namespace std::placeholders;
- _n 定义在对每个占位符名字,提供一个对应的using声明,
4、标准库function类型:
- 标准库定义了一组表示算术运算符、关系运算符、和逻辑运算符的类,每个类定义了一个指向命名操作的调用运算。
5、可调用对象与function
-
可调用对象:函数、函数指针、lambda表达式、bind创建的对象及重载了函数调用运算符的类。两个不同类型的可调用对象可能共享同一种 调用形式。 调用形式指明了调用返回的类型及传递给调用的实参类型,一种调用形式对应一个函数类型。
-
int(int, int); // 函数类型,接受两个int,返回一个int
-
由于lambda表达式有自己的未命名类类型、函数指针和函数的类型也由其返回值类型和实参类型决定。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGp02ca8-1680673833735)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\image-20230405105652762.png)]
-
可以使用标准库function创建一个具体的调用类型,即能够表示的对象的调用形式。
function<int<int,int>>
声明了一个function类型,表示接受两个int,返回一个int的可调用对象。可以使用这种方法定义一个计算器。 -
/* 使用function标准库定义调用形式: function<int<int,int>>实现一个int类型的计算器。 */ #include <iostream> #include <functional> #include <string> #include <map> using std::cout; using std::endl; using std::map; using std::string; using std::function; // 函数 int add(int a, int b){ return a + b; } // 命名lambda对象 auto mod = [](int a, int b)->int{return a % b;}; // 用户自定义的函数对象。 struct divide{ int operator()(int a, int b){ return a / b; } }; int main(int argc, char* argv[]){ map<string,function<int(int,int)>> biinops = { {"+", add}, {"-", std::minus<int>()}, {"*",[](int a, int b) {return a * b;}}, {"/",divide()}, {"%",mod} }; cout << biinops["+"](10,5) << endl; cout << biinops["-"](10,5) << endl; cout << biinops["*"](10,5) << endl; cout << biinops["/"](10,5) << endl; cout << biinops["%"](10,5) << endl; return 0; }
-
函数重载:同名函数,其形式参数(参数个数、类型、或顺序不同)。不能直接将重载函数的名字存入functon类型的对象中。这样会产生二义性。
-
int add(int i, int j){return i + j;} Sales_data add(const Sales_data&, const Sales_data&); map<string,function<int<int,int>>> binops; binops.insert({"+",add}); // 产生二义性,不知道是哪个add. // 正确的做法是存储函数指针而不是函数的名字 int(*fp)(int,int) = add; binops.insert({"+",fp});
-
6、隐式的类类型转换:
-
如果构造函数只接受一个实参,则它定义了转换为此类类型的隐式转换机制,将这种构造函数称为转换构造函数。
-
Sales_data() = default; // 定义了一种从string对象向Sales_data类的隐式类型转换 Sales_data(const std::string &s): bookNo(s) { } // 定义了一种从is对象向Sales_data类的隐式类型转换 Sales_data(std::istream &); // add the value of the given Sales_data into this object Sales_data& Sales_data::combine(const Sales_data &rhs) { units_sold += rhs.units_sold; // add the members of rhs into revenue += rhs.revenue; // the members of ``this'' object return *this; // return the object on which the function was called } // 即需要Sales_data的地方,可以使用string或istream替换 string null_book = "9-999-9999-9"; // string被隐式地转换为一个Sales_data对象,新生成的临时Sales_data对象被传递给combine item.combine(null_book);
-
只允许一步类型转换:
-
编译器只会自动执行一步类型转换
item.combine("9-999-9999-9")
是错误的。需要先将"9-999-9999-9"先转换成string,再将临时的string转换为Sales_data,编译器只能执行一步类型转换,所以是错误的。 -
上式如果想正确,需要显示的将字符串转换为string或Sales_data对象。
-
item.combine(string("9-999-9999-9")); item.combine(Sales_data("9-999-9999-9"));
-
-
类型转换运算符:
-
一个类型转换运算符必须是类的成员函数,不能声明返回类型,形参列表也必须为空,类型转换函数通常是const。
-
#include <iostream> using std::cout; using std::endl; class Fraction{ public: Fraction(int num, int den = 1): m_numberator(num), m_denomonator(den){} Fraction operator+(const double& d, const Fraction& f){ return Fraction(static_cast<int>(d*f.denomonator()) + f.numerator(), f.denomonator()); } operator double()const { cout << "operator double" << endl; return (double)(m_numberator)/ m_denomonator; } private: int m_numberator; int m_denomonator; }; int main(int argc, char** argv){ Fraction f(3,5); double d = 4 + f; cout << d << endl; return 0; }
-