突破编程_C++_基础教程(操作符重载)

1 操作符重载的基础

操作符重载是 C++ 中的一个重要概念,它允许程序员重新定义或重载已有的操作符,使其能够用于用户自定义的数据类型。这种重载的目的是为了使得用户自定义的数据类型能够像内置类型一样方便地进行运算。
具体来说,操作符重载的目的包括:
扩展运算符的适用范围:通过重载,可以使同一运算符作用于不同类型的数据时导致不同类型的行为,从而扩展 C++ 中提供的运算符的适用范围,以用于类所表示的抽象数据类型。
简化代码:操作符重载可以使代码更加简洁易懂。通过为自定义类型重载操作符,可以像使用内置类型一样使用这些操作符,而无需定义额外的函数来完成相同的操作。
提高代码可读性:操作符重载可以将概括性的抽象操作符具体化,便于外部调用而无需知晓内部具体运算过程,从而提高代码的可读性。
注意:操作符重载应当谨慎使用,以避免创建可能会令人困惑或误解的代码(比如更改了 && 的含义)。同时,重载的操作符不能改变其原有的含义和优先级,也不能改变操作数的数量。

1.1 操作符重载的定义和语法

重载操作符是通过在类定义中使用 operator 关键字后跟上要重载的操作符符号来实现的。重载的操作符可以是成员函数或非成员函数,也可以是友元函数。如下是一个简单样例(重载+操作符):

#include 

class MyClass 
{
public:
    MyClass(int val1 = 0, int val2 = 0) : m_val1(val1), m_val2(val2) {}

    // 重载 + 操作符,作为成员函数  
    MyClass operator+(const MyClass& other) const 
    {
        return MyClass(m_val1 + other.getVal1(), m_val2 + other.getVal2());
    }

public:
    int getVal1() const { return m_val1; }
    int getVal2() const { return m_val2; }

private:
    int m_val1;
    int m_val2;
};

int main() {
    MyClass obj1(1, 2);
    MyClass obj2(3, 4);
    MyClass obj3 = obj1 + obj2;  // 使用重载的 + 操作符  
    
    printf("obj3.getVal1() : %d\n", obj3.getVal1());
    printf("obj3.getVal2() : %d\n", obj3.getVal2());

    return 0;
}

上面代码的输出为:

obj3.getVal1() : 4
obj3.getVal2() : 6

在上面的代码中,+ 操作符被重载为 MyClass 类的成员函数。它接受一个 MyClass 类型的常量引用作为参数,并返回一个新的 MyClass 对象,其 m_val1 和 m_val2 成员是两个操作数对应成员的和。在 main 函数中,可以像使用内置类型的 + 操作符一样使用 obj1 + obj2,编译器会自动调用重载的 + 操作符来实现相应的操作。
需要注意的是,重载操作符的参数列表和返回类型必须与操作符的操作数类型和期望的结果类型相匹配。此外,重载的操作符不能改变其原有的含义、优先级和结合性。
除了成员函数重载操作符外,还可以将操作符重载为非成员函数或友元函数。非成员函数重载操作符需要显式地指定操作数的类型,而友元函数则可以访问类的私有和保护成员。

1.2 操作符重载的规则和限制

操作符重载有一些规则和限制,这些规则确保了操作符重载的合法性和一致性。以下是一些常见的操作符重载规则和限制:
操作符的优先级和结合性不能被改变
重载的操作符必须保持其原有的优先级和结合性。例如,重载的+操作符仍然具有加法的优先级和从左到右的结合性。
操作符的目数(操作数数量)不能被改变
重载的操作符必须保持其原有的目数。例如,重载的 ++ 操作符仍然是一个一元操作符,而重载的 + 操作符仍然是一个二元操作符。
不能创造新的操作符
不能重载一个 C++ 中不存在的操作符,也不能为一个操作符添加新的含义。
重载的操作符不能用于内置类型
重载的操作符只能用于用户自定义类型,不能用于内置类型(如 int 、 double 等)。这是为了保持内置类型操作符的语义一致性。
重载的操作符函数参数至少有一个必须是类类型
这是为了防止程序员通过重载操作符来改变内置类型的行为。
重载操作符的参数类型
对于成员函数重载的操作符,至少有一个参数必须是类类型(可以是this指针)。对于非成员函数重载的操作符,至少有一个参数必须是类类型。
一些操作符不能被重载
例如,.(成员访问操作符)、.*(成员指针访问操作符)、::(作用域解析操作符)和?:(条件操作符)等不能被重载。
这些规则和限制确保了操作符重载的一致性和可预测性,使得重载后的操作符仍然符合C++语言的语法和语义规则。同时,它们也防止了程序员通过重载操作符来创建可能导致混淆或误解的代码。

