C++ 运算符重载详解

一、什么是运算符重载

运算符重载实质还是一个 函数
通过重载运算符,可以让类在一些场景下使用起来更加方便。

二、语法

返回值类型 operator op (参数);​​​​​​​

示例:

ClassType& operator= (const ClassType& src); // 重载 “=” 运算符

三、实现方式

1. 类成员函数

即在类中定义一个成员函数来重载运算符。

示例:

#include 

using namespace std;

class Demo
{
public:
    Demo(int a)
    {
        this->a = a;
    }
    // 类成员函数实现运算符重载
    Demo & operator+ (int n)
    {
        this->a += n;
        return *this;
    }
    int getA()
    {
        return a;
    }
private:
    int a;
};

int main()
{
    Demo demo(1);
    // 重载 "+" 运算符后,对象可以直接加一个数
    demo = demo + 2;
    cout << demo.getA() << endl;
    return 0;
}

2. 友元函数

在类外部定义一个函数来重载运算符,并将这个函数声明为该类的友元函数,以达到可以访问类的私有属性的目的。

示例:

#include 

using namespace std;

class Demo
{
public:
    // 友元函数实现运算符重载
    friend Demo & operator+ (Demo & demo, int n);
    Demo(int a)
    {
        this->a = a;
    }
    int getA()
    {
        return a;
    }
private:
    int a;
};
// 重载 "+" 运算符
Demo & operator+ (Demo & demo, int n)
{
    demo.a += n;
    return demo;
}

int main()
{
    Demo demo(1);
    // 重载 "+" 运算符后,对象可以直接加一个数
    demo = demo + 2;
    cout << demo.getA() << endl;
    return 0;
}

3. 区别

  • 类成员函数实现默认带有一个 this 指针,而友元函数实现则没有
  • 要想实现类似 “cout << obj << endl;” 这样的效果,只能由友元函数重载左移运算符实现(后面介绍)

四、分类

1. 单目运算符

定义:

顾名思义,单目运算符就是只对一个操作数进行操作

可重载的单目运算符:

++(递增)、--(递减)、*(解除引用)、->(成员选择)、!(逻辑非)、&(取址)、~(求反)、+(正)、-(负)。

递增和递减:

递增和递减分为前置递增递减和后置递增递减,两个的区别还是很明显。

class Test
{
public:
    Test(int a)
    {
        this->a = a;
    }
    // 前置++
    Test & operator++()
    {
        a++;
        return *this;
    }
    // 前置--
    Test & operator--()
    {
        a--;
        return *this;
    }
    // 后置++
    // 通过一个站位参数来和前置++区分开来
    // 实现上,多了一个 tmp 变量,来实现先操作,后自增的效果
    Test operator++(int)
    {
        Test tmp(*this);
        this->a++;
        return tmp;
    }   
    // 后置--
    Test operator--(int)
    {
        Test tmp(*this);
        this->a--;
        return tmp;
    }
private:
    int a;
};
 

分析:如果想执行递增运算,可使用 ++object,也可以使用 object++,但应尽量选择前者,这样避免创建一个未被使用的临时拷贝。

解除引用运算符(*)和成员选择运算符(->):

这两个运算符在智能指针类编程中应用最广,这里不详细介绍了。

智能指针是封装常规指针的类,简化了内存管理。

2. 双目运算符

定义:

顾名思义,双目运算符就是对两个操作数进行操作

可重载的双目运算符:

运算符

名称

运算符

名称

,

逗号

<

小于

!=

不等于

<<

左移

%

求模

<<=

左移并赋值

%=

求模并赋值

<=

小于或等于

&

按位与

=

赋值、复制赋值和移动赋值

&&

逻辑与

==

等于

&=

按位与并赋值

>

大于

*

>=

大于或等于

*=

乘并赋值

>>

右移

+

>>=

右移并赋值

+=

加并赋值

^

异或

-

^=

异或并赋值

