第5章 运算符重载

运算符概述

        纯单目运算符,只能有一个操作数,包括:!、~、sizeof、new、delete 等

        纯双目运算符,只能有两个操作数,包括:[]、->、% 、 = 等

        三目运算符,有三个操作数,如“ ? : ”

        既是单目又是双目的运算符,包括: + 、 - 、 & 、 * 等

        多目运算符,如函数参数表 “()”。

        左值运算符是运算结果为左值的运算符,其表达式可出现在等号左边,如前置 ++、-- 以及赋值运算 = 、 += 、 *= 和 &= 等。右值运算符是运算结果为右值的运算符,如 + 、 - 、 >> 、 % 、后置 ++、-- 等。

        某些运算符要求第一个操作数为左值,如 ++、-- 、 = 、 += 、 &= 等。

运算符重载

C++预定义了简单类型的运算符重载,如3 + 5、3.2 + 5.3分别表示整数和浮点加法。

故C++规定运算符重载必须针对类的对象,即重载时至少有一个参数代表对象(类型如A、const A、A & 、const A & 、volatile A等) 。

C++用 operator加运算符进行运算符重载。对于普通运算符成员函数,this 隐含参数代表第一个操作数对象。

运算符分类

根据能否重载及重载函数的类型,运算符分为:

  1. 不能重载:sizeof、.、.*、::、 ? :
  2. 只能重载为类的普通成员函数: = 、– > 、()、[]
  3. 只能重载为普通函数:new、delete
  4. 其他运算符:可以重载为类的普通成员函数和普通函数,但不能重载为类的静态成员函数。

若运算符为左值运算符,则重载后运算符函数最好返回非只读引用类型(左值)。

当运算符要求第一个参数为左值时,不能使用 const 说明第一个参数(含this),例如 ++、--、 = 、 += 等的第一个参数。

重载运算符函数可以声明为类的友元;

重载的普通运算符成员函数也可定义为虚函数;

重载的非成员函数被视为普通函数。

重载运算符函数一般不能缺省参数,只有任意目的运算符() 省略参数才有意义。

重载不改变运算符的优先级和结合性。

重载一般也不改变运算符的操作数个数。特殊的运算符->、++、--除外。

class A;
int operator = (int, A&);           //错误, 不能重载为普通函数
A& operator +=(A&, A&);             //正确,重载 += 为普通函数
A& operator ==(A* a, A b[]);        //A * 和 A[] 参数不代表对象
class A {friend int operator=(int, A&);              //错误,不存在普通函数operator=()static int operator()(A&, int);            //错误,不能为静态成员static int operator+(A&, int);              //错误,不能为静态成员friend A& operator += (A&, A&);             //正确, operator += ( ) 是什么函数?A& operator ++();                           //隐含参数this代表一个对象
};
#include <iostream>
using namespace std;
class A {int  x;
public:int getx() const { return x; }            //const A *const this,代表对象A(int x) { A::x = x; }                    //A *const this
};
int operator+(const A& x, int y)  //普通函数: 参数const A &x 代表一个对象
{return x.getx() + y;
}           //能否写成:  return x.x + y ? 不能 x 是私有成员
int operator+(int y, A x)           //普通函数: 参数 A x 代表一个对象
{return x.getx() + y;
}
//不能声明 int operator+(A[6], int);  A[6]不是单个对象
//不能声明 int operator+(A*, int);    A* 是对象指针,属于简单类型,不代表对象
void main(void)
{A  a(6);                                          //调用A(int)时,实参&a传递给隐含形参thiscout << "a+7=" << a + 7;                          //调用int operator+(const A &, int)cout << "a+7=" << operator+(a, 7);  //调用int operator+(const A &, int)cout << "8+a=" << operator+(8, a);  //调用int operator+(int, A)cout << "8+a=" << 8 + a;                          //调用int operator+(int, A)
}

参数个数 

