day46——运算符重载

六、运算符重载

自定义的类中,一般是不支持运算符的,如果对于该类的对象,非要使用相关运算符,需要进行将运算符进行重载

运算符重载的好处:可以使得程序代码更加优雅、好看、简洁、高效

6.1 运算符种类

单、算、移、关、逻辑、条件表达式、赋值、逗号

6.2 运算符重载的定义

运算符重载,也是实现泛型编程的一种,能够实现“一符多用”,可以使用运算符重载来完成对象与对象之间的符号运算

6.3 运算符重载函数

1> 函数名:统一为 operator# //这里的#表示要被重载的运算符

2> 参数:根据运算符的功能和位置而定,全局函数版双目运算符参数有两个,成员函数版双目运算符参数一个

全局函数版,单目运算符参数有一个,成员函数版单目运算符参数无

3> 返回值:根据运算符的特征而定

6.4 调用时机和调用机制

调用时机:当对象使用该运算符时,系统自动调用运算符重载函数

调用机制:左调右参原则,运算符左侧是函数的调用者,运算符右侧是函数的参数

例如:a = b; //a.operator=(b);

s1 + s2; //s1.operator+(s2);

6.5 运算符重载函数的格式

1> 任意一个运算符重载都有两个版本:成员函数版和全局函数版

2> 实际使用过程中,只实现一种重载的情况,常用成员函数版

6.6 算术运算符

1> 种类:+、-、*、/、%

2> 算术运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数

3> 左操作(L):既可以是左值也可以是右值

右操作数(R):既可以是左值也可以是右值

运算结果:只能是右值

4> 格式:

成员函数版:const 类名 operator#(const 类名 &R) const

解析:第一个const保护运算结果不被修改

第二个const保护右操作数在运算过程中不被修改

第三个const保护左操作数自身在运算过程中不被修改

全局函数版:const 类名 operator#(const 类名 &L,const 类名 &R)

6.7 赋值类运算符重载

1> 种类:=、+=、-=、*=、/=、%=

2> 赋值运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数

3> 左操作(L):只能是左值

右操作数(R):既可以是左值也可以是右值

运算结果:左操作数自身的引用

4> 格式:

成员函数版:类名 & operator#(const 类名 &R)

全局函数版:类名 & operator#( 类名 &L,const 类名 &R)

6.8 关系运算符重载函数

1> 种类:>、<、==、!=、>=、<=

2> 关系运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数

3> 左操作(L):既可以是左值也可以是右值

右操作数(R):既可以是左值也可以是右值

运算结果:bool类型的右值

4> 格式:

成员函数版:const bool operator#(const 类名 &R) const

全局函数版:const bool operator#(const 类名 &L,const 类名 &R)

6.9 单目运算符

1> 种类:-(负号) 、!

2> 格式:# R; #表示运算符号,R表示右操作数

3> 操作数(R):既可以是左值也可以是右值

运算结果:同类的一个右值

4> 格式:

成员函数版:const 类名 operator#() const

全局函数版:const bool operator#(const 类名 &R)

6.10 自增自减运算

1> 种类:++ 、 --

2> 格式:# R; #表示运算符号,R表示右操作数

3> 操作数(R):只能是左值

运算结果:

前置:是自身的引用
    成员格式:类名 &operator#()
    全局格式:类名 &operator#(类名 &R)
后置:临时值
    成员格式:类名 operator#(int)
    全局格式:类名 operator#(类名 &R, int)

6.11 插入和提取运算符重载

1> 插入和提取运算符的来源

namespace std
{
    ostream cout;
    istream cin;
}

2> 由于运算符重载函数的调用遵循左调有参原则,所以,自定义类的重载函数,需要流对象和自定义类的对象

例如:cout << c1; //cout.operator<<(c1)

3> 此时,原则上来说,需要在流所在的类对象中,写成员函数,难度较大,所以,对于该类的重载函数,我们只能实现全局函数版,将该函数作为类的友元函数

4> 由于不需要使用istream和ostream类中的私有成员,所以,全局函数版的重载函数不需要在流对应的类中,声明友元函数

如果,重载函数中,需要使用自定义的类中的私有成员,则需要在自定义类中,声明该全局函数为友元函数

5> 格式:

ostream& operator<<(ostream& L, const 类名& R)

istream& operator>>(istream &L, 类名 &R)

6.12 类型转换运算符

1> 有时,需要将类对象进行强制类型转换为其他数据类型,此时就需要在类体内提供类型转换运算符重载函数

2> 定义格式: operator 要转换的类型() { 返回对应类型的数据 }

6.13 函数对象(仿函数)

1> 本质上就是重载 () 运算符,由于调用时,跟函数调用一样,所以称为仿函数

2> 定义格式: operator() (参数列表) {函数体内容}

3> 调用格式:对象名(实参名); //对象 . operator() (实参名)

6.14 运算符重载的限制

 不能重载运算符 ::(作用域解析)、.(成员访问)、.*(通过成员指针的成员访问)及 ?:(三元条件)。
不能创建新运算符,例如 **、<> 或 &|。
运算符 && 与 || 的重载失去短路求值。
重载的运算符 -> 必须要么返回裸指针,要么(按引用或值)返回同样重载了运算符 -> 的对象。
不可能更改运算符的优先级、结合方向或操作数的数量。
&&、|| 和 ,(逗号)在被重载时失去其特殊的定序性质,并且即使不使用函数调用记法,也表现为与常规的函数调用相似。
(C++17 前)

