1. 赋值运算符重载
1.1 运算符重载
在C++中,为了增强代码的可读性,支持运算符重载的操作。运算符重载是有特殊函数名的函数,也具有返回类型、函数名和参数列表。函数名为operator,返回类型以实际为准。
函数原型:返回类型 operator操作符(参数列表)
注意:
- 不能重载编译器没有的操作符,比如 operator@
- 这几个操作符不能重载:.* / ?: / sizeof / :: / .
- 重载操作符必须有一个类类型参数
- 不能改变运算符的原含义
- 类成员函数重载时,实际参数会比显式写出的多一个,因为有this指针存在
重载函数的定义有不同的方法,先来介绍第一种
//重载函数写在类外
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
//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;
}
int main()
{
Date d1(2025, 4, 26);
Date d2;
operator==(d1, d2);
d1.Print();
d2.Print();
cout << (d2 == d1) << endl;
return 0;
}
这么写的问题来了,成员变量如果都是私有的,在类外就访问不到。要么在类里写成员函数,要么把成员变量设为公共。都挺麻烦的,来看第二种。
// 重载函数写在类里
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 这里就相当于
// d1.operator==(d2);
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2025, 4, 26);
Date d2;
// 下面验证二者等价
d1.operator==(d2);
d2 == d1;
d1.Print();
d2.Print();
cout << (d2 == d1) << endl;
return 0;
}
验证上面我说过的等价
通过汇编可以发现二者调用了同一个函数,即证。
这里还要强调,在类里的重载函数的参数有一个隐藏的this指针,为了保证参数和操作数数量一致,就得改变写法。
1.2 赋值运算符重载
- 赋值运算符重载格式。
参数类型:const Date& 引用能提高效率
返回类型:Date& 引用能提高效率,返回值是为了满足多次赋值
返回*this:要符合连续赋值的含义
接下来我要解释引用和传值在效率上的差异
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
// ---------看图片---------
// 图片1
Date operator=(const Date d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
// 图片2
Date& operator=(const& Date d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
cout << "~Date()" << endl;
_year = -1;
_month = -1;
_day = -1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 4, 14);
// 拷贝构造
// 一个已经存在的对象,拷贝给另一个要创建初始化的对象
Date d2(d1);
Date d3 = d1;
Date d4(2024, 5, 1);
// 赋值拷贝/赋值重载
// 一个已经存在的对象,拷贝赋值给另一个已经存在的对象
d1 = d4;
d1 = d2 = d4;
d1 = d1;
return 0;
}
可以直观对比出引用后提高的效率。引起这种差异的原因来自自定义类型传值传参时会调用其拷贝构造函数(详细看C++ – 类与对象(上1))
现在我要解释上面提到的 “符合连续赋值的含义”
代码中有这么一段
d2接受d4后,如果没有返回类型,那d1该怎么接受d2?
- 赋值运算符只能重载成类的成员函数不能重载成全局函数。
因为在全局的会和编译器生成的冲突,当成规定记住就行
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。 注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。(和拷贝构造函数一样)
1.3 赋值运算符重载中引用和传值
上面我们比较了引用和传值在效率上的差距,那引用就是没有问题的吗?来看这段代码
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_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;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
~Date()
{
cout << "~Date()" << endl;
_year = -1;
_month = -1;
_day = -1;
}
private:
int _year;
int _month;
int _day;
};
Date& func()
{
//static Date d(2024, 4, 14); 一会看这里
Date d(2024, 4, 14);
return d;
}
int main()
{
Date& ref = func();
ref.Print();
return 0;
}
为什么会出现这种运行结果?
这里需要了解函数栈帧的创建和销毁。如果我们在func里创建d的时候加上static修饰就不会出现这种情况了。
总结:
如果这个对象的生命周期到了,会析构,就传值返回。
如果这个对象的生命周期没到,不会析构,就引用返回,减少拷贝。
2. const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
//void func(const Date* this)
void func() const
{
// ......
}
3. 取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。