重载函数种类不同,参数表列出的参数个数也不同。

  • 重载为普通函数:参数个数 = 运算符目数
  • 重载为普通成员:参数个数 = 运算符目数 - 1 (即this指针)
  • 重载为静态成员:参数个数 = 运算符目数(没有this指针)

有的运算符既为单目又为双目,如*, +, -等。

特殊运算符不满足上述关系:->双目重载为单目;前置++、--重载为单目, 后置++、-- 重载为双目;函数() 可重载为任意目。

()表示强制类型转换时为单参数;表示函数时可为任意个参数。

#include <string.h>
class SYMTAB;
struct SYMBOL {char* name;  int  value;  SYMBOL* next;   friend SYMTAB;
private:SYMBOL(char* s, int v, SYMBOL* n) { /*...*/ };   ~SYMBOL() { /*…*/ }
} *s;
class SYMTAB {SYMBOL* head;
public:SYMTAB() { head = 0; };     ~SYMTAB() { /*...*/ }SYMBOL* operator( )(char* s, int v, int w) { /*…*/ };
} tab;
void main(void) { s = tab(“a”, 1, 2); }   //等价 s = tab.operator()(“a”, 1, 2)

后置与前置

        运算符++和--都会改变当前对象的值,重载时最好将参数定义为只读引用类型(左值)。

后置运算应重载为返回右值的双目运算符函数:

        如果重载为类的普通函数成员,则该函数只需定义一个int类型的参数(已包含一个不用const修饰的this参数);

        如果重载为普通函数(C函数),则最好声明非const引用类型和 int 类型的两个参数(无this参数)。

前置运算应重载为返回左值的单目运算符函数:

        前置运算结果应为左值,其返回类型应该定义为非只读类型的引用类型;左值运算结果可继续++或--运算。

        如果重载为普通函数(C函数),则最好声明非const引用类型一个参数(无this参数)。

class A {int  a;friend A& operator--(A& x) { x.a--; return x; }  //前置运算 (普通函数), 返回左值friend A operator--(A&, int);                    //后置运算(普通函数), 返回右值
public:A& operator++() { a++;   return *this; }         //前置运算(成员函数)A operator++(int) { return  A(a++); }            //后置运算(成员函数)A(int x) { a = x; }
};  //A m(3); (--m)-- 可以;因为--m左值,其后--要求左值操作数
A operator--(A& x, int) {        //x左值引用,实参被修改return  A(x.a--);            //先取x.a返回A(x.a)右值,再x.a--
} //A m(3); (m--)--不可;因为m--右值,其后--要求左值操作数//重载双目–>,使其只有一个参数(单目),重载–>必须返回指针类型.
struct A { int a; A(int x) { a = x; } };
class  B {A  x;
public:A* operator ->() { return &x; };   //只有一个参数this, 故重载为单目B(int v) :x(v) {  }
} b(5);
void  main(void) {int i = b->a;                            //被解释为: i = (b.operator->( )) -> a, i = 5i = b.operator->()->a;                   //i = b.x.a = 5i = (*b.operator->()).a;    //i = (&*b.operator->( ))->a= b.operator ->( )->a
}

浅拷贝

        编译程序为每个类提供了缺省赋值运算符函数(浅拷贝),对类A而言,其成员函数原型为 A& operator=(const A&)。

        如果类自定义或重载了赋值运算函数,则优先调用类自定义或重载的赋值运算函数(不管是否取代型定义)。

        缺省赋值运算实现数据成员的浅拷贝复制,如果数据成员为指针类型,则不复制指针所指存储单元的内容。若类不包含指针,浅拷贝赋值不存在问题。

        如果函数参数要值参传递一个对象,当实参传值给形参时,若类A没有定义 A(const A&) 形式的构造函数,则值参传递也通过浅拷贝赋值实现。

         当类包含指针时,浅拷贝赋值可造成内存泄漏,并可导致页面保护错误或变量产生副作用:

