C++ 运算符重载详解

一、什么是运算符重载

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

二、语法

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

示例:

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

三、实现方式

1. 类成员函数

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

示例:

#include <iostream>

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 <iostream>

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 == &copySource) { // 先判断是否相等,避免重复赋值
        return *this;
    }
    // 拷贝的逻辑实现,避免浅复制问题
    return *this;
}

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

下标运算符([]):

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

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

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

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

3. 不能重载的运算符

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

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

函数运算符 operator():

#include <iostream>

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 <iostream>

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 <iostream>
#include <string.h>

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;
}

  • 23
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中的面向对象编程允许我们使用类和对象来组织和管理代码。在类中,可以定义成员函数和成员变量。成员函数是与类相关联的函数,它们可以访问类的成员变量并执行与该类相关的操作。成员变量是存储在类中的变量,它们描述了类的状态。 运算符重载C++ 中面向对象编程的一种强大功能。它允许您重新定义运算符以执行特定操作。例如,您可以重载“+”运算符以执行类对象的加法操作。运算符重载使您能够编写更直观和易于使用的代码。 友元函数是类的非成员函数,但它们可以访问类的私有成员。当您需要访问类的私有成员但不想使这些成员成为公共接口的一部分时,友元函数就会很有用。要声明一个友元函数,请在类定义中将其声明为友元。友元函数可以是全局函数或其他类的成员函数。 下面是一个示例类,其中包含运算符重载和友元函数: ```cpp #include <iostream> class MyClass { public: MyClass(int value) : value_(value) {} // 重载加号运算符,将两个 MyClass 对象相加 MyClass operator+(const MyClass& other) { return MyClass(value_ + other.value_); } // 将友元函数声明为 MyClass 的友元 friend void PrintValue(const MyClass& obj); private: int value_; }; // MyClass 的友元函数 void PrintValue(const MyClass& obj) { std::cout << "The value of MyClass is: " << obj.value_ << std::endl; } int main() { MyClass obj1(10); MyClass obj2(20); MyClass result = obj1 + obj2; PrintValue(result); return 0; } ``` 在这个例子中,我们定义了一个 MyClass 类,它包含一个成员变量 value_ 和一个构造函数。我们还重载了加号运算符,以便我们可以将 MyClass 对象相加。最后,我们定义了一个名为 PrintValue 的友元函数,该函数可以访问 MyClass 类的私有成员 value_。 在 main 函数中,我们创建了两个 MyClass 对象,将它们相加并将结果打印到控制台上。请注意,我们使用了友元函数 PrintValue 来打印 MyClass 对象的值,而不是直接访问 MyClass 对象的私有成员。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值