课堂笔记| 第五章:运算符重载

本节课要点:

  • 运算符重载 

 

考虑我们有一个meter类:

  • min —— 最小刻度
  • max —— 最大刻度
  • value —— 当前测量值
  • span —— 测量范围
#pragma once

#include <iostream>
#include "int_type.h"

class meter {
public:
    using int_type = ::int_type;
    using reference = int_type&;
    using different = int_type;

private:
    int_type min, max;
    int_type value;
    different span;

public:
    explicit meter(int_type a = 0, int_type b = 100) : min(a), max(b), value(a), span(b - a + 1) {}
    
    ~meter() {}
};

如果我们做出以下操作: 

meter m;
m = m + 10;

我们人类可以理解是 value + 10,但是编译器不知道该如何进行运算,因为 m 是一个类对象,而 10 是一个整数。因此,我们必须显式地告诉编译器 +运算符 的含义。

运算符重载

在C++中,很多的运算符被视为函数,称为运算符函数,其函数名字由关键字 operator 加上一个运算符构成。设运算符为@,则运算符函数的原型可以表示为:

返回值类型 operator @(参数列表);

1. 重载运算符函数的考虑因素

  • 运算符的原始语义
  • 重载形式的选择
  • 运算符是否可以级联 

 

2. 重载+运算符 

(1)首先分析加法的原始语义

左右操作数保持不变,产生一个新的值结果,这个值结果是一个右值。

(2)其次进行重载形式的选择

  • 若函数的结果是个新值,则函数最好以友元的形式重载。
  • 若函数的结果保存到操作数中,则函数最好以成员的形式重载。

经过加法运算,我们将得到一个新值,因此以友元的形式进行重载。

(3)最后加法是否可以级联。

显然可以。 

// meter.h

friend meter operator+(const meter & a, int_type b);

// meter.cpp

meter operator+(const meter & a, int_type b) {
    meter m{a}; // m是一个临时对象,{}代表统一初始化,调用m的复制构造函数
    m.value += b;
    return m; // m是一个右值,等于返回了一个右值引用
}

 

3. 重载=运算符 

 如果我们做出以下操作: 

meter m;
m = 5;

这将发生一次隐式类型提升。m是类类型,5是整型,5会被隐式地提升成类类型。在该过程中,会默认调用一个匿名对象的构造函数,完成对5的类型提升,最后再把匿名对象赋值给m。

为了避免得到我们不想要的结果,于是把构造函数声明成显示的,这样才不会被用于隐式类型提升:

explicit meter(int_type a = 0, int_type b = 100) : min(a), max(b), value(a), span(b - a + 1) {}

同时,我们需要重载赋值运算符:

meter & operator=(int_type v) { // 参数v是右操作数,this对象是左操作数
    this->value = v;
    return *this;
}

如果函数返回的是一个值类型,那么返回的是一个右值。

 

4. 重载++运算符 

(1)前缀自加

++ ++a; // 未报错。判断出可以级联
++a = 5; // 未报错。判断出返回的是一个左值

前缀自加运算符概要如下:

  • 作为成员重载
  • 函数没有参数
  • 函数要返回操作数的左值引用
meter & operator++() {
    ++value;
    return *this;
}

 

(2)后缀自加

a++ ++; // 报错。判断出返回的不是一个左值

后缀自加运算符概要如下:

  • 作为成员重载
  • 函数没有参数
  • 函数要返回一个临时右值
meter operator++(int) {
    auto m{*this};
    ++value;
    return m; // m是一个将亡对象,因此返回值类型为右值
}

前缀自加和后缀自加都没有参数,但为了区分两者,C++语法规定,重载的后缀自加函数必须有一个整型参数。因此,这个 int 是一个只有类型没有名字的占位参数

 

5. 重载<<运算符

运算符<<和>>的原始语义是数左移和右移,是两种位操作。在C++中,这两个运算符如果与流(stream)对象结合,其含义就改变了:<<是输出流运算符,>>是输入流运算符。可以看到,这两个运算符实际上已经被C++标准库重载了。 

流运算符概要如下:

  • 重载为类的友元。
  • 函数有两个参数,第一个参数是流对象的引用,第二个参数是有操作数。
  • 函数返回参数流对象的引用,以便于级联。
// meter.h

friend std::ostream & operator<<(std::ostream & os, const meter & m);

// meter.cpp

