【C++ 】string类操作全解析-CSDN博客
1.stirng类的模拟实现
1.1 经典的string类问题
上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢要求自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。大家看下以下string类的实现是否有问题?
// 为了和标准库区分,此处使用String
class String
{
public:/*String() :_str(new char[1]) {*_str = '\0';} */ //String(const char* str = "\0") 错误示范 //String(const char* str = nullptr) 错误示范 String(const char* str = "") { // 构造String类对象时,如果传递nullptr指针,可以认为程序非 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!!!"); String s2(s1);
}
说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
在 String(const char* str = "")
中,str
的类型必须是 const char*
,不能去掉 const
。
代码中默认参数 ""
是 字符串字面量(比如 "hello"
、""
这类用双引号包裹的内容),在 C++ 中: 字符串字面量存储在程序的「只读数据段」(不能被修改),其类型本质是 const char[]
(常量字符数组)。
当把字符串字面量赋值给指针时,编译器会自动将其转换为 const char*
(指向常量的指针)—— 这是为了防止通过指针修改只读内存中的内容(否则会触发「未定义行为」,比如程序崩溃)。
1.2 浅拷贝
浅拷贝的核心问题(多对象共享资源 + 重复释放)
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我夺,玩具损坏。
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了
重点:
当我们用一个 String
对象拷贝构造另一个对象时,如果没有自己实现拷贝构造函数,编译器会自动生成「浅拷贝构造函数」。
浅拷贝的本质是「位拷贝」—— 把原对象的所有成员变量的值,逐字节拷贝到新对象,包括指向资源的指针(_str
)。
图解:
String s1("hello"); // 1. 创建s1,申请内存存"hello"
String s2 = s1; // 2. 浅拷贝:用s1构造s2(编译器自动生成)
拷贝前(s1 单独存在):
s1:_str: 0x100(动态内存地址)_size: 5_capacity: 5↓0x100内存:['h','e','l','l','o','\0'] // s1管理的资源
浅拷贝后(s2 与 s1 的状态):
s1: s2:_str: 0x100 _str: 0x100 // 指针值相同!指向同一块内存_size: 5 _size: 5 // 数值拷贝_capacity: 5 _capacity: 5 // 数值拷贝↓0x100内存:['h','e','l','l','o','\0'] // 两个对象共享同一份资源
浅拷贝没有为新对象(s2)重新申请新的动态内存,只是让 s2 的 _str
指针,指向了和 s1 的 _str
相同的内存地址 —— 即 s1 和 s2 共享同一份动态内存资源。
[创建s1] → s1._str指向0x100(内存有效)↓
[浅拷贝s2] → s2._str也指向0x100(共享资源)↓
[程序结束,s2先析构] → delete 0x100(内存变为无效)↓
[ s1再析构 ] → 试图delete 0x100(无效内存)→ 访问违规!
1.3 深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
strcpy
的工作原理是:从源字符串逐个拷贝字符(包括终止符 '\0'
),直到遇到源字符串的 '\0'
才停止。
// 深拷贝构造函数(自己实现)
String(const String& s) {_size = s._size;_capacity = s._capacity;_str = new char[_capacity + 1]; // 1. 为新对象申请新内存strcpy(_str, s._str); // 2. 把原资源的数据拷贝过来
}
s1: s2:_str: 0x100 _str: 0x200 // 指针指向不同内存_size: 5 _size: 5_capacity: 5 _capacity: 5↓ ↓
0x100: "hello" 0x200: "hello" // 各自拥有独立资源