一.运算符重载的概念
1.为什么要对运算符进行重载
C++预定义的运算符只适合内置基本的数据类型,对于我们自定义的类类型来说,并不能够实现相同的操作,比如实现两个类对象的相加操作等,所以为了实现对类对象进行相同的操作,就需要对运算符进行重载。
2.运算符重载的规则
2.1为了防止用户对标准类型进行运算符重载,C++规定重载后的运算符的操作对象必须至少有一个是用户自定义的类型。
2.2使用运算符不能违反运算符原来的句法规则。例如int index;重载%时,函数内部为%index。
2.3不能修改运算符原先的优先级。
2.4不能创建一个新的运算符,例如不能定义operator**(...)来表示求幂。
2.5以下四种函数只能通过成员函数来进行重载:=赋值运算符,()函数调用运算符,[]下标运算符,->通过指针访问类成员的运算符。
3.C++运算符重载的实质
函数重载或函数的静态多态。
4.重载运算符的两种形式
重载为类的成员函数或重载为类的非成员函数。
重载为类的成员函数比重载为类的非成员函数时的参数少一个。
5.不能重载的运算符
运算符 | 原因 |
?: | 不支持三目运算符重载 |
. | 成员运算符不能重载 |
.* | 成员指针运算符不能重载 |
:: | 作用域限定运算符不能重载 |
sizeof | 字节个数运算符不能重载 |
运算符重载函数不能定义为静态成员函数,因为静态成员函数中没有this指针。
6.当程序中有函数重载时,函数的匹配原则和顺序是什么?
6.1名字查找。
6.2确定候选函数。
6.3精确匹配:参数匹配而不做转换,或只是做微不足道的转换,如数组名到指针、函数名到指向函数的指针、T到const T。
6.4提升匹配:即整数提升(如bool到int、char到int、short到int、float到double)。
6.5使用标准转换匹配:如int到double、double到int、double到long double、Derived*到Base*、T*到void*、int到unsigned int。
6.6使用用户自定义匹配。
6.7使用省略号匹配:类似于printf中省略号参数。
7.运算符重载的注意事项及总结
7.1运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
7.2运算符重载的目的是让语法更加简洁。
7.3运算符重载不能改变本来寓意,不能改变基础类型寓意。
7.4运算符重载的本质是另一种函数调用(是编译器去调用)。
7.5这个函数统一的名字叫operator。
7.6重载函数可以写成全局或成员函数。
7.7重载函数如果写成全局的,那么双目运算符左边的是第一个参数,右边的是第二个参数。
7.8重载函数如果写成成员函数,那么双目运算符的左边是this,右边是第一个参数。
7.9不能改变运算符优先级,不能改变运算符的参数个数。
二.加号运算符重载
1.同类型的对象相加
class Maker
{
public:
Maker(int id, int age)
{
this->id = id;
this->age = age;
}
//写成成员函数,那么只需要一个参数,这个参数是加号的右边
Maker operator+(Maker &m2)
{
Maker temp(this->id + m2.id, this->age + m2.age);
return temp;
}
public:
int id;
int age;
};
//全局方式 //3.编译器调用这个函数
//Maker operator+(Maker &p1,Maker &p2)//2.编译器检查参数是否对应,第一个参数是加的左边,第二参数是加号的右边
//{
// Maker temp(p1.id + p2.id, p1.age + p2.age);
//
// return temp;
//}
void test01()
{
Maker m1(1, 20);
Maker m2(2, 22);
//+也叫双目运算符
Maker m3=m1 + m2;//1.编译器看到两个对象相加,那么编译器会去找有没有叫operator+的函数
cout << "id:" << m3.id << " age:" << m3.age << endl;
//复数加
Maker m4 = m1 + m2 + m3;
cout << "id:" << m4.id << " age:" << m4.age << endl;
}
2.不同类型的对象相加
// 定义 Maker 类
class Maker
{
public:
// 构造函数,初始化 id 和 age
Maker(int id, int age)
{
this->id = id;
this->age = age;
}
public:
int id; // Maker 的 ID
int age; // Maker 的年龄
};
// 定义 Student 类
class Student
{
public:
// 默认构造函数,将 mid 初始化为 0
Student()
{
mid = 0;
}
// 构造函数,接受一个整数参数,初始化 mid
Student(int id)
{
mid = id;
}
public:
int mid; // Student 的 ID
};
// 重载 + 操作符,将 Maker 和 Student 相加
Student operator+(Maker &m, Student &s)
{
// 创建一个临时 Student 对象,其 ID 为 Maker 的 id 和 Student 的 mid 相加
Student tmp(m.id + s.mid);
return tmp;
}
// 重载 + 操作符,将 Student 和 Maker 相加
Student operator+(Student &s, Maker &m)
{
// 创建一个临时 Student 对象,其 ID 为 Maker 的 id 和 Student 的 mid 相加
Student tmp(m.id + s.mid);
return tmp;
}
// 测试函数
void test()
{
// 创建一个 Maker 对象 m1,ID 为 1,年龄为 18
Maker m1(1, 18);
// 创建一个 Student 对象 s1,ID 为 2
Student s1(2);
// 使用重载的 + 操作符,将 m1 和 s1 相加,将结果赋值给 s2
Student s2 = m1 + s1;
// 使用重载的 + 操作符,将 s1 和 m1 相加
s1 + m1;
}
三.减号运算符重载
/ 定义 Maker 类
class Maker
{
public:
// 构造函数,接受一个整数参数,初始化 id
Maker(int id)
{
this->id = id;
}
// 重载 - 操作符,将两个 Maker 对象相减
Maker operator-(Maker &m2)
{
// 创建一个临时 Maker 对象,其 id 为当前对象的 id 减去另一个 Maker 对象的 id
Maker tmp(this->id - m2.id);
// 返回结果
return tmp;
}
public:
int id; // Maker 的 ID
};
// 重载 - 操作符,将 Maker 对象和整数相减
int operator-(Maker &m, int b)
{
// 返回 Maker 对象的 id 减去整数 b 的结果
return m.id - b;
}
// 测试函数
void test()
{
// 创建两个 Maker 对象 m1 和 m2,分别初始化为 10 和 5
Maker m1(10);
Maker m2(5);
// 使用重载的 - 操作符,将 m1 减去 m2,结果赋值给 m3
Maker m3 = m1 - m2;
// 打印结果
cout << m3.id << endl;
// 使用重载的 - 操作符,将 m3 减去整数 5,结果赋值给变量 a
int a = m3 - 5;
// 打印结果
cout << a << endl;
}
四.左移和右移运算符重载(重点难点)
1.左移运算重载
1.1cout是对象,<<是左移运算符。
1.2重载左移运算符是为了直接打印对象。
1.3形参和实参是一个对象。
1.4不能改变库类中的代码。
1.5ostream中把拷贝构造函数私有化了。
1.6如果要和endl一起使用,那么必须返回ostream的对象。
class Maker
{
//如果要访问类的私有成员,那么把<<重载函数声明为友元
friend ostream& operator<<(ostream &out, Maker &m);
public:
Maker(int id,string name)
{
this->id = id;
this->name = name;
}
private:
int id;
string name;
};
//1.形参和实参是一个对象
//2.不能改变库类中的代码
//3.ostream中把拷贝构造函数私有化了
//4.如果要和endl一起使用,那么必须返回ostream的对象
ostream& operator<<(ostream &out, Maker &m)
{
cout << m.id <<" "<<m.name<< endl;
return out;
}
void test01()
{
Maker m(10,"小花");
cout << m << endl;
cout << endl;
/*
endl是一个函数
operator<<(endl)
*/
cout << 10;//内部重载了基础数据类型
}
2.右移运算符重载
// 定义 Maker 类
class Maker
{
// 声明友元函数,用于重载输入流操作符
friend istream &operator>>(istream &in, Maker &m);
public:
// 构造函数,接受名称和年龄参数,初始化 name 和 age
Maker(string name, int age)
{
this->name = name;
this->age = age;
}
// 返回年龄的公有方法
int getAge()
{
return age;
}
private:
string name; // 姓名
int age; // 年龄
};
// 重载输入流操作符,用于从输入流中读取 Maker 对象的数据
istream &operator>>(istream &in, Maker &m)
{
// 从输入流中读取年龄和姓名
in >> m.age;
in >> m.name;
return in;
}
// 测试函数
void test02()
{
// 创建两个 Maker 对象 m 和 m2,并初始化它们的姓名和年龄
Maker m("悟空", 15);
Maker m2("悟空2", 25);
// 从标准输入流中分别读取 m 和 m2 的数据
cin >> m >> m2;
// 输出 m 和 m2 的年龄
cout << m.getAge() << endl;
cout << m2.getAge() << endl;
}
五.赋值运算符重载
1.编译器默认给类提供了一个默认的赋值运算符重载函数
2.默认的赋值运算符重载函数进行了简单的赋值操作
// 定义 Maker 类
class Maker
{
public:
// 默认构造函数,将 id 和 age 初始化为 0
Maker()
{
id = 0;
age = 0;
}
// 构造函数,接受 id 和 age 作为参数,分别初始化对应成员变量
Maker(int id, int age)
{
this->id = id;
this->age = age;
}
public:
int id; // Maker 的 id
int age; // Maker 的年龄
};
// 测试函数
void test()
{
// 创建一个 Maker 对象 m1,使用第二个构造函数初始化,id 为 10,age 为 20
Maker m1(10, 20);
// 创建一个 Maker 对象 m2,使用默认构造函数初始化,id 和 age 都为 0
Maker m2;
// 将 m1 赋值给 m2,这将调用默认的赋值运算符重载函数
m2 = m1;
// 输出 m2 的 id 和 age
cout << m2.id << " " << m2.age << endl;
}
3.当类有成员指针时,然后在构造函数中申请堆区空间,在析构函数中释放堆区空间,会出现同一块空间释放两次,然后内存泄漏,所以要重写赋值运算符重载函数
class Student
{
public:
Student(const char *name)
{
pName = new char[strlen(name) + 1];
strcpy(pName, name);
}
//防止浅拷贝
Student(const Student &stu)
{
pName = new char[strlen(stu.pName) + 1];
strcpy(pName, stu.pName);
}
//重写赋值运算符重载函数
Student &operator=(const Student &stu)
{
//1.不能确定this->pName指向的空间是否能装下stu中的数据,所以先释放this->pName指向的空间
if (this->pName != NULL)
{
delete[] this->pName;
this->pName = NULL;
}
//2.申请堆区空间,大小由stu决定
this->pName = new char[strlen(stu.pName) + 1];
//3.拷贝数据
strcpy(this->pName, stu.pName);
//4.返回对象本身
return *this;
}
~Student()
{
if (pName != NULL)
{
delete[] pName;
pName = NULL;
}
}
void printStudent()
{
cout << "Name:" << pName << endl;
}
public:
char *pName;
};
void test02()
{
Student s1("悟空");
Student s2("小林");
s1.printStudent();
s2.printStudent();
s1 = s2;//赋值操作
s1.printStudent();
s2.printStudent();
//复数运算不会出错
//s1 = s2 = s3;
}
4.赋值运算符重载函数中,为什么要返回引用?
void test03()
{
Student s1("a");
Student s2("b");
Student s3("c");
s1 = s2 = s3;//s3赋值s2,s2赋值给s1
cout << &(s2 = s3) << endl;
cout << &s2 << endl;
//如果返回的是值,s2=s3这个表达式会产生一个新的对象
//s1=s2=s3,赋值运算符本来的寓意,是s3赋值s2,s2赋值给s1
//也就是说s2=s3这个表达式要返回s2这个对象,所以要返回引用
}
六.关系运算符重载
// 定义 Maker 类
class Maker
{
public:
// 默认构造函数,将 id 和 age 初始化为 0
Maker()
{
id = 0;
age = 0;
}
// 构造函数,接受 id 和 age 作为参数,分别初始化对应成员变量
Maker(int id, int age)
{
this->id = id;
this->age = age;
}
// 重载 == 运算符,用于比较两个 Maker 对象是否相等
bool operator==(Maker &m)
{
// 如果两个对象的 id 和 age 都相等,则返回 true,否则返回 false
if (this->id == m.id && this->age == m.age)
{
return true;
}
return false;
}
// 重载 != 运算符,用于比较两个 Maker 对象是否不相等
bool operator!=(Maker &m)
{
// 如果两个对象的 id 和 age 有一个不相等,则返回 true,否则返回 false
if (this->id != m.id || this->age != m.age)
{
return true;
}
return false;
}
public:
int id; // Maker 的 id
int age; // Maker 的年龄
};
// 测试函数
void test()
{
// 创建一个 Maker 对象 p1,使用第二个构造函数初始化,id 为 1,age 为 20
Maker p1(1, 20);
// 创建一个 Maker 对象 p2,使用默认构造函数初始化,id 和 age 都为 0
Maker p2;
// 使用重载的 == 运算符比较 p1 和 p2 是否相等,输出结果
if (p1 == p2)
{
cout << "真" << endl;
}
else
{
cout << "假" << endl;
}
// 使用重载的 != 运算符比较 p1 和 p2 是否不相等,输出结果
if (p1 != p2)
{
cout << "真" << endl;
}
else
{
cout << "假" << endl;
}
}
七.前置和后置++运算符重载(重点难点)
class Maker
{
friend ostream &operator<<(ostream &out, Maker &m);
public:
Maker(int a)
{
this->a = a;
}
//重载前置加加
Maker &operator++()
{
++this->a;
return *this;
}
//后置加加,
Maker operator++(int)//占位参数,必须是int
{
//后置加加,先返回,后加加
Maker tmp(*this);//1.*this里面的值a是等于2
++this->a;//这个对象的a等3
return tmp;
}
private:
int a;
};
ostream &operator<<(ostream &out, Maker &m)
{
out << m.a << endl;
return out;
}
void test02()
{
Maker m1(1);
cout << m1 << endl;//1
cout << ++m1 << endl;//2
//++(++m1);
cout << m1++ << endl;//2 这里返回的拷贝的tmp对象
cout << m1 << endl;//3 这里返回的是++this->a的对象
//同等条件下,优先使用前置加加,不需要产生新的对象和调用拷贝构造
}
八.数组下标运算符重载
class MyArray
{
public:
//重写赋值运算符重载函数
MyArray&operator=(const MyArray &m);
//要能当左右值
int &operator[](int index);
};
MyArray.cpp
//重写赋值运算符重载函数
MyArray&MyArray::operator=(const MyArray &m)
{
cout << "赋值函数" << endl;
//1.释放原来的空间
if (this->pArray != NULL)
{
delete[] this->pArray;
this->pArray = NULL;
}
this->mCapacity = m.mCapacity;
this->mSize = m.mSize;
//2.申请空间,大小由m决定
this->pArray = new int[m.mCapacity];
//3.拷贝数据
cout << "this->mSize:"<<this->mSize << endl;
for (int i = 0; i < this->mCapacity; i++)
{
this->pArray[i] = m.pArray[i];
}
return *this;
}
//要能当左右值
int &MyArray::operator[](int index)
{
/*
for (int i = 0; i < 20; i++)
{
arr[i] = i + 10;
}
for (int i = 0; i < 20; i++)
{
cout << arr[i] << " ";
}
*/
//赋值时,加加
if (this->mSize <=index)
{
this->mSize++;
}
return this->pArray[index];
}
数组下标重载.cpp
void test02()
{
MyArray arr;
for (int i = 0; i < 20; i++)
{
arr[i] = i + 10;
}
for (int i = 0; i < 20; i++)
{
cout << arr[i] << " ";
}
cout << endl;
MyArray arr2;
arr2 = arr;
for (int i = 0; i < 20; i++)
{
cout << arr2[i] << " ";
}
cout << endl;
cout << arr2.Size() << endl;
}