C++运算符重载相关的几个问题

1. 为什么需要重载运算符:

C++预定义的运算符,只能用于基本数据类型的运算:整型、实型、字符型、逻辑型等,且不能用于对象的运算。
当我们需要在对象之间进行运算时,就需要重载运算符。

如果不重载运算符,实际上也可以完成逻辑运算,但是每次需要对函数对象进行运算操作时都要手动实现一次比较麻烦, 重载运算符本质上相等于是对类对象的运算封装成了一个operator函数,每次需要运算时直接调用operator函数即可,更简便、直观。

2. C++运算符重载的基本形式:

返回值类型 + operator关键字 + 待重载的运算符(函数形参列表) 
{
    函数体
}

3. C++运算符重载举例:

#include <iostream>
using namespace std;

//Complex类表示一个复数
class Complex {
public:
    Complex() = default;
    Complex(double r = 0.0, double i = 0.0) : m_real(r), m_imag(i) {}

    Complex& operator=(const Complex& rhs) { //赋值运算符
        this->m_real = rhs.m_real;
        this->m_imag = rhs.m_imag;
        return *this;
    }

    Complex operator+(const Complex& rhs) {
        //返回的是一个临时对象,所以返回值类型不是引用
        return Complex(this->m_real + rhs.m_real, this->m_imag + rhs.m_imag);
    }

    friend Complex operator-(const Complex& lhs, const Complex& rhs);

    bool operator==(const Complex& rhs) {
        if(this->m_real == rhs.m_real && this->m_imag == rhs.m_imag) {
            return true;
        }
        return false;
    }

    void print() {
        cout << m_real << '.' << m_imag << endl;
    }

private:
    double m_real;
    double m_imag;
};


//非成员函数,类的友元函数
Complex operator-(const Complex& lhs, const Complex& rhs) {
    return Complex(lhs.m_real - rhs.m_real, lhs.m_real - rhs.m_real);
}

int main() {
    Complex a(2, 2);
    Complex b(1, 1);

    Complex c = a + b;	//等价于 a.operator+(b);
    Complex d = a - b;	//等价于 operator-(a, b);

    c.print();
    d.print();

    return 0;
}

------

3.3
1.1

本示例代码中的注意事项:

  1. 重载运算符作为类的成员函数与普通函数时的区别:

在上面的例子中,operator+ 是Complex类的成员函数,operator- 是Complex类的非成员函数(friend友元函数),从中可以看出,重载运算符在作为类的成员函数 与 普通函数 时的区别:

重载运算符为成员函数时,函数参数的个数为运算符的目数减一。
这是因为类的成员函数有一个隐式参数*this指针。

  1. operator+ 函数的入参与返回值:

operator+ 和 operator- 的入参是 const Complex& 引用类型,这是因为如果使用 “值传递” 的参数类型,会在入参时构造一个Complex对象,调用类的构造函数、析构函数,这会增加函数开销,所以正确的做法是使用引用类型传参;

operator+ 和 operator- 的返回值类型是Complex对象而不是引用,因为它们需要返回一个新对象给左值。

4. C++中哪些运算符不能被重载:

C++中的运算符可分为 9大类:

1. 赋值运算符:	=
2. 算符运算符:	+, -, *, /, %
3. 逻辑运算符:	&&, ||, !
4. 关系运算符:	>, <, >=, <=, ==, !=
5. 位操作符:		~(取反), <<(左移), >>(右移), &(位与), |(位或), ^(位异或)
6. 自增自减运算符:	++, --
7. 复合运算符:	+=, -=, *=, /=, %=, <<=, >>=
8. 条件运算符: ? :
9. 逗号运算符:	,

------
 
 这其中,有 1类运算符不能被重载:
 条件运算符:	? :

常见的一些操作符:

1. 取地址操作符:	&
2. 解引用操作符:	*
3. 调用操作符:		()	(调用操作符是一对圆括号,括住传递函数的实参列表)
4. 箭头操作符:	->	
5. 作用域操作符:	::
6. 点操作符:			.
7. 下标操作符:		[]
8. new/delete([])操作符:	[]
9. 成员指针访问操作符:	->*, .* (当要访问的类的成员是指针类型时使用)
10.输入和输出操作符:	>>, <<

------

这其中,有 3类操作符不能被重载:
作用域操作符:		::
点操作符:				.
成员指针访问操作符:	.*

总结:
C++中绝大部分的操作符允许重载,不能重载的运算符只有5个:

.				:	成员访问运算符
.*				:	成员指针访问运算符
::				:	域运算符
? :			:	条件运算符
sizeof		:	长度运算符

5. C++ 流插入和流提取运算符的重载:

std::cout << "hello";

