写在前面:
- 本系列专栏主要介绍C++的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程,笔者的原创部分主要在示例代码的注释部分。
- 除了参考下面的链接教程以外,笔者还参考了其它的一些C++教材(比如计算机二级教材和C语言教材),笔者认为重要的部分大多都会用粗体标注(未被标注出的部分可能全是重点,可根据相关部分的示例代码量和注释量判断,或者根据实际经验判断)。
- 由于C++基本继承了C语言的所有内容,建议读者先阅读C语言系列的专栏,有一些重点是仅在C语言系列专栏中有介绍的(比如二级指针、预处理等)。
- 如有错漏欢迎指出。
参考教程:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili
一、概述
(1)运算符重载是计算机语言固有多态性的体现。
(2)运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
(3)C++把重载的运算符视为特殊的函数,称为运算符函数,运算符函数就是函数重载的一种特殊情况。
(4)运算符函数作为非成员函数重载时,由于没有隐含的this指针,因此所有的操作数均出现在形参表中。
(5)重载的运算符保持其原有的操作数个数不变,比如“*”既可重载为一元运算符,又可重载为二元运算符,但“=”只能重载为二元运算符。
(6)对于内置的数据类型的表达式的的运算符是不可能改变的。(因此不用担心重载运算符会将运算符本身的功能覆盖)
(7)运算符重载应注意的几个问题:
①重载的运算符应保持其原有的基本语意。
②重载的运算符应尽可能保持其原有的特性,运算符的操作数个数、优先级和结合性必须保持,此外还需注意运算符是否要求第一操作数为左值操作数、是否修改第一操作数、操作的结果是否为有左值数据,并且要保证不改变第二操作数。
③运算符的重载应当配套,某些运算符之间关系密切,存在着某种逻辑上的联系,因此若需要重载其中的某一个,往往就意味着同组的其它运算符也需要重载。
④将具有副作用的运算符重载为非成员函数时,其第一参数代表第一操作数,应该按引用传递,以便使改变第一操作数的值称为可能,除了这种情况以外,其它参数既可以声明为非引用参数,也可以声明为引用参数。
⑤作为成员函数还是作为非成员函数重载:
[1]=、[]、()、->以及所有的类型转换运算符只能作为成员函数重载。
[2]如果允许第一操作数不是同类对象,而是其它数据类型,则只能作为非成员函数重载(如输入输出流运算符>>和<<)。
[3]若希望系统在必要时能够利用只需一个实参的构造函数自动对第一操作数进行转换,应将该运算符作为非成员函数重载,这种情况下运算符函数的参数应该是非引用参数,其它情况下运算符函数一般应作为成员函数重载。
⑥不要滥用运算符重载。
二、重载加法运算符“+”
(1)重载加法运算符“+”可以实现两个自定义数据类型相加的运算。
(2)“+”可以作为非成员函数重载,也可以作为成员函数重载(作为成员函数重载时,一个操作数由函数形参提供,一个操作数由this指针能够访问的数据成员提供)。
(3)加法应该生成两个对象相加的结果,但是不改变两个对象本身,因此应该在函数内定义一个新的对象存储二者相加的结果,将其作为返回值返回。
#include<iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
Person PersonAdd(Person &p) //通过自己写成员函数,实现两个对象相加属性后返回新的对象
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
/*Person operator+(Person &p) //通过成员函数重载+号;和下面那个全局函数重载+二选一
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}*/
};
Person operator+(Person &p1, Person &p2) //通过全局函数重载+
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
Person operator+(Person &p1, int num) //运算符重载也可以发生函数重载
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
void test01()
{
Person p1;
Person p2;
p1.m_A = 10;
p2.m_A = 10;
p1.m_B = 20;
p2.m_B = 30;
Person p3 = p1 + p2;
//Person p3 = p1.operator+(p2); 成员函数重载本质调用
//Person p3 = operator+(p1 ,p2); 全局函数重载本质调用
Person p4 = p3 + 10;
//Person p4 = operator+(p3 ,10);
cout << p3.m_A << " " << p3.m_B << endl;
cout << p4.m_A << " " << p4.m_B << endl;
}
int main() {
test01();
system("pause");
return 0;
}
三、重载减法运算符“-”
(1)重载减法运算符“-”可以实现两个自定义数据类型相减的运算。
(2)“-”可以作为非成员函数重载,也可以作为成员函数重载(作为成员函数重载时,一个操作数由函数形参提供,一个操作数由this指针能够访问的数据成员提供)。
(3)减法应该生成两个对象相减的结果,但是不改变两个对象本身,因此应该在函数内定义一个新的对象存储二者相减的结果,将其作为返回值返回。
#include<iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
Person PersonSub(Person &p) //通过自己写成员函数,实现两个对象相减属性后返回新的对象
{
Person temp;
temp.m_A = this->m_A - p.m_A;
temp.m_B = this->m_B - p.m_B;
return temp;
}
/*Person operator-(Person &p) //通过成员函数重载-号;和下面那个全局函数重载-二选一
{
Person temp;
temp.m_A = this->m_A - p.m_A;
temp.m_B = this->m_B - p.m_B;
return temp;
}*/
};
Person operator-(Person &p1, Person &p2) //通过全局函数重载-
{
Person temp;
temp.m_A = p1.m_A - p2.m_A;
temp.m_B = p1.m_B - p2.m_B;
return temp;
}
Person operator-(Person &p1, int num) //运算符重载也可以发生函数重载
{
Person temp;
temp.m_A = p1.m_A - num;
temp.m_B = p1.m_B - num;
return temp;
}
void test01()
{
Person p1;
Person p2;
p1.m_A = 10;
p2.m_A = 10;
p1.m_B = 20;
p2.m_B = 30;
Person p3 = p1 - p2;
//Person p3 = p1.operator-(p2); 成员函数重载本质调用
//Person p3 = operator-(p1 ,p2); 全局函数重载本质调用
Person p4 = p3 - 10;
//Person p4 = operator-(p3 ,10);
cout << p3.m_A << " " << p3.m_B << endl;
cout << p4.m_A << " " << p4.m_B << endl;
}
int main() {
test01();
system("pause");
return 0;
}
四、重载取负运算符“-”
(1)重载取负运算符“-”可以实现自定义数据类型取相反数的运算。
(2)“-”可以作为非成员函数重载,也可以作为成员函数重载(作为成员函数重载时,唯一的操作数由this指针能够访问的数据成员提供)。
(3)取负应该生成对象取相反数的结果,但是不改变对象本身,因此应该在函数内定义一个新的对象存储对象取负的结果,将其作为返回值返回。
#include<iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
Person operator-() //通过成员函数重载-号;和下面那个全局函数重载-二选一
{
Person temp;
temp.m_A = -(this->m_A);
temp.m_B = -(this->m_B);
return temp;
}
};
/*Person operator-(Person &p) //通过全局函数重载-
{
Person temp;
temp.m_A = -(p.m_A );
temp.m_B = -(p.m_B);
return temp;
}*/
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 20;
Person p2 = -p1;
//Person p2 = p1.operator-(); 成员函数重载本质调用
//Person p2 = operator-(p1); 全局函数重载本质调用
cout << p2.m_A << " " << p2.m_B << endl;
}
int main() {
test01();
system("pause");
return 0;
}
五、重载左移运算符“<<”
(1)重载左移运算符配合友元可以实现输出自定义数据类型。
(2)“<<”只可以作为非成员函数重载。
#include<iostream>
using namespace std;
class Person
{
friend ostream & operator<<(ostream &cout, Person &p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
//void operator<<(cout){} 通常不会用成员函数重载<<运算符,因为无法实现cout在左侧
};
ostream & operator<<(ostream &cout, Person &p) //本质是operator << (cout,p),简化为cout << p
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B ;
return cout;
}
void test01()
{
Person p(10,10);
cout << p << endl;
}
int main() {
test01();
system("pause");
return 0;
}
六、重载右移运算符“>>”
(1)重载右移运算符配合友元可以实现输入自定义数据类型。
(2)“>>”只可以作为非成员函数重载。
#include<iostream>
using namespace std;
class Person
{
friend istream & operator>>(istream &cout, Person &p);
public:
Person(int a = 0, int b = 10)
{
m_A = a;
m_B = b;
}
~Person()
{
cout << m_A << " " << m_B;
system("pause");
}
private:
int m_A;
int m_B;
//void operator>>(cin){} 通常不会用成员函数重载>>运算符,因为无法实现cin在左侧
};
istream & operator>>(istream &cin, Person &p) //本质是operator >> (cin,p),简化为cin >> p
{
cin >> p.m_A >> p.m_B;
return cin;
}
void test01()
{
Person p;
cin >> p;
}
int main() {
test01();
//system("pause");
return 0;
}
七、重载递增运算符“++”
(1)递增运算符分为前置递增和后置递增两种,前置递增返回引用,后置递增返回值。(前置递增可以“连续”,后置递增则不行)
(2)为了区分两种递增,重载这两个运算符时必须在格式上有所区别:重载后置递增时必须多一个虚拟参数“int”。
(3)“++”可以作为非成员函数重载,也可以作为成员函数重载。
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream& operator << (ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_A = 0;
m_B = 0;
}
MyInteger& operator++() //重载前置++运算符
{
m_A++; //直接对成员变量进行递增
return *this;
}
MyInteger operator++(int) //重载后置++运算符,int代表占位参数,可以用于区分前置和后置递增
{
MyInteger temp = *this; //先记录当前结果
m_A++; //再对成员变量进行递增
return temp; //最后将记录结果做返回
}
private:
int m_A;
int m_B;
};
ostream& operator << (ostream& cout, MyInteger myint)
{
cout << "m_A=" << myint.m_A;
return cout;
}
void test01()
{
MyInteger myint;
cout << myint << endl;
cout << ++myint << endl;
cout << ++(++myint) << endl;
cout << myint++ << endl;
cout << myint << endl;
}
int main() {
test01();
system("pause");
return 0;
}
八、重载递减运算符“--”
(1)递减运算符分为前置递减和后置递减两种,前置递减返回引用,后置递减返回值。(前置递减可以“连续”,后置递减则不行)
(2)为了区分两种递减,重载这两个运算符时必须在格式上有所区别:重载后置递减时必须多一个虚拟参数“int”。
(3)“--”可以作为非成员函数重载,也可以作为成员函数重载。
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream& operator << (ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_A = 0;
m_B = 0;
}
MyInteger& operator--() //重载前置--运算符
{
m_A--; //直接对成员变量进行递减
return *this;
}
MyInteger operator--(int) //重载后置--运算符,int代表占位参数,可以用于区分前置和后置递减
{
MyInteger temp = *this; //先记录当前结果
m_A--; //再对成员变量进行递减
return temp; //最后将记录结果做返回
}
private:
int m_A;
int m_B;
};
ostream& operator << (ostream& cout, MyInteger myint)
{
cout << "m_A=" << myint.m_A;
return cout;
}
void test01()
{
MyInteger myint;
cout << myint << endl;
cout << --myint << endl;
cout << --(--myint) << endl;
cout << myint-- << endl;
cout << myint << endl;
}
int main() {
test01();
system("pause");
return 0;
}
九、重载赋值运算符“=”
(1)赋值运算符只能作为成员函数重载。
(2)对于任何类,即使没有重载赋值运算符,仍然可以使用运算符=,在这种情况下,默认的赋值操作就是同类对象之间对应成员的逐一赋值(如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题)。
(3)重载赋值运算符时应注意:
①返回值应声明为引用,而函数体中总是用语句“return *this;”返回。
②如果形参声明为指向同类对象的引用或指针,应判别所指向的对象是否与被赋值的对象为同一对象。
③如果被赋值对象占用了动态空间或其它资源,应首先释放这些资源,以便接收新的资源。
④如果形参声明为指针或引用,通常应加上const修饰符。
⑤如果形参声明为指针,应判别是否为空,以便做出特殊处理。
⑥一个类如果需要重载赋值运算符,通常也就需要定义自己特有的拷贝构造函数,反之亦然。
#include<iostream>
using namespace std;
class Person
{
friend ostream& operator << (ostream& cout, Person p);
public:
Person(int age)
{
m_A = new int(age);
m_B = 0;
}
Person& operator=(const Person &p)
{
//先判断是否有属性数据在堆区,如果有就先释放,再进行深拷贝
if (m_A != NULL)
{
delete m_A;
m_A = NULL;
}
m_A = new int(*p.m_A); //深拷贝
m_B = p.m_B; //浅拷贝
return *this;
}
private:
int *m_A;
int m_B;
};
ostream& operator << (ostream& cout, Person p)
{
cout << "m_A=" << *p.m_A;
return cout;
}
void test01()
{
Person p1(18);
Person p2(20);
Person p3(10);
cout << p1 << endl;
cout << p2 << endl;
p2 = p1; //赋值操作
cout << p2 << endl;
p1 = p2 = p3; //相当于p1 = (p2 = p3),p2 = p3的结果为p3
cout << p1 << endl;
cout << p2 << endl;
cout << p3 << endl;
}
int main() {
test01();
system("pause");
return 0;
}
十、重载复合赋值运算符
(1)重载复合赋值运算符,如+=、-=等,也应遵循上述赋值运算符的注意事项。
(2)与赋值运算符不同的是,复合赋值运算符既可以作为成员函数重载,也可以作为非成员函数重载。
(3)以重载“+=”运算符为例:
#include<iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
Person& operator+=(Person &p) //通过成员函数重载+=号;和下面那个全局函数重载+=二选一
{
this->m_A = this->m_A + p.m_A;
this->m_B = this->m_B + p.m_B;
return *this;
}
};
/*Person& operator+=(Person &p1, Person &p2) //通过全局函数重载+=
{
p1.m_A = p1.m_A + p2.m_A;
p1.m_B = p1.m_B + p2.m_B;
return p1;
}*/
void test01()
{
Person p1;
Person p2;
p1.m_A = 10;
p2.m_A = 10;
p1.m_B = 20;
p2.m_B = 30;
p2 += p1;
cout << p1.m_A << " " << p1.m_B << endl;
cout << p2.m_A << " " << p2.m_B << endl;
}
int main() {
test01();
system("pause");
return 0;
}
十一、重载关系运算符
(1)重载关系运算符,可以让两个自定义类型对象进行对比操作。
(2)重载的关系运算符函数应返回逻辑值。
(3)关系运算符既可以作为成员函数重载,也可以作为非成员函数重载。
(4)以重载“==”运算符和“!=”运算符为例:
#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
Person(string name,int age)
{
m_Age = age;
m_Name = name;
}
bool operator==(Person &p)
{
if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
{
return true;
}
return false;
}
bool operator!=(Person &p)
{
if (this->m_Name != p.m_Name||this->m_Age != p.m_Age)
{
return true;
}
return false;
}
private:
int m_Age;
string m_Name;
};
void test01()
{
Person p1("张三", 18);
Person p2("张三", 18);
Person p3("刘六", 18);
if (p1 == p2)
{
cout << "p1和p2是相等的" << endl;
}
else
{
cout << "p1和p2是不相等的" << endl;
}
if (p1 != p3)
{
cout << "p1和p3是不相等的" << endl;
}
else
{
cout << "p1和p3是相等的" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
十二、重载函数调用运算符“()”
(1)函数调用运算符也可以重载。由于重载后使用的方式非常像函数的调用,因此称为仿函数。
(2)仿函数没有固定写法,非常灵活。
#include<iostream>
using namespace std;
#include<string>
class MyPrint
{
public:
void operator()(string test)
{
cout << test << endl;
}
private:
int m_Age;
string m_Name;
};
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test01()
{
MyPrint myPrint;
myPrint("hello world"); //由于使用起来非常类似于函数调用,因此它被称为仿函数
MyAdd myadd;
int ret = myadd(100, 100);
cout << "ret = " << ret << endl;
cout << MyAdd()(100, 100) << endl; //匿名对象MyAdd(),当前行执行完立刻被释放
}
int main() {
test01();
system("pause");
return 0;
}
十三、重载类型转换符
(1)类型转换符必须作为成员函数重载。
(2)在重载类型转换符时,由于运算符本身已经表示出了返回值类型,因此不需要返回值类型的声明。
(3)以重载长整型转换符为例:
#include<iostream>
using namespace std;
class Person
{
public:
char m_A;
int m_B;
operator long() const
{
return m_A;
}
};
void test01()
{
Person p1;
p1.m_A = 'a';
p1.m_B = 20;
cout << long(p1) << endl; //打印“97”
}
int main() {
test01();
system("pause");
return 0;
}
十四、重载下标访问运算符“[]”
下标访问运算符必须作为成员函数重载。
#include<iostream>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
int a[10];
int& operator[] (int i)
{
return a[i];
}
};
void test01()
{
Person p1;
p1.m_A = 90;
p1.m_B = 20;
p1.a[2] = 30;
cout << p1[2] << endl;
}
int main() {
test01();
system("pause");
return 0;
}