原文转载至:https://blog.csdn.net/liitdar/article/details/80654324

      https://blog.csdn.net/liitdar/article/details/80656156

1. 概述
1.1 what

operator 是C++的一个关键字,它和运算符(如=)一起使用,表示一个运算符重载函数,在理解时可将operator和运算符(如operator=)视为一个函数名。

使用operator重载运算符,是C++扩展运算符功能的方法。使用operator扩展运算符功能的原因如下:

    使重载后的运算符的使用方法与重载前一致
    扩展运算符的功能只能通过函数的方式实现(实际上,C++中各种“功能”都是由函数实现的)

1.2 why

对于C++提供的所有操作符,通常只支持对于基本数据类型和标准库中提供的类的操作,而对于用户自己定义的类,如果想要通过该操作符实现一些基本操作(比如比较大小,判断是否相等),就需要用户自己来定义关于这个操作符的具体实现了。

比如,我们要设计一个名为“person”的类,现在要判断person类的两个对象p1和p2是否一样大,我们设计的比较规则是按照其年龄来比较,那么,在设计person类的时候,就可以通过对操作符“==”进行重载,来使用操作符“==”对对象p1和p2进行比较了(根据前面的分析,实际上比较的内容应该是person类中的数据成员“age”)。

我们上面说的对操作符“==”进行重载,说是“重载”,是由于编译器在实现操作符“==”功能的时候,已经为我们提供了这个操作符对于一些基本数据类型的操作支持,只不过由于现在该操作符所操作的内容变成了我们自定义的数据类型(如class),而默认情况下,该操作符是不能对我们自定义的class类型进行操作的,所以,就需要我们通过重载该操作符,给出该操作符操作我们自定义的class类型的方法,从而达到使用该操作符对我们自定义的class类型进行运算的目的。

1.3 how

实现一个操作符重载的方式通常分为两种情况:

    将操作符重载实现为类的成员函数;
    操作符重载实现为非类的成员函数(即全局函数)。

1.3.1 将操作符重载实现为类的成员函数

在类体中声明(定义)需要重载的操作符,声明方式跟普通的成员函数一样,只不过操作符重载函数的名字是“关键字 operator +以及紧跟其后的一个C++预定义的操作符”,样式如下(person是我们定义的类):

 bool operator==(const person& ps)
{
if (this->age == ps.age)
{
return true;
}
return false;
}

示例代码(operator_test2.cpp)如下:

 #include <iostream>

    using namespace std;

    class person
{
private:
int age;
public:
person(int nAge)
{
this->age = nAge;
} bool operator==(const person& ps)
{
if (this->age == ps.age)
{
return true;
}
return false;
}
}; int main()
{
person p1(10);
person p2(10); if (p1 == p2)
{
cout << "p1 is equal with p2." << endl; }
else
{
cout << "p1 is not equal with p2." << endl;
} return 0;
}

编译并运行上述代码,结果如下:
  p1 is equal with p2.
通过上述结果能够知道:因为操作符重载函数“operator==”是person类的一个成员函数,所以对象p1、p2都可以调用该函数。其中的 if (p1 == p2) 语句,相当于对象p1调用函数“operator==”,把对象p2作为一个参数传递给该函数,从而实现了两个对象的比较。

1.3.2 操作符重载实现为非类的成员函数(即全局函数)

对于全局重载操作符,代表左操作数的参数必须被显式指定。

示例代码如下:

 #include <iostream>

    using namespace std;

    class person
{
public:
int age;
}; // 左操作数的类型必须被显式指定
// 此处指定的类型为person类
bool operator==(person const& p1 ,person const& p2)
{
if (p1.age == p2.age)
{
return true;
}
else
{
return false;
}
} int main()
{
person p1;
person p2;
p1.age = 18;
p2.age = 18; if (p1 == p2)
{
cout << "p1 is equal with p2." << endl;
}
else
{
cout << "p1 is NOT equal with p2." << endl;
} return 0;
}

编译并运行上述代码,结果如下:
  p1 is equal with p2
1.3.4 操作符重载的方式选择

