【解码现代 C++】:实现自己的智能 【String 类】

news/2024/7/7 19:34:38 标签: c++, 学习

目录

1. 经典的String类问题

1.1 构造函数

小李的理解

1.2 析构函数

小李的理解

1.3 测试函数

小李的理解

1.4 需要记住的知识点

2. 浅拷贝

2.1 什么是浅拷贝

小李的理解

2.2 需要记住的知识点

3. 深拷贝

3.1 传统版写法的String类

3.1.1 拷贝构造函数

小李的理解

3.1.2 赋值运算符重载

小李的理解

3.1.3 需要记住的知识点

4. 现代版写法的String类

4.1 拷贝构造函数

小李的理解

4.2 赋值运算符重载

小李的理解

4.3 需要记住的知识点

5. 写时拷贝(了解)

5.1 写时拷贝

小李的理解

5.2 需要记住的知识点

​编辑

6. 总结

小李的理解


 

专栏:C++学习笔记 

接上一篇:【掌握C++ string 类】——【高效字符串操作】的【现代编程艺术】

在C++中,std::string是一个非常常用的类,它封装了对C风格字符串的处理。但是,在某些情况下,我们可能需要自己实现一个类似string的类来展示对C++核心概念的掌握。本文将深入剖析一个自定义的String类的实现,特别关注其构造、拷贝构造、赋值运算符重载以及析构函数的实现。

1. 经典的String类问题

以下是一个初步的String类实现:

#include <iostream>
#include <cstring>
#include <cassert>

class String {
public:
    // 构造函数,默认参数为空字符串
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);  // 断言检查
            return;
        }
        _str = new char[strlen(str) + 1];  // 分配内存
        strcpy(_str, str);  // 拷贝字符串
    }

    // 析构函数
    ~String() {
        if (_str) {
            delete[] _str;  // 释放内存
            _str = nullptr;  // 避免悬挂指针
        }
    }

private:
    char* _str;
};

// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 默认拷贝构造
}

int main() {
    TestString();
    return 0;
}

1.1 构造函数

  • 功能:用于初始化对象。
  • 操作
    • 检查输入指针是否为nullptr
    • 分配足够的内存存储字符串。
    • 拷贝字符串内容到新分配的内存中。
小李的理解
  • 构造函数:就像你去商店买东西,店员先检查你要买的东西是否存在,然后给你打包好交给你。

1.2 析构函数

  • 功能:用于释放对象占用的资源。
  • 操作
    • 检查指针是否为空。
    • 释放内存。
    • 将指针置为nullptr以避免悬挂指针。
小李的理解
  • 析构函数:当你不需要某样东西时,店员会帮你处理掉它,并确保不会再使用它。

1.3 测试函数

  • 功能:验证构造函数和析构函数的工作情况。
  • 操作
    • 创建两个String对象。
    • 用默认的拷贝构造函数创建第二个对象。
小李的理解
  • 测试函数:就像试用新买的东西,确保它们都能正常工作。

1.4 需要记住的知识点

  • 默认的拷贝构造函数执行的是浅拷贝(shallow copy)。
  • 多个实例共享同一块内存,当其中一个实例被销毁时,其他实例会尝试访问已经释放的内存,导致程序崩溃。

2. 浅拷贝

浅拷贝是指编译器仅仅复制对象中的值(即指针地址),而不是指针所指向的内容。这意味着多个对象会共享同一份资源,如上例中的字符数组:

String s1("hello bit!!!");
String s2(s1); // 浅拷贝,s1和s2共享同一块内存

2.1 什么是浅拷贝

  • 定义:浅拷贝只复制指针地址,多个对象共享同一份资源。
  • 问题:当一个对象释放内存时,其他对象会访问无效内存,导致程序崩溃。
小李的理解
  • 浅拷贝:就像两个孩子共用一个玩具,只有一个玩具,两人要共享。一个孩子用坏了,另一个孩子也不能用了。

2.2 需要记住的知识点

  • 浅拷贝会导致多个对象共享同一块内存。
  • 释放其中一个对象时,其他对象会尝试访问已释放的内存,导致程序崩溃。

3. 深拷贝

深拷贝则会创建对象时复制资源的内容,使每个对象拥有一份独立的资源。这需要显式定义拷贝构造函数和赋值运算符。以下是一个实现深拷贝的String类:

3.1 传统版写法的String

#include <iostream>
#include <cstring>
#include <cassert>

