1 运算符重载
1.1 定义
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
运算符重载在C++中是一个强大的特性,可以使代码更具可读性和可维护性。关键点的简要总结:
- 函数名:关键字
operator
后面接需要重载的运算符符号。- 函数原型:
返回值类型 operator操作符(参数列表)
。- 限制:
- 不能创建新的运算符,例如
operator@
。- 重载运算符必须有一个类类型参数。
- 内置类型的运算符含义不能改变,例如内置的整型
+
。- 作为类成员函数重载时,形参比操作数数目少1,因为成员函数的第一个参数为隐藏的
this
。- 以下运算符不能重载:
.*
、::
、sizeof
、?:
、.
。这些限制确保了运算符重载的使用不会破坏语言的基本语义和一致性。
1.2 示例
// 全局的operator==
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;
}
int main()
{
Test ();
return 0;
}
结果输出
2 赋值运算符重载
2.2 赋值运算符重载的格式
在C++中,赋值运算符重载的格式如下:
class 类名 {
public:
类名& operator=(const 类名& 对象名);
};
具体来说,赋值运算符重载函数的格式包括以下几个部分:
- 返回值类型:通常是类的引用类型(
类名&
),以支持链式赋值。 - 函数名:关键字
operator
后面接赋值运算符=
。 - 参数列表:通常是一个常量引用类型的参数(
const 类名&
),以避免不必要的拷贝。
以下是一个完整的示例,展示如何重载赋值运算符:
#include <iostream>
#include <cstring>
class MyStr {
private:
char* name;
int id;
public:
MyStr() : id(0), name(nullptr) {}
MyStr(int _id, const char* _name) {
id = _id;
name = new char[strlen(_name) + 1];
strcpy(name, _name);
}
MyStr(const MyStr& str) {
id = str.id;
name = new char[strlen(str.name) + 1];
strcpy(name, str.name);
}
MyStr& operator=(const MyStr& str) {
if (this != &str) {
delete[] name;
id = str.id;
name = new char[strlen(str.name) + 1];
strcpy(name, str.name);
}
return *this;
}
void display()
{
cout << name << endl;
}
~MyStr() {
delete[] name;
}
};
int main() {
MyStr str1(1, "example");
MyStr str2;
str2 = str1;
str1.display();
str2.display();
return 0;
}
在这个示例中:
MyStr& operator=(const MyStr& str)
是赋值运算符重载函数。- 它首先检查自赋值(
this != &str
),然后释放已有的资源,最后分配新资源并复制数据。
2.2.赋值运算符重载的要求
1 赋值运算符只能重载成类的成员函数
赋值运算符(
operator=
)只能重载成类的成员函数,不能重载成全局函数。这是因为赋值运算符需要访问对象的私有成员,而全局函数无法直接访问类的私有成员。赋值运算符的重载必须是成员函数的原因包括:
- 访问权限:成员函数可以访问类的私有和保护成员,而全局函数不能。
- 自赋值检查:成员函数可以使用
this
指针来检查自赋值(this != &other
),而全局函数没有this
指针。原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
2. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值,当用户没有显式实现赋值运算符重载时,编译器会生成一个默认的赋值运算符重载。这个默认的赋值运算符会逐字节地进行浅拷贝,即对每个成员变量进行简单的值拷贝。
需要注意的是:
- 内置类型成员变量:直接进行值拷贝。
- 自定义类型成员变量:调用对应类的赋值运算符重载来完成赋值。
以下是一个示例,展示默认赋值运算符的行为:
#include <iostream>
#include <cstring>
class MyStr {
private:
char* name;
int id;
public:
MyStr() : id(0), name(nullptr) {}
MyStr(int _id, const char* _name) {
id = _id;
name = new char[strlen(_name) + 1];
strcpy(name, _name);
}
~MyStr() {
delete[] name;
}
// 默认赋值运算符
};
int main() {
MyStr str1(1, "example");
MyStr str2;
str2 = str1; // 使用默认赋值运算符
return 0;
}
在这个示例中,str2 = str1
使用了编译器生成的默认赋值运算符。由于 MyStr
类包含指针成员 name
,默认赋值运算符会导致浅拷贝,即 str2.name
和 str1.name
指向同一块内存。这可能会导致双重释放内存的问题。
为了避免这种情况,通常需要显式重载赋值运算符,确保进行深拷贝,如之前的示例所示。
2.3 前置++和后置++重载
在C++中,前置++和后置++运算符可以重载,以便自定义类型的对象能够使用这些运算符。以下是如何重载这两个运算符的示例:
1 前置++运算符重载
前置++运算符在自增对象之前返回对象本身。其函数原型如下:
class MyClass {
private:
int value;
public:
MyClass() : value(0) {}
// 前置++运算符重载
MyClass& operator++() {
++value; // 先自增
return *this; // 返回自增后的对象
}
int getValue() const {
return value;
}
};
2 后置++运算符重载
后置++运算符在自增对象之后返回对象的原始值。为了区分前置和后置版本,后置版本需要一个 int
参数(该参数不使用,仅用于区分)。其函数原型如下:
class MyClass {
private:
int value;
public:
MyClass() : value(0) {}
// 后置++运算符重载
MyClass operator++(int) {
MyClass temp = *this; // 保存当前状态
++value; // 自增
return temp; // 返回自增前的对象
}
int getValue() const {
return value;
}
};
示例代码
以下是一个完整的示例,展示如何使用前置++和后置++运算符重载:
#include <iostream>
using namespace std;
class MyClass {
private:
int value;
public:
MyClass() : value(0) {}
// 前置++运算符重载
MyClass& operator++() {
++value;
return *this;
}
// 后置++运算符重载
MyClass operator++(int) {
MyClass temp = *this;
++value;
return temp;
}
int getValue() const {
return value;
}
};
int main() {
MyClass obj;
cout << "Initial value: " << obj.getValue() << endl;
++obj; // 使用前置++
cout << "After prefix ++: " << obj.getValue() << endl;
obj++; // 使用后置++
cout << "After postfix ++: " << obj.getValue() << endl;
return 0;
}
在这个示例中:
- 前置++运算符 (
++obj
) 先自增对象的值,然后返回自增后的对象。 - 后置++运算符 (
obj++
) 先保存对象的当前状态,自增对象的值,然后返回自增前的对象。