【解码现代 C++】:实现自己的智能 String 类
创作时间:
作者:
@小白创作中心
【解码现代 C++】:实现自己的智能 String 类
引用
CSDN
1.
https://blog.csdn.net/2303_77720864/article/details/140204792
在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和临时对象简化代码,实现异常安全。 - 写时拷贝:优化资源管理,通过引用计数延迟深拷贝操作。
热门推荐
如何用行程计划表高效安排你的旅行?
电磁炉故障排查与维修指南:15种常见问题及解决方案
初中化学高效学习指南:5个科学方法助你轻松提分
谁研究水獭?关于这些迷人动物的一切
美国普遍采用铜制子弹,为何我们坚持用钢?是什么原因造成的?
如何获取Android API文档
星穹铁道2.4版本平民玩家满星配队指南
河南省正在建设一条重要南北向高速公路
预防“小眼镜”,一场需要全社会攻坚的“视力保卫战”
投壶的游戏规则和玩法
三个理财目标的设定和实现方式是什么?这些设定和实现方式怎样合理规划财务?
教选菇菌|专家讲解8大菇菌干,比拼特性/功效/食法
大拇指指甲凹陷 引起大拇指指甲凹陷的原因
晕血是怎么回事
狗狗喜欢什么样的玩具,如何选择适合它们的玩具?
花椒艾叶泡脚的功效与作用及禁忌
红茶绿茶功效大不同!绿茶降胆固醇?减肥/降血压喝哪种?
面试中如何沟通协作能力
颈动脉斑块高发!40+人群超四成中招,60+几乎人人有
小麦种植和收获全指南(知晓种植时间,轻松收获好品质小麦)
如果可以,改变过去会否影响现在
三评“理性追星”之二:破除唯流量论 重塑饭圈健康生态
跑步后恢复的5个黄金法则,让你快速“充满电”!
四年级数学 | 运算律(练习六 教学建议)
烧茄子,切记不要过油和直接下锅炒,教你一招,软绵入味好吃下饭
车管所上牌时间及流程详解
饭后漱口的重要性及正确方法
黄晓明阚清子同款“暴瘦果蔬汁”火了 成都营养科医生:减肥是错觉,不推荐
实战复盘+案例拆解:APP评分功能如何做?
郑强教授“坟守不过四代”,清明节,为何年轻人越来越不愿意扫墓