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

06-C++类和对象强化

一、实现 String 类

1.string 类介绍

string 类是 c++ 里一个字符串操作相关的类,简化了一些复杂的字符串操作。

  • 在C语言中的一些字符串操作
int main() {
    // 定义字符数组并初始化
    char str[32] = "hello world";
    char str2[32] = "";

    // 字符串拷贝
    strcpy(str2, str);
    printf("str2 = %s\n", str2);
    
    // 字符串长度
    int len = strlen(str2);
    printf("str2长度为:%d\n", len);

    // 比较字符串大小
    char str3[32] = "hallo world";
    int ret = strcmp(str2, str3);
    if (ret > 0)
        printf("%s 大于 %s\n", str2, str3);
    else if (ret == 0)
        printf("%s 等于 %s\n", str2, str3);
    else
        printf("%s 小于 %s\n", str2, str3);
    return 0;
}
  • 运行结果
str2 = hello world          
str2长度为:11              
hello world 大于 hallo world
  • 通过 string 类实现上面的字符串操作
int main() {
    // 定义字符数组并初始化
    string str = "hello world";
    string str2 = "";

    // 字符串拷贝
    str2 = str;
    cout << "str2 = " << str2 << endl;

    // 字符串长度
    int len = str2.size();
    cout << "str2长度为:" << len << endl;

    // 比较字符串大小
    string str3 = "hallo world";
    if (str2 > str3)
        cout << str2 << " 大于 " << str3 << endl;
    else if (str2 == str3)
        cout << str2 << " 等于 " << str3 << endl;
    else
        cout << str2 << " 小于 " << str3 << endl;
    return 0;
}
  • 运行结果
str2 = hello world          
str2长度为:11              
hello world 大于 hallo world
  • 说明:上面分别是C语言实现的字符串的基本操作,以及通过c++ string 类实现的字符串的基本操作,可以看出,string 类用起来会更加方便。

2.实现自己的 MyString 类

这里实现一些 String 类中比较简单常用的功能。

2.1 MyString 类的成员

先设计好 MyString 类有哪些成员变量和成员函数。

  • 类的定义
// 定义一个 MyString 类
class MyString
{
    friend istream& operator>>(istream &in, MyString &obj);
    friend ostream& operator<<(ostream &out, MyString obj);
private:
    // 字符串的长度
    int size;
    // z字符串首元素地址
    char *str;
public:
    // 构造函数
    MyString();
    MyString(char *str);
    MyString(const MyString &obj);
    // 析构函数
    ~MyString();
    // 获取字符串的长度
    int Size();
    // 重载运算符
    char& operator[](int index);
    MyString& operator=(MyString &obj);
    MyString& operator=(char *str);
    bool operator>(MyString &obj);
    bool operator==(MyString &obj);
    bool operator<(MyString &obj);
    bool operator>(char *str);
};

2.2实现 MyString 的拷贝析构

MyString 类的无参构造、有参构造、拷贝构造、析构函数的代码实现。

  • 构造析构实现
MyString::MyString() {
    size = 0;
    str = NULL;
}

MyString::MyString(char *str) {
    size = strlen(str);
    // 多加一个放 \0
    this->str = new char[size + 1];
    // strcpy 会拷贝 \0
    strcpy(this->str, str);
}

MyString::MyString(const MyString &obj) {
    size = obj.size;
    this->str = new char[size + 1];
    strcpy(str, obj.str);
}

MyString::~MyString() {
    if (str != NULL)
    {
        delete [] str;
        str = NULL;
    }
}

2.3得到字符串的长度

即得到字符串对象中第一个字符到 \0 之间的字符的个数。

  • 字符串长度
int MyString::Size() {
    return size;
}

2.4输出字符串

如果我们直接 cout 输出字符串对象,因为对象中的成员除了字符串首元素地址,还有字符串的长度,因此不能直接输出,需要重载 << 运算符,又因为运算符的左值不是自定义对象,因此要通过全局函数重载 << 运算符,同时别忘了将函数添加友元。

  • 输出字符串:
// 打印字符串,重载 << 运算符
ostream& operator<<(ostream &out, MyString obj)
{
    out << obj.str;
    return out;
}

2.5输入字符串

键盘获取输入,为对象中成员变量赋值,但是需要实现cin >> 字符串对象,需要重载 >> 运算符。其左值也是一个非自定义对象,因此通过全局函数实现,将全局函数设置为友元。

  • 输入字符串