1.3 可重载和不可重载的操作符列表

在C++中,大部分操作符都是可以被重载的,但是有一些特殊的操作符是不能被重载的。以下是一些常见的可重载和不可重载的操作符列表:

可重载的操作符

  • 算术操作符:+-*/%
  • 关系操作符:==!=><>=<=
  • 逻辑操作符:&&||!
  • 位操作符:&|^~<<>>
  • 赋值操作符:=+=-=*=/=%=&=|=^=<<=>>=
  • 下标操作符:[]
  • 前缀和后缀操作符:++--
  • 函数调用操作符:()
  • 逗号操作符:,
  • 内存管理操作符:newnew[]deletedelete[]

不可重载的操作符

  • 三目操作符:? :
  • 成员访问操作符:.
  • 成员指针访问操作符:.*
  • 作用域解析操作符:::
  • 条件操作符:?:
  • sizeof 操作符
  • typeid 操作符
  • alignof 操作符
  • noexcept 操作符( C++11 起)
    需要注意的是,虽然大部分操作符都可以被重载,但是重载操作符时应该谨慎使用,避免创建可能导致混淆或误解的代码。同时,重载的操作符应该保持其原有的语义和用法,不应该改变其原有的含义、优先级和结合性。

2 成员函数重载操作符

2.1 重载算术操作符

算术操作符包括 +-*/% ,如下为样例代码:

#include 

class MyClass 
{
public:
    MyClass(int val1 = 0, int val2 = 0) : m_val1(val1), m_val2(val2) {}

    // 重载 + 操作符,作为成员函数  
    MyClass operator+(const MyClass& other) const 
    {
        return MyClass(m_val1 + other.getVal1(), m_val2 + other.getVal2());
    }

    // 重载 - 操作符,作为成员函数  
    MyClass operator-(const MyClass& other) const
    {
        return MyClass(m_val1 - other.getVal1(), m_val2 - other.getVal2());
    }

    // 重载 * 操作符,作为成员函数  
    MyClass operator*(const MyClass& other) const
    {
        return MyClass(m_val1 * other.getVal1(), m_val2 * other.getVal2());
    }

    // 重载 / 操作符,作为成员函数  
    MyClass operator/(const MyClass& other) const
    {
        return MyClass(m_val1 / other.getVal1(), m_val2 / other.getVal2());
    }

    // 重载 % 操作符,作为成员函数  
    MyClass operator%(const MyClass& other) const
    {
        return MyClass(m_val1 % other.getVal1(), m_val2 % other.getVal2());
    }

public:
    int getVal1() const { return m_val1; }
    int getVal2() const { return m_val2; }

private:
    int m_val1;
    int m_val2;
};

int main() {
    MyClass obj1(1, 2);
    MyClass obj2(3, 4);
    MyClass obj; 
    obj = obj1 + obj2;  // 使用重载的 + 操作符      
    printf("After overloading + operator, obj.getVal1() : %d\n", obj.getVal1());
    printf("After overloading + operator, obj.getVal2() : %d\n", obj.getVal2());
    obj = obj1 - obj2;  // 使用重载的 - 操作符      
    printf("After overloading - operator, obj.getVal1() : %d\n", obj.getVal1());
    printf("After overloading - operator, obj.getVal2() : %d\n", obj.getVal2());
    obj = obj1 * obj2;  // 使用重载的 * 操作符      
    printf("After overloading * operator, obj.getVal1() : %d\n", obj.getVal1());
    printf("After overloading * operator, obj.getVal2() : %d\n", obj.getVal2());
    obj = obj1 / obj2;  // 使用重载的 / 操作符      
    printf("After overloading / operator, obj.getVal1() : %d\n", obj.getVal1());
    printf("After overloading / operator, obj.getVal2() : %d\n", obj.getVal2());
    obj = obj1 % obj2;  // 使用重载的 % 操作符      
    printf("After overloading %% operator, obj.getVal1() : %d\n", obj.getVal1());
    printf("After overloading %% operator, obj.getVal2() : %d\n", obj.getVal2());

    return 0;
}