-=

减并赋值

|

按位或

->*

指向成员的指针

|=

按位或并赋值

/

||

逻辑或

/=

除并赋值

[]

下标运算符

加法、减法:

Date operator + (int daysToAdd) { // TODO }
Date operator - (int daysToAdd) { // TODO }

+=、-=、-=、*=、/=、%=、<<=、>>=、^=、|=、&=:

void operator += (int daysToAdd) { // TODO }
// 其余均和上式类似

等于(==)和不等于(!=),<、>、<=、>= 等:

bool operator== (const ClassType& compareTo) { // TODO }
bool operator!= (const ClassType& compareTo) { // TODO }

复制赋值运算符(=):

ClassType& operator= (const ClassType& copySource) {
    if (this == ©Source) { // 先判断是否相等,避免重复赋值
        return *this;
    }
    // 拷贝的逻辑实现,避免浅复制问题
    return *this;
}

如果没有重载赋值运算符,系统会有一个默认的实现,但可能导致浅复制的问题。如果要实现深复制,就需要重载赋值运算符。

下标运算符([]):

const char& operator[] (int index) const {
    return buffer[index];
}

逻辑与(&&)和逻辑或(||):

不推荐重载这两个运算符。

原因:我们知道 && 和 || 是有短路的,即 &&前面为假时,后面将不会计算了,同理 || 前面为真时也将跳过后面的计算。但是如果是重载了这两个运算符,则会失去短路效果。

3. 不能重载的运算符

成员运算符(.)、指针成员选择(.*)、作用域解析(::)、条件三目运算符(?:)、获取类型大小(sizeof)。

4. 一些比较特别的运算符重载

函数运算符 operator():

#include 

using namespace std;

class Demo
{
public:
    void operator () (const char *input) const {
        cout << input << endl;
    }
};

int main()
{
    Demo demo;
    // 调用函数运算符,参数为一个字符串
    demo("hello world");
    return 0;
}

operator() 让对象变得像函数,这个运算符也被称为 operator() 函数。

当然上例中的 operator() 的参数、返回值、实现都是可以根据具体需求改变的。

自定义字面量:​​​​​​​

语法:

ReturnType operator "" YourLiteral(ValueType value) {
    // conversion code here
}

在一些表示温度、千米等特殊的单位,C++ 本身是没有这些东西的,但是我们可以像下面这样定义自己的字面量:

// 都转换为以 “米” 为单位
long double operator"" _mm(long double x) { return x / 1000; }
long double operator"" _m(long double x)  { return x; }
long double operator"" _km(long double x) { return x * 1000; }

参数 ValueType 只能是下面几个之一:
unsigned long long int、long double、char、wchar_t、char16_t、char32_t、
const char*、const char* 和 size_t、const wchar* 和 size_t、const char16_t* 和 size_t、const char32_t* 和 size_t,即:

void operator"" _test(unsigned long long int a) {}
void operator"" _test(long double a) {}
void operator"" _test(char a) {}
void operator"" _test(wchar_t a) {}
void operator"" _test(char16_t a) {}
void operator"" _test(char32_t a) {}
void operator"" _test(const char* a) {}
void operator"" _test(const char* a, size_t b) {}
void operator"" _test(const wchar_t* a, size_t b) {}
void operator"" _test(const char16_t* a, size_t b) {}
void operator"" _test(const char32_t* a, size_t b) {}

使用:

Temperature operator"" _C(long double celcius) {
    return Temperature(celcius + 273);
}
 
Temperature t = 0.0_C;

使用 cout << obj << endl; 打印类对象:

#include 

using namespace std;

class Test
{
public:
    // 使用友元函数使其可以访问类的私有属性
    friend ostream & operator << (ostream& os, Test & test);
    Test(int a)
    {
        this->a = a;
    }
private:
    int a;
};

// 这里只能使用友元函数来实现,因为我们没法修改 cout 的左移函数的代码实现
ostream& operator << (ostream& os, Test & test)
{
    os << test.a;
    return os;
}

int main()
{
    Test test(10);
    cout << test << endl;
    return 0;
}

五、MyString 实例

最后,我们再来看一个 MyString 的实例,熟悉一下运算符重载的使用把。

MyString.h:

#pragma once

#define _CRT_SECURE_NO_WARNINGS

#include 
#include 

using namespace std;

class MyString
{
public:
    MyString(); // 无参构造方法 MyString str;
    MyString(const char *str); // 有参构造方法 MyString str("hello world");
    MyString(const MyString & str); // 复制构造方法 MyString str2(str1); MyString str2 = str1;
    ~MyString(); // 析构方法

    MyString& operator= (const MyString & str); // 重载 "=" 运算符 str2 = str1;
    MyString& operator= (const char *str); // 重载 "=" 运算符 str2 = "hello world";
    char& operator[] (int index); // 重载 "[]" 运算符
    bool operator==(const MyString & str); // 重载 "==" 运算符
    bool operator!=(const MyString & str); // 重载 "!=" 运算符
    bool operator>(const char *str); // 重载 ">" 运算符
    bool operator<(const char *str); // 重载 "<" 运算符
    bool operator>(const MyString & str); // 重载 ">" 运算符
    bool operator<(const MyString & str); // 重载 "<" 运算符
    friend ostream & operator<<(ostream & out, MyString & str); // 重载 "<<" 运算符,友元函数的实现方式

    int length();
    const char * c_str();

private:
    char *m_str;
    int m_len;
};

MyString.cpp:

#include "MyString.h"

MyString::MyString()
{
    m_len = 0;
    m_str = new char[m_len + 1];
    strcpy(m_str, "");
}

MyString::MyString(const char * str)
{
    m_len = str ? strlen(str) : 0;
    m_str = new char[m_len + 1];
    strcpy(m_str, str);
}

MyString::MyString(const MyString & str)
{
    m_len = str.m_len;
    m_str = new char[m_len + 1];
    strcpy(m_str, str.m_str);
}

MyString::~MyString()
{
    if (m_str)
    {
        delete [] m_str;
        m_str = NULL;
        m_len = 0;
    }
}

MyString & MyString::operator=(const MyString & str)
{
    if (this == &str)
    {
        return *this;
    }
    // 释放旧内存
    if (m_str)
    {
        delete[] m_str;
        m_len = 0;
    }
    m_len = str.m_len;
    m_str = new char[m_len + 1];
    strcpy(m_str, str.m_str);
    return *this;
}

MyString & MyString::operator=(const char * str)
{
    // 释放旧内存
    if (m_str)
    {
        delete[] m_str;
        m_len = 0;
    }
    m_len = str ? strlen(str) : 0;
    m_str = new char[m_len + 1];
    strcpy(m_str, str);
    return *this;
}

char & MyString::operator[](int index)
{
    return m_str[index];
}

bool MyString::operator==(const MyString & str)
{
    if (m_len != str.m_len)
    {
        return false;
    }
    return strcmp(m_str, str.m_str) == 0;
}

bool MyString::operator!=(const MyString & str)
{
    return !operator==(str);
}

bool MyString::operator>(const char * str)
{
    return strcmp(m_str, str) > 0;
}

bool MyString::operator<(const char * str)
{
    return strcmp(m_str, str) < 0;
}

bool MyString::operator>(const MyString & str)
{
    return strcmp(m_str, str.m_str) > 0;
}

bool MyString::operator<(const MyString & str)
{
    return strcmp(m_str, str.m_str) < 0;
}

ostream & operator<<(ostream & out, MyString & str)
{
    out << str.m_str;
    return out;
}

int MyString::length()
{
    return m_len;
}

const char * MyString::c_str()
{
    return m_str;
}

你可能感兴趣的:(C/C++,运算符重载)