首页 c++Prime NO14
文章
取消

c++Prime NO14

重载运算与类型转换

14.1 基本概念

他们的名字有关键字operator和其后要定义的运算符号共同组成 重载的运算符也包含返回类型、参数列表以及函数体 如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上,因此其显式参数数量比运算符的运算对象总数少一个

直接调用重载运算符函数

1
2
3
4
5
6
data1 + data2;
operator+ (data1,data2);//等价表示
//成员函数
data1+= data2;
data1.operator+=(data2);
//this绑定到data1的地址

使用与内置类型一致的含义

  • 如果类执行IO操作,则定义移位运算符与其内置类型的IO保持一致
  • 定义operator==则也应该定义operator!=
  • 如果一个类包含内在的单序比较,则定义operator<,以及其他关系操作
  • 逻辑与关系运算符应返回bool,算术返回一个类类型,赋值和复合运算符返回左侧运算对象的一个引用

赋值和复合赋值运算符

+= 运算符应该与其内置版本一致,即先执行+在执行=

选择作为成员或者非成员

当我们把运算符定义为成员函数时,其左侧运算对象必须时运算符所属类的一个对象

1
2
3
4
string s = "world";
string t = s + "!" ;//正确,可以吧一个const char* 加到string 对象
string u = "hi" + s;//如果+ 是string的成员函数,则错误
string+定义为普通的非成员函数

14.2 输入和输出运算符

14.2.1 重载输出运算符«

通常情况下,«第一个形参是一个 非常量ostream对象的引用,第二个形参一般是一个常量的引用

输出运算符尽量减少格式化操作

也即尽量不要打印 换行符

输入输出运算符必须是非成员函数

14.2.2 重载输入运算符 »

输入运算符必须能处理输入可能失败的情况

当读取操作发生错误时,输入运算符应该负责从错误中恢复

14.3 算术和关系运算符

14.3.1 相等运算符

14.3.2 关系运算符

14.4 赋值运算符

已经介绍过拷贝赋值和移动赋值运算符,为了与内置类型的赋值运算符保持一致,新的赋值运算符将返回其左侧运算对象的引用

复合赋值运算符

14.5 下标运算符

下标运算符必须是成员函数 如果一个类包含下标运算符,则它通常定义两个版本,一个返回普通引用,一个是类的常量成员并且返回常量引用

表示容器的类通常可以通过元素在容器中的位置访问元素

14.6 递增和递减运算符

建议将其定义为类的成员函数 应该同时定义前置版本和后置版本

1
i++;i--;

定义前置递增/递减运算符 返回递增或者递减后对象的引用

区分前置和后置运算符

后置版本接受一个额外(不被使用)int 类型的形参,当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参

1
StrBlobPtr operator++(int);//后置版本

后置版本:递增/递减对象但是返回原值 故递增递减前要记录对象的状态

显式地调用后置运算符

通过函数调用的方式调用后置版本,必须为其整型参数传递一个值

1
2
3
StrBlobPtr p(a1);
p.operator++(0);//后置版本
p.operator--();//前置版本

14.7 成员访问运算符

解引用运算符(*)和箭头运算符(->) 箭头运算符必须是类的成员,解引用运算符通常也是类的成员

14.8 函数调用运算符

例如

1
2
3
4
5
struct absInt{
    int operator() (int val) const{
        return val <0 ? -val:val;
    }
};

函数调用运算符必须是成员函数 如果类定义了调用运算符,则该类的对象称为函数对象

含有状态的函数对象类

函数对象类通常含有一些数据成员,这些成员被用于定制调用运算符中的操作 例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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');

14.8.1 lambda是函数对象

当我们编写了一个lambda后,编译器将该表达式翻译成一个未命名类的未命名对象,在lambda表达式产生的类中含有一个重载的函数调用运算符

表示lambda以相应捕获行为的类

lambda表达式产生的类不含默认贡藕早函数、赋值运算符以及默认析构函数

cpp11标准为什么要增加lambda?什么情况下会使用lambda,什么情况下使用类 lambda是通过匿名的函数对象来实现的,我们可以把lambda看作是函数对象在使用方式上进行的简化。 当代码需要一个简单函数且其并不会在其他地方被使用时,就可以用lambda来实现,如果它被多次使用且需要保存某些状态,则使用函数对象。

14.8.2 标准库定义的函数对象

标准库定义了一组表示算数运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行明明操作的调用运算符。

1
2
3
plus<int> intAdd;//可执行int加法的函数对象
negate<int> intNegate;//可对int取反的函数对象
………………

其类型都在functional头文件中

在算法中使用标准库函数对象

14.9 重载、类型转换与运算符

通过定义类型转换运算符可以做到类类型的类型转换。 转换构造函数和类型转换运算符共同定义了类类型转换,有时候也叫做用户定义的类型转换。

14.9.1 类型转换运算符

类型转换运算符时类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。 一般形式如下:

1
operator type() const;

其中 type表示某种类型

一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。类型转换函数通常为const

定义含有类型转换运算符的类

举个例子:

1
2
3
4
5
6
7
8
9
10
11
class SmallInt{
public:
    SmallInt(int i = 0 ): val(i)
    {
        if (i< 0 || i > 255)
            throw out_of_range("Bad SmallInt Value");
    }
    operator int() const {return val;}
private:
    size_t val;
}

SmallInt类既定义了向类类型的转换,也定义了从类类型向其他类型的转换。

  • 构造函数将算数类型的值转换成 SmallInt对象
  • 类型转换运算符将SmallInt对象转换成 int;
    1
    2
    3
    
    SmallInt si;
    si = 4;//首先将4隐式地转换成SmallInt,然后调用SamllInt::operator=;
    si+3;//首先将si 隐式地转换成int,然后执行整数的加法
    

    显式的类型转换运算符

    cpp11新标准引入了显式的类型转换运算符 ```cpp class SmallInt{ public: SmallInt(int i = 0 ): val(i) { if (i< 0 || i > 255) throw out_of_range(“Bad SmallInt Value”); } explicit operator int() const {return val;} private: size_t val; }

static_cast (si) +3// 正确显式地请求类型转换;

1
2
3
4
5
#### 转换为bool
cpp11新标准下,IO标准库通过定义一个向bool的显式类型转换实现同样的目的。
例如:
```cpp
while( std::cin>> value)

14.9.2 避免有二义性的类型转换

……………………

14.9.3 函数匹配与重载运算符

重载的运算符也是重载的函数 表达式中的运算符候选函数集既应该包括成员函数,也应该包括非成员函数

本文由作者按照 CC BY 4.0 进行授权
热门标签
文章内容

c++Prime NO13

c++Prime NO15

热门标签