上面代码的输出为:

After overloading + operator, obj.getVal1() : 4
After overloading + operator, obj.getVal2() : 6
After overloading - operator, obj.getVal1() : -2
After overloading - operator, obj.getVal2() : -2
After overloading * operator, obj.getVal1() : 3
After overloading * operator, obj.getVal2() : 8
After overloading / operator, obj.getVal1() : 0
After overloading / operator, obj.getVal2() : 0
After overloading % operator, obj.getVal1() : 1
After overloading % operator, obj.getVal2() : 2

2.2 重载关系操作符

关系操作符包括 ==!=><>=<= ,如下为样例代码:

#include 

class MyClass 
{
public:
    MyClass(int val1 = 0, int val2 = 0) : m_val1(val1), m_val2(val2) {}

    // 重载 == 操作符,作为成员函数  
    bool operator==(const MyClass& other) const
    {
        return (m_val1 == other.getVal1()) && (m_val2 == other.getVal2());
    }

    // 重载 != 操作符,作为成员函数  
    bool operator!=(const MyClass& other) const
    {
        return !(*this == other);
    }

    // 重载 < 操作符,作为成员函数  
    bool operator<(const MyClass& other) const
    {
        return (m_val1 < other.getVal1()) && (m_val2 < other.getVal2());
    }

    // 重载 > 操作符,作为成员函数  
    bool operator>(const MyClass& other) const
    {
        return other < *this;
    }

    // 重载 <= 操作符,作为成员函数  
    bool operator<=(const MyClass& other) const
    {
        return !(*this > other);
    }

    // 重载 >= 操作符,作为成员函数  
    bool operator>=(const MyClass& other) const
    {
        return !(*this < other);
    }

public:
    int getVal1() const { return m_val1; }
    int getVal2() const { return m_val2; }

private:
    int m_val1;
    int m_val2;
};

int main() {
    MyClass obj1(1, 2);
    MyClass obj2(3, 4);
    if (obj1 == obj2) // 使用重载的 == 操作符 
    {
        printf("After overloading == operator, obj1 == obj2 : true\n");
    }
    else
    {
        printf("After overloading == operator, obj1 == obj2 : false\n");
    }
    
    // 其他关系操作符  `!=`、`>`、`<`、`>=`、`<=` 类似
    return 0;
}

上面代码的输出为:

After overloading == operator, obj1 == obj2 : false

注意:一般只需要重载 ==< 两个关系操作符,则其他关系操作符可以通过它们来实现。
在重载这些操作符时,重要的是要保持一致性,即如果 a < b 为真,那么 b > a 也应该为真,并且 a <= b 和 b >= a 也应该相应地为真。同样,如果 a < b 和 b < c 都为真,那么 a < c 也应该为真。

2.3 重载逻辑操作符

重载逻辑操作符 &&||
在C++中,重载逻辑操作符会失去短路特性,因为重载的操作符是普通的函数或成员函数,它们并不具备内置逻辑操作符的短路行为。短路特性是内置逻辑操作符的特性,当操作符左侧的表达式已经可以确定整个逻辑表达式的结果时,右侧的表达式将不会被执行或评估。
当重载逻辑操作符时,则需要显式地定义操作符的行为,包括如何处理短路的情况。由于重载的操作符不是内置的,它们不会自动具有短路特性。因此,需要手动实现短路逻辑。
下面的代码展示了如何重载逻辑与( && )操作符,并手动实现短路特性:

#include   

class MyClass {
public:
    MyClass(bool val = false) : val(val) {}

    // 重载逻辑与操作符  
    friend MyClass operator&&(const MyClass& objLeft, const MyClass& objRight)
    {
        // 短路逻辑:如果lhs为false,则无需评估rhs  
        if (!objLeft.val) {
            return MyClass(false);
        }
        // 否则,继续评估rhs并返回结果  
        return MyClass(objLeft.val && objRight.val);
    }

