IT狗工作室

第7篇:C++重载操作符

2020-10-13  本文已影响0人  铁甲万能狗

在C ++中,我们可以使操作符(operator)为用户定义的类在调用层可以像一般运算表达式一样参与运算。 这意味着C ++能够为操作符提供数据类型的特殊含义,这种能力称为操作符重载(operator overloading)。例如,我们可以在string之类的类中重载操作符“ +”,以便仅使用+即可连接两个字符串。

重载“+”操作符

下面我们用一个类似超市的结算小程序来作为本小节的示例
Good类接口

#ifndef GOOD_HH
#define GOOD_HH
#include <string>
class Good
{
    std::string d_name;
    double d_price;

public:
    Good();
    Good(std::string, double);
    double price() const;
    void set_price(double price);
    std::string name() const;
    bool operator<(const Good &obj) const;
    //两个Good实例 1+Good实例2
    Good operator+(Good const &good);
};
#endif

Customer接口

#ifndef CUSTOMER_HH
#define CUSTOMER_HH
#include "../header/good.hh"
#include <iostream>
#include <map>
#include <string>

class Customer
{
    std::string d_fullname; //用户名
    double d_pay;           //应付金额
    double d_bonus;         //奖励积分
    double d_count;         //购买数量
    double d_discnt;        //让利总计
    std::map<Good, double> cart;

public:
    Customer(std::string, double);
    //购买
    void buy(const Good, double);
    //支付
    void payment();
    //结帐信息

    void display_info();

private:
    //折扣
    void discount(const Good &, double);
};
#endif

类实现
下面的重点就是 Good Good::operator+(Good const &obj)的实现,“+”操作符的重载函数内部封装了两个Good类中的d_name字符串的拼接操作,以及两个Good对象的d_price的加法赋值。

Good Good::operator+(Good const &obj)
{
    this->d_price += obj.d_price * 0.95;
    this->d_name = this->d_name + "和" + obj.d_name;
}

调用代码

int main(int argc, char const *argv[])
{
    Good a{"香蕉", 5.7};
    Good b{"奇异果", 13.4};
    Good c{"榴莲", 13.5};
    Good d{"苹果", 5.7};
    Good e{"奶蕉", 7.7};

    Good r = a + e;

    Customer p1{"Lisa", 800};

    p1.buy(a, 23);
    p1.buy(c, 12);
    p1.buy(d, 32);
    p1.buy(r, 45);

    p1.payment();

    p1.display_info();
    return 0;
}

程序输出


因此,我们知道重载操作符实质上就是重载函数,函数内部封装了运算对象(即:对象内部各种数据成员)的对应运算操作。

重载operator[]

除非你是你自己实现类似动态数组的顺序存储结构,才需要重载索引操作符,使用标准库的的顺序容器,重载索引操作符显得有些画蛇添足。

以下是有关[]重载的一些特殊情况

  1. 当我们自己实现的顺序存储结构,需要检查索引越界问题,[]的重载可能很有用
  2. 我们必须在函数中通过引用返回,因为像“ arr [i]”之类的表达式可以用作左值

关于重载下标运算符的具体示例可以看我这篇文章,里面说的很详细
《C++ 数据结构--动态顺序表的实现》

重载operator ++

重载增量运算符(operator)和减量运算符(operator--)会带来一个小问题:每个运算符都有两个版本,因为它们可以用作后缀运算符(例如x)或用作前缀运算符(例如x)。

用作后缀运算符时,该值的对象作为右值,临时const对象返回,且后递增变量本身从视图中消失。 用作前缀运算符时,变量会递增,其值将作为左值返回,并且可以通过修改前缀运算符的返回值来再次更改。 尽管在运算符重载时不需要这些特性,但强烈建议在任何重载的增量或减量运算符中实现这些特性。

假设我们围绕size_t值类型定义一个包装器类。 这样的类可以提供以下(部分显示)接口:

class Customer
{
    size_t d_bonus;         //奖励积分

public:
    Customer(std::string, size_t);
    Customer &operator++();
}

类的最后一个成员声明前缀重载的增量运算符。 返回的左值是Customer&。 该成员很容易实现:

Customer &Customer::operator++()
{
    ++d_bonus;
    return *this;
}