可以根据以下因素,确定把一个操作符重载为类的成员函数还是全局函数:

    如果一个重载操作符是类成员,那么只有当与它一起使用的左操作数是该类的对象时,该操作符才会被调用;而如果该操作符的左操作数确定为其他的类型,则操作符必须被重载为全局函数;
    C++要求'='、'[]'、'()'、'->'操作符必须被定义为类的成员操作符,把这些操作符通过全局函数进行重载时会出现编译错误
    如果有一个操作数是类类型(如string类),那么对于对称操作符(比如==操作符),最好通过全局函数的方式进行重载。

1.3.5 操作符重载的限制

实现操作符重载时,需要注意:

    重载后操作符的操作数至少有一个是用户定义类型;
    不能违反原来操作数的语法规则;
    不能创建新的操作符;
    不能重载的操作符包括(以空格分隔):sizeof . .* :: ?: RTTI类型运算符
    =、()、[]、以及 ->操作符只能被类的成员函数重载

1.3.6 操作符重载的详细用法
首先介绍为什么要对赋值运算符“=”进行重载。某些情况下,当我们编写一个类的时候,,并不需要为该类重载“=”运算符,因为编译系统为每个类提供了默认的赋值运算符“=”,使用这个默认的赋值运算符操作类对象时,该运算符会把这个类的所有数据成员都进行一次赋值操作。例如有如下类:

  class A
{
public:
int a;
int b;
int c;
};

那么对这个类的对象进行赋值时,使用默认的赋值运算符是没有问题的。示例代码(operator_test4.cpp)如下:

#include <iostream>

    using namespace std;

    class ClassA
{
public:
int a;
int b;
int c;
}; int main()
{
ClassA obj1;
obj1.a = 1;
obj1.b = 2;
obj1.c = 3; ClassA obj2;
obj2 = obj1; cout << "obj2.a is: " << obj2.a << endl; return 0;
}

编译并执行上述代码,结果如下:
  obj2.a is: 1
从上述结果能够知道:通过使用系统默认的赋值运算符“=”,可以让对象obj2中的所有数据成员的值与对象obj1相同。这种情况下,编译系统提供的默认赋值运算符可以正常使用。

但是,在下面的示例中,使用编译系统默认提供的赋值运算符,就会出现问题了。示例代码(operator_test5.cpp)如下:

#include <iostream>
#include <string.h> using namespace std; class ClassA
{
public:
ClassA()
{ } ClassA(const char* pszInputStr)
{
pszTestStr = new char[strlen(pszInputStr) + 1];
strncpy(pszTestStr, pszInputStr, strlen(pszInputStr) + 1);
}
virtual ~ClassA()
{
delete pszTestStr;
}
public:
char* pszTestStr;
}; int main()
{
ClassA obj1("liitdar"); ClassA obj2;
obj2 = obj1; cout << "obj2.pszTestStr is: " << obj2.pszTestStr << endl;
cout << "addr(obj1.pszTestStr) is: " << &obj1.pszTestStr << endl;
cout << "addr(obj2.pszTestStr) is: " << &obj2.pszTestStr << endl; return 0;
}

编译并运行上述代码,结果如下:

obj2.pszTestStr is: liitdar

addr(obj1.pszTestStr) is: 0x7ffdb7702da8

addr(obj2.pszTestStr) is: 0x7ffdb7702d98
Error in:double free or corruption
上述错误信息说明:当obj1和obj2进行析构的时候,由于重复释放了一块内存,导致程序崩溃报错。在这种情况下,就需要我们重载赋值运算符“=”了。

我们修改一下前面出错的代码示例,现编写一个包含赋值运算符重载函数的类,代码(operator_test5.cpp)如下:

  #include <iostream>