// 输入字符串,重载 >> 运算符
istream& operator>>(istream &in, MyString &obj)
{
    // 如果字符串不为空,先指向空
    if (obj.str != NULL)
    {
        delete [] obj.str;
        obj.str = NULL;
    }

    // 定义一个临时变量存放键盘输入的值
    char temp[1024] = "";
    in >> temp;
    obj.size = strlen(temp);
    obj.str = new char[obj.size + 1];
    strcpy(obj.str, temp);
    return in;
}
  • 说明:
    1. 这里的输入不是追加,因为不知道用户具体会输入多少字符,不确定以前的空间是否足够,因此先判断以前的空间是否为 NULL ,释放之前的空间,申请新空间存放字符串;
    2. 因为不确定用户输入多少字符串,不知道该申请多大的空间,于是通过一个临时变量保存字符串,再测字符串长度,根据实际长度申请堆区空间;
    3. 这里是要对对象的字符串指针变量写入值,要改变其内容,因此使用引用传递。

2.6操作单个字符

操作单个字符,就是通过下标索引,读取或修改字符串中的某个字符,需要重载 [] 运算符。左值为自定义对象,通过类的成员函数实现运算符重载。

  • 重载 []
char &MyString::operator[](int index) {
    // 先判断是否为空
    if (NULL == str)
    {
        cout << "字符串为空,无法操作" << endl;
        exit(-1);
    }

    // 判断索引是否越界
    if (index >=0 && index <size)
    {
        return str[index];
    }
    else
    {
        cout << index << "越界" << endl;
        exit(-1);
    }
}
  • 说明:exit 是直接退出当前进程。

2.7对象给对象赋值

一个对象给另外一个对象赋值,需要重载 = 运算符,同时考虑两种情况,一种是 对象1 = 对象2,另一种是对象 = 字符串

  • 对象给对象赋值
// 对象赋值给对象
MyString& MyString::operator=(MyString &obj) {
    // 先判断左值是否为空,非空要先释放
    if (str != NULL)
    {
        delete [] str;
        str = NULL;
    }
    // 将右变对象的值拷贝给左边对象,深拷贝
    size = obj.size;
    str = new char[size + 1];
    strcpy(str, obj.str);
    return *this;
}

// 将字符串赋值给对象
MyString& MyString::operator=(char *str) {
    // 先判断左值是否为空,非空要先释放
    if (this->str != NULL)
    {
        delete [] this->str;
        this->str = NULL;
    }
    // 将右变对象的值拷贝给左边对象,深拷贝
    size = strlen(str);
    this->str = new char[size + 1];
    strcpy(this->str, str);
    return *this;
}
  • 说明:
    1. 上面的第一个函数中,如果返回值为引用,那么参数也必须为引用,且第二个参数的返回值也必须为引用,目的是为了保证链式操作中类型匹配;
    2. 假设一种情况 str3 = str2 = str1 = "hello world":会先执行str1 = "hello world",调用上面的第二个函数,然后将赋值后的 str1 的引用返回。此时再执行str2 = str1,将上一次执行的结果作为参数传给第一个函数的 MyString &obj,参数为引用,类型匹配,如果第二个函数返回的是值不是引用,那么这里的链式操作就完成不了。完成了将 str1 的值赋给 str2 后,会继续返回 str2 的引用,再次调用第一个函数,将上一次执行的结果 str2 的引用传给MyString &obj,如果第一个函数的返回值不是引用,是值的话,这里传参对值取别名会报错,因此返回值和参数的类型必须匹配。

2.8比较字符串对象大小

比较字符串大小,即重载 > < == 运算符。

  • 比较字符串大小