    // 重载转换为bool的操作,以便在需要时自动转换  
    operator bool() const 
    {
        return val;
    }

public:
    bool val;
};

int main() {
    MyClass objLeft(true);
    MyClass objRight(false);

    // 使用重载的逻辑与操作符  
    if (objLeft && objRight)
    {
        printf("both objLeft and objRight are true\n");
    }
    else 
    {
        printf("at least one of objLeft and objRight is false\n");
    }

    return 0;
}

上面代码的输出为:

at least one of objLeft and objRight is false

重载逻辑与操作符&&(以及逻辑或操作符||)通常需要定义为friend函数,这是因为这些操作符通常涉及两个对象,并且它们需要访问对象的私有(private)或保护(protected)成员。friend函数可以访问类的私有和保护成员,即使它们不是类的成员函数。
当重载&&操作符时,通常有两个操作数,它们都是类类型的对象。为了能够让这个操作符访问这两个对象的私有成员,并且返回一个正确类型的对象,你需要一个非成员函数,而这个非成员函数需要被声明为friend,以便它可以访问类的私有成员。
重载逻辑操作符 !
重载逻辑非操作符 ! 通常是一个一元操作符,即它只对一个操作数起作用。对于自定义类型,可以通过定义成员函数或非成员函数来重载这个操作符。在大多数情况下,将其定义为成员函数更为直观,因为逻辑非通常是对对象自身状态的否定。如下为样例代码:

#include   

class MyClass {
public:
    MyClass(bool val = false) : val(val) {}

    // 重载逻辑非操作符 !  
    bool operator!() const {
        return !val;
    }

public:
    bool val;
};

int main() {
    MyClass obj(true);

    // 使用重载的逻辑与操作符  
    if (!obj)
    {
        printf("!obj is true\n");
    }
    else 
    {
        printf("!obj is false\n");
    }

    return 0;
}

上面代码的输出为:

!obj is false

2.4 重载位操作符

重载位操作符 &(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)通常用于整数类型的位操作。对于二元操作符( & 、| 、^ ),可以选择将它们定义为成员函数或非成员函数。然而,对于一元操作符( ~ )和移位操作符( << 、 >> ),它们通常被重载为成员函数,因为这些操作通常涉及到对象自身的状态。如下为样例代码:

#include   

class MyClass {
public:
    MyClass(int val = 0) : m_val(val) {}

    // 重载按位与操作符 &  
    MyClass operator&(const MyClass& other) const 
    {
        return MyClass(m_val & other.getVal());
    }

    // 重载按位或操作符 |  
    MyClass operator|(const MyClass& other) const 
    {
        return MyClass(m_val | other.getVal());
    }

    // 重载按位异或操作符 ^  
    MyClass operator^(const MyClass& other) const 
    {
        return MyClass(m_val ^ other.getVal());
    }

    // 重载按位取反操作符 ~(作为成员函数)  
    MyClass operator~() const 
	{
        return MyClass(~m_val);
    }

    // 重载左移操作符 <<  
    MyClass operator<<(int shift) const 
	{
        return MyClass(m_val << shift);
    }

    // 重载右移操作符 >>  
    MyClass operator>>(int shift) const 
	{
        return MyClass(m_val >> shift);
    }

public:
    int getVal() const {  return m_val; }

private:
    int m_val;

};

// 输出运算符重载,用于更友好的输出  
std::ostream& operator<<(std::ostream& os, const MyClass& obj) 
{
    os << obj.getVal();
    return os;
}

int main() 
{
    MyClass a(5);    // 二进制: 0101  
    MyClass b(3);    // 二进制: 0011  

    std::cout << "a & b: " << (a & b) << std::endl;  // 输出: a & b: 1 (二进制: 0001)  
    std::cout << "a | b: " << (a | b) << std::endl;  // 输出: a | b: 7 (二进制: 0111)  
    std::cout << "a ^ b: " << (a ^ b) << std::endl;  // 输出: a ^ b: 6 (二进制: 0110)  
    std::cout << "~a: " << (~a) << std::endl;       // 输出: ~a: -6 (二进制: 1011,取反后加1得到补码表示)  
    std::cout << "a << 1: " << (a << 1) << std::endl;  // 输出: a << 1: 10 (二进制: 1010)  
    std::cout << "a >> 1: " << (a >> 1) << std::endl;  // 输出: a >> 1: 2 (二进制: 0010)  

    return 0;
}

