运算符重载(一)

  • 运算符重载允许把标准运算符(如+、—、*、/、<、>等)应用于自定义数据类型的对象。
  • 直观自然,可以提高程序的可读性。
  • 体现了C++的可扩充性。
  • 运算符重载仅仅只是语法上的方便,它是另一种函数调用的方式。
  • 运算符重载,本质上是函数重载。
  • 不要滥用重载、因为它只是语法上的方便,所以只有在涉及的代码更容易写、尤其是更易读时才有必要重载。

实际上对于运算符的重载在之前的学习中也已经使用到了,只是没系统的提到,下面来用代码进行说明:

先用普通的方式来实现一下复数的运算:

Complex.h:

#ifndef _COMPLEX_H_
#define _COMPLEX_H_

class Complex
{
public:
    Complex(int real, int imag);
    Complex();
    ~Complex();

private:
    int real_;//复数的实部
    int imag_;//复数的虚部
};


#endif    //_COMPLEX_H_

Complex.cpp:

#include "Complex.h"

Complex::Complex(int real, int imag) : real_(real), imag_(imag)
{

}

Complex::Complex() {

}

Complex::~Complex()
{

}

如果要实现两个复数相加,则先定义一个方法:

Complex.h:

#ifndef _COMPLEX_H_
#define _COMPLEX_H_

class Complex
{
public:
    Complex(int real, int imag);
    Complex();
    ~Complex();

    Complex& add(const Complex& otherComplex);//复数加法运算
    void display() const;//打印复数
private:
    int real_;//复数的实部
    int imag_;//复数的虚部
};


#endif    //_COMPLEX_H_

Complex.cpp:

#include "Complex.h"
#include <iostream>
using namespace std;

Complex::Complex(int real, int imag) : real_(real), imag_(imag)
{

}

Complex::Complex() {

}

Complex::~Complex()
{

}

Complex& Complex::add(const Complex& otherComplex) {
    real_ += otherComplex.real_;
    imag_ += otherComplex.imag_;
    return *this;
}

void Complex::display() const {
    cout<<real_<<"+"<<imag_<<"i"<<endl;
}

编写测试代码:

#include "Complex.h"

int main(void) {
    Complex c1(3, 5);
    Complex c2(4, 6);
    c1.add(c2);

    c1.display();
    
    return 0;
}

编译运行,结果显而易见:

以上就是以成员函数的方式来实现两个复数的加法运算,但是,这种方式不太直观,而且有个毛病就是c1对象发生的改变,如果要实现下面这种运算呢?

这时运算符的重载就派上了用场了,上面的这种写法现在肯定是编译通不过的:

其中运算法函数的有两种方式:成员函数重载、非成员函数重载。下面分别来实现:

  •  成员函数原型的格式:
      函数类型 operator 运算符(参数表);
  • 成员函数定义的格式:
      函数类型 类名::operator 运算符(参数表){

        函数体;

       }

 Complex.h:

#ifndef _COMPLEX_H_
#define _COMPLEX_H_

class Complex
{
public:
    Complex(int real, int imag);
    Complex();
    ~Complex();

    Complex& add(const Complex& otherComplex);//复数加法运算
    void display() const;//打印复数

    Complex operator+(const Complex& otherComplex);//+号运算符重载
private:
    int real_;//复数的实部
    int imag_;//复数的虚部
};


#endif    //_COMPLEX_H_

Complex.cpp:

编译运行:

 

实际上:

Complex c3 = c1 + c2; 等价于: Complex c3 = c1.operator + (c2);

下面来验证下:

编译运行:

可见这个加法运算实际上就是函数调用,但是这种等价写法不是那么直观,所以还是将代码还原。

  • 友元函数原型的格式:
    friend 函数类型 operator 运算符(参数表);
  • 友元函数定义的格式:
    friend 函数类型 类名::operator 运算符(参数表){

      函数体;

      }

在编译运行之前,先思考一个问题:成员函数重载和非成员函数成载能否共存呢?下面编译一下:

从结果来看是可以共存的,但是在vS6.0中是不允许的,所以平常最好只要写其中一个就成了,不要共存。

如果是以友元的方式重载,那下面这句的话等价于:

  •  运算符重载不允许发明新的运算符。
  • 不能改变运算符操作对象的个数。
  • 运算符被重载后,其优先级和结合性不会改变。
  • 不能重载的运算符:
  • 关于选择成员函数重载还是友元函数重载,有下面一些规划:
    ①、一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。
    ②、以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。
    ③、类型转换运算符只能以成员函数方式重载。
  • ④、流运算符只能以友元的方式重载。