这条语句是如何起作用的?

  • 实际上,cout 是在 iostream 头文件中定义的 ostream类的对象;
  • <<运算符 能够用在 cout 上是因为,在 ostream类对<<运算符进行了重载。

实际上的效果是这样的:

// #include <iostream> //iostream 头文件中:

class ostream {
public:
	ostream& operator<<(const int n) {

    }
    
    ostream& operator<<(const char* s) {

    }
};

ostream cout;		//cout是ostream类的对象,cout也是ostream类型

cout<< 1 ;
等价于:
cout.operator<<(1);

因此,如果想要把我们自定义的类型通过 cout << 的方式进行打印,就需要重载 ostream类的流插入运算符<<

#include <iostream>
using namespace std;

class Complex {
public:
    Complex(double real = 0.0, double imag = 0.0) : m_real(real), m_imag(imag) {}

    friend ostream& operator<<(ostream& o, const Complex& s);

private:
    double m_real;
    double m_imag;
};

ostream& operator<<(ostream& o, const Complex& s) {
    o << s.m_real << '.' << s.m_imag;
    return o;
}

int main() {
    Complex c(1, 2);
    cout << c << endl;

    return 0;
}

------
运行结果:
1.2

------
解释:
"cout << c" 这句相当于是调用了代码中的 operator<< 运算符重载函数,
其中: cout 是operator<<(ostream& o, const Complex& s) 中的第一个参数入参,Complex类对象c是函数的第二个参数入参。
返回值 ostream& 只是一种书写习惯,没有实际作用。

《C++ Primer》中对于 重载运算符<< 的表述:

  通常情况下,输出运算符的第一个形参是一个非常量ostream对象的引用。之所以ostream是非常量是因为向流写入内容会改变其状态(实际上是向cout对象中写入内容);而该形参是引用是因为我们无法直接复制一个ostream对象。

  第二个形参一般来说是一个常量的引用,该常量是我们要打赢的类类型。第二个形参是引用的原因是我们希望避免复制实参;而之所以该形参可以是常量是因为(通常情况下)打印对象不会改变对象的内容。

  输入输出运算符必须是非成员函数,且必须是friend友元函数。

重载流提取运算符>> 举例:

#include <iostream>
using namespace std;

class Complex {
public:
    Complex() = default;

    friend istream& operator>>(istream& i, Complex& c);
    friend ostream& operator<<(ostream& o, const Complex& c);
private:
    double m_real;
    double m_imag;
};

istream& operator>>(istream& i, Complex& c) {
    i >> c.m_real >> c.m_imag;
    return i;
}

ostream& operator<<(ostream& o, const Complex& c) {
    o << c.m_real << '.' << c.m_imag;
    return o;
}

int main() {
    Complex c;
    cin >> c;
    cout << c << endl;

    return 0;
}

------
运行结果:
1
2
1.2

6. C++自增运算符和自减运算符的重载:

  • 自增运算符和自减运算符的特殊之处在于有前置和后置之分,而普通的重载形式无法区分这两种情况。为了解决这个问题,后置版本接受一个额外的(不被使用)int类型的形参。当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参。这个形参的唯一作用就是区分前置版本和后置版本的函数,而不是真的要在实现后置版本时参与运算。
  • C++语言并不要求递增和递减运算符必须是类的成员,但是因为它们改变的正好是所操作对象的状态,所以 建议将其设定为成员函数
  • 为了与内置版本保持一致:前置运算符应该返回递增或递减后 对象的引用;后置运算符应该返回对象的原值(递增或递减之前的值),返回的形式是一个值而非引用
  • 对于后置版本,在递增/递减对象之前,需要先记录对象的状态(额外定义一个临时变量,这也是后置版本的性能不及前置版本的原因), 然后递增/递减对象的值,但是返回原值。

举例:

class StrBlobPtr() {
public:
    StrBlobPtr& operator++();		//前置运算符
    StrBlobPtr& operator--();
    
    StrBlobPtr operator++(int);		//后置运算符
    StrBlobPtr operator--(int);
private:
    int curr;
};

StrBlobPtr& operator++() {
    ++curr;
    return *this;
}
StrBlobPtr& operator--() {
    --curr;
    return *this;
}

StrBlobPtr operator++(int) {
    StrBlobPtr ret = *this;		//对于后置版本,在递增对象之前需要记录对象的状态,这也是后置版本的性能不如前置版本的原因
    ++*this;
    return ret;		//递增/递减对象的值,但是返回原值
}
StrBlobPtr operator--(int) {
    StrBlobPtr ret = *this;
    --*this;
    return ret;
}

显式的调用后置运算符:

StrBlobPtr p(a1);
p.operator++(0);		//后置版本
p.operator++();			//前置版本
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值