运算符重载

学习运算符重载,让运算符能做一些原来做不了的事情,方便它的使用

一、运算符重载的概念

1.什么是运算符重载

1.重载:重新载入,就像之前学的函数重载,对一个已有的函数赋值一个新的定义,因此同一个函数名就可以有不同的含义。

2.运算符也是可以重载的,比如cout在输出一个变量的时候,能接受不同类型的数据并输出,他就是重载了<<运算符,这个就是运算符重载

3.所以运算符重载指的是对已有的运算符重新定义新的运算规则,以适应不同的数据类型,当然重载之后之前的运算规则还是有的

2.为什么要进行运算符重载

1.运算符重载之后可以让运算符去适应不同的数据类型,对于基本数据类型,系统给出了运算符的操作规则,对于自定义数据类型来说,系统不知道该给出什么规则

class student 
{
    int id;
    int age;
    char name[20];
    public:
    student(int id,int age,const char* name)
    {
        this->id=id;
        this->age=age;
        strcpy(this->name,name);
    }
}
student stu1(1,23,"张三");
student stu2(2,24,"李四");
stu1+stu2;//如果是这样相加,那么应该加的是什么呢?编译器是不知道的,所以编译器就提供了运算符重载这个机制,让用户自定义运算符的运算规则

二、运算符重载

1.定义

1.关键字:operator,通过关键字来定义运算符重载(跟写个函数一样)

2.定义:

函数返回值类型 operator 要加载的运算符(参数列表)

{

函数体;

}

这里把运算符的使用,理解为调用函数,只是和平时的调用函数有一点区别

#include<iostream>
#include<string>
using namespace std;
class student
{
    int id;
    int age;
    string name;
public:
    student(int age)
    {
        this->age = age;
        id = 1;
        name = "sss ";
    }
    student(int id, int age, string name)
    {
        this->id = id;
        this->age = age;
        this->name = name;
    }
    void showstudent()
    {
        cout << id << "\t" << age << "\t" << name << endl;
    }
​
    //重载 + 号 双目运算符,this占一个参数
    student operator+(const student& p1)//这个函数会返回一个新的对象
    {
        //这个重载就是让两个对象相加,加的是各自的年龄
        int x = this->age + p1.age;
        student p(x);
        return p;//返回的是一个对象,会调用拷贝构造
    }
    //重载 - 号
    int operator-(int x)
    {
        return this->id - x;
    }
    
};
//1.操作这个运算符之后,返回值类型是什么
int main()
{
    student p1(0, 1, "yunfei");
    int x = p1.operator-(1);//显示调用-号重载
    cout << x << endl;
​
    student stu1(1, 23, "张三");
    student stu2(2, 24, "李四");
    student stu3 = stu1.operator+(stu2);
    student stu4 = stu1 + stu2;//隐式调用 + 号重载
    stu3.showstudent();
    system("pause");
    return 0;
}

注意:因为我们这个运算符是在类中写的,所以是通过对象调用的,那么this指针会占一个参数,而且是第一个参数,也就是说我们重载一个运算符,是在类中,而这个运算符是个单目运算符,那么参数列表就不用写东西了,是双目运算符,那么就需要传另一个参数进来

绝大部分的运算符重载都可以参照上面这个+号重载

2.特点

1.几乎所有的运算符都可以被重载,除了 . :: ()?():() sizeof()

2.运算符重载基本出现在类中和结构体中

3.运算符可以理解为函数的一个表现

3.注意事项

1.使用成员函数来实现运算符重载时,少写一个参数,因为第一个参数就是this指针。

2.一般情况下,单目运算符重载,使用成员函数进行重载更方便(不用写参数)

3.一般情况下,双目运算符重载,使用友元函数更直观

方便实现a+b和b+a相同的效果,成员函数方式无法实现。

例如: 100 + cow; 只能通过友元函数来实现

cow +100; 友元函数和成员函数都可以实现

特殊

= () [ ] -> 不能重载为类的友元函数!!!(否则可能和C++的其他规则矛盾),只能使用成员函数形式进行重载。