// 重载 > 运算符
bool MyString::operator>(MyString &obj) {
    if ((str == NULL) || (obj.str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(str, obj.str) > 0)
        return true;
    return false;
}

// 重载 == 运算符
bool MyString::operator==(MyString &obj) {
    if ((str == NULL) || (obj.str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(str, obj.str) == 0)
        return true;
    return false;
}

// 重载 < 运算符
bool MyString::operator<(MyString &obj) {
    if ((str == NULL) || (obj.str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(str, obj.str) < 0)
        return true;
    return false;
}

除了比较两个字符串对象的大小,也可能比较一个字符串对象和一个字符串的大小,这里只以重载 > 举例。

  • 比较字符串对象与字符串的大小
bool MyString::operator>(char *str) {
    if ((this->str == NULL) || (str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(this->str, str) > 0)
        return true;
    return false;
}
  • 说明:比较两个字符串对象大小,以及比较字符串对象和字符串大小时,一定要先判断字符串是否指向 NULL,否则可能访问非法内存,导致段错误。

二、MyString 类完整代码

  • 代码演示
#include <iostream>
#include <string.h>

using namespace std;

// 定义一个 MyString 类
class MyString
{
    friend istream& operator>>(istream &in, MyString &obj);
    friend ostream& operator<<(ostream &out, MyString obj);
private:
    // 字符串的长度
    int size;
    // z字符串首元素地址
    char *str;
public:
    // 构造函数
    MyString();
    MyString(char *str);
    MyString(const MyString &obj);
    // 析构函数
    ~MyString();
    // 获取字符串的长度
    int Size();
    char& operator[](int index);
    MyString& operator=(MyString &obj);
    MyString& operator=(char *str);
    bool operator>(MyString &obj);
    bool operator==(MyString &obj);
    bool operator<(MyString &obj);
    bool operator>(char *str);
};

MyString::MyString() {
    size = 0;
    str = NULL;
}

MyString::MyString(char *str) {
    size = strlen(str);
    // 多加一个放 \0
    this->str = new char[size + 1];
    // strcpy 会拷贝 \0
    strcpy(this->str, str);
}

MyString::MyString(const MyString &obj) {
    size = obj.size;
    this->str = new char[size + 1];
    strcpy(str, obj.str);
}

MyString::~MyString() {
    if (str != NULL)
    {
        delete [] str;
        str = NULL;
    }
}

int MyString::Size() {
    return size;
}

// 打印字符串,重载 << 运算符
ostream& operator<<(ostream &out, MyString obj)
{
    out << obj.str;
    return out;
}

// 输入字符串,重载 >> 运算符
istream& operator>>(istream &in, MyString &obj)
{
    // 如果字符串不为空,先指向空
    if (obj.str != NULL)
    {
        delete [] obj.str;
        obj.str = NULL;
    }

    // 定义一个临时变量存放键盘输入的值
    char temp[1024] = "";
    in >> temp;
    obj.size = strlen(temp);
    obj.str = new char[obj.size + 1];
    strcpy(obj.str, temp);
    return in;
}

char &MyString::operator[](int index) {
    // 先判断是否为空
    if (NULL == str)
    {
        cout << "字符串为空,无法操作" << endl;
        exit(-1);
    }

    // 判断索引是否越界
    if (index >=0 && index <size)
    {
        return str[index];
    }
    else
    {
        cout << index << "越界" << endl;
        exit(-1);
    }
}

MyString &MyString::operator=(MyString &obj) {
    // 先判断左值是否为空,非空要先释放
    if (str != NULL)
    {
        delete [] str;
        str = NULL;
    }
    // 将右变对象的值拷贝给左边对象,深拷贝
    size = obj.size;
    str = new char[size + 1];
    strcpy(str, obj.str);
    return *this;
}

MyString& MyString::operator=(char *str) {
    // 先判断左值是否为空,非空要先释放
    if (this->str != NULL)
    {
        delete [] this->str;
        this->str = NULL;
    }
    // 将右变对象的值拷贝给左边对象,深拷贝
    size = strlen(str);
    this->str = new char[size + 1];
    strcpy(this->str, str);
    return *this;
}

bool MyString::operator>(MyString &obj) {
    if ((str == NULL) || (obj.str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(str, obj.str) > 0)
        return true;
    return false;
}

bool MyString::operator==(MyString &obj) {
    if ((str == NULL) || (obj.str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(str, obj.str) == 0)
        return true;
    return false;
}

bool MyString::operator<(MyString &obj) {
    if ((str == NULL) || (obj.str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(str, obj.str) < 0)
        return true;
    return false;
}

bool MyString::operator>(char *str) {
    if ((this->str == NULL) || (str == NULL))
    {
        cout << "存在空字符串,无法比较" << endl;
        exit(-1);
    }
    if (strcmp(this->str, str) > 0)
        return true;
    return false;
}


int main()
{
    cout << "------------------- 重载<< ------------------" << endl;
    MyString str1 = "hello world!";
    cout << str1 << endl;
    // 得到字符串的长度
    cout << "size = " << str1.Size() << endl;

    cout << "------------------- 重载>> ------------------" << endl;
    MyString str2;
    cout << "请输入一个字符串(不带空格):";
    cin >> str2;
    cout << str2 << endl;

    cout << "------------------- 重载[] ------------------" << endl;
    MyString str3 = "hello";
    cout << str3[4] << endl;
    str3[1] = 'E';
    cout << str3 << endl;

    cout << "------------------- 重载= ------------------" << endl;
    MyString str4;
    MyString str5;
    str5 = str4 = "hello human";
    cout << str4 << endl;
    cout << str5 << endl;

    cout << "------------------- 重载比较运算符 ------------------" << endl;
    MyString str6, str7;
    cout << "请输入两个字符串:";
    cin >> str6 >> str7;
    if (str6 > str7)
        cout << str6 << "大于" << str7 << endl;
    else if (str6 == str7)
        cout << str6 << "等于" << str7 << endl;
    else
        cout << str6 << "小于" << str7 << endl;
}
  • 运行结果
------------------- 重载<< ------------------
hello world!
size = 12
------------------- 重载>> ------------------
请输入一个字符串(不带空格):helloworld
helloworld
------------------- 重载[] ------------------        
o
hEllo
------------------- 重载= ------------------
hello human
hello human
------------------- 重载比较运算符 ------------------
请输入两个字符串:hallo hello
hallo小于hello

三、其它知识点补充

1.不建议重载 && 和 ||

不要重载 && 和 || ,因为用户无法实现 && 和 || 的短路特性。

  • 代码演示
class Complex {
public:
    int flag;
    
    // 构造函数
    Complex(int flag) {
        this->flag = flag;
    }
    // 重载 += 运算符
    Complex &operator+=(Complex &complex) {
        this->flag = this->flag + complex.flag;
        return *this;
    }
    // 重载 && 运算符
    bool operator&&(Complex &complex) {
        return this->flag && complex.flag;
    } 
};

int main() {
    Complex complex1(0); //flag 0
    Complex complex2(1); //flag 1

    if (complex1 && (complex1 += complex2)) {
        cout << "真!" << endl;
    } else {
        cout << "假!" << endl;
    }
    return 0;
}
  • 运行结果
真!
  • 说明
    1. 对象complex1的 flag 变量值为0,按道理来说,执行的结果应该为 "“假!” ,但这里的结果却为 “真!”;
    2. 因为在执行if (complex1 && (complex1 += complex2))的时候,(complex1 += complex2)会先触发operator+=(Complex &complex)函数的调用,从而将运算结果 1 赋值给 complex1 的 flag 导致最终的条件为 (1 && 1),因此结果为 “真!”;
    3. 所以,我们重载 && 运算符的时候 没法实现其短路特性。

其它不能重载的运算符:. :: .* ?: sizeof

2.智能指针

智能指针可以帮助我们省略一部分内存操作,通过在外包裹一层外壳,间接自动实现堆区空间的释放等操作。

  • 代码演示
class Data {
private:
    int num;
public:
    Data() {
        cout << "Data的无参构造" << endl;
    }

    Data(int num) {
        this->num = num;
        cout << "Data的有参构造 num=" << num << endl;
    }

    void showData() {
        cout << "num=" << num << endl;
    }

    ~Data() {
        cout << "Data的析构函数" << endl;
    }
};

class SmartPoint {
private:
    Data *sp;
public:
    SmartPoint() : sp(NULL) {}

    SmartPoint(Data *sp) {
        this->sp = sp;
    }

    SmartPoint(const SmartPoint &ob) {
        this->sp = new Data;
        *sp = *ob.sp;
    }

    ~SmartPoint() {
        delete sp;
    }

    Data *operator->() {
        return sp;
    }

    Data &operator*() {
        return *sp;
    }
};

int main() {
    SmartPoint bp(new Data(100));
    bp->showData();
    (*bp).showData();
    return 0;
}
  • 运行结果
Data的有参构造 num=100
num=100               
num=100               
Data的析构函数

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

相关文章:

  • UDP接收和断线重连代码注入案例
  • vue使用树形结构展示文件和文件夹
  • 港大发布OpenCity: 大模型驱动下的智慧城市“新内核“
  • 关于python的数据分析与应用
  • Linux上vi(vim)编辑器使用教程
  • MYSQL---------支持数据类型
  • unity中的UI系统---GUI
  • 如何使用Termux 通过 SSH 连接到远程服务器
  • vue3 如何封装aixos
  • 【AI数学基础】线性代数:内积和范数
  • 换肤-主题:使用 CSS 自定义属性 -- var()函数 详解
  • 报错:websocket注入为null,已解决!
  • 在 uni-app 中使用 wxml-to-canvas 的踩坑经验总结
  • 【开源免费】基于SpringBoot+Vue.JS大学城水电管理系统(JAVA毕业设计)
  • Spark基本介绍
  • 《新概念模拟电路》-电流源电路
  • android开发从入门进阶到高级学习资料集合
  • 日期时间选择(设置禁用状态)
  • ChatUML:AI自动生成UML图表
  • National Science Review 基于柔性光栅结构色的触觉感知方法及传感器