【C++ Primer Chapter 14 总结】操作符重载和转换
1. 重载的操作符函数的形参数量要与操作符的操作数相同。
2. 如果操作符函数是成员函数,则第一个(左)操作数绑定到隐式的this指针。
3. 操作符函数必须是类的成员或具有至少一个类类型形参。(因为内置类型的操作符已定义且不能更改)
4. 重载运算符的返回类型通常应该与内置版本兼容:逻辑和关系运算符应该返回布尔值,算术运算符应该返回一个值的类类型,以及赋值和复合赋值操作应该返回左操作数的引用。
5.=,(),->操作符必须是成员函数。
6. 改变其对象状态的操作符,或者与给定类型(如自增、自减和解引用)密切相关的操作符,通常应该是成员函数。
7. 对称操作符,即那些可以转换任意操作数的操作符,例如算术操作符、相等操作符、关系操作符和位操作符,通常应该定义为普通非成员函数。
8.输入输出操作符的重载。不能是成员函数。
ostream &operator<<(ostream &os, const Sales_data &item) { os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } istream &operator>>(istream &is, Sales_data &item) { double price; is >> item.bookNo >> item.units_sold >> price; if (is) item.revenue = item.units_sold * price; else item = Sales_data(); return is; }
9.算数和关系操作符。
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; sum += rhs; return sum; } bool operator==(const Sales_data &lhs, const Sales_data &rhs) { return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue; } bool operator!=(const Sales_data &lhs, const Sales_data &rhs) { return !(lhs == rhs); }
10. 如果一个类有一个下标操作符,它通常应该定义两个版本:一个返回普通引用,另一个是const成员并返回对const的引用。
当下标操作符应用于const对象时,它应该返回对const的引用,这样就不能对返回的对象赋值。
class StrVec { public: std::string& operator[](std::size_t n) { return elements[n]; } const std::string& operator[](std::size_t n) const { return elements[n]; } private: std::string *elements; };
11.自增/自减操作符。
前缀操作符应该是返回对递增或递减对象的引用。
普通的函数重载无法区分前置和后置++/--,因此后置++版本会额外需要一个int参数以示区分。
在间接调用操作符函数(使用操作符)时没有区别,但直接使用函数调用时,调用后置++必须带有参数,否则调用的是前置++。
class StrBlobPtr { public: StrBlobPtr& operator++(); // 前缀自增 StrBlobPtr& operator--(); // 前缀自减 StrBlobPtr operator++(int); // 后缀自增 StrBlobPtr operator--(int); // 后缀自减 }; StrBlobPtr& StrBlobPtr::operator++() { check(curr, "increment past end of StrBlobPtr"); ++curr; return *this; } StrBlobPtr& StrBlobPtr::operator--() { --curr; check(-1, "decrement past begin of StrBlobPtr"); return *this; } StrBlobPtr StrBlobPtr::operator++(int) { StrBlobPtr ret = *this; ++*this; return ret; } StrBlobPtr StrBlobPtr::operator--(int) { StrBlobPtr ret = *this; --*this; return ret; } StrBlobPtr p(a1); // p points to the vector inside a1 p.operator++(0); // 直接使用函数调用,后缀++ p.operator++(); // 前缀++
12.函数调用操作符。
重载调用操作符的类允许像使用函数一样使用其类型的对象。即: 通过“对象名+(参数列表)”的方式使用一个类,其实质是对operator()操作符的重载。
定义了调用操作符的类的对象被称为函数对象。
struct absInt {
int operator()(int val) const {
return val < 0 ? -val : val;
}
};
int i = -42;
absInt absObj;
int ui = absObj(i); // 调用对象会运行其重载的调用操作符
13.函数对象类通常包含用于自定义调用操作符中的操作的数据成员。
函数对象可以用作通用算法的参数。
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); PrintString errors(cerr, '\n'); errors(s); for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
14. lambda是函数对象。
当编写lambda时,编译器将该表达式转换为未命名类的未命名对象。从lambda生成的类包含重载函数调用操作符。
从lambda表达式生成的类有一个 = deleted 的默认构造函数、= deleted 的赋值操作符和一个默认析构函数。
stable_sort(words.begin(), words.end(), [](const string &a, const string &b) { return a.size() < b.size(); }); // similar class ShorterString { public: bool operator()(const string &s1, const string &s2) const { return s1.size() < s2.size(); } };
15.lambda函数的捕获列表按值获得变量时,对应的类需要将该变量设置为数据成员。
按引用获得变量时,则不需要,因为编译器可以直接使用该引用的变量。
auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return s.size() >= sz; }); class SizeComp { SizeComp(size_t n): sz(n) { } bool operator()(const string &s) const { return s.size() >= sz; } private: size_t sz; };
16. 标准库定义的一组表示算术、关系和逻辑操作符的类。
表示运算符的函数对象类通常可用于覆盖算法使用的默认运算符。
greater<T> less<T> sort(svec.begin(), svec.end(), greater<string>()); // 调用greater<T>类的无命名对象的函数调用操作符
17. c++的可调用对象:函数和指向函数的指针,lambda,由bind创建的对象,以及重载函数调用操作符的类。
18. 可调用对象有一个类型。例如,每个lambda都有自己独特的(未命名的)类类型。
两个具有不同类型的可调用对象可能共享相同的调用签名( call signature)。调用签名指定调用对象返回的类型和传递的参数类型。
int(int, int) // 调用签名对应于函数类型 int add(int i, int j) { return i + j; } // 普通函数 auto mod = [](int i, int j) { return i % j; }; // lambda struct div { // 类 int operator()(int denominator, int divisor) { return denominator / divisor; } };
19.使用标准库类型function表示一类可调用对象。
function<T>是一个模版类,int (int, int)是调用签名,可以表示一类可调用对象:有两个int参数并返回int。
function<int(int, int)> // function<int (int, int)>是一个类,存储的类型是调用签名为int (int, int)的可调用对象 function<int(int, int)> f1 = add; // 函数指针 function<int(int, int)> f2 = div(); // 函数对象类的对象 function<int(int, int)> f3 = [](int i, int j) { return i * j; }; // lambda cout << f1(4,2) << endl; // 6 cout << f2(4,2) << endl; // 2 cout << f3(4,2) << endl; // 8 map<string, function<int(int, int)>> binops = { {"+", add}, {"-", std::minus<int>()}, {"/", div()}, {"*", [](int i, int j) { return i * j; }}, // 未命名的lambda {"%", mod} }; // 命名的lambda binops["+"](10, 5); // calls add(10, 5) binops["-"](10, 5); // uses the call operator of the minus<int> object binops["/"](10, 5); // uses the call operator of the div object binops["*"](10, 5); // calls the lambda function object binops["%"](10, 5); // calls the lambda function object
20.如果有重载函数,则不能直接存储重载函数的名字。
int add(int i, int j) { return i + j; } Sales_data add(const Sales_data&, const Sales_data&); // overloaded map<string, function<int(int, int)>> binops; binops.insert( {"+", add} ); // error: 哪个add? // 解决: int (*fp)(int,int) = add; // 使用函数指针 binops.insert( {"+", fp} ); binops.insert( {"+", [](int a, int b) {return add(a, b);} }); // 或者lambda
21. 转换操作符是一种特殊的成员函数,它将类类型的值转换为其他类型的值。
operator type() const; // 类型转换操作符没有参数列表,没有返回类型,但是必须返回函数名类型一样的值 class SmallInt { public: SmallInt(int i = 0): val(i) { if (i < 0 || i > 255) throw std::out_of_range("Bad SmallInt value"); } operator int() const { return val; } private: std::size_t val; }; SmallInt si; si = 4; // 将 4 隐式转换为 SmallInt 类型后调用 SmallInt::operator= si + 3; // 将 si 隐式装换为 int 类型后做整数加法
22.显示的类型转换操作符只能通过static_cast<type>调用。只有在条件判断语句中才会由class类型隐式转换到其他类型。
class SmallInt { public: explicit operator int() const { return val; } // other members as before }; SmallInt si = 3; si + 3; static_cast<int>(si) + 3;
23. 在条件语句中使用流对象,都使用为IO类型定义的bool转换函数。
while (std::cin >> value) // cin由istream操作符bool转换函数隐式转换
24. 有两种方式可以实现多种转换路径。
-
两个类提供相互转换。
struct B; struct A { A() = default; A(const B&); // converts a B to an A }; struct B { operator A() const; // also converts a B to an A }; A f(const A&); B b; A a = f(b); // error: 歧义,不知道调用哪个转换函数 A a1 = f(b.operator A()); // ok: 显式调用 A a2 = f(A(b));
-
当一个类定义了多个到其他类型的转换操作时,如果这些类型之间,那么也会产生歧义。e.g.: 给定的类定义了多个与算术类型的转换。
struct A { A(int = 0); A(double); operator int() const; operator double() const; }; void f2(long double); A a; f2(a); // error: 歧义:f(A::operator int()) or f(A::operator double()) long lg; A a2(lg); // error 歧义:A::A(int) or A::A(double)
25. 当调用是通过类类型的对象(或通过此类对象的引用或指针)进行时,则只考虑该类的成员函数。
当在表达式中使用重载操作符时,无法表明使用的是成员函数还是非成员函数。
class SmallInt { friend SmallInt operator+(const SmallInt&, const SmallInt&); public: SmallInt(int = 0); // conversion from int operator int() const { return val; } // conversion to int private: std::size_t val; }; SmallInt s1, s2; SmallInt s3 = s1 + s2; // uses overloaded operator+ int i = s3 + 0; // error:歧义,无法知道是s3(SmallInt)转换成int类型,还是0(int)转换成SmallInt类型