运算符重载(operator)

news2024/11/16 2:34:06
语法:
返回值 operator 运算符 ( 参数 )
例:
Point operator+(const Point &p1,const Point &p2);
//输出 Point 类型的输出运算符重载函数
ostream & operator<<(ostream& out,Point &p)
{
    cout << "输出运算符重载函数" << endl;
    out << "Point(" << p.x << "," << p.y << ")";
    return out;
}
//输入 Point 类型的输入运算符重载函数
istream & operator>>(istream& in,Point &p)
{
    cout << "输入运算符重载函数" << endl;
    in >> p.x >> p.y;
    return in;
}
        C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。
        运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的 C++ 多态。目的在于让人能够用同名的函数来完成不同的基本操作。
不允许进行重载的运算符:
        1.三目运算符 (?:)
        2.成员访问运算符(.)
        3.作用域运算符(::)
        4.成员指针访问运算符(-> ,*)
        5.sizeof 运算符
        6.typeid 运算符
        6.四种强制类型转换运算符

运算符重载规则

        1. C++规定重载后的运算符的操作对象必须至少有一个是用户定义的类型,为什么呢?假如有两个数,int a,b;现在重载+运算符对 a,b 进行运算,得到一个 a*b 的值,这肯定是不合乎逻辑的。可能重载以后会有二义性,导致程序不知道该执行哪一个(是自带的的还是重载后的函数)
        2. 使用运算符不能违法运算符原来的句法规则。例如不能把一目运算符重载为两目运算符。
        3. 不能修改运算符原先的优先级。
        4.不能定义新的运算符。
        5. 大多数运算符可以通过成员函数和非成员函数进行重载但是下面这四种运算符只能通过成员函数进行重载:
= 赋值运算符
() 函数调用运算符
[ ] 下标运算符
        6. 除了上述的规则,其实我们还应该注意在重载运算符的时候遵守一些明智的规则:例如:不要将+ 运算符重载为交换两个对象的值。

重载的方式

        重载运算符的两种形式:分别为重载为类的非成员函数和重载为类的成员函数

重载为类的非成员函数

        重载为类的非成员时,通常我们都将其声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数。但是非友元又不是类的成员函数是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。
        如下,使用非成员函数方式重载+运算符对类进行加法运算
#include <iostream>
#include<string.h>
using namespace std;
class A
{
    private:
        int a;
        int b;
    public:
        A(int x,int y):a(x),b(y){}
        void show()
        {
            cout << "a:" << a << "b:" << b <<endl;
        }
        friend A operator+(A& a1,A& a2);
};
A operator+(A& a1,A& a2)
{
    return A(a1.a+a2.a,a1.b+a2.b);
}
int main(int argc, char const *argv[])
{
    A a(1,2);
    A b(2,3);
    A c = a + b;
    c.show();
    return 0;
}
        其他的一些二元算术运算符就不再举例说明了,依次去推即可。我们再举例两个个二元流运算符<<和>>,实现要对运算符进行重载,我们就一定要清楚,运算符的操作数都应该为啥类型,上面我们重载+号是为了实现两个 A 类相加,得到一个 A 类,所以运算符重载函数的参数就是两个 A 类引用,返回值 为 A 类,现在对<<和>>进行重载,我们希望可以通过<<输出类的成员变量,利
用>>去键盘获取成员变量的值,所以右边的类型应该是一个类引用,左边的话 <<为 ostream&,>>为 istream&,返回值也应该是 ostream&或 istream&( 目的是返回值可以做左值并且进行连续的流运算操作。 )
#include <iostream>
#include<string.h>
using namespace std;
class A
{
    private:
        int a;
        int b;
    public:
        A(int x,int y):a(x),b(y){}
        void show()
        {
            cout << "a:" << a << "b:" << b <<endl;
        }
        friend istream& operator>>(istream& is,A& a1);
        friend ostream& operator<<(ostream& os,A& a1);
};
istream& operator>>(istream& is,A& a1)
{
    is >> a1.a >> a1.b;
    return is;
}
ostream& operator<<(ostream& os,A& a1)
{
    os << a1.a << a1.b;
    return os;
}
int main(int argc, char const *argv[])
{
    A a(1,2);
    A b(2,3);
    cin >> a >> b;
    cout << a << b << endl;
    return 0;
}

        接着我们举例一个一元运算符自增自减的重载
        自增和自减运算符都是一元运算符,而且都会改变自身的内容,因此左边参数不能是常量而只能是引用类型。又因为自增分为后缀 i++和前缀++i 两种形式(自减也一样,下面就只举自增的例子了)。后缀自增返回的值不能做左值而前缀自增返回的值则可以做左值。为了区分前自 增和后自增,系统规定对前缀自增的运算符函数上添加一个 int 类型的参数作为区分的标志。