如果运算符的第一个操作数要求使用隐式类型转换,则必须为友元函数(成员函数方式的第一个参数是this指针)

4.规则

1.为了防止对标准类型进行运算符重载,C++规定重载运算符的操作对象至少有一个不是标准类型,而是用户自定义的类型

比如不能重载 1+2

但是可以重载 cow + 2 和 2 + cow // cow是自定义的对象

2.不能改变原运算符的语法规则, 比如不能把双目运算符重载为单目运算

3.不能改变原运算符的优先级

4.不能创建新的运算符,比如 operator**是非法的,

operator*是可行

5.不能对以下这四种运算符,使用友元函数进行重载

= 赋值运算符,()函数调用运算符,[ ]下标运算符,->通过指针访问类成员

6.不能对禁止重载的运算符进行重载

不能被重载的运算符

成员访问.
域运算::
内存长度运算sizeof
三目运算? : :
预处理#

可以被重载的运算符

双目运算符+ - * / %
关系运算符== != < <= > >=
逻辑运算符&& || !
单目运算符+(正号) -(负号) *(指针) &(取地址) ++ --
位运算& | ~ ^ <<(左移) >>(右移)
赋值运算符= += -= *= /= %= &= |= ^= <<= >>=
内存分配new delete new[ ] delete[ ]
其他( ) 函数调用 -> 成员访问 [ ] 下标 , 逗号

1.赋值运算符的重载,一定要使用引用参数(这样可以连续赋值 a=b=c)

2.如果一个类有指针成员,而且使用了动态内存分配,那么一定要定义自己的拷贝构造函数【要使用深拷贝】,避免调用自动生成的拷贝构造函数

因为自动生成的拷贝构造函数,是浅拷贝!

5.使用友元函数,实现运算符重载

1.类在已经实现且部分修改的情况下下,需要进行运算符重载,就可以通过友元的方式来进行重载

#include<iostream>
#include<string>
using namespace std;
class person
{
    int id;
    int age;
    string name;
public:
    person(int id, int age, string name)
    {
        this->id = id;
        this->age = age;
        this->name = name;
    }
    void showperson()
    {
        cout << id << "\t" << age << "\t" << name << endl;
    }
    //重载+号
    friend int operator+(person&p1, person&p2);
​
};
//形参使用的是类对象的引用,在实参传对象的时候不会调用拷贝构造
int operator+(person&p1, person&p2)
{
    return p1.id + p2.id;
}
​
int main()
{
    person stu1(1, 23, "张三");
    person stu2(2, 24, "李四");
    int x = operator+(stu1, stu2);//显示调用
    int y = stu1 + stu2;//隐式调用
    cout << x << endl << y << endl;
    system("pause");
    return 0;
}

6.其他运算符重载

1.左移右移运算符重载:

#include<iostream>
using namespace std;
class person
{
    int id;
public:
    person(int id)
    {
        this->id = id;
    }
    friend ostream& operator<<(ostream& os, person& p1);
    friend istream & operator>>(istream & in, person & p2);
};
​
//左移右移运算符重载,必须在类外重载,通过友元实现
ostream& operator<<(ostream& os, person& p1)//左移运算符
{
    os << p1.id << endl;
    return os;//返回的是一个cout,而且只能用引用
}
istream & operator>>(istream & in, person & p2)//右移运算符
{
    in >> p2.id;
    return in;
}
​
int main()
{
    person p1(10), p2(20);
    cin >> p1 >> p2;
    cout << p1 << endl << p2 << endl;
    
    
    system("pause");
    return 0;
}

2.前++,后++运算符重载:

