赋值运算符重载
- 1.运算符重载
- 2.赋值运算符重载
- 3.取地址及const取地址操作符重载
如果一个类中什么成员都没有,那么该类简称为空类。而空类中其实并不是真的什么都没有,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
- 构造函数:主要完成初始化工作
- 析构函数:主要完成资源的清理工作
- 拷贝构造函数:主要用于使用同类对象初始化创建对象
- 赋值运算符重载:主要是把一个对象赋值给另一个对象
- 普通对象取地址重载
- const对象取地址重载(取地址重载很少会自己实现)
本篇文章,我们来学习赋值运算符重载,顺便简单了解一下普通取地址重载和const取地址重载。
1.运算符重载
概念:
对于内置类型而言,可以直接使用各种运算符,对于自定义类型而言,不能直接使用各种运算符,我们需要使用运算符重载来使自定义类型可以直接使用各种运算符,为此C++ 引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符 (参数列表)
特性:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载的运算符不必是成员函数,但必须有一个自定义类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 当一个运算符是双操作数的时候,第1个参数是左操作数,第2个参数是右操作数。
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针,指向调用对象。非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数数目相同。
- 运算符重载是一种函数,运算符有几个操作数,相应地函数就有几个参数,返回值是运算符运算后的结果。
- operator+()函数的名称使得可以使用函数表示法或者运算符表示法来调用它,当自定义类型对象使用运算符运算时,编译器会使用相应的运算符函数替换运算符。运算符左侧的对象是调用对象,运算符右边的对象是作为参数被传递的对象。
- .*、. 、 ::、 sizeof、 ?: 注意这5个运算符不能重载。这个经常在笔试选择题中出现。
- 大多数运算符都可以通过成员或非成员函数进行重载,但这4个运算符只能通过成员函数进行重载,分别是赋值运算符=、函数调用运算符()、下标运算符[]、通过指针访问类成员的运算符->
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
cout << (d1 == d2) << endl;
}
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
Date& operator+(const Date& d) const
{
this->year+=d.year;
this->month+=d.month;
this->day+=d.day;
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1999,2,4);
Date d2(199,4,5);
d1+d2;//实际上是d1.operate+(d2);
return 0;
}
重载运算符作为成员函数还是非成员函数?
对于很多运算符来说,可以选择使用成员函数或者非成员函数来实现运算符重载,一般来说,非成员函数应该是友元函数,这样它才能直接访问类的私有数据,但如果是将对象当成一个整体使用而无需访问其中的私有成员,那么非成员函数也无需是友元函数。
加法运算符需要两个操作数,对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显示地传递;对于友元版本来说,两个操作数都作为参数来传递。
在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式。因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。
2.赋值运算符重载
参数类型: const T&,传递引用可以提高传参效率
返回值类型: T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值,返回*this :要符合连续赋值的含义。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
//检测是否自己给自己赋值。
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
if (&left != &right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
}
return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员
原因:赋值运算符重载如果不在类中作为成员函数显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
用户没有显式实现时,编译器会生成一个默认的赋值运算符重载,默认的赋值运算符重载属于浅拷贝,对内置类型成员变量以值的方式逐字节拷贝。而对自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
DataType* _array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2;
s2 = s1;
return 0;
}
因此和拷贝构造函数一样,当对象中涉及对资源的申请时,我们需要自己实现赋值运算符来完成深拷贝。
3.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认的取地址重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!