#include <iostream>
#include<string.h>
using namespace std;
class A
{
    private:
        int a;
        int b;
    public:
        A(int x,int y):a(x),b(y){}
        void show()
        {
            cout << "a:" << a << "b:" << b <<endl;
        }
        friend A operator++(A &a1,int); //后++
        friend A& operator++(A &a1); //前++,因为可以++++a,但是不可以 a++++,所以前++要返回引用
        friend A operator--(A &a1,int); //后--
        friend A& operator--(A &a1); //前--
};
A operator++(A &a1,int) //后++
{
    A st = a1;
    a1.a++;
    a1.b++;
    return st;
}
A& operator++(A &a1) //前++,因为可以++++a,但是不可以 a++++,所以前++要返回引用
{
    ++a1.a;
    ++a1.b;
    return a1;
}
A operator--(A &a1,int) //后--
{
    A st = a1;
    a1.a--;
    a1.b--;
    return st;
}
A& operator--(A &a1) //前--,返回引用同理
{
    --a1.a;
    --a1.b;
    return a1;
}
int main(int argc, char const *argv[])
{
    A a(1,2);
    A b(2,3);
    a++;
    a.show();
    ++a;
    a.show();
    b--;
    b.show();
    --b;
    b.show();
    return 0;
}

        接下来我们来看一种情况,我们重载一个+运算符,实现一个类加上一个整数的效果
#include <iostream>
#include<string.h>
using namespace std;
class A
{
    private:
        int a;
        int b;
    public:
        A(int x,int y):a(x),b(y){}
        void show()
        {
            cout << "a:" << a << "b:" << b <<endl;
        }
        friend A operator+(A& a1,int x);
};
A operator+(A& a1,int x)
{
    return A(a1.a + x,a1.b);
}
int main(int argc, char const *argv[])
{
    A a(1,2);
    A b(2,3);
    A c = a + 3;
    c.show();
    return 0;
}

        我们可以看到这样是没有任何问题的,但如果我把主函数中的 A c = a + 3;改成 A c = 3 + a;按 照+运算符的交换规则,这样应该是可以正确执行的才对,可是在编译时却会报一堆错误
        因为我们重载+运算符时,第一个参数是一个 A 类引用,第二个参数是一个整型数据,所以就 固定了左操作数要是一个 A 类的对象,右操作数是一个整型,改动位置的话就会有问题,所以我们一般进行类和基本类型进行可调换位置的运算时,类都承担右值。

重载为类的成员函数

        在学习类的 this 指针的时候我们知道成员函数是默认有一个隐藏参数指向该类的地址的(this),那么也就是说,如果我们将运算符重载函数声明为类的成员函数后,就不需要传递调用者自身的一个对象了。而且,以下运算符必须作为类的成员函数去重载。
1.函数运算符()
2.下标索引运算符[]
3.赋值运算符=
4.成员访问运算符->
以下运算符不能重载为类的成员函数:
1.流运算符
我们来看示例:
#include <iostream>
#include<string.h>
using namespace std;
class A
{
    private:
        int a;
        int b;
    public:
        A(int x,int y):a(x),b(y){}
        void show()
        {
            cout << "a:" << a << "b:" << b <<endl;
        }
        A operator+(A& a1);
};
A A::operator+(A& a1)
{
    return A(a1.a + this->a,a1.b + this->b);
}
int main(int argc, char const *argv[])
{
    A a(1,2);
    A b(2,3);
    A c = b + a;
    c.show();
    return 0;
}

        上面是一个+运算符的重载,只有一个参数,因为成员函数默认有一个 this 指针,指向调用对 象,那么当执行 b+a 时,就确实相当于 b.operator+(a);,函数中的 this 就是指向的 b,如果是a+b 那么 this 就指向 a。
        再来举例一个一元运算符
#include <iostream>
#include<string.h>
using namespace std;
class A
{
    private:
        int a;
        int b;
    public:
        A(int x,int y):a(x),b(y){}
        void show()
        {
            cout << "a:" << &a << "b:" << b <<endl;
        }
        int* operator&()
        {
            return &this->a;
        }
};
int main(int argc, char const *argv[])
{
    A a(1,2);
    cout << &a << endl;
    a.show();
    return 0;
}