#include <string.h>
#include <iostream>
using namespace std;
class STRING {char* s;
public:STRING(const char* c) { strcpy(s = new char[strlen(c) + 1], c); }STRING(const STRING& s);                            //深拷贝构造函数STRING(STRING&& s) noexcept;                        //移动构造函数virtual STRING& operator=(const STRING& s);         //深拷贝赋值函数virtual STRING& operator=(STRING&& s) noexcept;     //移动赋值函数virtual char& operator[](int x) { return s[x]; }virtual STRING operator+(const STRING&) const;virtual STRING& operator+=(const STRING& s) { return *this = *this + s; };virtual ~STRING() noexcept { if (s) { delete[]s; s = 0; }; };
};
STRING STRING::operator+(const STRING& c) const {char* t = new char[strlen(s) + strlen(c.s) + 1];STRING r(strcat(strcpy(t, s), c.s));   //strcpy、strcat返回tdelete[] t;    return r;
}
STRING& STRING::operator=(const STRING& cs) {delete[] s;strcpy(s = new char[strlen(cs.s) + 1], cs.s);  return *this;
}
void main(void) {STRING  s1(“123”), s2(“abc”); s3(“hello”);(s1 = s1 + s2) = s2;  //重载“=”返回左值,可连续赋值否则不可//等价于 s1 = s1 + s2;  s1 = s2;   s1被连续赋值s1 += s3;s3[0] = 'T';   // s3[0] = 调用 char &operator[](int x) 返回左值
}

 注意

  • 应定义 T(const T&) 形式的深拷贝构造函数;
  • 应定义 T(T&&) noexcept 形式的移动构造函数;
  • 应定义 virtual T& operator = (const T&) 形式的深拷贝赋值运算符;
  • 应定义 virtual T& operator = (T&&) noexcept 形式的移动赋值运算符;
  • 应定义 virtual ~T() 形式的虚析构函数;
  • 在定义引用 T& p = *new T() 后,要用 delete & p 删除对象;
  • 在定义指针 T * p = new T() 后,要用 delete p 删除对象;

强制类型转换

        如定义了合适的类型转换函数,就可以完成操作数的类型转换;

        如定义了合适的构造函数,就可以构造符合类型要求的对象,构造函数也可以起到类型转换的作用。

        对象与不同类型的数据进行运算,可能出现在双目运算符的左边和右边,为此,可能需要定义多种运算符重载函数。

        只定义几种运算符重载函数是可能的,即限定操作数的类型为少数几种乃至一种。如果运算时对象类型不符合操作数的类型,则可以通过类型转换函数转换对象类型,或者通过构造函数构造出符合类型要求的对象。

//定义“复数 + 复数”、“复数 + 实数”、“复数 + 整数”、 “复数 - 复数”、“复数 - 实数”、“复数 - 整数”
//几种运算(还有复数同实数乘除运算, 等等):
class COMPLEX {double r, v;
public:COMPLEX(double r1, double v1);COMPLEX operator+(const COMPLEX& c) const;COMPLEX operator+(double d) const;COMPLEX operator+(int d) const;COMPLEX operator-(const COMPLEX& c) const;COMPLEX operator-(double d) const;COMPLEX operator-(int d) const;… … … …  //更多的 +、- 运算法重载函数
};

单参数构造函数的特殊性

        单参数的构造函数具备类型转换作用,必要时能自动将参数类型的值转换为要构造的类型。

        以下通过定义单参数构造函数简化重载(同时注意C++会自动将int转为double):