std::ostream & operator<<(std::ostream & os, const meter & m) {
    return os << '(' << m.min << ',' << m.max << ',' << m.value << ')';
}

 

6. 重载类型转换运算符

  • 装箱(boxing):把一个标量类型转换为类类型。
  • 拆箱(unboxing):把一个类类型转换为标量类型。

现有一个process类,我们需要能够完成meter和process之间的互相转换。

process.h

#pragma once

#include <iostream>

class process {
public:
    using percent_t = unsigned short;

private:
    percent_t percent;

public:
    process(percent_t per = 0) : percent(per) {}

    percent_t& value() { 
        return percent;
    }

    friend std::ostream& operator<<(std::ostream& os, const process& p);
};

重载的运算符函数名实际上就是一种类型的名字。 

meter -> process

// meter.h

operator process();

// meter.cpp

meter::operator process() { // 加上作用域,防止成为全局函数
    return process{(value - min) * 100 / span}; // 创建一个临时对象并返回
}

process -> meter

// process.h

operator meter();

// process.cpp

process::operator meter() {
    meter t{0, 100};
    return t = static_cast<meter::int_type>(percent * 100); // 强制类型转换,静态转换;如果不能转换,编译器不会放过
}

static_cast<meter::int_type>(percent * 100);  // 强制类型转换,静态转换。

如果不能转换,编译器不会放过;如果不写,编译器将对这个类型转换过程袖手旁观

 

注意: 为了防止互相包含,形成死套,我们需要超前声明。

  • 在meter.h超前声明process是一个类
  • 在meter.cpp中进行实现

对于process同理。

// meter.h

class process; // forwarding

 

7. 重载()运算符

reference operator()() {
    return value;
}

使用:

meter m;
int value = m();

()作用在类对象上时,形式非常类似于函数调用,但“函数名”不是真正代表了函数,而是一个对象的名字。这种对象因此得名函数对象。 

 

可调用对象:

  • 函数。
  • lambda表达式。
  • 函数对象。

 


meter.h 

#pragma once

#include <iostream>
#include "int_type.h"

//forwarding
class process;

class meter {
public:
    using int_type = ::int_type; //using int_type = int;
    using reference = int_type&;
    using different = int_type;

private:
    int_type min, max;
    int_type value; //因为value是不确定的,所以不需要给参数
    different span; //最小值和最大值的距离

public:
    //1.使用默认参数
    //2.把构造函数声明成显式的,这样才不会被用于隐式类型提升
    explicit meter(int_type a = 0, int_type b = 100) : min(a), max(b), value(a), span(b - a + 1) {}
    //复制控制 —— 不需要。因为都是数值类型,所以编译器帮我们对拷了
    ~meter() {}
    
    //meter:如果函数返回一个值类型,那返回的是一个右值
    meter & operator=(int_type v) { //参数v是右操作数,this对象是左操作数
        this->value = v;
        return *this;
    }

    meter & operator++() {
        ++value;
        return *this;
    }

    meter operator++(int) { //只有类型没有名字,称为占位参数
        auto m{*this};
        ++value;
        return m; //m是一个将亡对象,因此返回值类型为右值
    }

    //类型转换运算符operator int_type,没有参数也没有返回值
    //关键字——operator,类型名——int_type
    operator int_type() {
        return value;
    }

    operator process();

    reference operator()() {
        return value;
    }

    //meter & a:隐藏修改a的可能,不符合加法语义;b本来就是一个简单类型,加不加限定无所谓
    //因为产生新值,所以返回一个右值
    friend meter operator+(const meter & a, int_type b); //函数名字是operator+,之间可以有空格

    friend std::ostream & operator<<(std::ostream & os, const meter & m);
};

 

meter.cpp

#include <iostream>
#include "meter.h"
#include "process.h"

meter operator+(const meter & a, int_type b) {
    meter m{a}; //m是一个临时对象,{}代表统一初始化,调用m的复制构造函数
    m.value += b;
    return m; //m是一个右值,等于返回了一个右值引用
} //可以级联(又不是在=左边,啊喂!)

//std::ostream & os 不能加const,因为<<要修改os
std::ostream & operator<<(std::ostream & os, const meter & m) {
    return os << '(' << m.min << ',' << m.max << ',' << m.value << ')';
}

//加上作用域,防止成为全局函数
meter::operator process() {
    //(value - min) * 100 / span 就是算成一个百分比
    return process{(value - min) * 100 / span}; //创建一个临时对象并返回
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值