重载运算符使用成员函数还是非成员函数
- 非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数数目相同;而成员版本所需的参数数目少一个,因为其中的一个操作数是被隐式地传递的调用对象
- 定义重载运算符时,必须选择上述的其中一种格式,不能同时选择这两种格式,因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误
- 选择何种格式:对于某些运算符来说,成员函数是唯一合法的选择,在其他情况下这两中格式没有太大的区别,有时,根据类设计,使用非成员函数版本可能更好(尤其是为类定义类型转换时)
重载运算符的限制
通常情况下,虽然C++支持,但不应该重载逗号、取地址、逻辑与、逻辑或运算符
c++ primer 第5版 14.1 基本概念——某些运算符不应该被重载
参考书籍:
C++ Primer Plus(第6版)——11.2.2 重载限制
重载运算符实例
1.重载前置++和后置++运算符
#include <iostream>
namespace jj01
{
class CNumber
{
public:
CNumber(const int& num)
:m_num(num)
{
}
CNumber& operator++() //< 前置++
{
++this->m_num;
return *this;
}
CNumber operator++(int) //< 后置++
{
CNumber tmp = *this;
++*this; //< 调用前置++
return tmp;
}
friend std::ostream& operator<<(std::ostream& ostr, const CNumber& n)
{
ostr << n.m_num;
return ostr;
}
private:
int m_num;
};
void Test01()
{
CNumber n = 3;
std::cout << "n = " << ++n << std::endl; //< 4
std::cout << "n = " << n++ << std::endl; //< 4,返回++前的副本4,但n内部的m_num已改变5
std::cout << "n = " << n << std::endl; //< 5
}
}
2.重载运算符中的友元函数
如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数的顺序。
#include <iostream>
//! 在为类重载二元运算符时,常常需要用友元函数,友元函数的作用可以调整参数的使用顺序,例如CPerson对象*d还是d*CPerson对象
//! 如果不使用友元函数,则只有是CPerson对象*d
class CPerson
{
public:
CPerson(){}
explicit CPerson(double a)
{
this->m_a = a;
}
//! 1. 成员函数重载运算符*, 则限制了该运算符*的使用方式,只能p1 = p2*d(这里p1,p2是CPerson对象)
CPerson operator*(double d)
{
CPerson p;
p.m_a = this->m_a * d;
return p;
}
//! 2. 友元函数重载运算符*,则可以随意调整运算符的使用方式,p1 = p2*d或者p1 = d*p2,只需要调整括号内的参数顺序即可(const CPerson& per, double d)
friend CPerson operator*(double d, const CPerson& per) //< 注:在函数声明(头文件)和定义(源文件)分离的情况中,只有在类声明中的原型中才能使用friend关键字。除非函数定义也是原型,否则不能在函数定义中使用该关键字
{
CPerson p;
p.m_a = d * per.m_a;
return p;
}
private:
double m_a;
};
int main()
{
CPerson p1(5);
double x = 2;
CPerson p2;
p2 = x * p1; //< 这里如果注释掉友元函数,则会报错 ;在友元函数中翻译为operator*(x, p1)
p2 = p1 * x; //< 这里使用成员函数重载运算符*, 翻译为p1.operator*(x)
std::cin.get();
return 0;
}
参考书籍:
- C++ Primer Plus(第六版)——11.3友元
3. 重载<<运算符
#include <iostream>
using namespace std;
//! 重载<<运算符
class CPerson
{
public:
CPerson(int a) :m_a(a){}
//! 1. 使用友元函数重载<<运算符
/*friend ostream& operator<<(ostream &out, const CPerson& per)
{
out << per.m_a <<" ";
return out;
}*/
//! 2. 使用成员函数重载<<运算符,则这里使用的时候得把对象放在前面 per << out, 显然这种
ostream& operator<<(ostream &out)
{
out << this->m_a;
return out;
}
private:
int m_a;
};
int main()
{
CPerson p1(5);
CPerson p2(10);
//cout << p1; //< 1. 友元函数的方式:这里转化为 operator<<(cout, p1); 后面还可以接着接<< p2,可以使用cout << p1 << p2
//p1 << cout; //< 2. 成员函数的方式:这里转化为 p1.operator<<(cout),显然这里只能p1 << cout,无法实现连续输出 p2 << p1 << cout会报错
//! 综合1和2的方式,所以要实现连续的输出,则只能使用友元函数的方式
cin.get();
return 0;
}
4. 重载[ ]运算符(继承)
namespace jj01
{
class CArr
{
public:
CArr()
:m_nSize(0)
,m_pArr(nullptr)
{
m_pArr = new char[16]();
memset(m_pArr, 'u', 16);
}
const char& operator[](std::size_t nPos) const
{
return m_pArr[nPos];
}
char& operator[](std::size_t nPos)
{
return (const_cast<char&>(static_cast<const CArr&>(*this)[nPos]));
}
private:
std::size_t m_nSize;
char* m_pArr;
};
class CMyArr: public CArr
{
public:
void ShowInfo()
{
std::cout << (*this)[3] << std::endl; //< 调用基类的重载运算符[],结果为u
}
};
void Test01()
{
//! 直接调用的父类的重载运算符char& operator[]函数
//! 打印结果为 u
CMyArr ar;
std::cout << ar[3] << std::endl;
// 打印结果为 u
ar.ShowInfo();
}
}
参考书籍
C++ primer plus(第6版)——11.3.2 常用的友元:重载<<运算符