当前位置: 首页 > article >正文

C++(重载)

string类对象为什么可以使用 运算符 操作。

C++中把运算符当作函数处理,string类之所以能使用运算符操作,是因为该类中重载了运算符这种特殊的成员函数。

一、运算符函数

C++中把运算符当作函数处理,在表达式中遇到运算符时,编译器会先寻找相关的运算符函数。

例如a+b,会先寻找 [const]T[&] operator+ ([const] T& a,[const] T& b) 格式的函数,这种函数又被称为全局运算符函数,运算对象都做为参数传递给该函数。

如果运算对象是自定义类型,还可以把运算符函数实现该类型的内部,a+b的运算符函数格式为 [const] T[&] operator+([const]T& that) [const],这种函数又被称为成员运算符函数,一般由左边的运算对象发起调用,右边的运算对象作为参数。

二、运算符重载

在设计结构、联合、类时,为了让它们的对象的操作更简便、易懂,我们会为它们实现部分运算符函数,这种行为被称为运行符重载,我们以自定义string为例进行讲解运算符重载。

1、双目运算符重载
重载为成员运算符函数

1、左边的运算对象如果可能是常量,则需要把运算符函数设置为常函数。

2、如果右边的运算对象可能是常量,则需要给形参设置const属性。

3、一般在运算符函数中不修改运算对象的数据,而是由左右的运算对象的运算结果产生临时对象。

#include <iostream>
#include <stdlib.h>
using namespace std;
​
class Int
{
    int num;
public:
    Int(int num):num(num) {}
    // 双目运算符 成员函数
    const Int operator+(const Int& that)const
    {
        return Int(num+that.num);
    }
    const Int operator-(const Int& that)const
    {
        return Int(num-that.num); 
    }
    const Int operator*(const Int& that)const
    {
        return Int(num*that.num); 
    }
    const Int operator/(const Int& that)const
    {
        return Int(num/that.num); 
    }
    const Int operator%(const Int& that)const
    {
        return Int(num%that.num); 
    }
};

重载为全局运算符函数

给类对象优先重载成员运算符函数时,必须要在类的内部实现,当无法修改对象的源码时,也就无法在结构、联合、类的内部增加成员运算符函数,又想给类对象增加运行符函数,这种情况只能定义全局的运算符函数,把运算对象都作为参数传递给运算符函数。

如何在全局运行符函数内访问私有成员—友元

当一个非成员函数,需要访问类中的私有成员时,可以把函数设置为该类的friend函数(也叫友元函数),这样在函数内就可以访问类的所有成员变量和成员函数。

一般情况下,在类的内部进行声明该函数(头文件内),并在声明的前面添加friend关键字,在类外实现友元函数。

也可以直接在类的内部实现有友函数,即使在类的内部实现也依然不是成员函数。

当重载全局运算符函数基本上都需要使用友元函数,我个人建议把友元函数实现在类的内部。

#include <iostream>
#include <stdlib.h>
using namespace std;
​
class Int
{
    int num;
public:
    Int(int num):num(num) {}
    void show(void)const
    {
        cout << "num=" << num << endl;
    }
    friend const Int operator-(const Int& i1,const Int& i2);
    friend const Int operator*(const Int& i1,const Int& i2);
    friend const Int operator/(const Int& i1,const Int& i2);
    friend const Int operator%(const Int& i1,const Int& i2);
};
​
const Int operator-(const Int& i1,const Int& i2)
{
    return Int(i1.num-i2.num+100);
}
const Int operator*(const Int& i1,const Int& i2)
{
    return Int(i1.num*i2.num+100);
}
const Int operator/(const Int& i1,const Int& i2)
{
    return Int(i1.num/i2.num+100);
}
const Int operator%(const Int& i1,const Int& i2)
{
    return Int(i1.num%i2.num+100);
}

3、输入、输出运算符重载

1、cin、cout是标准库中具有输入、输出功能的类对象,它的类名叫istream、ostream,当我们想给某种类对象增加输入、输出的功能时,就需要实现 >>、<<运算符函数对于该类对象的支持,也就是重载输入、输出运算符。

2、由于cin、cout都在>>、<<运算的左边,如果想实现成员运算符函数,就需要在istream、ostream类中实现,而我们无法增加或修改istream、ostream类的代码,所以只能实现全局的 >>、<< 运算符函数。