#include <string.h> using namespace std; class ClassA
{
public:
ClassA()
{ }
ClassA(const char* pszInputStr)
{
pszTestStr = new char[strlen(pszInputStr) + 1];
strncpy(pszTestStr, pszInputStr, strlen(pszInputStr) + 1);
}
virtual ~ClassA()
{
delete pszTestStr;
}
// 赋值运算符重载函数
ClassA& operator=(const ClassA& cls)
{
// 避免自赋值
if (this != &cls)
{
// 避免内存泄露
if (pszTestStr != NULL)
{
delete pszTestStr;
pszTestStr = NULL;
} pszTestStr = new char[strlen(cls.pszTestStr) + 1];
strncpy(pszTestStr, cls.pszTestStr, strlen(cls.pszTestStr) + 1);
} return *this;
} public:
char* pszTestStr;
}; int main()
{
ClassA obj1("liitdar"); ClassA obj2;
obj2 = obj1; cout << "obj2.pszTestStr is: " << obj2.pszTestStr << endl;
cout << "addr(obj1.pszTestStr) is: " << &obj1.pszTestStr << endl;
cout << "addr(obj2.pszTestStr) is: " << &obj2.pszTestStr << endl; return 0;
}

编译并运行上述代码,结果如下:
obj2.pszTestStr is: liitdar

addr(obj1.pszTestStr) is: 0x7ffdb7702da8

addr(obj2.pszTestStr) is: 0x7ffdb7702d98
通过上述结果能够看到,我们利用赋值运算符重载函数,解决了对象赋值的情况下,析构函数中过程中多次释放同一块内存的问题。

对于上述代码,有以下几点需要说明:

    当为一个类的对象赋值(可以用本类对象为其赋值,也可以用其它类型的值为其赋值)时,该对象(如本例的obj2)会调用该类的赋值运算符重载函数,进行具体的赋值操作。如上述代码中的“obj2 = obj1;”语句,用obj1为obj2赋值,则会由obj2调用ClassA类的赋值运算符重载函数。
    语句“ClassA obj2;
              obj2 = obj1;“
    和语句“ClassA obj2 = obj1;”在调用函数上是有区别的:前者第一句是对象obj2的声明及定义,调用类ClassA的无参构造函数,所以“obj2 = obj1;”一句是在对象obj2已经存在的情况下,用obj1来为obj2赋值,调用的是赋值运算符重载函数;而后者,是用obj1来初始化obj2,调用的是拷贝构造函数。关于拷贝构造函数的语句样式为“ClassA(const ClassA& cls)”,关于拷贝构造函数的内容,此处不进行详述。
    当程序没有显式地提供一个以“本类或本类的引用”为参数的赋值运算符重载函数时,编译器会自动生成一个默认的赋值运算符重载函数。

2.2 示例代码2

示例代码(operator_test6.cpp)如下:

#include<iostream>
#include<string> using namespace std; class Data
{
private:
int data; public:
// 构造函数
Data()
{
};
// 构造函数
Data(int _data):data(_data)
{
cout << "This is constructor" << endl;
}
// 赋值运算符重载函数
Data& operator=(const int _data)
{
cout << "This is operator=(int _data)" << endl;
data = _data; return *this;
}
}; int main()
{
// 调用构造函数
Data data1(1);
Data data2, data3;
// 调用赋值运算符重载函数
data2 = 1;
// 调用默认的赋值运算符重载函数
data3 = data2; return 0;
}

编译并执行上述代码,结果如下:
This is constructor
This is operator=(int _data)
上述结果说明:“data2 = 1;”语句调用了我们提供的以int型参数(而非本类或本类的引用)为形参的赋值运算符重载函数,而“data3 = data2;”的成功执行,说明该语句调用了编译器提供的默认的赋值运算符重载函数。

如果将上述代码中赋值运算符重载函数去掉,重新编译执行,结果如下:
This is constructor

This is constructoe
上述结果说明,当用一个非类A的值(如上面的int类型值)为类A的对象赋值时:

    如果检测到构造函数和赋值运算符重载函数同时存在,则会调用赋值运算符重载函数;
    如果检测到的构造函数,就会调用这个构造函数。

总结

综合上述示例内容,我们可以知道针对以下情况,需要显式地提供赋值运算符重载函数(即自定义赋值运算符重载函数):

    用非类A类型的值为类A的对象赋值时(当然,这种情况下我们可以不提供相应的赋值运算符重载函数,而只提供相应的构造函数,如更改后的示例代码2)。
    当用类A类型的值为类A的对象赋值,且类A的数据成员中含有指针的情况下,必须显式提供赋值运算符重载函数(如示例代码1)。