我们来看看举例说明几个必须重载为成员函数的运算符,首先是赋值运算符
#include <iostream>
#include<string.h>
using namespace std;
class A
{
    private:
        int a;
        int b;
    public:
        A(int x,int y):a(x),b(y){}
        void show()
        {
            cout << "a:" << a << "b:" << b <<endl;
        }
        A& operator=(A& a1)
        {
            this->a = a1.a;
            this->b = a1.b;
            return *this;
        }
};
int main(int argc, char const *argv[])
{
    A a(1,3);
    A b(3,5);
    b = a;
    b.show();
    return 0;
}
        为什么赋值运算符必须要被重载为成员函数呢,因为在类中默认有一个赋值操作,如果我们将赋值运算符重载函数作为类外函数去实现,那么当类去使用赋值操作时,他会优先调用它内部默认的赋值操作,不会调用全局函数。
        类成员访问运算符( -> )可以被重载,但它较为麻烦。它被定义用于为一个类赋予"指针"行为。运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。
        运算符 -> 通常与指针引用运算符 * 结合使用,用于实现"智能指针"的功能。这些指针是行为与正常指针相似的对象,唯一不同的是,当您通过指针访问对象时,它们会执行其他的任务。比如,当指针销毁时,或者当指针指向另一个对象时,会自动删除对象。
        间接引用运算符 -> 可被定义为一个一元后缀运算符,如:
class Ptr{
    //...
    X * operator->();
};
下面我们来看看->运算符的一个重载示例:
#include <iostream>
#include <vector>
using namespace std;
// 假设一个实际的类
class Obj
{
    static int i, j;
    public:
        void f() const { cout << i++ << endl; }
        void g() const { cout << j++ << endl; }
};
// 静态成员定义
int Obj::i = 10;
int Obj::j = 12;
// 为上面的类实现一个容器
class ObjContainer
{
    vector<Obj*> a;
    public:
        void add(Obj* obj)
        { 
            a.push_back(obj); // 调用向量的标准方法
        }
        friend class SmartPointer;
};
// 实现智能指针,用于访问类 Obj 的成员
class SmartPointer
{
    ObjContainer oc;
    int index;
    public:
        SmartPointer(ObjContainer& objc)
        { 
            oc = objc;
            index = 0;
        }
        // 返回值表示列表结束
        bool operator++() // 前缀版本
        { 
            if(index >= oc.a.size() - 1) return false;
            if(oc.a[++index] == 0) return false;
            return true;
        }
        bool operator++(int) // 后缀版本
        { 
            return operator++();
        }
        // 重载运算符 ->
        Obj* operator->() const
        {
            if(!oc.a[index])
            {
                cout << "Zero value";
                return (Obj*)0;
            }
            return oc.a[index];
        }
};
int main() 
{
    const int sz = 10;
    Obj o[sz];
    ObjContainer oc;
    for(int i = 0; i < sz; i++)
    {
        oc.add(&o[i]);
    }
    SmartPointer sp(oc); // 创建一个迭代器
    do {
        sp->f(); // 智能指针调用
        sp->g();
    } while(sp++);
    return 0;
}

        函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。
        下面的实例演示了如何重载函数调用运算符 ()。
#include <iostream>
using namespace std;
class Distance
{
     private:
         int feet; // 0 到无穷
         int inches; // 0 到 12
     public:
         // 所需的构造函数
         Distance(){
             feet = 0;
             inches = 0;
         }
         Distance(int f, int i){
             feet = f;
             inches = i;
         }
         // 重载函数调用运算符
         Distance operator()(int a, int b, int c)
         {
             Distance D;
             // 进行随机计算
             D.feet = a + c + 10;
             D.inches = b + c + 100 ;
             return D;
         }
         // 显示距离的方法
         void displayDistance()
         {
             cout << "F: " << feet << " I:" << inches << endl;
         }
};
int main()
{
     Distance D1(11, 10), D2;
     cout << "First Distance : ";
     D1.displayDistance();
     D2 = D1(10, 10, 10); // invoke operator()
     cout << "Second Distance :";
     D2.displayDistance();
     return 0;
}
        下标操作符 [] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能。在重载下标运算符 “[ ]” 时,认为它是一个双目运算符,例如 X[Y] 可以看成
