原创 【博客大赛】《C++ Primer》学习笔记(32)重载操作符与转换式

2016-4-25 23:28 1092 8 8 分类: MCU/ 嵌入式 文集: Qt和Cpp

果然进入Part III了之后,难度也不一样了。
copy control实际上没怎么看懂,过段时间还得回头加深理解。
Anyway,现在继续往前学习,进入重载操作与转换式:
Overloaded operations and Conversions

14.1 Basic Concepts
----------------------------------------------------------------
重载的操作符有特殊的名称:关键字operator,跟随操作符的symbol。
重载操作符的函数,和所以其他函数一样,具备返回值、参数列表和函数体。

另外,函数调用的操作符“()”,也可以被重载。
如果重载操作符的函数是类的成员函数的话,左操作数隐式的绑定在this指针上。
大多数的操作符都可以被重载,除了以下几种:
::  .*  .  ?:

我们不能重载build-in类型的操作,也不能发明新的操作符。
重载过的操作符的优先级和结合性,与重载之前相同。

比如下面的代码:
class Foo {
public:
    Foo() = default;
    Foo &operator+(const Foo&);
    int i;
private:
    string *ps;
};

Foo &Foo::operator+(const Foo &rhs)
{
    i += rhs.i;
    return *this;
}

int main(int argc, char *argv[])
{
    Foo f1, f2;
    Foo f3 = f1 + f2;
    Foo f4;
    f4.operator+(f3);

    Foo f5 += f1;

    return 0;
}

由于成员函数的左操作符绑定在this指针上,这里的重载“+”只有一个操作数。
另外注意,重载的操作符实际上是个函数,因此它不能保证原始操作符的evaluated顺序,
尤其是,“&&”和“||”没有短路功能,因此所有的操作数都会被evaluated,因此最好不要reload它们。
最好不要reload的操作符:
,  &  &&  ||

什么时候应该重载操作符呢:
 * 类中有IO操作,则重载shift操作符。
 * 类中有比较操作,则重载==,而且应该重载!=。
 * 类中有单一的自然排序,则重载<,而且应该重载所有的关系操作符。
 * 重载操作符的返回值,应该和build-in保持兼容。

什么时候应该将操作符重载为成员函数呢:
 * =,[],(),->应该被重载为成员函数。
 * 混合赋值的函数,一般应该重载为成员函数。
 * 和对象的state密切相关的,或者和类type绑定(++,--,*)的操作符应该重载为成员函数。
 * sysmmetric操作符,比如算术、相等、关系、bitwise操作符,应该重载为普通函数。
string类就将“+”重载成了非成员函数,因此下面两个表达式都合法:
string u1 = "Hi" + s;
string u2 = s + "Hi";

14.2 Input and Output Operators
----------------------------------------------------------------
包含IO操作的类,一般都会定义input和output操作。
output操作符的第一个参数通常是ostream对象的nonconst引用(因为需要更改ostream状态);
output操作符的第二个参数通常是类的const引用(因为要避免copy操作)。

input和output操作符必须重载为非成员函数;
由于它们对私有成员的访问,因此通常将它们声明为friends。

input操作符通常得处理出错的情况,output操作符则不担心这个问题。
什么时候input操作会出错:
 * stream的数据类型出错。
 * 读取到了end-of-file,或者其他istream错误。

14.3 Arithmetic and Relational Operators
----------------------------------------------------------------
一般来说,如果一个类定义了算术操作和related compound assignment操作,
那么通常应该使用后者来实现前者。

一般来说,如果定义了“==”,那么“<”最好和它的物理含义兼容,
因此其实有些类,我们并不需要为它定义“<”操作。

14.4 Assignment Operators
----------------------------------------------------------------
它可以被重载,它必须被定义为成员函数,它应该是copy-assigning、move-assigning兼容。

14.5 Subcript Operators
----------------------------------------------------------------
下标操作符必须重载为成员函数。

14.6 Increment and Decrement Operators
----------------------------------------------------------------
这两个操作符,对于迭代器来说非常常见。
一般来说,建议将它们实现为成员函数。
一般来说,prefix和postfix应该一起实现。

比如下面的代码:
Foo &operator++();
Foo &operator--();
Foo operator++(int);
Foo operator--(int);

14.7 Member Access Operators
----------------------------------------------------------------
“->”,必须被重载为成员函数;
“*”,一般需要被重载为成员函数。
( * 这一节很有难度,需要以后再深入的学习!!!)

14.8 Function-Call Operators
----------------------------------------------------------------
函数调用的操作符“()”,也可以被重载。
比如下面的代码:
struct absInt {
    int operator()(int val) const {
        return (val < 0)? -val : val;
    }
};

int main(int argc, char *argv[])
{
    absInt absObj;
    int ui = absObj(-10);
    cout << ui << endl;
    return 0;
}

当使用类重载“()”替代函数的功能时,由于类里面可以存储state,它比函数更加灵活。
重载“()”的函数必须是类的成员函数。
这样的类,我们称之为函数对象:function objects.

函数对象通常和算法结合起来使用。
比如下面的代码:
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;
};

int main(int argc, char *argv[])
{
    vector<string> vs = {"123", "456", "789", "abc", "defg"};
    for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
    return 0;
}

也就是说,作为算法的函数参数,可以是lambda表达式、bind函数,以及函数对象类。
实际上,lambda表达式就是没有命名的函数对象。

库里面定义了一些操作的模板,比如plus:
plus<int> intAdd;
int sum = intAdd(10, 20);
cout << sum << endl;

输出结果是:
[marianna@localhost Debug]$ ./hello2
30

库函数对象,也可以和算法结合起来使用。
比如下面的代码:
vector<string> vs = {"123", "456", "789", "abc", "defg"};
for_each(vs.begin(), vs.end(), PrintString(cout, ' '));
cout << endl;

sort(vs.begin(), vs.end(), greater<string>());
for_each(vs.begin(), vs.end(), PrintString(cout, ' '));
cout << endl;

输出结果是:
[marianna@localhost Debug]$ ./hello2
123 456 789 abc defg
defg abc 789 456 123

库函数对象可以直接作用于指针,这样我们可以比较地址的高低(直接比较的话,某些情况下无意义)。
另外,function模板可以用来将函数指针、函数对象、lambda表达式都转换成指定的函数类型:
function<int(int, int)> f1 = add;     //函数指针
function<int(int, int)> f2 = dev();   //函数对象
function<int(int, int)> f3 = [](int i, int j) {return i * j}  //lambda表达式

这样,可以方便的用函数名称key定义函数类型的map:
map<string, int(*)(int, int)> binops;
或者:
map<string, function<int(int, int)>> binops;

14.9 Overloading, Conversions, and Operators
----------------------------------------------------------------
转换操作符(Conversion Operators):
operator type() const;
比如:
operator int() const { return val; }

它必须是类的成员函数,不能有返回值,参数表必须为空,并且总是const的。
转换操作符总是隐式的发生,因此无法为它传递参数。
转换操作符虽然没有返回值,但是它必须返回一个type类型的值。

慎用类的类型转换!

 

文章评论0条评论)

登录后参与讨论
我要评论
0
8
关闭 站长推荐上一条 /2 下一条