3、由于输入、输出过程中需要使用cin、cout记录成功、失败等状态,所以输入、输出函数的istream、ostream要使用引用,且不能用const修饰。

4、输入、输出过程中还可能要访问对象的 私有、保护的成员,所以全局的 >>、<< 运算符函数有必要设置成友元函数(右边对象)。

friend ostream& operator<<(ostream& os,const <类名>& obj)
{
    return os << obj.成员1 << obj.成员1 << ...;
}
friend istream& operator>>(istream& is,<类名>& str)
{
    return is >> obj.成员1 >> obj.成员1 >> ...;
}

练习:实现自定义的MyString类,实现它的构造、拷贝构造、赋值、析构,并重载以下运算符。

==
>
<
>=
<=
!=
​
+
+=
>>
<<

4、单目运算符的重载

单目运算符的重载与双目运算符重载的规则几乎相同,只是运算对象数量不同而已

重载为成员运算符函数

唯一的运算对象就是函数的调用者,参数列表为空,[cosnt] T[&] operator<单目运算符>(void)[const]

#include <iostream>
#include <stdlib.h>
using namespace std;
​
class Int
{
    int num;
public:
    Int(int num):num(num) {}
    const Int operator-(void)const
    {
        return Int(0-num);
    }
    bool operator!(void)const
    {
        return !num;
    }
    const Int operator~(void)const
    {
        return Int(~num);
    }
    Int* operator&(void)
    {
        return (Int*)&num;
    }
    const Int* operator&(void)const
    {
        return (const Int*)&num;
    }
    void show(void)const
    {
        cout << "num=" << num << endl;
    }
};

重载为全局运算符函数

与双目运算符一样,只有无法修改运算对象的代码时,才会重载全局运算符函数,参数列只有一个,[cosnt] T[&] operator<单目运算符>([const] T&)[const]

#include <iostream>
#include <stdlib.h>
using namespace std;
​
class Int
{
    int num;
public:
    Int(int num):num(num) {}
    void show(void)const
    {
        cout << "num=" << num << endl;
    }
    
    friend const Int operator-(const Int& that)
    {
        return Int(0-that.num);
    }
    friend bool operator!(const Int& that)
    {
        return !that.num;
    }
    friend const Int operator~(const Int& that)
    {
        return ~that.num;
    }
    friend Int* operator&(Int& that)
    {
        return (Int*)&that.num;
    }
    friend const Int* operator&(const Int& that)
    {
        return (const Int*)&that.num;
    }
};

注意:尽量重载为成员运算符函数。

5、自变运算符重载
前自变运算符重载 ++i/--i

前自变运算符与普通的单目运算符重载方法相同。

后自变运算符重载 ++i/--i

后自变运算符的运算对象也只有一个,为了区别前自变,我们需要在参数列增加一个什么都不做的int关键字,我们把这种用法叫哑元

#include <iostream>
#include <stdlib.h>
using namespace std;
​
class Int
{
    int num;
public:
    Int(int num):num(num) {}
    void show(void)const
    {
        cout << "num=" << num << endl;
    }
    Int& operator++(void)
    {
        num++;
        cout << "前++" << endl;
        return *this;
    }
    Int operator++(int) // 哑元
    {
        cout << "后++" << endl;
        return Int(num++);  
    }
    /*
    friend Int& operator++(Int& that)
    {
        cout << "前++" << endl;
        that.num++;
        return that;
    }
    friend Int operator++(Int& that,int) // 哑元
    {
        cout << "后++" << endl;
        return Int(that.num++);
    }
    */
};

6、new/delete运算符的重载

在C++中是把new/delete关键字当作运算符,也就是运算符函数,所以如果有特殊需要可以对它们进行重载,重载格式参考单目运算符。

new运算符的重载:

new 运算符有两中用法,也就有两个格式重载函数。

格式1:new TYPE

该格式既可以重载为全局函数,也可以重载为成员函数。

格式2:new(ptr) TYPE

该格式只能重载为成员函数,因为C++标准库中已经为该格式的函数。

new/delete运算符有什么用:

1、记录下申请、释放的内存块的地址,方便程序员检测是否有内存泄漏。