class String {
public:
    // 构造函数
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);  // 断言检查
            return;
        }
        _str = new char[strlen(str) + 1];  // 分配内存
        strcpy(_str, str);  // 拷贝字符串
    }

    // 拷贝构造函数
    String(const String& s) {
        _str = new char[strlen(s._str) + 1];  // 分配内存
        strcpy(_str, s._str);  // 拷贝字符串
    }

    // 赋值运算符重载
    String& operator=(const String& s) {
        if (this != &s) {  // 自我赋值检查
            char* pStr = new char[strlen(s._str) + 1];  // 分配新内存
            strcpy(pStr, s._str);  // 拷贝字符串
            delete[] _str;  // 释放旧内存
            _str = pStr;  // 更新指针
        }
        return *this;
    }

    // 析构函数
    ~String() {
        if (_str) {
            delete[] _str;  // 释放内存
            _str = nullptr;  // 避免悬挂指针
        }
    }

private:
    char* _str;
};

// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 使用拷贝构造函数
    String s3 = s2;  // 使用赋值运算符重载
}

int main() {
    TestString();
    return 0;
}

3.1.1 拷贝构造函数

  • 功能:创建新对象时分配新的内存,并拷贝字符串内容。
  • 操作
    • 分配足够的内存存储字符串。
    • 拷贝字符串内容到新分配的内存中。
小李的理解
  • 拷贝构造函数:就像父母给每个孩子都买一份玩具,各自玩各自的,不会有冲突。

3.1.2 赋值运算符重载

  • 功能:确保自我赋值时不会出错,并实现深拷贝。
  • 操作
    • 检查自我赋值(如s = s)。
    • 分配新内存,拷贝字符串,释放旧内存,并更新指针。
小李的理解
  • 赋值运算符重载:就像你决定换掉旧的玩具,先买个新的,再把旧的处理掉,确保整个过程不会出错。

3.1.3 需要记住的知识点

  • 拷贝构造函数和赋值运算符必须显式定义以实现深拷贝。
  • 深拷贝确保每个对象都有独立的资源,避免共享同一块内存。

4. 现代版写法的String

现代C++中,可以利用临时对象和swap函数简化赋值运算符的实现:

#include <iostream>
#include <cstring>
#include <cassert>
#include <algorithm>  // 包含swap函数

class String {
public:
    // 构造函数
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);  // 断言检查
            return;
        }
        _str = new char[strlen(str) + 1];  // 分配内存
        strcpy(_str, str);  // 拷贝字符串
    }

    // 拷贝构造函数
    String(const String& s)
        : _str(nullptr) {
        String strTmp(s._str);  // 创建临时对象
        swap(_str, strTmp._str);  // 交换内容
    }

    // 赋值运算符重载
    String& operator=(String s) {
        swap(_str, s._str);  // 交换内容
        return *this;
    }

    // 析构函数
    ~String() {
        if (_str) {
            delete[] _str;  // 释放内存
            _str = nullptr;  // 避免悬挂指针
        }
    }

private:
    char* _str;
};

// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 使用拷贝构造函数
    String s3 = s2;  // 使用赋值运算符重载
}

int main() {
    TestString();
    return 0;
}

4.1 拷贝构造函数

  • 功能:利用临时对象实现深拷贝。
  • 操作
    • 创建一个临时对象。
    • 交换临时对象和当前对象的内容。
小李的理解
  • 拷贝构造函数:就像把新玩具给孩子,然后把旧玩具处理掉,确保整个过程不会出错。

4.2 赋值运算符重载

  • 功能:利用临时对象和swap函数简化赋值运算符的实现。
  • 操作
    • 利用临时对象进行深拷贝。
    • 交换临时对象和当前对象的内容。
小李的理解
  • 赋值运算符重载:就像在家里换家具时,先把新家具搬进来,再把旧家具搬走,确保整个过程不会出错。

4.3 需要记住的知识点

  • 现代C++中,利用swap和临时对象简化赋值运算符的实现,可以确保异常安全。
  • 这种方法使得代码简洁且高效。

5. 写时拷贝(了解)

写时拷贝(Copy-On-Write, COW)是一种优化技术,在实现浅拷贝的基础上增加引用计数。每次拷贝时增加引用计数,只有在实际写操作发生时才进行深拷贝:

#include <iostream>
#include <cstring>
#include <cassert>

class String {
public:
    // 构造函数
    String(const char* str = "") {
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
        _refCount = new int(1);  // 引用计数
    }

    // 拷贝构造函数
    String(const String& s)
        : _str(s._str), _refCount(s._refCount) {
        ++(*_refCount);  // 增加引用计数
    }

    // 赋值运算符重载
    String& operator=(const String& s) {
        if (this != &s) {
            if (--(*_refCount) == 0) {  // 释放旧资源
                delete[] _str;
                delete _refCount;
            }
            _str = s._str;
            _refCount = s._refCount;
            ++(*_refCount);  // 增加引用计数
        }
        return *this;
    }

    // 析构函数
    ~String() {
        if (--(*_refCount) == 0) {  // 释放资源
            delete[] _str;
            delete _refCount;
        }
    }

private:
    char* _str;
    int* _refCount;  // 引用计数指针
};