作业:

将myString类中能够实现的操作都实现一遍

#include <iostream>
#include <cstring>
#include <string>
class myString {
private:
    char* str;
    int length;

public:
    // 构造函数
    myString(const char* s = "") { // 定义一个名为myString的构造函数,接受一个指向字符数组的指针作为参数,默认值为空字符串
        length = strlen(s); // 计算传入字符串的长度
        str = new char[length + 1]; // 为新的字符数组分配内存空间,长度为原字符串长度加1(用于存储空字符'\0')
        strcpy(str, s); // 将传入的字符串复制到新分配的字符数组中
    }
    // 析构函数
    ~myString() {
        delete[] str;
    }
    // operator=为字符串赋值
    myString& operator=(const myString& other) {
        if (this != &other) {
            delete[] str;
            length = other.length;
            str = new char[length + 1];
            strcpy(str, other.str);
        }
        return *this;
    }
    // operator+=为字符串赋值
    myString& operator+=(const myString& other) {
        if (this != &other) {
            char* new_str = new char[length + other.length + 1];
            strcpy(new_str, str);
            strcat(new_str, other.str);
            delete[] str;
            str = new_str;
            length += other.length;
        }
        return *this;
    }
    // 输出运算符重载
    friend std::ostream& operator<<(std::ostream& cout, const myString& obj) {
        cout << obj.str;
        return cout;
    }
    // 输入运算符重载
    friend std::istream& operator<<(std::istream& cin, const myString& obj) {
        cin >> obj.str;
        return cin;
    }


    // at访问指定字符,有边界检查
    char at(int index) const {
        if (index >= 0 && index < length) {
            return str[index-1];
        } else {
            std::cerr << "Index out of bounds" << std::endl;
            return '\0'; // 返回空字符作为错误标志
        }
    }
    // 重载 operator[]
    char operator[](int index) {
        return str[index-1];
    }
    //返回指向字符串首字符的指针
    const char*  data(  )const {
        return str;
    }
    //返回字符串的不可修改的C字符数组版本
    const char* c_str() const {
        return str;
    }
    //检查字符串是否为空
    bool empty() const {
        return length == 0;
    }

    size_t size() const {
        return length;
    }

    int capacity() const {
        return length + 1;
    }

    void clear() {
        delete[] str;
        str = nullptr;
        length = 0;
    }
    //后附字符到结尾
    void push_back(char c) {
        // 创建一个新数组,长度比原字符串多一个字符的空间,用于存放新添加的字符
        char* newStr = new char[length + 2];
        // 将原字符串复制到新数组中
        strcpy(newStr, str);
        // 在新数组的末尾添加新字符
        newStr[length] = c;
        newStr[length + 1] = '\0'; // 在新字符串末尾添加空字符,表示字符串结束
        // 更新成员变量
        delete[] str; // 删除原字符串
        str = newStr; // 将新字符串赋值给成员变量
        length++; // 增加字符串长度
    }
    //移除末尾字符
    void pop_back() {
            if (length > 0) {
                length--;
                str[length] = '\0';
            }
        }
    //比较俩个字符串
    bool operator==(const myString& other) const {
            if (length != other.length) {
                return false;
            }
            for (int i = 0; i < length; ++i) {
                if (str[i] != other.str[i]) {
                    return false;
                }
            }
            return true;
        }
};


int main() {
    myString str1("Hello");
    myString str2("World");
    myString str3("");
    std::cout<<str1<<std::endl;
    str1=str2;
    std::cout<<str1<<std::endl;
    str1="good";
    std::cout<<str1<<std::endl;
    std::cout << "str1的第1位字母: " << str1.at(1) << std::endl;
    std::cout << "str1的第2位字母: " << str1.operator[](2) << std::endl;
    myString file1=str2.data();
    std::cout << file1<< std::endl;
    myString file2=str2.c_str();
    std::cout << file2<< std::endl;
    if(str1.empty()){
        std::cout << "str1为空"<< std::endl;
    }else{
        std::cout << "str1不为空"<< std::endl;
    }
    if(str3.empty()){
        std::cout << "str3为空"<< std::endl;
    }else{
        std::cout << "str3不为空"<< std::endl;
    }
    std::cout << "str1中字符数为:"<<str1.size()<< std::endl;
    std::cout << "str1中容量为为:"<<str1.capacity()<< std::endl;
    str1.clear();
    if(str1.empty()){
        std::cout << "str1为空"<< std::endl;
    }else{
        std::cout << "str1不为空"<< std::endl;
    }
    str1="good";
    str1+=str2;
    std::cout<<"str1为"<<str1<<std::endl;
    std::cout<<"str2为"<<str2<<std::endl;
    char ch;
    std::cin >> ch;
    str1.push_back(ch); // 将字符ch添加到字符串末尾
    std::cout << str1 << std::endl;
    str1.pop_back();
    std::cout << str1 << std::endl;
    std::cout<<"str1为"<<str1<<std::endl;
    std::cout<<"str2为"<<str2<<std::endl;
    if (str1 == str2) {
            std::cout << "str1和str2相等" << std::endl;
        } else {
            std::cout << "str1和str2不相等" << std::endl;
        }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值