本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- 通过前面几节,我们介绍了C++的类与对象、构造与析构函数、拷贝、成员变量、特殊变量等相关知识,这一篇将详细介绍了C++的运算符重载,我感觉这是C++的一个很伟大的发明,让自定义类型有了和普通数据一样进行运算;
- C语言后面也会继续更新知识点,如内联汇编;
- 本人现在正在写一个C语言的图书管理系统,1000多行代码,包含之前所学的所有知识点,包括链表和顺序表等数据结构,请大家耐心等待!!预计国庆前写完更新。
文章目录
什么是运算符重载
运算符重载,就是赋予运算符新的含义。它是重载系统内部运算符的函数,是实现类静态多态性的方式之一。它本质上是函数重载。
让我们来看一下这个需求,需要对比A1同学和A2同学年龄的大小:
// 假设都属于学生类
Student A1("wy", 18), A2("Wq", 19);
// 想要对比谁的年龄大,试想一下,能不能像正常比较大小那样直接比较呢?
cout << A1 < A2 << endl;
答案当然是可以的,这个就是我们这节课要学习的**“运算符重载”**,这样进行重载后,类更像是一种自定义数据类型。
实际上,我们已经在不知不觉之中使用了运算符重载。列如:
我们习惯使用的对整数,浮点数运用+、-、*、/,其实计算机对整数和浮点数的操作过程是不一样的,这个主要是整数和浮点数的编码规则不同(详细请看《深入了解计算机系统》),但由于C++已经对运算符进行了重载,所以才能都适用。
又如<<本来是左移运算符,但在输出操作中,与cout搭配,当作输出流运算符了
运算符重载的意义
我们平常常见的算术运算符、逻辑运算符等运算符都是系统库函数里面已经存在的,所以运算符可以直接用于我们常用的数据类型。然而对于我们自定义的类实例化的对象,系统没有与之相关的运算符可以操作,但是为了使我们编写程序时更加便捷,C++提供了一种方式——运算符重载,来满足我们对于类对象进行的操作。
总的来说就是,C++的数据是对类的抽象,运算符重载就可以让我们自定义类型可以像平常数据类型一样进行运算。
运算符重载限制
C++中绝大部分的运算符允许重载,少部分不允许重载,详细描述如下(忘了现查即可):
可以重载的运算符
- 算术运算符:+ - * / %
- 自增、自减运算符:++ –
- 位运算符:| & ~ ^ << >>
- 逻辑运算符:|| && !
- 关系运算符:== != < > <= >=
- 赋值运算符:= += -= /= %= &= |= ^= <<= >>=
- 单目运算符:+ - * &
- 动态内存分配:new delete new[] delete[]
- 其他运算符:() -> , []
不能重载的运算符
- . 成员访问运算符
- :: 域运算符
- .* ->* 成员指针访问运算符
- szieof 长度运算符
- ?: 条件运算符
运算符重载规则
- 重载运算符函数可以对运算符作出新的解释﹐但原有基本语义不变:
不改变
运算符的优先级不改变
运算符的结合性不改变
运算符所需要的操作数不能
创建新的运算符
- 语法:
- 一个运算符被重载后,原有意义没有失去,只是定义了相对一特定类的一个新运算符。
举例,上面比较年龄的实现:
#include <iostream>
using namespace std;
class Student
{
public:
Student(std::string name, int age)
:m_name(name),
m_age(age)
{
}
bool operator<(const Student& other)
{
return this->m_age < other.m_age;
}
private:
std::string m_name;
int m_age;
};
int main()
{
Student a1("w", 18), a2("y", 19);
bool res = a1 < a2;
cout << boolalpha << res << endl;
// 输出结果: true
return 0;
}
为什么直接一下操作输出呢?
cout << a1 < a2;
因为优先级的问题,<< 大于 < ,如果使用以上代码,则实际它运行的是:
(cout << a1) << a2;
而类是输出不了的,他不是标准类型,不能匹配,需要我们自定义内容,具体怎么操作,还需要往下学习。
运算符重载方法
两种重载方法
重载为成员函数或全局(友元)函数(友员函数非必要不用)
// 实现字符串内容连接
MyObject operator+(const char* str)
{
MyObject temp(size + strlen(str) + 1);
strcpy(temp.c_str, c_str); // 拷贝
strcat(temp.c_str, str); // 连接
}
两种形式的选择:
- 左操作数(或者只有左操作数并且)是本类的对象时,可选用成员函数形式。
MyObject& operator=(const MyObject& other);
MyObject& operator=(const char* str);
- 左操作数不是本类的对象,必须采用非成员函数的形式,一般是友元函数。
这一种情况一般用于输出对象,因为对象不是标准类型,他不能直接输出,那如果我想要执行一下操作就直接输出该学生信息呢?
Student a1("wy", 18);
cout << a1;
这个时候就可以用我们的运算符重载了,如下:
#include <iostream>
using namespace std;
class Student
{
friend ostream& operator<<(ostream& out, const Student& other);
public:
Student(std::string name, int age)
:m_name(name),
m_age(age)
{
}
bool operator<(const Student& other)
{
return this->m_age < other.m_age;
}
private:
std::string m_name;
int m_age;
};
// 友员一般在类外实现,但是在类内中间实现也行
ostream& operator<<(ostream& out, const Student& other)
{
out << "Name: " << other.m_name << ", Age: " << other.m_age << endl;
return out;
}
int main()
{
Student a1("w", 18), a2("y", 19);
cout << a1;
// 输出:Name: w, Age: 18
return 0;
}
当然也可以重载输入:
friend istream& operator>>(istream& out, const Student& other);
注意点
一般单目运算符最好被重载为成员函数;双目运算符重载为友元函数。
注意:有些运算符不能重载为友元函数,它们是:=,(),[]和->。
- 具有可交换性的双目运算符最好两种形式都有(成员函数时适用左操作数为本类对象,友元函数时适用左操作数为其他类的对象),也就是需要重载两种运算符1
// 如下
"123" + zc;
zc + "123"; // 两种都需要重载
·
单目运算符重载
什么是单目,什么是双目呢?顾名思义,就是运算符的操作数的个数
- 单目就是一个操作数,比如++,a++,操作数只有一个a,写成a++b是非法的。
- 双目就是两个操作数,最熟悉的就是+,a+b,计算ab的和
- 三目就是三个操作数,目前只有一个条件运算符,?运算符,比如a?b;c,当a的值为真时,结果是b,否则结果是c。
单目运算符
一些只需要一个操作数的运算符称为一元运算符(或单目运算符)。
重载自增、自减法
返回类型不同
形参不同
效率不同
Student& operator++() { this->m_age += 1; return* this; } Student operator++(int) { Student temp = *this; ++* this; return temp; }
双目运算符重载
一些需要两个操作数的运算符称为二元运算符(或双目运算符)
// 对年龄相加
int operator+(const Student& a1, const Student& a2)
{
return a1.m_age + a2.m_age;
}
同样的道理,三目就是操作数为三个。
类型转换重载
类型重载就是没有返回值类型的,自动推断,如下一个例子:
//类型转换强转
//1.没有返回类型
operate int()
{
return
}
//
class Text
{
private:
int m_a;
public:
operator int()
{
return int(m_a);
}
}
int main()
{
Text a(99);
int b = a; //将对象赋值给一个变量
rerurn 0;
}
- 上面a(99)运算过程,a会在class找,找能赋值的运算符重载
注意: 与运算符 = 重载的区别, a = 99; 这样是 = 重载,将一个值赋值给对象 , 即左操作数不是本类对象