1.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,其也具有返回值类型,函数名字以及参数列表,其返回值类型、参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
这里参数个数跟操作数个数一致
注意:
①不能通过连接其他符号来创建新的操作符:比如
operator@
②重载操作符必须有一个类类型参数
③用于内置类型的运算符,其含义不能改变,例如:内置的整型
+
,不 能改变其含义
④作为类成员函数重载时,其形参看起来比操作数数目少
1
,因为成员函数的第一个参数为隐藏的this指针
⑤ .* :: sizeof ?: .
注意以上
5
个运算符不能重载。
.*
运算符的使用规则如下:
#include<iostream>
using namespace std;
class OB
{
public:
void func()
{
cout << "void func()" << endl;
}
};
typedef void(OB::*PtrFunc)() ;//成员函数指针类型
int main()
{
// 函数指针
// void (*ptr)();
// 成员函数要加&才能取到函数指针,全局函数可加也可以不加
PtrFunc fp = &OB::func;//定义成员函数指针fp指向函数func
//普通函数规定函数名就是地址,成员函数前面要加一个&
OB temp;//定义ob类对象temp
(temp.*fp)();//.*是把成员函数的指针取取出来,帮助我们调用成员函数的指针
//这里fp不是temp的成员
return 0;
}
下面是对一个==的运算符重载:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int GetYear()
{
return _year;
}
//private:
int _year;
int _month;
int _day;
};
// 重载成全局,无法访问私有成员
// 1、提供这些成员get和set
// 2、友元
// 3、重载为成员函数(一般使用这种)
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Date d3(2024, 4, 14);
Date d4(2024, 4, 15);
// 显式调用
operator==(d3, d4);
// 直接写,转换调用,编译会转换成operator==(d3, d4);
d3 == d4;//如果全局和成员函数同时存在,优先去调类里面的成员函数
//这就意味着如果重载了成员函数,再去重载全局函数就没有意义了
return 0;
}
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;
}
//标准赋值重载几个注意的点:
// 1.带返回值 2.支持连续赋值并且用引用返回,因为出了作用域还在
// 3.判断一下自己给自己赋值
// //赋值重载是默认成员函数,不能写成全局的,可以显式写,这是一个规定
Date& operator=(const Date& d)
{
if(this!=&d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;//不允许显式传this,实参和形参也不允许写this
//*this是d2也在栈上
}
private:
int _year ;
int _month ;
int _day ;
};
int main()
{
Date d3(2024, 4, 14);
Date d4(2024, 4, 15);
// 显式调用
d3.operator=(d4);//如果有两个操作数,注意是按照操作数是按照顺序来的,不能颠倒顺序
// 转换调用 等价于d3.operator=(d4);
d3 = d4;
return 0;
}
②赋值运算符只能重载成类的成员函数不能重载成全局函数,我们可以重载赋值运算符,但是无论形参的类型是什么,赋值运算符都必须定义为成员函数。
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 =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
③用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
if (this != &t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d1;
Date d2;
d1 = d2;
return 0;
}
编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,所以
如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必
须要实现。
跟拷贝构造类似
Date或者MyQueue默认生成的赋值就够用了
但是类似Stack/List等都需要们自己实现赋值重载要完成深拷贝。
Date或者MyQueue默认生成的赋值就够用了
但是类似Stack/List等都需要们自己实现赋值重载要完成深拷贝。
// 这里会发现下面的程序会崩溃掉,需要用深拷贝去解决
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_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.返回值和返回引用的比较
注意赋值表达式的返回值是等式左值
这里的k,j,d2分别是对应的赋值表达式的返回值
注意:传值返回并不是d作为返回值,因为d出了作用域就销毁了,传值返回会生成一个当前对象的拷贝,拷贝的是一个临时对象,临时对象作为这个表达式的返回值,这个临时对象的生命周期不在这个函数里面,用引用返回就可以不生成这里的拷贝了,用引用返回是生成的d的别名。
这里的func函数换成如下就可以避免这种情况了:
Date& func()
{
static Date d(2024, 4, 14);//new出来的也可以,malloc出来的也可以,主要看销不销毁
return d;
}
传值返回和传引用返回对应的是下面这两种情况:
下面是一个传引用返回的举例:
4.const成员
将
const
修饰的
“
成员函数
”
称之为
const
成员函数
,
const
修饰类成员函数,实际修饰该成员函数
隐含的
this
指针
,表明在该成员函数中
不能对类的任何成员进行修改。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//void Printf(Date* const this)
//void Print()
//{
// cout << "Print()" << endl;
// cout << "year:" << _year << endl;
// cout << "month:" << _month << endl;
// cout << "day:" << _day << endl << endl;
//}
//void Printf(const Date* const this)
void Print() const //这里的const修饰的是*this,将this类型改为const Date*
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1(2022, 1, 13);
d1.Print();//d1.Printf(&d1);//Date*
const Date d2(2022, 1, 13);//d2.Printf(&d2);
//因为这里是const Date d2,const对象取地址就是const Date*
//故d2.printf(&d2)中参数类型为const Date* ,const在*之前,修饰指向的内容不能修改
d2.Print();//这里必须调用含有const的Print(),否则会导致权限的放大
}
int main()
{
Test();
return 0;
}
结论:
①const对象不可以调用非const成员函数
②非const对象可以调用const成员函数
③不修改的成员函数都可以加上const
5.取地址和const取地址操作符重载
这两个默认成员函数一般不用重载,编译器默认会生成,
只有特殊情况,才需
要重载,比如
想让别人获取到指定的内容!
一个例子如下:
class A
{
public:
//自定义类型只要需要用运算符都得重载
// 默认成员函数:我们不实现,编译器会自己实现,我们实现了编译器就不会自己实现了
// 自定义类型一般不需要我们自己实现也能取地址
// 除非不想让别人取到这个类型对象的真实地址
A* operator&()
{
cout << "A* operator&()" << endl;
return nullptr;
}
const A* operator&() const
{
cout << "const A* operator&() const" << endl;
return (const A*)0xffffffff;
}
private:
int _a1 = 1;
int _a2 = 2;
int _a3 = 3;
};
int main()
{
A aa1;
const A aa2;
cout << &aa1 << endl;
cout << &aa2 << endl;
return 0;
}