运算符重载
一、运算符重载基础
1.语法
运算符重载函数的语法:返回值 operator 运算符 (参数列表)。
运算符重载函数的返回值与运算符本身的含义一致。
2.优点
C++将运算符重载扩展到自定义的数据类型,它可以让对象操作更加美观。
例如:字符串string用(+)拼接、cout用两个小于号(<<)输出。
3.种类
1) 非成员函数版本的重载运算符函数:形参个数与运算符需要操作的个数相同。
#include <iostream>
using namespace std;
class supman
{
friend void operator+(supman& boy, int num);
friend int main();
public:
string m_name;
supman()
{
m_name.clear(); m_score = 0;
cout << "调用了supman()构造函数" << endl;
}
private:
int m_score;
void show() //自我介绍
{
m_name = "小马哥";
cout << " m_name:" << m_name << " " << "m_age:" << m_score << endl;
}
};
void operator+(supman& boy,int num)
{
boy.m_score = boy.m_score + num;
}
int main()
{
supman boy;
boy + 20;
boy.show();
}
运行结果如下:
调用了supman()构造函数
m_name:小马哥 m_age:20
C:\code\day\x64\Debug\day.exe (进程 37272)已退出,代码为 0。
按任意键关闭此窗口. . .
分析:先创建类supman的对象boy,执行默认构造函数,之后调用重载运算符函数。
2) 成员函数版本的重载运算符函数:形参个数比运算符的操作个数少一个,其中一个操作数隐式传递了调用对象。
#include <iostream>
using namespace std;
class supman
{
friend int main();
public:
string m_name;
supman()
{
m_name.clear(); m_score = 0;
cout << "调用了supman()构造函数" << endl;
}
supman& operator+(int num) //成员函数版本的重载运算符函数,在类中对象的引用相当于自己this,可以省略。
{
m_score = m_score + num;
return *this;
}
private:
int m_score;
void show() //自我介绍
{
m_name = "小马哥";
cout << " m_name:" << m_name << " " << "m_age:" << m_score << endl;
}
};
int main()
{
supman boy;
boy + 20+10;
boy.show();
}
运行结果如下:
调用了supman()构造函数
m_name:小马哥 m_age:30
C:\code\day\x64\Debug\day.exe (进程 10980)已退出,代码为 0。
按任意键关闭此窗口. . .
如果同时重载了非成员函数和成员函数两个版本,就会导致二义性,出现编译错误。
4.注意细节
- 返回自定义数据类型的引用可以让多个运算符表达式串联起来。(不要返回局部变量的引用) 例如boy+20+10;
- 重载函数的函数列表决定了操作数的位置,也就是实参与形参的数据类型应一一对应;
- 重载函数的参数列表中至少有一个是用户自定义的类型,防止程序员为内置数据类型重载运算符;
- 如果运算符重载既可以是成员函数也可以是全局变量,应该优先考虑成员函数,这样更符合运算符重载的初衷;
- 重载函数不能违背运算符原来的含义和优先级;
- 不能创建新的运算符。
二、重载关系运算符
重载关系运算符(==、>=、<=、!=、>、<)用于比较两个自定义数据类型的大小。
可以使用非成员函数和成员函数两种版本,建议采用成员函数版本。
例:
#include <iostream>
using namespace std;
class supman
{
public:
string m_name;
int m_score;
int m_grade;
int m_level;
supman(string name, int score, int grade, int level) :m_name(name), m_score(score), m_grade(grade), m_level(level)
{}
bool operator == (const supman& man)
{
if ((m_score + m_grade + m_level) == (man.m_score + man.m_grade + man.m_level))
return true; return false;
}
bool operator > (const supman & man)
{
if ((m_score + m_grade + m_level) > (man.m_score + man.m_grade + man.m_level))
return true; return false;
}
bool operator < (const supman & man)
{
if ((m_score + m_grade + m_level) < (man.m_score + man.m_grade + man.m_level))
return false; return true;
}
};
int main()
{
supman boy1("小马哥", 87, 65, 88);
supman boy2("小雷哥", 86, 97, 83);
if (boy1 == boy2) cout << "两位小哥平分秋色" << endl;
else if (boy1 > boy2) cout << "小马哥一马当先" << endl;
else cout << "小雷哥su7碉堡了" << endl;
}
运行结果如下:
小雷哥su7碉堡了
C:\code\day\x64\Debug\day.exe (进程 22048)已退出,代码为 0。
按任意键关闭此窗口. . .
三、重载左移运算符
重载左移运算符(<<)用于输出自定义对象的成员变量,在实际开发中很有价值(调试和日志)。
只能使用非成员函数版本。
如果输出对象的私有成员,可以配合友元一起使用。
1.简介
如果对象中有数组,重载下标运算符[],操作对象中的数组就像操作普通函数一样方便。
下标运算符[]必须以成员函数的形式进行重载。
2.语法
下标运算符重载函数的语法:
返回值类型 & operator[] (参数) ; 或 const 返回值类型 &operator[] (参数) const;
1)使用第一种声明方式,[]不仅可以访问数组元素,还可以修改数组元素;
#include <iostream>
using namespace std;
class supman
{
public:
string m_name[3];
int m_score;
supman()
{
m_name[0] = "小马哥 "; m_name[1] = "小雷哥 "; m_name[2] = "小王哥";
}
void show()
{
cout << m_name[0] << m_name[1] << m_name[2] << endl;
}
string & operator[](int i)
{
return m_name[i];
}
};
int main()
{
supman boy;
boy.m_name[1] = "小军哥 "; //对第二个元素进行修改
boy[1];
boy.show();
}
运行结果如下:
小马哥 小军哥 小王哥
C:\code\day\x64\Debug\day.exe (进程 9828)已退出,代码为 0。
按任意键关闭此窗口. . .
2)使用第二种声明方式,[]只能访问而不能修改数组元素。
#include <iostream>
using namespace std;
class supman
{
public:
string m_name[3];
int m_score;
supman()
{
m_name[0] = "小马哥 "; m_name[1] = "小雷哥 "; m_name[2] = "小王哥";
}
void show() const //由于对象boy为const
{
cout << m_name[0] << m_name[1] << m_name[2] << endl;
}
const string & operator[](int i) const
{
return m_name[i];
}
};
int main()
{
const supman boy;
boy[1];
boy.show();
}
运行结果如下:
小马哥 小雷哥 小王哥
C:\code\day\x64\Debug\day.exe (进程 23020)已退出,代码为 0。
按任意键关闭此窗口. . .
在实际开发中,我们应该同时提供以上两种形式,这样做是为了适应const对象,因为通过const对象只能调用const成员函数,如果不提供第二种形式,那么将无法访问const对象的任何数据元素。
在重载函数中,可以对下标做合法性检查,防止数组越界。
四、重载赋值运算符
C++编译器可能会给出类添加四个函数:
- 默认构造函数,空实现;
- 默认析构函数,空实现;
- 默认拷贝构造函数,对成员变量进行浅拷贝;
- 默认赋值函数,对成员变量进行浅拷贝。
1.定义
对象的赋值运算是用一个已经存在的对象,给另一个已经存在的对象赋值。
如果类的定义中没有重载赋值函数,编译器就会提供一个默认赋值函数。反之,编译器将不再提供默认赋值函数。
2.语法
重载赋值函数的语法:类名 & operator = (const 类名 & 源对象)。
3.注意
- 编译器提供的默认赋值函数,是浅拷贝。
- 如果对象中不存在堆区内存空间,默认赋值函数可以满足需求,是否需要深拷贝。
- 赋值运算和拷贝构造不同:拷贝构造是指原来的对象不存在,用已存在的对象进行构造,赋值运算是指已经存在了两个对象,把其中一个对象的成员变量的值赋给另一个对象的成员变量。
五、重载new、delete运算符
重载new和delete运算符的主要目的是为了自定义内存分配的细节。(内存池:快速分配和归还,无碎片)
1.编译器的工作
在C++中,使用new时,编译器做了两件事情:
1) 调用标准库函数operator new()分配内存;
2) 调用构造函数初始化内存。
使用delete时,也做了两件事情:
1)调用析构函数;
2)调用标准库函数operator delete()释放内存。
2.重载函数
构造函数和析构函数由编译器调用,我们无法控制。但是,可以重载内存分配函数operator new()和释放函数operator delete()。
1)重载内存分配函数的语法:void* operator new(size_t size);
参数必须是size_t,返回值必须是void*。
2)重载内存释放函数的语法:void operator delete(void* ptr)
参数必须是void* (指向由operator new()分配的内存),返回值必须是void。
注意:
- 重载的new和delete可以是全局函数,也可以是类的成员函数。
- 为一个类创建new和delete时,尽管不必显示的使用static,但实际上仍在创建static成员函数。
- 编译器看到使用new创建自定义的类的对象时,它选择成员版本的operator new()而不是全局版本的new()。
- new[]和delete[]也可以重载。
六、重载括号运算符
括号运算符()也可以重载,对象名可以当成函数来使用(函数对象、仿函数)。
1.语法
括号运算符重载函数的语法:返回值类型 operator()(参数列表)
2.注意事项
- 括号运算符必须以成员函数的形式进行重载;
- 括号运算符重载函数具备普通函数全部的特征;
- 如果函数对象与全局函数同名,按作用域规则选择调用的函数。
3.函数对象的用途
1)表面像函数,部分场景中可以替代函数,在STL中得到广泛的应用;
2)函数对象本质是类,可以用成员变量存放更多的信息;
3)函数对象有自己的数据类型;
4)可以提供继承体系。
七、重载一元运算符
1.种类
可重载的一元运算符
1)++自增 2)-- 自减 3)!逻辑非 4)& 取地址
5)~ 二进制反码 6)* 解引用 7)+ 一元加 8)- 一元求反
一元运算符通常出现在它们所操作的对象的左边。
但是,自增运算符++和自减运算符–有前置和后置之分。
C++规定,重载++或–时,如果重载函数有一个int形参,编译器处理后置表达式时,将调用这个重载函数。
2.语法
成员函数版:supman & operator++(); //++前置
成员函数版:supman operator++(int); //后置++
非成员函数版:supman & operator++(supman &); //++前置
非成员函数版:supman operator++(supman &,int); //后置++