【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类型
 
posted @ 2021-05-29 13:56  萌新的学习之路  阅读(108)  评论(0编辑  收藏  举报