上面代码的输出为:

a & b: 1
a | b: 7
a ^ b: 6
~a: -6
a << 1: 10
a >> 1: 2

2.5 重载赋值操作符

重载赋值操作符(如 =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=)通常用于自定义类型的赋值和复合赋值行为。这些操作符的重载允许定义自己的逻辑,以处理赋值或复合赋值时的特定行为。如下为样例代码:

#include   

class MyClass 
{
public:
    // 构造函数  
    MyClass(int val = 0) : m_val(val) {}

    // 重载赋值操作符 =  
    MyClass& operator=(const MyClass& other)
    {
        if (this != &other) // 防止自赋值  
        { 
            m_val = other.getVal();
        }
        return *this;
    }

    // 重载复合赋值操作符 +=  
    MyClass& operator+=(const MyClass& other) 
    {
        m_val += other.getVal();
        return *this;
    }

    // 重载复合赋值操作符 <<=  
    MyClass& operator<<=(int shift) 
    {
        m_val <<= shift;
        return *this;
    }

    // 其他复合赋值操作符类似

    int getVal() const { return m_val; }

private:
    int m_val;

};

int main() 
{
    MyClass obj1(1);
    MyClass obj2(2);

    obj1 += obj2;       // obj1 = obj1 + obj2  
    printf("after obj1 += obj2 , obj1.getVal() : %d\n", obj1.getVal());

    obj1 <<= 1;         // obj1 = obj1 << 1  
    printf("after obj1 <<= 1 , obj1.getVal() : %d\n", obj1.getVal());

    return 0;
}

上面代码的输出为:

after obj1 += obj2 , obj1.getVal() : 3
after obj1 <<= 1 , obj1.getVal() : 6

2.6 重载下标操作符

重载下标操作符 [] 允许使用方括号语法来访问自定义类型的元素。这对于创建类似数组或容器的自定义类型非常有用。如下为样例代码:

#include   
#include  

class MyClass 
{
public:
    // 构造函数  
    MyClass(int size) : m_datas(size,0) {}

    // 重载下标操作符 []  
    int& operator[](int index)
    {
        if (index < 0 || index >= m_datas.size()) 
        {
            throw std::out_of_range("out of range");
        }
        return m_datas[index];
    }

private:
    std::vector<int> m_datas;

};

int main() 
{
    MyClass myArray(10); // 创建一个大小为10的数组

    // 使用下标操作符设置和获取值  
    myArray[2] = 2;
    printf("myArray[2] = %d\n", myArray[2]);

    return 0;
}

上面代码的输出为:

myArray[2] = 2

2.7 重载前缀和后缀操作符

重载前缀( ++ )和后缀( ++ 、 – )自增和自减操作符允许自定义类型的自增和自减行为。这些操作符的重载通常用于类,这些类代表可以递增或递减的值,例如计数器或迭代器。如下为样例代码:

#include   
#include  

class MyClass 
{
public:
    // 构造函数  
    MyClass(int val = 0) : m_val(val) {}

    // 重载前缀自增操作符 ++  
    MyClass& operator++() 
    {
        ++m_val;
        return *this;
    }

    // 重载后缀自增操作符 ++  
    MyClass operator++(int) 
    {
        MyClass temp = *this;
        ++m_val;
        return temp;
    }

    // 重载前缀自减操作符 -- 与后缀自减操作符 -- 类似

    int getVal() const { return m_val; }

private:
    int m_val;

};

int main() 
{
    MyClass obj(10); 

     // 使用前缀自增  
    ++obj;
    printf("after ++obj , obj.getVal() : %d\n", obj.getVal());

    // 使用后缀自增  
    obj++;
    printf("after obj++ , obj.getVal() : %d\n", obj.getVal());

    return 0;
}

上面代码的输出为:

after ++obj , obj.getVal() : 11
after obj++ , obj.getVal() : 12

在上面代码中,MyClass 类有一个私有成员 m_val,用于存储计数器的当前值。前缀自增和自减操作符直接修改 m_val 并返回 *this 的引用,这样可以进行链式操作。后缀自增和自减操作符则先保存当前值,然后修改 m_val ,并返回修改之前的值。注意,后缀操作符的参数是一个未使用的整数,这只是一个标记,用于区分前缀和后缀版本的操作符。