#include<iostream>
using namespace std;
class person
{
    int id;
public:
    person(int id)
    {
        this->id = id;
    }
​
    //单目运算符,只需要一个this参数
    person& operator++()//前++
    {
        this->id++;
        return *this;
    }
​
    person& operator++(int)//后++,int是一个占位符,用来区分前++和后++的
    {
        static person temp = *this;//引用不能返回局部变量,要用静态变量
        this->id++;
        return temp;
    }
    friend ostream& operator<<(ostream& os, person& p1);
    friend istream & operator>>(istream & in, person & p2);
};
​
//左移右移运算符重载,必须在类外重载,通过友元实现
ostream& operator<<(ostream& os, person& p1)//左移运算符
{
    os << p1.id << endl;
    return os;//返回的是一个cout,而且只能用引用
}
istream & operator>>(istream & in, person & p2)//右移运算符
{
    in >> p2.id;
    return in;
}
​
int main()
{
    person p1(10), p2(20);
​
    cout << p1;//10
    cout << p1++;//10
    cout << p1;//11
    cout << ++p1;//12
    cout << p1;//12
​
​
    system("pause");
    return 0;
}

3.等号运算符重载:

#include<iostream>
using namespace std;
class person
{
    char* name;
public:
    //构造函数
    person(const char* name)
    {
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
    
    person& operator=(person&p1)//用不用引用传参,要看返回的对象会不会消失
    {
        if (this->name != NULL)
        {
            delete[]this->name;
            this->name = NULL;
        }
        //申请内存
        this->name = new char[strlen(p1.name) + 1];
        //拷贝赋值
        strcpy(this->name, p1.name);
        return *this;
    }
    void show()
    {
        cout << name << endl;
    }
    ~person()//如果有申请内存,就要加上析构函数
    {
        if (name != NULL)
        {
            delete[]name;
            name = NULL;
        }
    }
};
​
int main()
{
    {
        person p1("张三"), p2("李四"), p3("王五");
        p1 = p2 = p3;
        p1.show();
        p2.show();
        p3.show();
    }//加上大括号,让对象死亡,就能调用析构函数
    system("pause");
    return 0;
}

4.智能指针和==号运算符重载:

#include<iostream>
using namespace std;
​
class person
{
    int id;
public:
    person(int id)
    {
        this->id = id;
    }
    void show()
    {
        cout << id << endl;
    }
    
    bool operator==(person& p)
    {
        return this->id == p.id;
    }
    ~person()
    {
        cout << "person的析构函数" << endl;
    }
};
​
class smartpointer
{
    person* ps;//包含你要new出来的对象的类的指针
public:
    smartpointer(person* p)
    {
        //指向相同的内存空间
        ps = p;
    }
    //重载->
    person* operator->()//传回来的是地址,不是对象,不用引用
    {
        return ps;
    }
    //重载*
    person& operator*()//返回的是对象,会调用拷贝构造,所以返回值用引用,就不会再调用拷贝构造了
    {
        return *ps;//得到一个对象,
    }
    ~smartpointer()
    {
        if (ps != NULL)
        {
            delete ps;
            ps = NULL;
        }
    }
​
};
​
​
int main()
{
    {
        smartpointer p(new person(5));
        p->show();
        (*p).show();
        person p1(1), p2(3);
        cout << (p1 == p2) << endl;
    }//有三个对象,所以析构函数执行了三次
    system("pause");
    return 0;
}

5.[]运算符重载:

#include<iostream>
using namespace std;
class person
{
    char* name;
public:
    person(const char* name)
    {
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
    
    char& operator[](int index)
    {
        return name[index];
    }
    
    ~person()
    {
        if (name != NULL)
        {
            delete[]name;
            name = NULL;
        }
        cout << "这是析构函数" << endl;
    }
};
int main()
{
    person p("asdfg");
    cout << p[3] << endl;
    system("pause");
    return 0;
}

三、引用作为函数返回值

1.以引用返回函数值,定义函数时需要在函数名前加 &

2.用引用返回一个函数值的最大好处是:在内存中不产生被返回值的副本

3.返回值为引用的时候,返回的是一个地址,隐形指针

4.当返回值不是引用时,编译器会专门给返回值分配出一块内存的

引用作为返回值,必须遵守以下规则:

(1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

(3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

  • 8
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值