class COMPLEX {double r, v;
public:COMPLEX(double  r1);COMPLEX(double  r1, double v1) { r = r1; v = v1; }            COMPLEX operator-(const COMPLEX& c) const;            
};
//定义COMPLEX m(3),m + 2转换为m + 2.0转换为 m + COMPLEX(2.0)。

        单参数的构造函数相当于类型转换函数,单参数的 T::T(const A) 、 T::T(A&&) 、T::T(const A&) 等相当于A类到T类的强制转换函数。

        强制类型转换函数:operator 类型表达式()。由于转换后的类型就是函数的返回类型,所以强制类型转换函数不需要定义返回类型, 也不带输入参数(表示将 this 对象转换为其他类型)。

        同时定义 A::operator T() 和 T::T(const A&) 容易出现二义性错误。

        按照C++约定,强制类型转换的结果通常为右值,故最好不要将类型转换函数的返回值定义为左值,也不应该修改当前被转换的对象(参数表后用const说明this)。

        C++规定转换的类型表达式不包含() 和[], 只能使用引用、指针。如operator int A::** const& ()正确,而 operator int(*)* () 是错误的。

struct A {int  i;A(int v) { i = v; }virtual operator int() const { return  i; }  //类型转换返回右值
} a(5);
struct B {int  i, j;B(int x, int y) { i = x;  j = y; }operator int() const { return  i + j; }  //类型转换返回右值operator A() const { return A(i + j); }  //类型转换返回右值
} b(7, 9), c(a, b);
void  main(void) {           //分析下面各条语句的执行过程int  i = 1 + a;          //a.operator int() => 1 + intA  x = b;                //x(b):  b.operator A() => A(const &A) (编译器提供)A  y(a);                 //A(const A &) (编译器提供)x = b;                   //b.operator A() => A(int) => x.operator=() (编译器提供) => ~A()x = b + a;        //b.operator int() + a.operator int()=> A(int) => x.operator=()=> ~A()printf(“ % d % d”, a, (int)a);      //VFT地址    5
}
//若去掉A:: operator int() 中的virtual, printf ( ) 结果 ? 5   5
#include<iostream>
using std::cout;
using std::endl;
class A
{int  a;
public:A(int x) { cout << "A(int x) called" << endl; a = x; }A(const A& x) { cout << "A(const A& x)  called" << endl; a = x.a; }A& operator = (const A& x) { cout << "A& operator =  called" << endl; a = x.a;  return *this; };operator int() const { cout << "operator int() const  called" << endl; return a + a; }~A() {}
};
void  main()
{ cout << "line 16:" << endl; A a(1), b(a);     //A(int), A(const A &)cout << "line 17:" << endl; A c = 1;          //A(1)cout << "line 18:" << endl; A d = a;          //A(a),不调用operator=() 这里是初始化不是赋值??cout << "line 19:" << endl; c = 1;            //A(1), operator=(), ~A()cout << "line 20:" << endl; d = a;            //operator=()cout << "line 21:" << endl; d = 1 + a;        //a.operator int(), A(int), operator=(), ~A()
}

Problem:

(1) 若没有构造函数 A::A(const A& x) ?

        调用编译器提供的默认的浅拷贝构造函数。

(2) 可以定义构造函数A::A(const A x) 吗?

        不能,报错:类的复制构造函数不能带有类型的参数

        这是因为如果用形参,编译器会生成一个临时的实参变量用于赋值,而它本身就要用到这个构造函数。假如通过了编译,也会造成死循环。

class A
{int  a;
public:A(int x) { a = x; }A& operator=(const A& x) { a = x.a;  return *this; };operator int() const { return a + a; }~A() { }
};void  main()
{A a(1), b(a);     //A(int), A(const A &)A c = 1;          //A(1)A d = a;          //A(a),不调用operator=()c = 1;            //A(1), operator=(), ~A()d = a;            //operator=()d = 1 + a;        //a.operator int(), A(int), operator=(), ~A()
}