要定义后缀运算符,需要定义该运算符的重载版本,并期望使用(虚拟)int参数。 这可能被认为是错误的,或者是函数重载的可接受的应用程序。 无论您对此有何看法,都可以得出以下结论:

后缀增量运算符在Customer类的接口中声明如下:

    //后缀增量操作符重载
    Customer operator++(int);

实现如下,请注意,运算符的参数并未使用。 在实现和声明中消除前缀和后缀运算符的歧义只是实现的一部分。

Customer Customer::operator++(int)
{
    Customer tmp{*this};
    ++d_bonus;
    return tmp;
}

在上面的示例中,增加当前对象的语句提供了空位保证,因为它仅涉及对原始类型的操作。 如果初始副本构造抛出异常,则原始对象不会被修改,如果return语句抛出异常,则对象已被安全地修改。 但是增加一个对象本身可能会引发异常。 在这种情况下,如何实现增量运算符? 再次,swap是我们最好的选择。 当数据成员增量执行增量操作可能抛出时,以下是前缀和后缀运算符提供了有力的保证:

重载new操作符和delete操作符

当运算符new重载时,它必须定义一个void *返回类型,并且其第一个参数的类型必须为size_t。 默认运算符new仅定义一个参数,但是重载版本可以定义多个参数。 第一个没有明确指定,但是从重载了new运算符的类的对象的大小推导得出。 在本节中,将讨论重载运算符new。

new和delete操作符的作用域

new运算符的函数原型

void *operator new(size_t n);

重载的new运算符接收的大小为size_t类型,该大小指定要分配的内存字节数。 重载的new的返回类型必须为void *。重载的函数返回一个指向分配的内存块开头的指针。

delete运算符的函数原型

void operator delete(void*);

该函数接收一个必须删除的void *类型的参数。 函数不应该返回任何东西。
注意:默认情况下,重载的new和delete运算符函数都是静态成员。 因此,他们无权访问此指针。
类接口

class Good
{
    std::string d_name;
    double d_price;

public:
    Good(std::string, double);
    //重载new操作符原型
    void *operator new(size_t n);
    //重载delete操作符
    void operator delete(void *);

    void display();
};

类实现

void *Good::operator new(size_t n)
{
    std::cout << "重载new操作符" << std::endl;
    void *p = ::new Good();
    return p;
}

void Good::operator delete(void *p)
{
    std::cout << "重载delete操作符" << std::endl;
    free(p);
    p = NULL;
}

void Good::display()
{
    std::cout << "品名:" << d_name << std::endl;
    std::cout << "价格:" << d_price << "RMB" << std::endl;
}

调用代码

#include "source/good.cpp"
#include <iostream>

int main(int argc, char const *argv[])
{
    Good *g = new Good("香焦", 10);
    g->display();

    delete g;
    return 0;
}
示例输出

在上面的新重载函数中,是通过new运算符分配了动态内存,但是它是全局的new运算符,否则它内部将递归调用因为new的内容将一次又一次地重载,就像下面的代码

void *p=new Good(); //错误的示例

正确的示例,通过::作用域操作符,从全局调用new操作符

void *p=::new Good();

在全局作用域重载new和delete操作符

void *operator new(size_t n){
     std::cout<<"全局作用域重载new操作符"<<std::endl;
     void *p=malloc(n);
     return p;
}

void operator delete(void *p){
     std::cout<<"全局作用域重载delete操作符"<<std::endl;
      free(p);
      p=NULL;
}

int main(){
      int n=5;
      int *p=new int[5];
      
    for(int i=0;i<n;i++){
        p[i]=i;
    }

   for(size_t i=0;i<n;i++){
        std::cout<<p[i]<<std::endl;
  }

 delete p;
}

在上面的代码中,在new操作符的重载函数中,我们无法使用::new int [5]分配内存,因为它将以递归方式进行。 我们只需要使用malloc分配内存。
输出

全局作用域重载new操作符
0 1 2 3 4
全局作用域重载delete操作符

重载new/delete操作符的原因

operator函数和普通函数有什么区别?

重载操作符的注意事项

操作员重载规则

上一篇下一篇

猜你喜欢

热点阅读