2.8 重载函数调用操作符

在 C++ 中,可以通过重载函数调用操作符 operator() 来使得一个对象可以像函数一样被调用。如下为样例代码:

#include   

class MyClass
{
public:
	int operator()(int val1, int val2)
	{
		return val1 + val2;
	}	
};

int main()
{
	MyClass obj;
	int res = obj(1, 2);  // 调用重载的函数调用操作符
	printf("obj(1, 2) : %d\n", res);

	return 0;
}

上面代码的输出为:

obj(1, 2) : 3

在上面代码中,定义了一个名为 MyClass 的类,它有一个重载的函数调用操作符 operator() 。这个操作符接收两个 int 类型的参数,并返回它们的和。
需要注意的是,虽然可以重载函数调用操作符来使得对象像函数一样被调用,但这并不意味着对象真的变成了一个函数。对象仍然是对象,只是提供了一种新的方式来访问它的某些功能。在实际编程中,应该谨慎使用这个函数调用操作符的重载,避免引起混淆或误解。

2.9 重载逗号操作符

逗号操作符通常用于在表达式中顺序执行多个操作,并返回最后一个操作的结果。通常不建议重载逗号操作符,如果确实需要重载,可以通过在类定义中添加一个名为operator,的成员函数来实现。这个函数应该接收一个参数(即逗号后面的值),并返回一个与类类型相同的对象,以便可以连续使用逗号操作符。如下为样例代码:

#include   

class MyClass
{
public:
	// 重载逗号操作符  
	MyClass& operator,(int val) 
	{
		m_val = val;
		return *this;	// 返回当前对象的引用,以便继续链式调用  
	}

	int getVal() const { return m_val; }

private:
	int m_val;
};

int main()
{
	MyClass obj;

	// 使用重载的逗号操作符  
	obj, 1, 2, 3;
	printf("after obj, 1, 2, 3 obj.getVal() : %d\n", obj.getVal());

	return 0;
}

上面代码的输出为:

after obj, 1, 2, 3 obj.getVal() : 3

在上面代码中,MyClass 有一个成员变量 m_val ,以及一个重载的逗号操作符 operator, 。当逗号操作符被调用时,它会打印传入的新值,并更新 m_val 成员变量。然后,它返回当前对象的引用,以便可以连续使用逗号操作符。

2.10 重载内存管理操作符

在 C++ 中,可以为特定的类重载 new 和 delete 操作符以及它们的数组版本 new[] 和 delete[] ,以自定义该类对象的内存管理行为。这种重载通常在类的内部进行,而不是在全局作用域。重载这些操作符允许控制对象的创建和销毁过程,例如,可以实现自定义的内存分配策略、跟踪对象的创建和销毁等。如下为样例代码:

#include  // 为了使用 std::bad_alloc  

class MyClass 
{
public:
	// 重载 new 操作符  
	void* operator new(size_t size) 
	{
		void* ptr = malloc(size);
		if (!ptr) 
		{
			throw std::bad_alloc();
		}
		// 在这里可以添加自定义的内存分配逻辑  
		return ptr;
	}

	// 重载 delete 操作符  
	void operator delete(void* ptr) noexcept 
	{
		if (ptr) 
		{
			// 在这里可以添加自定义的内存释放逻辑  
			free(ptr);
		}
	}

	// 重载 new[] 操作符  
	void* operator new[](size_t size) 
	{
		void* ptr = malloc(size);
		if (!ptr) 
		{
			throw std::bad_alloc();
		}
		// 在这里可以添加自定义的数组内存分配逻辑  
		return ptr;
	}

		// 重载 delete[] 操作符  
	void operator delete[](void* ptr) noexcept 
	{
		if (ptr) 
		{
			// 在这里可以添加自定义的数组内存释放逻辑  
			free(ptr);
		}
	}

	// 类的其他成员...  
};

注意,当为类重载 new 和 delete 时,通常也应该重载 new[] 和 delete[] 以保持一致性。这是因为当使用数组形式的 new 创建对象时, C++ 会调用 new[] ,而不是 new 。同样,当使用 delete[] 释放数组形式的对象时,会调用 delete[] ,而不是 delete 。
重载这些操作符时,必须确保可以正确地管理内存,避免内存泄漏,并且在内存分配失败时抛出 std::bad_alloc 异常。