重载new和delete

        运算符函数new和delete定义在头文件new.h中,new的参数就是要分配的内存的字节数。其函数原型为:

        extern void* operator new(unsigned bytes);

        extern void operator delete(void* ptr);

        在使用运算符new分配内存时,使用类型表达式而不是值表达式作为实参,编译程序会根据类型表达式计算内存大小并调用上述new函数。例如:new long[20] 。

        按上述函数原型重载,new和delete可重载为普通函数,也可重载为类的静态成员函数。

        OS的最小内存分配单位为节(16字节, 即使new char), 故重载new可先分得OS一大块内存,然后再分给需要单个字符的指针变量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/241690.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#,字符串匹配(模式搜索)有限自动机(Finite Automata)算法的源代码

一、有限状态自动机 图中两个圆圈&#xff0c;也叫节点&#xff0c;用于表示状态&#xff0c;从图中可以看成&#xff0c;它有两个状态&#xff0c;分别叫0和1。从每个节点出发&#xff0c;都会有若干条边。当处于某个状态时&#xff0c;如果输入的字符跟该节点出发的某条边的内…

go语言(八)---- map

map的声明方式有以下三种。 package mainimport "fmt"func main() {//第一种声明方式//声明map1是一个map类型&#xff0c;key是String&#xff0c;value是Stringvar myMap1 map[string] stringif myMap1 nil {fmt.Println("myMap1 是一个空map")}//在使…

idea中使用git提交代码报 Nothing To commit No changes detected

问题描述 在idea中右键&#xff0c;开始将变更的代码进行提交的时候&#xff0c;【Commit Directory】点击提交的时候 报 Nothing To commit No changes detected解决方案 在这里点击Test 看看是不是能下面显示git版本&#xff0c;不行的话 会显示一个 fix的字样&#xff0c;行…

阿里云ECS使用docker搭建mysql服务

目录 1.确保正确安装好docker 2.安装mysql镜像 3.创建容器&#xff08;设置端口映射、目录映射&#xff09; 1.确保正确安装好docker 安装教程&#xff1a; 阿里云ECS(CentOS镜像)安装docker-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135686614?spm10…

Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务

技术背景 我们在对接Unity下推送模块的时候&#xff0c;遇到这样的技术诉求&#xff0c;开发者希望在Android的Unity场景下&#xff0c;获取到前后摄像头的数据&#xff0c;并投递到RTMP服务器&#xff0c;实现低延迟的数据采集处理。 在此之前&#xff0c;我们已经有了非常成…

如何下载OpenStreetMap(OSM)最新数据

OpenStreetMap&#xff08;OSM&#xff09;是一个开源的地图项目&#xff0c;旨在创建和提供免费、可自由使用、可编辑的地图数据和地图服务。以下是关于OpenStreetMap的一些关键信息&#xff1a; 社区驱动&#xff1a; OpenStreetMap是由一个全球性的志愿者社区共同创建和维护…

新上线一个IT公司微信小程序

项目介绍 项目背景: 一家IT公司,业务包含以下六大块: 1、IT设备回收 2、IT设备租赁 3、IT设备销售 4、IT设备维修 5、IT外包 6、IT软件开发 通过小程序,提供在线下单,在线制单,在线销售,业务介绍,推广,会员 项目目的: 业务介绍: 包含企业业务介绍 客户需…

怎么样的布局是符合可制造性的PCB布局?

满足可制造性、可装配性、可维修性要求&#xff0c;方便调试的时候于检测和返修&#xff0c;能够方便的拆卸器件&#xff1a; 1&#xff09;极性器件的方向不要超过2种&#xff0c;最好都进行统一方向等要求&#xff0c;如图1-1所示&#xff1b; 图1-1 极性器件方向统一摆放 2…

【Qt5】QString的成员函数trimmed

2024年1月19日&#xff0c;周五下午 QString 的 trimmed 方法是用于移除字符串两端的空白字符&#xff08;空格、制表符、换行符等&#xff09;的方法。它返回一个新的字符串&#xff0c;该字符串是原始字符串去除两端空白后的结果。 下面是一个简单的示例&#xff1a; #incl…

轻量化/高效扩散模型文献综述

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

Unity—配置lua环境变量+VSCode 搭建 Lua 开发环境

每日一句&#xff1a;保持须臾的浪漫&#xff0c;理想的喧嚣&#xff0c;平等的热情 Windows 11下配置lua环境变量 一、lua-5.4.4版本安装到本地电脑 链接&#xff1a;https://pan.baidu.com/s/14pAlOjhzz2_jmvpRZf9u6Q?pwdhd4s 提取码&#xff1a;hd4s 二、高级系统设置 此电…

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -投票帖子排行实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…

如何实现 H5 秒开?

我在简历上写了精通 H5&#xff0c;结果面试官上来就问&#xff1a; 同学&#xff0c;你说你精通 H5 &#xff0c;那你能不能说一下怎么实现 H5 秒 由于没怎么做过性能优化&#xff0c;我只能凭着印象&#xff0c;断断续续地罗列了几点&#xff1a; 网络优化&#xff1a;http2、…

如何在CentOS 7 中基于OpenSSL 1.0 搭建Python 3.0 环境

1、下载 通过https://www.python.org/ftp/python/下载Python安装包&#xff0c;这里下载Python-3.10.9.tgz&#xff1b; 2、上传 借助MobaXterm等工具将Python安装包上传至/opt目录&#xff1b; 3、解压 将JDK压缩文件解压至/opt目录&#xff1a;tar -xvf /opt/Python-3.1…

基于Django的Python应用—学习笔记—功能完善

一、让用户可以输入信息 创建forms.py 创建基于表单的页面的方法几乎与前面创建网页一样&#xff1a;定义一个 URL &#xff0c;编写一个视图函数并编写一个模板。一个主要差别是&#xff0c;需要导入包含表单 的模块forms.py 。 from django import forms from .models impor…

YOLOv5改进 | 主干篇 | 华为移动端模型GhostnetV2一种移动端的专用特征提取网络

一、本文介绍 本文给大家带来的改进机制是华为移动端模型Ghostnetv2,华为GhostNetV2是为移动应用设计的轻量级卷积神经网络(CNN),旨在提供更快的推理速度,其引入了一种硬件友好的注意力机制,称为DFC注意力。这个注意力机制是基于全连接层构建的,它的设计目的是在通用硬…

高效火情监测,科技助力森林防火【数字地球开放平台】

数字地球开放平台-以卫星遥感为核心的空天信息服务开放平台 (geovisearth.com) 2019年3月30日&#xff0c;四川省凉山州木里县爆发了一场森林火灾&#xff0c;火点位于海拔3800米左右&#xff0c;地形险峻、坡度陡峭、谷深难以抵挡火势。在扑救的过程中&#xff0c;27名森林消防…

计算机网络-ACL实验

一、NAT实验配置 NAT实验配置 通过基本ACL匹配VLAN 10网段&#xff0c;然后在出口设备NAT转换只要匹配到VLAN10地址则进行转换。 核心交换机 # 配置VLAN和默认路由&#xff0c;配置Trunk和Access接口 interface Vlanif10ip address 192.168.10.254 255.255.255.0 # interface V…

SpringMVC下半篇之整合ssm

4.ssm整合 4.1.创建表 CREATE TABLE account (id int(11) NOT NULL AUTO_INCREMENT,name varchar(20) DEFAULT NULL,money double DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8;4.2.创建工程 4.3.pom.xml <?xml version"1.0" encoding&…

MeterSphere本地化部署实践

项目结构 搭建本地环境 安装JDK11&#xff0c;配置好JDK环境&#xff0c;系统同时支持JDK8和JDK11安装IEAD&#xff0c;配置JDK环境配置maven环境,IDEA配置(解压可以直接使用)无限重置IDEA试用期配置redis环境(解压可以直接使用) 配置kafka环境 安装mysql-5.7环境&#xff…