C++的重载操作符(operator)介绍的更多相关文章

  1. 重载操作符 operator overloading 学习笔记

    重载操作符,只是另外一种调用函数的方法和表现方式,在某些情况它可以让代码更简单易读.注意不要过度使用重载操作符,除非它让你的类更简单,让你的代码更易读. 1语法 如下: 其中友元,关键字不是必须的,但 ...

  2. C++重载操作符operator

    operator是C++关键字,用于对C++进行扩展: 1.可以被重载的操作符:new,new[],delete,delete[],+,-,*,/,%,^,&,|,~,!,=,<,> ...

  3. 重载操作符 'operator'

    operator 是 C++ 的(运算符的)重载操作符.用作扩展运算符的功能. 它和运算符一起使用,表示一个运算符函数,理解时应将  [operator+运算符] 整体上视为一个函数名. 要注意的是: ...

  4. C++的重载操作符(operator)介绍(转)

    本文主要介绍C++中的重载操作符(operator)的相关知识. 1. 概述 1.1 what operator 是C++的一个关键字,它和运算符(如=)一起使用,表示一个运算符重载函数,在理解时可将 ...

  5. [019]转--C++ operator关键字(重载操作符)

    原博客:http://www.cnblogs.com/speedmancs/archive/2011/06/09/2076873.html operator是C++的关键字,它和运算符一起使用,表示一 ...

  6. C++ operator关键字(重载操作符)(转)

    operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名. 这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面要使运算 ...

  7. C++中operator关键字(重载操作符)

    operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名. 这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面要使运算 ...

  8. C++ operator(重载操作符) 【转】

    转自:http://www.cnblogs.com/xiangxiaodong/archive/2012/02/12/2348144.html operator是C++的关键字,它和运算符一起使用,表 ...

  9. C++ operator关键字(重载操作符)

    operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名.     这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面 ...

随机推荐

  1. 浏览器输入URL之后,HTTP请求返回的完整过程

    1.输入url,按下回车时,先做一个redirect(重定向),因为浏览器可能记录本机的地址已经永久跳转成新的地址,所以一开始浏览器就先要判断下需不需要重定向,以及重定向到哪里:2.然后第二步就是看A ...

  2. Linux Bash命令杂记(tr col join paste expand)

    Linux Bash命令杂记(tr col join paste expand) tr命令 tr命令可以将输入的数据中的某些字符做替换或者是作删除 tr [-ds] STR d: 删除输入数据的中的S ...

  3. Go语言核心36讲(Go语言基础知识二)--学习笔记

    02 | 命令源码文件 我们已经知道,环境变量 GOPATH 指向的是一个或多个工作区,每个工作区中都会有以代码包为基本组织形式的源码文件. 这里的源码文件又分为三种,即:命令源码文件.库源码文件和测 ...

  4. 随机生成文章的AI(C++)

    #include <iostream> #include <cstdlib> #include <ctime> #include <fstream> u ...

  5. Java(18)抽象类

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201625.html 博客主页:https://www.cnblogs.com/testero ...

  6. Fikker 管理平台弱口令

    官网:www.fikker.com 应用介绍:Fikker 是一款面向 CDN/站长 的专业级网站缓存(Webcache)和反向代理服务器软件(Reverse Proxy Server). 发现过程: ...

  7. vue3.x异步组件

    在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块 vue2.x 曾经简单的异步组件 components: { AsyncComponent: () =& ...

  8. Noip模拟8 2021.6.17

    T1 星际旅行 仔细一看,发现像一个欧拉路(简称一笔画). 满足"可以一笔画"的条件是: 1.所有点都有偶数条连边; 2.有偶数个点连奇数条边; 满足以上两个条件的任意一个即可一笔 ...

  9. 模拟赛18 T1 施工 题解

    前言: 真的是不容易啊.这个题在考场上想到了最关键的性质,但是没写出来. 后来写出来,一直调,小错不断. 没想到改的最后一个错误是两个int 乘起来爆了int 其实最后我还是觉得复杂度很假.\(n^2 ...

  10. 如何理解Stand SPI Dual SPI 和Quad SPI??

    1.首先看一下接口 Standard SPI: CLK, /CS, DI, DO, /WP, /Hold Dual SPI: CLK, /CS, IO0, IO1, /WP, /Hold Quad S ...