[ ]-----双目运算符;
X-----左操作数;
Y-----右操作数。
下面的实例演示了如何重载下标运算符 []。
#include<iostream>
using namespace std;
class Vector4{
    private:
        int v[4];
    public:
        Vector4(int a1,int a2,int a3,int a4){
            v[0]=a1;v[1]=a2;v[2]=a3;v[3]=a4;
        }
        int &operator[](int bi); //声明下标运算符 [] 重载函数
}; 
int &Vector4::operator[](int bi){ //定义下标运算符 [] 重载函数
    if(bi<0||bi>=4){ //数组的边界检查
        cout<<"Bad subscript!\n";
        exit(1);
    }
    return v[bi];
}
int main(){
    Vector4 ve(0,1,2,3);
    cout<<ve[2]<<endl; //ve[2] 相当于 ve.operator[](2)
    ve[3]=ve[2]; 
    cout<<ve[3]<<endl;
    ve[2]=22;
    cout<<ve[2];
    return 0;
}
        其实,通过上面很多例子可以看出,运算符重载的目的无非就是为了实现自定义变量的运 算,或者就是为了让程序的安全性提高。例如[]运算符的重载,可以做边界检测

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

01. Python基础环境搭建

目录 1、什么是Python 2、Python的特点 3、基础环境搭建 3.1、下载安装解释器 3.2、hello world 4、集成开发环境&#xff1a;PyCharm 4.1、下载安装Pycharm 4.2、新建简单Demo 1、什么是Python Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。Pytho…

Android EditText 实现强制性弹出只能输入英文的键盘

如果 EditText 控件不做任何特殊处理&#xff0c;例如笔者手机默认弹出的是百度输入法的软键盘&#xff0c;可实现中英文切换&#xff0c;并且自带英文单词智能联想功能&#xff08;与系统安装输入法和设置相关&#xff09;。但在某些应用场景下&#xff0c;例如在英语APP里练习…

在CSDN上挣点外快的小tips

作为一个在csdn上也挣了一点辛苦费的博主&#xff0c;个人简单总结了两个方法。 1、道德的方法 如上图&#xff0c;可以把自己曾经做过的一些设计或其它资源类的内容&#xff0c;打包传到CSDN的资源池中&#xff0c;有条件的可以写个文章引流一下&#xff0c;运气好的话会有人下…

Python基础入门(6)----Python控制流:if语句、for循环、while循环、循环控制语句

文章目录 Python控制流:if语句、for循环、while循环、循环控制语句if语句示例:for循环示例:while循环示例:循环控制语句示例:最佳实践Python控制流:if语句、for循环、while循环、循环控制语句 控制流是编程中的基础概念,它允许我们根据不同的条件执行不同的代码块,或者…

中西部地区教育正获优质均衡高质量发展

教育部日前消息&#xff0c;2022年我国基础教育财政性教育经费达3.2万亿元&#xff0c;比2015年增加了1.3万亿元&#xff0c;年均增长7.7%。近些年来&#xff0c;我国基础教育各学段财政总投入和生均支出逐年只增不减&#xff0c;为推动基础教育优质均衡发展提供了有力支撑。 …

antd 表单项联动验证时,disabled属性不生效

在antd表单中&#xff0c;表单项验证条件是根据其他表单项的值决定的&#xff0c;比如当前【时间限制】选择【无】时&#xff0c;【限制时段】表单项不可用 所以我单纯使用 disabled{sourceForm.getFieldValue().timeLimit}是不生效的&#xff0c;因为disabled切换会涉及到st…

怎么在相册里去水印?三种方法教你去除

当你查看相册时&#xff0c;有时可能会注意到一些照片上有水印&#xff0c;这可能会让人感到不满,不管你是想保存这些照片还是与他人分享&#xff0c;水印往往会影响图片的观赏效果&#xff0c;不过别担心我将向你介绍一些简单的方法&#xff0c;帮助你在相册中轻松去除这些水印…

解决docker tag打标签时报错:Error response from daemon: no such id

现象&#xff1a; 原因&#xff1a; docker tag时不仅仅要Repository仓库名&#xff0c;也需要原有的tag作为版本号 解决办法&#xff1a; docker tag 原有仓库名: 原有tag值 新的打标名称 问题解决&#xff01;

适合汽车应用的MAX49017ATA/VY、MAX40025AAWT、MAX40025CAWT、MAX40026ATA/VY(线性)微功耗比较器