2、防止用户大量分配、释放小块内存,产生内存碎片。

3、默认的new只能给类对象分配内存,重载后可以给成员指针一起分配内存,该方法适合重载为类的成员函数。

void* operator new(size_t size)
{
    void* ptr = malloc((size/4+1)*4);
    cout << ptr << "自定义的new"<< endl;
    return ptr;
}
void operator delete(void* ptr)
{
    cout << ptr << "自定义的delete" << endl;
    free(ptr);
}
#include <iostream>
#include <stdlib.h>
using namespace std;
​
class Student
{
    char sex;
    short age;
    float score;
public:
    char* name;
    void* operator new(size_t size)
    {
        cout << "自定义的new运算符成员函数" << endl;
        Student* stup = (Student*)malloc(size);
        stup->name = (char*)malloc(20);
        return stup;
    }
    void operator delete(void* ptr)
    {
        cout << "自定义的delete运算符成员函数" << endl;
        Student* stup = (Student*)ptr;
        free(stup->name);
        free(stup);
    }
};
int main(int argc,const char* argv[])
{
    Student* stup = new Student;
    delete stup;
    return 0;
}

三、运算符重载的局限性

1、无法重载的运算符
. 成员访问运算符
:: 作用域运算符
sizeof 长度运算符
?: 条件运算符
# 预处理符号
2、只能重载为成员函数的运算符

在C++中,有几个函数只能被重载为成员函数,它们分别是:

  1. 赋值运算符重载=,也叫赋值函数,要进行深拷贝时就需要重载。

  2. 索引运算符重载[],可以把对象伪装成数组使用。

  3. 函数调用运算符重载(),可以把对象伪装成函数使用。

  4. 成员访问运算符重载->,可以把对象伪装成指针使用。

    需要注意的是,这些函数只能被定义为成员函数,而不能被定义为全局函数或友元函数。除了上述函数,其他函数可以被定义为全局函数或友元函数。

3、优先重载为成员函数

只有无法修改该对象的代码时,才建议重载为全局函数,但重载全局函数需要在运算对象的类内设置友元,所以语法上支持,但实际情况是个死局。

4、重载运算符时要注意的问题

1、重载运算符的目标是为了提高代码可读性、实用性,如果达不该效果建议定义为函数。

2、重载运算符函数时,要符合情况情理,不要反人性,不要成为你炫技的工具。

总结:

1、理解string类为什么可以使用运算符操作。

2、理解什么是友元、哑元。

3、熟练掌握输入、输出运算符的重载。

4、复习减少内存碎片、内存泄漏的方法。


http://www.kler.cn/a/308538.html

相关文章:

  • centos7上安装mysql
  • 【前端】JavaScript高级教程:线程机制与事件机制
  • 无插件H5播放器EasyPlayer.js网页web无插件播放器vue和react详细介绍
  • 生成模型——PixelRNN与PixelCNN
  • jQuery笔记
  • 从0开始机器学习--Day23--支持向量机
  • Skytower
  • 第二百三十五节 JPA教程 - JPA Lob列示例
  • k8s(kubernetes)的PV / PVC / StorageClass(理论+实践)
  • 数据库的操作:SQL运算符(算法/比较/逻辑/位)
  • 【大模型专栏—进阶篇】智能对话全总结
  • 力扣100题——动态规划(二)
  • React Native防止重复点击
  • 详解 Pandas 的透视表函数
  • PHP智能化云端培训考试系统小程序源码
  • YOLOv5 Detect.py 改变检测框box线条的粗细,隐藏检测框的检测信息,只显示检测框box
  • PHP在现代Web开发中的高效应用与最佳实践
  • Linux杂项知识
  • 深入解析:如何通过网络命名空间跟踪单个进程的网络活动(C/C++代码实现)
  • Vue3.0组合式API:setup()函数
  • SpringBoot 消息队列RabbitMQ在代码中声明 交换机 与 队列使用注解创建
  • Linux | 进程间通信:管道、消息队列、共享内存与信号量
  • 快速排序
  • 《Oracle(一)- 基础》
  • 【webpack4系列】编写可维护的webpack构建配置(四)
  • COTERRORSET—— LLM训练新基准让模型从自身错误中学习