C++的类对象之间的运算和结构变量一样,对象之间可以用“=”进行赋值,但是不能用“==”,“!=”,“>”,“<”“>=”“<=”进行比较,除非这些运算符经过了“重载”。
一、运算符重载基本概念
运算符重载:就是对已有的运算符 (C++ 中预定义的运算符) 赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为。
通过运算符重载,我们可以扩展C++中提供的运算符的适用范围,是指能够作用于对象。
Tips:
(1)运算符重载的实质是函数重载,可以重载为普通函数,也可以重载为成员函数
(2)把含运算符的表达式转换成对运算符函数的调用。
(3)把运算符的操作数转换成运算符函数的参数。
(4)运算符被多次重载时,根据实参的类型决定调用哪个运算符函数。
基本形式: 返回值类型operator 运算符(形参表){....}
二、运算符的重载
注意事项:
(1)重载为成员函数时,参数个数为运算符目数减一。
(2)重载为普通函数时,参数个数为运算符目数。
运算符重载为友元函数:
一般情况下 将运算符重载为类的成员函数是比较好的选择
但有时,我们不得不把运算符重载为普通函数,但是普通函数无法访问类的私有成员,我们就需要把运算符重载为友元函数,使得类的私有成员对该运算符重载函数开放。
Complex c3=c1+c2 本质上是 Complex c3=operator+(c1,c2)
Complex c4=c3-c1 本质上是 Complex c4=operator-(c3,c1)
三、赋值运算符的重载
有时候希望赋值运算符两边的类型可以不匹配,比如,把一个int类型变量赋值给一个Complex对象,或把一个 char * 类型的字符串赋值给一个字符串对象, 此时就需要重载赋值运算符“=”。
注意:赋值运算符“=”只能重载为成员函数
特别地,s1=s2无需重载,因为这是complex类对象=complex类对象
但是,这样实际上是一种浅拷贝,即会导致s1和s2所指向的内存相同(默认的=是在做指针传递,因为我们定义的str为指针类型),使得你在改变s1的值时也会改变s2的值(反之亦是)。
因此我们可能需要重载赋值运算符“=”,重新开辟出一块内存区域,来达到深拷贝的目的。
但是这样可能出现s1=s1,导致报错,所以不妨这样修改:
复制构造函数同样会有深拷贝与浅拷贝的问题,可以这样修改:
四、运算符重载实战练习(可变长整型数组)
#include<iostream>
using namespace std;
class CArray {//可变长数组类
private:
int size;//保存数组长度
int* ptr;//指针,指向动态分配的的数组地址
public:
CArray(int a = 0);//构造函数,表示创建一个长度为a的数组
CArray(const CArray& a);//复制构造函数,预防浅拷贝
~CArray();//析构函数,释放内存
void push_back(int i);//在数组尾部增加一个元素
int length(){return size;}//返回数组长度
int &operator[](int i) { return ptr[i]; }//传回的是一个地址
//返回值不能使用int,因为这样的话不支持a[i]=5
CArray& operator=(const CArray& a);
//赋值号的作用是使“=”左边对象里存放的数组,大小和内容都和右边的对象一样
};
CArray::CArray(int a):size(a)
{
if (a == 0)//如果长度为0,不分配内存
{
ptr = NULL;
}
else//否则分配对应长度的内存
{
ptr = new int[a];
}
}
CArray::CArray(const CArray& a)
{
if (a.ptr)//如果传入非空
{
ptr = new int[a.size];
memcpy(ptr, a.ptr, sizeof(int) * a.size);
size = a.size;
}
else//如果传入为空
{
ptr = NULL;
size = 0;
}
}
CArray::~CArray()
{
if (ptr)delete[] ptr;
}
void CArray::push_back(int i)
{
if (ptr)
{
int* tmp = new int[size + 1];//创建临时数组
memcpy(tmp, ptr, sizeof(int) * size);//拷贝原数组至临时数组
delete[] ptr;//释放原数组
ptr = tmp;//再令原数组指向临时数组的内存空间
}
else
{
ptr = new int[1];
}
ptr[size] = i;
size++;
}
CArray& CArray::operator=(const CArray& a)
{
if (ptr == a.ptr) //防止 a=a 这样的赋值导致出错
{
return *this;
}
if (a.ptr == NULL)
{ //如果 a 里面的数组是空的
if (ptr)//若原数组非空
{
delete[] ptr;
size = 0;
ptr = NULL;
}
}
else//若a数组非空
{
if (ptr)
{
delete[] ptr;
ptr = new int[a.size];
size = a.size;
memcpy(ptr, a.ptr, sizeof(int) * a.size);
}
else
{
ptr = new int[a.size];
size = a.size;
memcpy(ptr, a.ptr, sizeof(int) * a.size);
}
}
return *this;
};
int main() {
CArray a; //开始里的数组是空的
for (int i = 0; i < 5; ++i)
a.push_back(i);
CArray a2, a3;
a2 = a;
for (int i = 0; i < a.length(); ++i)
cout << a2[i] << " ";
a2 = a3; // a2 是空的
for (int i = 0; i < a2.length(); ++i) // a2.length() 返回 0
cout << a2[i] << " ";
cout << endl;
a[3] = 100;
CArray a4(a);
for (int i = 0; i < a4.length(); ++i)
cout << a4[i] << " ";
return 0;
}
五、流插入运算符和流提取运算符的重载
在c++编写程序时,我们如果要输入/输出一些东西,一般需要用到(cin)>>和(cout)<<运算符,称为流插入运算符(>>)与流提取运算符(<<)。
首先我们来看流提取运算符,cout是在iostream中定义的,ostream类的对象。<<能作用在cout上是因为在iostream中对<<进行了重载,考虑了一些string、char、int、double等c++基本类的输出。但是如果我们要输出一个自定义类的对象,则我们需要另写一个重载函数,可以形象理解为cout.operator<<(自定义类对象)。
考虑前面提到的复数类,如果我们需要输出一个复数类对象为a+bi的形式,则可以如下重载:
由于需要访问类中的私有成员real和img,我们需要把该重载函数定义为友元函数。
考虑创建一个a=1+2i,执行以下代码:
我们再来看流插入运算符,cin是在iostream中定义的,istream类的对象。>>能作用在cin上是因为在iostream中对>>进行了重载,考虑了一些string、char、int、double等c++基本类的输入。但是如果我们要输入一个自定义类的对象,则我们需要另写一个重载函数,可以形象理解为cin.operator>>(自定义类对象)。
考虑复数类,如果我们需要输入一个复数类对象,输入两个double类的数a,b,创建一个a+bi的复数,则可以如下重载:
由于需要访问类中的私有成员real和img,我们需要把该重载函数定义为友元函数。
考虑输入2 3,输出结果如下:
六、类型转换运算符和自增、自减运算符的重载
Ⅰ.有一类特殊的运算符:类型转换运算符,如double(),int(),char()等,通过这些运算符可以对数据类型进行强制转化,同样的,我们也可以对自定义的类对象进行强行的类型转换,但这需要我们对相应的类型转换运算符进行重载。
例如,我们对Complex类进行强制double类型转化,我们规定为返回Complex的实部real,则可以进行如下重载:
Ⅱ.c++中还存在一种特殊的运算符,即自增自减运算符,自增自减各有两种形式,有前置/后置之分(如++i为先加后取,i++为先取后加),为了区分所重载的是迁至运算符还是后置运算符,c++有如下规定:前置运算符作为一元运算符重载;后置运算符作为二元运算符重载,需要多写一个没用的参数。
我们仍然以Complex复数类为例
Ⅲ.我们还可以对指针操作“->”进行重载操作
运算符重载的注意事项:
(1)C++ 不允许定义新的运算符;
(2)重载后运算符的含义应该符合日常习惯;
(3)运算符重载不改变运算符的优先级;
(4)以下运算符不能被重载:".", ".*", "::", "?:", sizeof
(5)重载运算符(), [], ->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。
(6)重载运算符是为了让它能作用于对象,因此重载运算符,不允许操作数都不是对象 (有一个操作数是枚举类型也可以)。