// 测试函数
void TestString() {
    String s1("hello bit!!!");
    String s2(s1);  // 增加引用计数
    String s3 = s2;  // 增加引用计数
}

int main() {
    TestString();
    return 0;
}

5.1 写时拷贝

  • 定义:通过引用计数实现资源共享,仅在写操作时进行深拷贝。
  • 操作
    • 每次拷贝时增加引用计数。
    • 只有在实际写操作发生时才进行深拷贝。
小李的理解
  • 写时拷贝:就像兄弟姐妹共享一个玩具,只有在其中一个想要修改玩具时,才会给他一个新的玩具。

5.2 需要记住的知识点

  • 写时拷贝通过引用计数优化资源管理,减少不必要的深拷贝操作。
  • 这种技术在某些情况下能提高性能,但也有复杂性增加和多线程不安全的问题。

6. 总结

实现一个自定义的String类,最重要的是理解和正确实现构造函数、拷贝构造函数、赋值运算符重载和析构函数。通过深拷贝和写时拷贝等技术,可以确保对象管理资源的正确性和高效性。

小李的理解
  • 构造函数:初始化对象,确保资源正确分配。
  • 析构函数:释放资源,避免内存泄漏。
  • 拷贝构造函数:深拷贝确保每个对象有独立资源。
  • 赋值运算符重载:自我赋值检查,深拷贝确保安全赋值。
  • 现代C++:利用swap和临时对象简化代码,实现异常安全。
  • 写时拷贝:优化资源管理,通过引用计数延迟深拷贝操作。


http://www.niftyadmin.cn/n/5537352.html

相关文章

Unity+OpenCV+Dlib实现换脸+图片生成+上传服务器+生成二维码[纯干货]

UnityOpenCVDlib实现换脸图片生成上传服务器生成二维码 功能描述 一句话描述&#xff1a;让游客体验一下当宇航员的乐趣。 具体功能&#xff1a;游客通过摄像头拍照&#xff0c;生成有着“自己的脸”的宇航员的图片&#xff0c;然后展示二维码&#xff0c;供游客下载。 效果…

Elasticsearch 使用聚合进行数据分析

在大数据时代&#xff0c;数据的价值不仅仅在于存储&#xff0c;更在于如何从海量数据中提取出有价值的信息。Elasticsearch&#xff0c;作为一个强大的搜索引擎和数据分析平台&#xff0c;通过其内置的聚合&#xff08;Aggregations&#xff09;功能&#xff0c;为我们提供了一…

数据库详细复习第三章SQL语句

SQL 第三章&#xff1a;SQL语句3.1 SQL概述3.1.3 SQL 语句类型1、数据定义语句2、数据操纵语言3、数据查询语言4、数据控制语言5、事务处理语言 3.1.4 SQL数据类型1、字符串型2、整数型3、浮点数型4、货币型5、日期型 3.2 数据定义语句3.2.1 数据库的定义3.2.2 数据库表对象的定…

大数据面试题之数据库(1)

目录 数据库中的事务是什么&#xff0c;MySQL中是怎么实现的 MySQL事务的特性? 数据库事务的隔离级别?解决了什么问题?默认事务隔离级别? 脏读&#xff0c;幻读&#xff0c;不可重复读的定义 MySQL怎么实现可重复读? 数据库第三范式和第四范式区别? MySQL的…

千益畅行,旅游卡,如何赚钱?

​ 赚钱这件事情&#xff0c;只有自己努力执行才会有结果。生活中没有幸运二字&#xff0c;每个光鲜亮丽的背后&#xff0c;都是不为人知的付出&#xff01; #旅游卡服务#

自定义一个背景图片的高度,随着容器高度的变化而变化,小于图片的高度时裁剪,大于时拉伸100%展示

1、通过js创建<image?>标签来获取背景图片的宽高比&#xff1b; 2、当元素的高度大于原有比例计算出来的高度时&#xff0c;背景图片的高度拉伸自适应100%&#xff0c;否则高度为auto&#xff0c;会自动被裁减 3、背景图片容器高度变化时&#xff0c;自动计算背景图片的…

Arduino - 74HC595 4 位 7 段显示器

Arduino - 74HC595 4 位 7 段显示器 Arduino - 74HC595 4-Digit 7-Segment Display) A standard 4-digit 7-segment display is needed for clock, timer and counter projects, but it usually requires 12 connections. The 74HC595 module makes it easier by only requir…

SpringBoot项目,配置文件pom.xml的结构解析

pom.xml 是 Maven 项目对象模型&#xff08;Project Object Model&#xff09;的配置文件&#xff0c;它定义了 Maven 项目的基本设置和构建过程。以下是 pom.xml 文件的基本结构和一些常见元素的解析&#xff1a; 项目声明 (<project>): <modelVersion>: 通常设置…