一、MAX49017ATA/VY 内置基准电压源的1.7V、双通道微功耗比较器 MAX49017是一款节省空间的双通道比较器&#xff0c;内置基准电压源&#xff0c;提供推挽输出。该器件通过了AEC-Q100认证&#xff0c;非常适合汽车应用&#xff0c;例如汽车电池监控系统、信息娱乐系统音响主机和…

冰点还原精灵 Deep Freeze 管理密码忘了怎么办?

环境&#xff1a; Win10 专业版 Deep Freeze V.8.63 问题描述&#xff1a; 冰点还原精灵 Deep Freeze 管理密码忘了怎么办&#xff1f; 解决方案&#xff1a; 1.找一台安装Deep Freeze V.8.63相同版本的电脑&#xff0c;进入PE复制C盘Persi0.sys文件 2.到要破解电脑上进入…

DAY 12 结构体(重点) 共用体 枚举01

1.结构体 1.概念 将基本类型或构造类型封装在一起&#xff0c;拥有独立空间&#xff0c;这个数据结构 就叫结构体。 结构体 使用的关键字 struct. 一个结构体可以定义多个结构体变量 2.定义 注意&#xff1a; 1,结构体定义一般在头文件中 2,不要在定义结构体时给其成员初始…

7.判断素数----不知道哪里错了

#include<stdio.h>void fun(int n) { int i;for(i2;i<n;i){if(n%i0)break;}if(in)printf("%d是素数\n",n);elseprintf("%d不是素数",n); }int main(){int n;scanf("d",&n);fun(n);return 0;}

王干娘和西门庆-UMLChina建模知识竞赛第4赛季第18轮

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 只要最先全部答对前3题&#xff0c;即可获得本轮优胜。 所有题目的回答必须放在同一条消息中&…

虚幻引擎:如何使用 独立进程模式进行模拟

第一步:先更改配置 第二步,在启动的两个玩家里面,一个设为服务器,一个链接进去地图就可以了 1.设置服务器 2.另一个玩家链接

kubernetes存储-volumes

目录 一、Volumes的简介 二、emptyDir卷 1、emptyDir的引入 2、emptyDir 的使用场景 3、多容器共享volumes 4、emptyDir缺点 三、hostPath卷 1、hostPath卷简介 2、创建hostPath卷 3、NFS共享文件 四、PersistentVolume&#xff08;持久卷&#xff09; 1、PV与P…

利用SPDK改善NVMe存储I/O性能

长期以来&#xff0c;SATA、SAS盘统治着企业级存储&#xff0c;虽然前些年 SSD固态存储介质的出现对 AHCI协议类型的存储访问带来了一定性能提升&#xff0c;但与 CPU 的计算速度相比&#xff0c;只能是杯水车薪&#xff0c;并且 SSD 的性能也不能充分地得到发挥。传统存储技术…

缓存-Spring Cache 缓存抽象

缓存-Spring Cache 缓存抽象 Spring从版本3.1开始提供非侵入的将Cache集成到Spring应用的方式。Spring Cache提供Cache的统一抽象&#xff0c;支持集成各种不同的缓存解决方案。从4.1版本开始&#xff0c;提供了注解和更多的定制参数。 Spring Cache 抽象提供了对Java方法的缓存…

图形推理 | 判断推理

文章目录 一、位置规律二、样式规律三、属性规律四、数量规律 一、位置规律 平移 方向&#xff1a;直线&#xff08;上下、左右、斜对角线&#xff09;、绕圈&#xff08;顺逆时针&#xff09;常见步数&#xff1a;恒定、递增&#xff08;等差&#xff09; 旋转 方向&#xff…

优化仓储分拣流程——WMS系统的分拣方法

在现代物流管理中&#xff0c;仓储分拣是一个至关重要的环节。为了提高分拣效率和准确性&#xff0c;越来越多的企业采用WMS系统来支持分拣操作&#xff0c;帮助企业优化仓储分拣流程。 1. 分拣策略制定&#xff1a; WMS系统可以根据不同的业务需求和产品特性&#xff0c;制定…

机器学习 - DBSCAN聚类算法:技术与实战全解析

目录 一、简介DBSCAN算法的定义和背景聚类的重要性和应用领域DBSCAN与其他聚类算法的比较 二、理论基础密度的概念核心点、边界点和噪声点DBSCAN算法流程邻域的查询聚类的形成过程 参数选择的影响 三、算法参数eps&#xff08;邻域半径&#xff09;举例说明&#xff1a;如何选择…