这里用一个整数类来说明这个运算符的重载,如下:

Integer.h:

#ifndef _INTEGER_H
#define _INTEGER_H

class Integer
{
public:
    Integer(int n);
    ~Integer();
    void display() const;
private:
    int n_;
};

#endif    //_INTEGER_H

Integer.cpp:

#include "Integer.h"
#include <iostream>
using namespace std;

Integer::Integer(int n)    : n_(n) {

}

Integer::~Integer()
{
}

void Integer::display() const{
    cout<<n_<<endl;
}

编写测试代码:

#include "Integer.h"
#include <iostream>
using namespace std;

int main(void) {
    Integer n(100);
    n.display();
}

编译运行:

接下来实现它的++运算,先将其测试代码写出来,当然现在是编译通不过滴:

下面需要重载++运算符,先学习一下基本概念:

①、前置++运算符重载:

  • 成员函数的方式重载,原型为:
    函数类型 & operator++();
  • 友元函数的方式重载,原型为:
    friend 函数类型 & operator++(类类型 &);

②、后置自增和后置自减的重载:

  • 成员函数的方式重载,原型为:
    函数类型 & operator++(int);
  • 友元函数的方式重载,原型为:
    friend函数类型 & operator++(类类型 &,int);

下面来具体实现一下:

编译运行:

而如果以友元的方式重载,需要多加一个参数,如其它重载一样:

编译:

所以将之前的重载方式注释掉:

编译运行其结果跟之前的第一种方式是一样的。

下面来看下后置++运算符的重载:

编写测试代码:

根据后置运算符的语法可以推断n++的整个表示式是没有+1的,也就是n3应该等于101,而对于n自身来讲是要+1的,所以n应该等于102,那结果是102、101么?下面运行一下:

其主要原因还是这样写有问题:

那如何解决这个问题,那就是用临时对象来解决,具体如下:

由于函数原形变了,则在头文件中的声明也得变化一下:

再次编译运行:

这时就正确了,而它的友远方式实现跟前置++类似,如下:

其运行结果跟成员函数的重载一样。

【注意】:能用成员函数重载解决的就尽量用成员函数的方式,否则才用友员的方式。

关于这个运算符重载用之前我们已经写过的字符串类来说明:

String.h:

#ifndef _STRING_H
#define _STRING_H

class String
{
public:
    String(const char* str="");
    ~String();
private:
    char* str_;
};


#endif // _STRING_H

String.cpp:

#include "String.h"

String::String(const char* str)
{
    int len = sizeof(str) + 1;
    str_ = new char[len];
    memset(str_, 0, len);
    strcpy(str_, str);
}

String::~String()
{
    delete[] str_;
}

另外还需要实现深拷贝,关于为什么要实现深拷贝,可以参考博文:http://www.cnblogs.com/webor2006/p/5084247.html,具体代码如下:

编译运行:

再次编译:

再次编译:

下面来实现赋值运算符,因为默认情况下:

String s1("aaa");

String s2 = s1;等价于s2.str_=s1.str_实现的也是浅拷贝,而当两个对象释放时,都会释放同一个str_就会出问题,所以需要实现赋值运算符实现深拷贝,具体如下:

String.cpp:

#pragma warning(disable:4996)
#include "String.h"
#include <string.h>
#include <iostream>
using namespace std;

String::String(const char* str)
{
    str_ = allocAndCpy(str);
}

String::String(const String& other) {
    str_ = allocAndCpy(other.str_);
}

String& String::operator = (const String& other) {
    if(this == &other)
        return *this;

    delete[] str_;
    str_ = allocAndCpy(other.str_);
    return *this;
}

char* String::allocAndCpy(const char* str) {
    int len = sizeof(str) + 1;
    char* newStr = new char[len];
    memset(newStr, 0, len);
    strcpy(newStr, str);
    return newStr;
}

String::~String()
{
    delete[] str_;
}

void String::display() const {
    cout<<str_<<endl;
}

测试代码:

编译运行:

如果是这样编写测试代码,还能编译通过么?

编译运行:

这是由于当执行s3="xxx"时会调用转换构造函数:

如果不让其转换构造,那还能支持这种写法么?

见证奇迹:

其实可以通过重载运算赋值运算符来解决,如下:

再次编译运行:

 先来编写测试代码再来去实现非运算符重载:

当然目前的代码是无法编译通过的:

下面来实现非运算符重载:

 

编译运行:

原文地址:https://www.cnblogs.com/webor2006/p/5351990.html