3 非成员函数重载操作符

在 C++ 中,非成员函数也可以重载操作符,这种重载通常用于定义两个类之间的操作符行为。非成员函数重载操作符通常被声明为友元函数,以便它们可以访问类的私有和保护成员。非成员函数重载操作符通常用于定义二元操作符的行为,这些操作符作用于类的两个对象。
如下是一个非成员函数重载 + 操作符的样例,其他的重载类似:

#include   

class MyClass
{
public:
	// 构造函数  
	MyClass(int val1,int val2) : m_val1(val1), m_val2(val2) {}

	// 重载 + 操作符为非成员函数  
	friend MyClass operator+(const MyClass& lhs, const MyClass& rhs);

	int getVal1() const { return m_val1; }
	int getVal2() const { return m_val2; }

private:
	int m_val1;
	int m_val2;
};

// 非成员函数重载 + 操作符  
MyClass operator+(const MyClass& objLeft, const MyClass& objRight)
{
	return MyClass(objLeft.m_val1 + objRight.m_val1, objLeft.m_val2 + objRight.m_val2);
}

int main()
{
	MyClass obj1(1, 2);
	MyClass obj2(3, 4);

	MyClass obj = obj1 + obj2;

	printf("obj.getVal1() : %d\n", obj.getVal1());
	printf("obj.getVal2() : %d\n", obj.getVal2());

	return 0;
}

上面代码的输出为:

obj.getVal1() : 4
obj.getVal2() : 6

在这个例子中, + 操作符被重载为一个非成员函数,并且被声明为 MyClass 类的友元。这意味着它可以访问 MyClass 类的私有成员 m_val1 和 m_val2 。非成员函数 operator+ 接受两个 MyClass 对象作为参数,并返回一个新的 MyClass 对象,该对象是两个输入对象对应成员的和。
由于 operator+ 是友元函数,因此它可以在类的外部定义,但仍然可以访问类的私有和保护成员。这允许为两个 MyClass 对象定义加法操作,而无需将这些对象作为类的成员函数传递。
重载非成员函数操作符的一个优点是,当操作符作用于不同类型的对象时,可以定义其行为。例如,可以重载 + 操作符,以便它可以将一个 MyClass 对象和一个 double 值相加。这无法使用类的成员函数重载实现,因为成员函数的第一个参数总是类的对象。

4 操作符重载的注意事项

在 C++ 中重载操作符时,需要注意一些常见的错误和使用误区。以下是一些注意事项:
(1)不要过度使用操作符重载
操作符重载应该用于提供直观、自然的语法,而不是为了模仿语言本身不支持的操作。不要滥用操作符重载来创建含义模糊或令人困惑的接口。
(2)保持操作符的语义一致性
当重载一个操作符时,应该尽量保持该操作符的语义与它在其他上下文中的含义一致。例如,重载 + 操作符时,它应该表示加法操作,而不是其他任何不相关的操作。
(3)避免重载常用操作符
重载像&&||!等常用逻辑操作符可能会导致代码可读性和可维护性下降。这些操作符在C++中有特定的含义,重载它们可能会导致混淆。
(4)注意返回类型
重载操作符时,要注意返回类型。例如,一元操作符(如 +- )通常返回操作数的类型,而二元操作符(如 +-*/ )通常返回一个新的对象,该对象表示操作的结果。
(5)友元函数与成员函数的选择:根据需要选择将操作符重载为友元函数还是成员函数。友元函数允许访问类的私有和保护成员,这对于非成员操作符重载通常是有用的。然而,如果操作符的行为强烈依赖于类的状态,则将其作为成员函数可能更为合适。
(6)处理异常和错误
在重载的操作符中,要确保正确处理异常和错误。如果操作符的实现可能抛出异常,那么应该捕获这些异常并适当地处理它们。
(7)考虑对称性和交换性
对于一些二元操作符(如==!=<>等),确保它们的行为是对称的,并且满足交换律(如果适用)。例如,如果a == b为真,那么b == a也应该为真。

你可能感兴趣的:(突破编程_C++_基础教程,c++)