1、运算符重载
什么是重载?所谓重载,就是重新赋予新的含义。运算符重载就是赋予运算符新的含义(新功能),其本质是一个函数。
为什么要重载运算符?C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型是没办法操作的,此时就需要重载运算符来实现。
运算符函数定义的一般格式如下:
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
运算符重载时要遵循以下规则:
*除了类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、sizeof运算符和三目运算符"?:",其他运算符都可以重载。 *重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。
*运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。
*重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。
*运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。
*运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似。
*一个运算符被重载后,原有意思没有失去,只是定义了相对一特定类的一个新运算符。
运算符重载的两者方法
重载为类成员函数来实现;
重载为友元函数(全局函数)来实现;
联系与区别
成员函数具有this指针,友元函数没有this指针;
两者使用方法相同;
两者实现方式不同,传递参数不同,应用场景不同;
方法选择
当无法修改左操作数的类时,使用友元函数进行重载;
=, [], ()和->操作符只能通过成员函数进行重载;
<<和>>需要使用友元函数实现;
友元函数重载运算符常用于操作符左右数据类型不同的情况,如1+a(在成员函数中不合法),但a+1合法。
实例
下面通过几个实例熟悉重载运算符的使用
例1:全局函数重载“+”
#include <iostream>
using namespace std;
class Complex{
public:
Complex(int a = 0, int b = 0){
this->a = a;
this->b = b;
}
void print(){
cout << "a:b => " << a << ":" << b << endl;
}
public:
int a;
int b;
};
Complex operator+(Complex &c1, Complex &c2){
Complex tmp(c1.a + c2.a, c1.b + c2.b);
return tmp;
}
void main(){
int a = 0, b = 0;
int c = a + b; //1 基础类型编译器知道怎么做
//用户定义复杂类型需要用户重载运算符编译才知道怎么做
Complex c1(1, 2), c2(3, 4);
Complex c3 = c1 + c2;
c3.print();
system("pause");
return;
}
例2:友元函数和成员函数重载运算符
#include <iostream>
using namespace std;
class Complex{
public:
Complex(int a = 0, int b = 0){
this->a = a;
this->b = b;
}
void print(){
cout << a << " : " << b << endl;
}
public:
//成员函数实现“-”运算符重载
Complex operator-(Complex &c2){
Complex tmp(this->a - c2.a, this->b - c2.b);
return tmp;
}
//前置--
Complex& operator--(){
this->a--;
this->b--;
return *this;
}
private:
int a;
int b;
friend Complex operator+(Complex &c1, Complex &c2);
friend Complex& operator++(Complex &c1);
};
//友元函数(全局函数)“+”运算符重载
Complex operator+(Complex &c1, Complex &c2){
Complex tmp(c1.a + c2.a, c1.b + c2.b);
return tmp;
}
//前置++
Complex& operator++(Complex &c1){
c1.a++;
c1.b++;
return c1;
}
void main()
{
Complex c1(1, 2), c2(3, 4);
// 成员函数重载运算符
Complex c3 = c1 + c2;
c3.print();
// 成员函数重载运算符
Complex c4 = c2 - c1;
c4.print();
// 友元函数重载运算符
++c1;
c1.print();
// 友元函数重载运算符
--c1;
c1.print();
system("pause");
return;
}
例2重载了单目运算符“++”和“--”,这两个运算符有两种使用方式,分别是前置和后置,例2只是实现了运算符之前功能的重载,那么运算符后置功能怎么重载呢?这就需要借助占位符来实现了,请看例子:
例3:后置“++”和“--”运算符重载
#include <iostream>
using namespace std;
class Complex{
public:
Complex(int a = 0, int b = 0){
this->a = a;
this->b = b;
}
void print(){
cout << a << " : " << b << endl;
}
public:
//后置--
Complex operator--(int){
Complex tmp = *this;
this->a--;
this->b--;
return tmp;
}
private:
int a;
int b;
friend Complex operator++(Complex &c1, int);
};
//后置++
Complex operator++(Complex &c1, int){
Complex tmp = c1;
c1.a++;
c1.b++;
return tmp; // 由于是后置++,因此需要返回++前的对象
}
void main(){
Complex c1(1, 2);
// 友元函数重载运算符
c1++;
c1.print();
// 友元函数重载运算符
c1--;
c1.print();
system("pause");
return;
}
例4:“=”运算符重载
我们知道类的默认拷贝构造函数属于“浅拷贝”,这样就会导致在类实例化对象之间进行赋值操作时可能产生内存泄露问题,如:分别实例类对象obj1和obj2,当执行obj2=obj1时,obj2的指针就会指向obj1的内存空间,obj2原来内存空间泄露;另外当obj1删除时会释放对应的内存空间,而此时obj2指向的内存空间和obj1相同,当obj2删除时会再次释放同一个内存空间,造成内存泄露,因此我们需要重载“=”运算符来避免这个问题。
#include <iostream>
#include <String>
using namespace std;
class Student{
public:
Student(const char *name, const int age){
int _len = strlen(name);
m_name = (char *)malloc(_len + 1); //
strcpy_s(m_name, _len+1, name);
m_age = age;
}
// Student obj2 = obj1;
// 方法一:手工编写拷贝构造函数实现深copy
Student(const Student& obj1){
int _len = strlen(obj1.m_name);
m_name = (char *)malloc(_len + 1);
strcpy_s(m_name, _len+1, obj1.m_name);
m_age = obj1.m_age;
}
// Student obj2 = obj1;
// 方法二:重载等号操作符实现深copy
Student& operator=(Student &obj1){
if (this->m_name != NULL){
delete[] m_name;
m_age = 0;
}
this->m_name = new char[strlen(obj1.m_name) + 1];
strcpy_s(m_name, strlen(obj1.m_name)+1, obj1.m_name);
this->m_age = obj1.m_age;
return *this;
}
~Student(){
if (m_name != NULL){
free(m_name);
m_name = NULL;
m_age = 0;
}
}
protected:
private:
char *m_name;
int m_age;
};
//对象析构的时候 出现coredump
void test()
{
Student obj1("xiaoming", 10);
Student obj2 = obj1; //调用用户实现的拷贝构造函数,实现深拷贝
Student obj3("liming", 11);
obj3 = obj1; // 等号操作符
obj1 = obj2 = obj3; // 需要返回引用
}
int main(){
test();
cout << "end..." << endl;
system("pause");
return 0;
}
注意obj3 = obj1和obj1 = obj2 = obj3是不同的。当只是使用obj3 = obj1时,等号运算符重载函数返回元素和引用都可以;但是,如果要实现obj1 = obj2 = obj3功能,则必须返回引用,因为此时需要左值操作。
例5:不要重载“||”和“&&”
操作符“||”和“&&”内置实现了短路规则,而当重载这两个操作符是无法实现短路规则,导致函数中的参数都会被求值,无法达到预定效果,因此不要重载这两个运算符。
#include <cstdlib>
#include <iostream>
using namespace std;
class Test{
public:
Test(int i){
this->m_a = i;
}
Test operator+ (const Test& obj){
Test ret(0);
ret.m_a = m_a + obj.m_a;
return ret;
}
bool operator&& (const Test& obj){
return m_a && obj.m_a;
}
private:
int m_a;
};
// && 从左向右
int main(){
int a1 = 0;
int a2 = 1;
if (a1 && (a1 + a2)){
cout << "a1,a2 结果为真..." << endl;
}
Test t1 = 0;
Test t2 = 1;
if(t1 && (t1 + t2)){
cout << "t1,t2 结果为真..." << endl;
}
system("pause");
return 0;
}
当对以上代码正常情况我们会认为if(t1 && (t1 + t2))不会执行(t1+t2)因为前面t1已经为假了,但当对代码调试时,你会发现代码进入了(t1+t2)的过程,因此重载“&&”无法实现短路规则。
例6:综合实例
这里给一个实现自定义数组的实例,有助于熟悉重载运算符在实际项目中的使用。
myarray.h
#pragma once
#include <iostream>
using namespace std;
class Array{
public:
Array(int length);
Array(const Array& obj);
~Array();
public:
void setData(int index, int valude);
int getData(int index);
int length();
//函数返回值当左值,需要返回一个引用(元素本身)
int& operator[](int i);
//重载=
Array& operator=(Array &a1);
//重载 ==
bool operator==(Array &a1);
//重载 !=
bool operator!=(Array &a1);
private:
int m_length;
int *m_space;
};
myarray.cpp
#include "myarray.h"
Array::Array(int length)
{
if (length < 0){
length = 0;
}
m_length = length;
m_space = new int[m_length];
}
//重写拷贝构造函数
Array::Array(const Array& obj){
this->m_length = obj.m_length;
this->m_space = new int[this->m_length];
for (int i = 0; i<m_length; i++){
this->m_space[i] = obj.m_space[i];
}
}
Array::~Array(){
if (m_space != NULL){
delete[] m_space;
m_space = NULL;
m_length = -1;
}
}
//a1.setData(i, i);
void Array::setData(int index, int valude){
m_space[index] = valude;
}
int Array::getData(int index){
return m_space[index];
}
int Array::length(){
return m_length;
}
// a[i] = 1,因为要当左值使用,所以要返回引用
int& Array::operator[](int i){
return m_space[i];
}
//a2 = a1;
Array& Array::operator=(Array &a1){
if (this->m_space != NULL){
delete[] m_space;
m_length = 0;
}
m_length = a1.m_length;
m_space = new int[m_length];
for (int i = 0; i<m_length; i++){
m_space[i] = a1[i]; // 因为已经重载了[]操作符
}
return *this;
}
//if (a3 == a1)
bool Array::operator==(Array &a1){
if (this->m_length != a1.m_length){
return false;
}
for (int i = 0; i<m_length; i++){
if (this->m_space[i] != a1[i]){
return false;
}
}
return true;
}
bool Array::operator!=(Array &a1){
return !(*this == a1);
}
Test.cpp
#include <iostream>
#include "myarray.h"
using namespace std;
int main(){
Array a1(10);
{
cout << "\na1: ";
for (int i = 0; i<a1.length(); i++) {
a1.setData(i, i);
a1[i] = i; // 调用[]操作符重载函数
}
for (int i = 0; i<a1.length(); i++) {
cout << a1[i] << " ";
}
cout << endl;
}
Array a2 = a1;
{
cout << "\na2: ";
for (int i = 0; i<a2.length(); i++) {
cout << a2.getData(i) << " ";
}
cout << endl;
}
Array a3(5);
{
a3 = a1;
a3 = a2 = a1;
cout << "\na3: ";
for (int i = 0; i<a3.length(); i++){
cout << a3[i] << " ";
}
cout << endl;
}
// ==和!=
{
if (a3 == a1) {
printf("equality\n");
}
else {
printf("inequality\n");
}
if (a3 != a1) {
printf("inequality\n");
}
else {
printf("equality\n");
}
}
system("pause");
return 0;
}
————————————————
版权声明:本文为CSDN博主「sampson MrLiang」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lisemi/article/details/93618161