本文是上一篇博文:C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=)的延续,需要比较完整的了解C++面向对象编程:重载操作符,可以先浏览C++面向对象编程之六:重载操作符(<<,>>,+,+=,==,!=,=),浏览过的博友忽略这段话即可。
接着上一篇博文,假设我们有一个MyInt类,实现的功能跟内置的类型int一样。那么下面我们为这个MyInt类实现一下相关的操作符重载吧
重载操作符++,--
1.重载为成员函数还是非成员函数?
对于++,--操作符而言,C++没有规定重载为成员函数还是非成员函数,但++,--操作会改变非共有成员变量的属性,如果重载为非成员函数,需要设置该非成员函数为类的友元,这破坏了类的封装性,所以对于++,--操作符而言,更建议重载为类的成员函数。
2.形参
++操作有前++和后++,--操作有前--和后--,为了区分它们,C++规定将后缀式操作符函数额外增加一个无用的int类型形参,这个额外无用的int类型形参,存在的唯一目的是,只是作为一个标记,将前缀操作符和后缀操作符区分开。当使用后缀式操作符(例如:后++,后--等)的时候,编译器将0传递给这个形参,作为这个形参的实参。
3.返回值类型
a.对于前缀操作符,例如:
int a = 1;
cout << "++++a = " << ++++a << endl; //打印结果:++++a = 3
cout << "a = " << a << endl; //打印结果:a = 3
所以,我们设计MyInt类时,前++,前--的返回值类型为MyInt&,因为假如返回值类型为MyInt,第3行的打印结果:a = 2,跟内置的类型不一致了。
b.对于后缀操作符,例如:
int b = 1;
cout << "b++" << b++ << endl; //打印结果:b = 1
cout << "b = " << b << endl; //打印结果:b =2
//cout << "b++++" << b++++ << endl; //错误:C++是不允许后缀操作符链式操作的
所以,我们设计MyInt类时,后++,后--的返回值类型为const MyInt,因为假如返回值类型为MyInt&,第2行的打印结果:b = 2,跟内置类型不一致了。
#include <iostream>
using namespace std;
class MyInt
{
friend ostream& operator<<(ostream &cout, const MyInt &a);
friend istream& operator>>(istream &cin, MyInt &a);
public:
MyInt():m_num(0)
{
}
MyInt(const int num):m_num(num)
{
}
MyInt& operator=(const MyInt &b)
{
m_num = b.m_num;
return *this;
}
MyInt& operator++()
{
++m_num;
return *this;
}
const MyInt operator++(int)
{
MyInt temp = *this;
m_num++;
return temp;
}
MyInt& operator--()
{
--m_num;
return *this;
}
const MyInt operator--(int)
{
MyInt temp = *this;
m_num--;
return temp;
}
private:
int m_num;
};
ostream& operator<<(ostream &cout, const MyInt &a)
{
cout << a.m_num;
return cout;
}
istream& operator>>(istream &cin, MyInt &a)
{
cin >> a.m_num;
return cin;
}
int main(int argc, char *argv[])
{
MyInt a = 1;
cout << "a = " << a << endl;
cout << "++++a = " << ++++a << endl;
cout << "a = " << a << endl;
MyInt b = 1;
cout << "b = " << b << endl;
cout << "b++ = " << b++ << endl;
cout << "b = " << b << endl;
//cout << "b++++" << b++++ << endl; //错误:后缀操作符不允许链式操作
MyInt c = 1;
cout << "c = " << c << endl;
cout << "----c = " << ----c << endl;
cout << "c = " << c << endl;
MyInt d = 1;
cout << "d = " << d << endl;
cout << "d-- = " << d-- << endl;
cout << "d = " << d << endl;
//cout << "d---- = " << d---- << endl; //错误:后缀操作符不允许链式操作
return 0;
}
重载下标操作符[]
假设我们要设计一个IntArray类,并为这个类重载下标操作符[]
1.重载为成员函数还是非成员函数?
C++规定,重载下标操作符[]必须为类的成员函数。这是为什么呢?对于重载操作符为全局函数和重载操作符为类的成员函数,形参的个数看上去是有区别的,一般重载操作符为类的成员函数,其形参个数看起来比重载操作符为全局函数的形参个数少1个,实际上,重载操作符为类的成员函数,这个成员函数有一个隐含的this指针形参,限定为操作符的第一个操作数,所以对于编译器而言。形参个数并没有少1个的。而重载操作符为全局函数,这个全局函数的第一个形参则为操作符的第一个操作数。所以将重载下标操作符为类的成员函数,那么第一个形参是隐含的this指针,第二个形参是下标索引。是跟内置类型,例如:int arr[10] = {0};cout << arr[9] << endl; //第一个形参的实参为arr,第二个形参的实参为下标9,这样的命名规范是一致的。但如果C++没有规定重载下标操作符[]必须为类的成员函数,那么我们要设计一个IntArray类,并为这个类重载下标操作符[],我们完全可以为这个IntArray类重载下标操作符[]为全局函数,定义成这个全局函数的第一个形参为下标索引,第二个形参为类名的引用,即:IntArray& operator[](const int index, IntArray&);函数体内我们同样可以实现功能,我们用函数调用法:IntArray arr[10] = {0}; cout << operator(9, arr) << endl; 这样我们同样可以取出数组中对应下标的值,看起来也没有错。但简化的调用版本为:9[arr];这就是错误的了,所以C++规定,重载下标操作符[]必须为类的成员函数
2.形参
因为重载下标操作符[]必须为类的成员函数,第一个形参是隐含形参,即为成员函数内隐含的this指针,第二个形参是下标索引,定义为:const int index
3.返回值类型
//情况1
int arr1[3] = {1, 2, 3}; //左值
int a = arr1[0]; //右值
arr1[2] = 5;
//情况二
const int arr2[3] = {10, 20, 30}; //常量数组
cout << arr2[0] << endl; //可以通过下标正常访问常量数组
arr2[2] = 5; //错误:常量对象的值是不允许被修改的
a.为了适应第一种情况,重载下标操作符可以做左值,也可以做右值的特性。所以返回值类型为:int&,并且应该将该重载函数重载为普通成员函数,因为重载下标操作符做左值时,是允许修改值的,例如:arr[2] = 5
b.为了适应第二种情况,对于常量数组对象,常量对象只能调用常函数,并且常函数是不能修改成员变量的属性的,所以应该将重载函数重载为常成员函数。并且常量对象的值是不允许被修改的,所以返回值类型为:const int&
综上所述:我们重载下标操作符[]时,应该重载两个版本,如下:
int& operator[](const int index); //适用于重载下标操作符做左值或右值时
const int& operator[](const int index) const; //适用于常量对象使用下标操作符读对应下标的数据时
#include <iostream>
#include <cassert>
using namespace std;
class IntArray
{
public:
IntArray(const int length)
{
assert(length > 0);
m_length = length;
mp_arr = new int[length]();
}
IntArray(const IntArray &intArr)
{
m_length = intArr.m_length;
mp_arr = new int[m_length]();
for (int i = 0; i < m_length; i++)
{
mp_arr[i] = intArr.mp_arr[i];
}
}
~IntArray()
{
delete [] mp_arr;
mp_arr = NULL;
}
int& operator[](const int index)
{
return mp_arr[index];
}
const int& operator[](const int index) const
{
return mp_arr[index];
}
private:
int m_length;
int *mp_arr;
};
int main(int argc, char *argv[])
{
IntArray arr(3);
for (int i = 0; i < 3; i++)
{
cout << "arr[" << i << "] = " << arr[i] << endl;
arr[i] = i;
}
cout << endl;
for (int i = 0; i < 3; i++)
{
cout << "arr[" << i << "] = " << arr[i] << endl;
}
return 0;
}
重载箭头操作符->
1.重载为成员函数还是非成员函数?
C++规定,重载箭头操作符->必须为类的成员函数。
2.形参
因为重载箭头操作符->必须为类的成员函数,第一个形参是成员函数内隐含的this指针
3.返回值类型
返回指向对象的指针,看设计类的需求。
解引用操作符*和箭头操作符->通常用在实现智能指针的类中。
example:通过智能指针,设计一个怪物类,这类怪物具有分身技能,可以1变为2,分身越多,给每只怪物额外加成的属性就越多,同时这类怪物具有魅惑功能,可以将另一种怪物魅惑后,成为自己的分身。
#include <iostream>
#include <unistd.h>
//#include <windows.h> //windows平台下屏蔽上一行代码,打开这一行代码
using namespace std;
#define BASE_BLOOD_ADD 100
#define BASE_MAGIC_ADD 50
class SmartPoint;
class AttrAdd
{
friend class SmartPoint;
public:
AttrAdd(const int bloodAdd, const int magicAdd):m_bloodAdd(bloodAdd), m_magicAdd(magicAdd)
{
}
int getBloodAdd() const
{
return m_bloodAdd;
}
int getMagicAdd() const
{
return m_magicAdd;
}
private:
int m_bloodAdd; //血量加成值
int m_magicAdd; //魔法加成值
};
class SmartPoint
{
friend class Monster;
public:
void updateAttrAdd()
{
mp_attrAdd->m_bloodAdd = BASE_BLOOD_ADD * m_counter;
mp_attrAdd->m_magicAdd = BASE_MAGIC_ADD * m_counter;
}
private:
SmartPoint(AttrAdd* const p_attrAdd):m_counter(1), mp_attrAdd(p_attrAdd)
{
updateAttrAdd();
}
~SmartPoint()
{
cout << "~SmartPoint()" << endl;
delete mp_attrAdd;
}
int m_counter;
AttrAdd* const mp_attrAdd; //根据自己复刻出来的怪物数量,给同属的怪物给与额外的属性加成值指针。
};
class Monster
{
friend ostream& operator<<(ostream &cout, const Monster &m);
public:
Monster(AttrAdd* const p, const int monsterId, const string name, const int blood, const int magic):
mp_attrAdd(new SmartPoint(p)), m_monsterId(monsterId), m_name(name), m_blood(blood), m_magic(magic)
{
}
Monster(const Monster &m):mp_attrAdd(m.mp_attrAdd)
{
++mp_attrAdd->m_counter;
mp_attrAdd->updateAttrAdd();
m_monsterId = m.m_monsterId;
m_name = m.m_name;
m_blood = m.m_blood;
m_magic = m.m_magic;
}
~Monster()
{
cout << "~Monster(): 怪物数量 = " << mp_attrAdd->m_counter << endl;
--mp_attrAdd->m_counter;
mp_attrAdd->updateAttrAdd();
if (mp_attrAdd->m_counter == 0)
delete mp_attrAdd;
}
Monster& operator=(const Monster &m)
{
//右值
++m.mp_attrAdd->m_counter;
m.mp_attrAdd->updateAttrAdd();
//左值
--mp_attrAdd->m_counter;
mp_attrAdd->updateAttrAdd();
if (mp_attrAdd->m_counter == 0)
delete mp_attrAdd;
m_monsterId = m.m_monsterId;
m_name = m.m_name;
m_blood = m.m_blood;
m_magic = m.m_magic;
mp_attrAdd = m.mp_attrAdd;
return *this;
}
AttrAdd& operator*() const //重载解引用操作符
{
return *mp_attrAdd->mp_attrAdd;
}
AttrAdd* operator->() const //重载箭头操作符->
{
return mp_attrAdd->mp_attrAdd;
}
int getMonsterId() const
{
return m_monsterId;
}
string getName() const
{
return m_name;
}
int getBlood() const
{
return m_blood + ((*this)->getBloodAdd());
}
int getMagic() const
{
return m_magic + ((*this)->getMagicAdd());
}
private:
int m_monsterId; //怪物id
string m_name;
int m_blood; //血量
int m_magic; //魔法
SmartPoint* mp_attrAdd;
};
ostream& operator<<(ostream &cout, const Monster &m)
{
cout << m.m_name << ",id = " << m.m_monsterId << ",基础血量 = " << m.m_blood << ",额外血量加成 = " <<
m->getBloodAdd() << ",总血量 = " << m.getBlood() << ",基础魔法 = " << m.m_magic << ",额外魔法加成 = "
<< m->getMagicAdd() << ",总魔法 = " << m.getMagic();
return cout;
}
int main(int argc, char *argv[])
{
string temp;
Monster m1((new AttrAdd(0, 0)), 10001, "雪女", 10000, 1000);
cout << "场景内出现了一只怪物:" << m1.getName() << endl;
cout << m1 << endl;
cout << "---------------------" << endl << endl;
sleep(3);
Monster m2(m1);
cout << m2.getName() << " 触发了分身术技能,目前场景内有2只 " << m1.getName() << " 并且每只" << m1.getName() <<"属性都有加强" << endl;
cout << m2 << endl;
cout << "---------------------" << endl << endl;
sleep(4);
Monster m3(m2);
cout << m3.getName() << " 触发了分身术技能,目前场景内有3只 " << m2.getName() << " 并且每只" << m1.getName() <<"属性都有加强" << endl;
cout << m3 << endl;
cout << "---------------------" << endl << endl;
sleep(4);
Monster m11 = Monster((new AttrAdd(0, 0)), 10002, "紫衣仙子", 10000, 1000);
cout << "场景内出现了另一种怪物:" << m11.getName() << endl;
cout << "目前场景内有两种怪物:有1只 " << m11.getName() << ",有3只 " << m3.getName() << endl;
cout << m3 << endl;
cout << m11 << endl;
cout << "---------------------" << endl << endl;
sleep(4);
temp = m2.getName();
m2 = m11;
cout << m11.getName() << " 对 " << temp << " 发动了魅惑技能,把1只 " << temp << " 魅惑成了自己的分身," <<
m11.getName() << " 属性加强," << temp << " 属性减弱" << endl;
cout << "目前场景内有两种怪物:有2只 " << m11.getName() << ",有2只 " << m3.getName() << endl;
cout << m1 << endl;
cout << m11 << endl;
cout << "---------------------" << endl << endl;
sleep(5);
temp = m1.getName();
m1 = m11;
m3= m11;
cout << m11.getName() << " 对 " << temp << " 发送了魅惑技能,把剩余的2只 " << temp << " 魅惑成了自己的分身,"
<< m11.getName() << "属性加强," << temp << " 从场景内消失" << endl;
cout << "目前场景内有一种怪物:有4只 " << m11.getName() << endl;
cout << m11 << endl;
cout << "---------------------" << endl << endl;
return 0;
}
重载函数调用操作符()
1.重载为成员函数还是非成员函数
C++规定,重载函数调用操作符必须为类的成员函数,而且一个类可以定义函数调用操作符的多个版本,由形参的个数和参数类型进行区分。
2.形参
不确定,根据实际开发需求而定
3.返回值类型
不确定,根据实际开发需求而定
#include <iostream>
using namespace std;
class CallFunc
{
public:
void operator()(int a) //重载函数调用操作符()
{
cout << a << endl;
}
void operator()(int a, int b) //重载函数调用操作符()
{
cout << a << endl;
cout << b << endl;
}
};
int main(int argc, char *argv[])
{
CallFunc print;
int a = 1;
int b = 2;
print(a);
print(b);
print(a, b);
return 0;
}
好了,C++重载操作符举例先写到这。下面总结一下
1.C++允许我们自定义的类类型重载操作符,从而让我们自定义的类类型也能像内置数据类型使用一样直观,进行加减乘除,比较大小等等。
2.重载操作符应该遵守跟内置的数据类型一样的使用习惯,重载操作符不改变操作符的优先级。
3.大部分重载操作符可以为全局函数或类的成员函数,重载操作符为全局函数时,形参个数看起来比重载操作符为类的成员函数多一个,实际上,重载操作符为类的成员函数,有一个隐含的this指针形参,为操作符的第一个操作数。对于操作符简化调用来说,形参是有顺序的,所以我们在重载操作符时,应该考虑形参的顺序,让其适应简化调用,避免例如像出现9[]等等问题。
4.重载操作符为全局函数时,应该将全局函数设置为类的友元(friend)。
5.必要时应该重载赋值操作符=,避免两个对象内部的指针指向同一片内存空间,导致出现悬垂指针的问题。
6.重载操作符操作符的本质是重载函数,例如:重载操作符为类的成员函数时,本质是调用了该类的成员函数。例如:MyInt c = a + b; //本质是:调用了MyInt类的成员函数,即 MyInt c = a.operator+(b);
7.不能的重载的操作符有
. :类的对象访问操作符
.* ->* :类的对象或或类的对象指针访问类中的函数指针操作符
:: :域操作符
?= :条件操作符
sizeof :长度操作符
# :预处理操作符
8.必须重载为类的成员函数的操作符有
= :赋值操作符
[] :下标操作符
-> :箭头操作符
() :函数调用操作符