前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【深入探索 C++ STL 容器 string】——字符串世界里的神奇魔法棒

【深入探索 C++ STL 容器 string】——字符串世界里的神奇魔法棒

作者头像
换一颗红豆
发布2024-12-20 11:00:14
发布2024-12-20 11:00:14
9300
代码可运行
举报
文章被收录于专栏:C++进阶之路C++进阶之路
运行总次数:0
代码可运行

1、STL简介

STL 即标准模板库(Standard Template Library),是 C++ 标准库的一部分,它提供了一系列通用的模板类和函数,大大提高了程序员的开发效率和代码的可维护性。以下是关于 STL 的详细介绍:

Containers - C++ Reference

https://legacy.cplusplus.com/reference/stl/?kw=stl

1.1、STL 的组成部分:

容器(Containers):用于存储和管理数据的对象,如 vector、list、queue、stack、set、map等。 迭代器(Iterators):用于遍历容器中的元素,相当于一种广义的指针。迭代器提供了一种统一的方式来访问不同类型容器中的元素,使得算法可以独立于容器的具体实现进行编写。 算法(Algorithms):STL 提供了大量通用的算法,如排序、搜索、复制、删除等,这些算法可以作用于各种容器。例如,std::sort 函数可以对多种容器中的元素进行快速排序。 仿函数(Function objects):也称为仿函数,是一种重载了函数调用运算符 () 的类对象,可以像函数一样被调用。 适配器(Adapters):用于对容器、迭代器或函数对象进行适配和转换,使其能够满足特定的需求。例如,stackqueue 实际上是对其他容器的适配器,它们通过适配底层容器来实现特定的数据结构功能。

d39adf9091744052a1f329159f91ef6c.png
d39adf9091744052a1f329159f91ef6c.png

1.2、STL的优点

代码复用性高:STL 中的容器、算法等都是通用的模板,可以在不同的项目中重复使用,避免了重复编写类似的代码,提高了开发效率。 可维护性好:由于 STL 是标准库的一部分,具有较高的稳定性和可靠性。使用 STL 编写的代码结构清晰,易于理解和维护。 跨平台性:STL 是 C++ 标准的一部分,因此在不同的操作系统和编译器上具有良好的兼容性,可以保证代码在不同环境下的一致性。

1.3、必要性

STL是我们作为C++开发学习者,必须要掌握的内容,有了它,我们可以站在巨人的肩膀上,夸健步如飞的快速开发!下面就让我们进入STL的第一个话题string。

2、标准库中的string

参考文档:

cplusplus.com/reference/string/string/?kw=string

a08907f265f54db98c3161952e5b8567.png
a08907f265f54db98c3161952e5b8567.png

string本质上是basic_string类模板的实例化,头文件为<string>。

3、string的底层basic_string

cplusplus.com/reference/string/basic_string/

bfa786eba918429fa429367bec9e268d.png
bfa786eba918429fa429367bec9e268d.png

4、string的常用接口

4.1、构造函数:constructor

2ba7e98a6e6b4385bc50619f7f84ec22.png
2ba7e98a6e6b4385bc50619f7f84ec22.png

下面我重点讲解几个常用的构造函数

4.1.1、string()

构造空的striing,即空字符串

代码语言:javascript
代码运行次数:0
复制
//空字符串
string s;
4.1.2、string(const char* s)

用c-string,即字符串来构造string。

代码语言:javascript
代码运行次数:0
复制
string s("helloworld");
4.1.3、string(size_t n, char c)

用n个字符char构造string

代码语言:javascript
代码运行次数:0
复制
//n个字符char构造
string s(10, 'a');
4.1.4、string(const string& s)

拷贝构造,用已经创建的string对象构造string

代码语言:javascript
代码运行次数:0
复制
string s1("helloworld");
//拷贝构造
string s2(s1);

4.2、string的容量操作 Capacity

143c4ad96c294e409c84e5958c2ec077.png
143c4ad96c294e409c84e5958c2ec077.png

我们也是挑出几个重点常用的讲解:

4.2.1、size()

返回string中有效字符的个数(不包含'\0')

代码语言:javascript
代码运行次数:0
复制
	string s("helloworld");
	cout << s.size() << endl;
4.2.2、length()

返回string中有效字符的个数(不包含'\0') size()与length()方法底层实现原理完全相同,引入size()的原因是为了与STL中其他容器的接口保持一致,一般情况下基本都是用size()

代码语言:javascript
代码运行次数:0
复制
	string s("hello");
	cout << s.length() << endl;
4.2.3、capacity()

返回string的总容量大小

代码语言:javascript
代码运行次数:0
复制
	//有效字符个数size为 10,容量capacity为15
	string s("helloworld");
	cout << s.capacity() << endl;
4.2.4、empty()

检测string是否为空字符串,是返回true,否则返回false。

代码语言:javascript
代码运行次数:0
复制
	//有效字符个数size为 10,容量capacity为15
	string s("helloworld");
	cout << s.capacity() << endl;
4.2.5、clear()

清空string中所有有效字符,但是不影响容量。

代码语言:javascript
代码运行次数:0
复制
string s1("helloworld");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
//清空后为空字符
cout << s1.size() << endl;
cout << s1.capacity() << endl;
4.2.6、reserve(size_t n)

为string字符串预留n个容量,不改变有效元素个数。 注意:当预留的容量n小于string当前字符串容量时,reserve(),不会改变容量大小! 如果n大于当前字符串容量,则该函数使容器将其容量增加到n个字符(或更大)

n小于当前字符串容量的情况:

代码语言:javascript
代码运行次数:0
复制
string s("hello");
//观察当前容量
cout << s.capacity() << endl;
//如果n小于当前容量,则不会更改容量
s.reserve(10);
cout << s.capacity() << endl;

输出:

74086e245f1d406293d6291c18f1f395.png
74086e245f1d406293d6291c18f1f395.png

n小于当前字符串容量的情况:

代码语言:javascript
代码运行次数:0
复制
string s("hello");
//观察当前容量
cout << "string的初始容量:"<<s.capacity() << endl;
//如果n大于当前容量,则会更改容量为n,或者更大
s.reserve(20);
cout <<"reserve更改后的容量:"<< s.capacity() << endl;

输出:

788edf99c6894a2bb0e7d13538e5bf44.png
788edf99c6894a2bb0e7d13538e5bf44.png

这里我们reserve(20),但实际容量增加到了31,说明实际可能会更改比n更大!

4.2.7、resize()

将有效字符的个数该成n个,多出的空间用'\0'或者字符c填充. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不 同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数 增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4.2.7.1、resize(size_t n)

代码语言:javascript
代码运行次数:0
复制
string s("hello");
cout <<"s初始的有效字符个数:"<< s.size() << endl;
s.resize(10);
cout << "resize后s的有效字符个数:" << s.size() << endl;

我们可以看到resize(size_t n)确实将s的有效字符个数size,更改为n。

输出:

f0bd9f619f0e448796992f858bedcb48.png
f0bd9f619f0e448796992f858bedcb48.png

那么多余的容量是否由'\0'来填充呢?通过调试观察:

resize前的有效字符:

1072c4b87e5d4dff85e9deb0fccb441f.png
1072c4b87e5d4dff85e9deb0fccb441f.png

resize后的有效字符:

440e0b2bc6c346f6a2fd799d9ab3d9ad.png
440e0b2bc6c346f6a2fd799d9ab3d9ad.png

4.2.7.2、resize(size_t n, char c)

代码语言:javascript
代码运行次数:0
复制
void Test()
{
	string s("hello");
	cout <<"s初始的有效字符个数:"<< s.size() << endl;
	s.resize(10,'a');
	cout << "resize后s的有效字符个数:" << s.size() << endl;
}

我们可以看到resize(size_t n,char c)确实也将s的有效字符个数size,更改为n。

输出:

42e538cf8bb54848a010962378b544c0.png
42e538cf8bb54848a010962378b544c0.png

那么多余的容量又是如何处理的呢?

resize前的有效字符:

82ef832132814e8f9e9508dceceecf4b.png
82ef832132814e8f9e9508dceceecf4b.png

resize后的有效字符:

b4cc66e00958457b8ca47bb85545dc36.png
b4cc66e00958457b8ca47bb85545dc36.png

多余的字符确实是用参数char c来填充的!

4.2.7.3、resize对capacity的影响

如果resize的大小没有超出容量大小,则不会影响capacity。 如果超出容量大小,则会更改容量。

代码语言:javascript
代码运行次数:0
复制
string s("hello");
cout <<"s初始的有效字符个数:"<< s.size() << endl;
cout << "s初始的容量大小:" << s.capacity() << endl;
s.resize(20,'a');
cout << "resize后s的有效字符个数:" << s.size() << endl;
cout << "resize后s的容量:" << s.capacity() << endl;

输出:

a657f36bd05e48489fec5827fcbb4e12.png
a657f36bd05e48489fec5827fcbb4e12.png

4.3、string类对象的访问及遍历操作

ec1863b917dd46548e59dccc336c8745.png
ec1863b917dd46548e59dccc336c8745.png
4.3.1、operator[]

operator[],支持我们像访问数组元素一样,访问string中pos位置的字符。注意下标和数组一样,也是从0开始!

代码语言:javascript
代码运行次数:0
复制
string s("helloworld");
for (size_t i = 0; i < s.size(); ++i)
{
	cout << s[i] << " ";
}
cout << endl;

输出:

1e29190fc72b4d13ab066e9428090fb2.png
1e29190fc72b4d13ab066e9428090fb2.png

除了访问,operator[ ]也支持修改string中pos位置的字符。

代码语言:javascript
代码运行次数:0
复制
string s("helloworld");
for (size_t i = 0; i < s.size(); ++i)
{
	cout << s[i] << " ";
	s[i]+=1;
}
cout << endl;
cout << s << endl;

输出:

f2ba2febab59461bab2be401419c41fb.png
f2ba2febab59461bab2be401419c41fb.png
4.3.2、begin()和end()

利用正向迭代器对string进行访问。迭代器同样也支持修改string的字符。

代码语言:javascript
代码运行次数:0
复制
void Test()
{
	string s("A character sequence");
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
    //修改该位置的字符
		*it += 1;
    //迭代器往后遍历
		++it;
	}
	cout << endl;
	cout <<s<< endl;
}

输出:

a4916807e01b4576ba756499297025b3.png
a4916807e01b4576ba756499297025b3.png
4.3.3、rbegin()和rend()

利用反向迭代器遍历,与正向迭代器同理。这里注意反向迭代器是倒着遍历string,与begin()到end()一致,rbegin()到rend()迭代器的操作也是++。

代码语言:javascript
代码运行次数:0
复制
string s("A character sequence");
auto it = s.rbegin();
while (it != s.rend())
{
	cout << *it << " ";
	*it += 1;
	//反向迭代器也是++
	++it;
}
cout << endl;
cout << s << endl;

输出:

a66a59f8792f4cafa1b9afc40a725e51.png
a66a59f8792f4cafa1b9afc40a725e51.png
4.4.4、C++11支持的范围for遍历
代码语言:javascript
代码运行次数:0
复制
string s("hello _zwy!");
for (auto ch : s)
{
	cout << ch << " ";
	ch += 1;
}
cout << endl;

输出:

91fddfceb4774109bcf4500b87251546.png
91fddfceb4774109bcf4500b87251546.png

4.4、string类对象的增删查改操作

4.4.1、 push_back()

在string字符串的后面追加一个字符c。

代码语言:javascript
代码运行次数:0
复制
string s("helloworld");
s.push_back('a');
cout << s << endl;
s.push_back('b');
cout << s << endl;
s.push_back('!');
cout << s << endl;

输出:

25d17cc83efb47f49bebb3aba85150ce.png
25d17cc83efb47f49bebb3aba85150ce.png
4.4.2、append ()

在string的后面追加字符串

54df7079fdb946dd8a870cc7630bb0df.png
54df7079fdb946dd8a870cc7630bb0df.png
代码语言:javascript
代码运行次数:0
复制
string s("hello");
//追加一个 char* 字符串
s.append("world");
cout << s << endl;

//追加一个string对象
string s1("!world");	
s.append(s1);
cout << s << endl;

//追加n个字符char
s.append(3, '!');
cout << s << endl;

//追加char*字符串的 前n个字符,超出字符串长度,可能会抛异常
s.append("append", 7);
cout << s << endl;

输出:

dc0ba5d293b2483fac68794e36bf6f07.png
dc0ba5d293b2483fac68794e36bf6f07.png
4.4.3、operator+=(常用)

字符以及字符串的拼接。

bc283b3d6cf645c08545be8e827f4724.png
bc283b3d6cf645c08545be8e827f4724.png
代码语言:javascript
代码运行次数:0
复制
string s1("hello");
string s2("world");
//+=string 对象
s1 += s2;
//+=字符c
s1 += '!';
//+= char* 字符串
s1 += "operator+=";
cout << s1 << endl;
6e43fc809cbf4510964aeefc8dbfe0df.png
6e43fc809cbf4510964aeefc8dbfe0df.png
4.4.4、c_str()

返回该字符串的地址,或者指针。

代码语言:javascript
代码运行次数:0
复制
string s("helloworld!");
//字串串的地址会打印出整个字符串
cout << s.c_str() << endl;
//强转为void* 打印地址
cout << (void*)s.c_str() << endl;

输出:

a0cb5ca86cc241c6a150f855b8b650f8.png
a0cb5ca86cc241c6a150f855b8b650f8.png
4.4.5、size_t find()
8540456db7c84fcc90e65fa1a034cb73.png
8540456db7c84fcc90e65fa1a034cb73.png

在字符串指定位置(默认从头开始查找)pos开始往后查找字符串内容,返回第一个匹配的第一个字符的位置。如果没有找到返回npos。

代码语言:javascript
代码运行次数:0
复制
string s1("helloworld!");
string s2("hello");
//从默认位置在s1中向后查找string对象
size_t ret1 = s1.find(s2);
cout << "s1在s1中出现的位置:" << ret1 << endl;

//从s1中下标为5向后查找字符 'o'
size_t ret2 = s1.find('o', 5);
cout <<"字符'o'在s1中的位置:" <<ret2 << endl;

//从s1中下标为1向后查找char* 字符串
size_t ret3 = s1.find("world", 1);
cout << "字符串world在s1中的位置:" << ret3 << endl;

//从s1中下标为3向后查找char* 字符串的前n个字符
size_t ret4 = s1.find("loworld!good", 3, 5);
cout << "字符串loworld!good的前5个字符在s1的位置:"<< ret4 << endl;

输出:

4c979b2607424814be530c47b46d985b.png
4c979b2607424814be530c47b46d985b.png
4.4.6、size_t rfind()
8c8f8912166342d99464399ecb36e004.png
8c8f8912166342d99464399ecb36e004.png

在字符串指定位置(默认从最后2开始查找)pos开始往前查找字内容符串,返回第一个匹配的第一个字符的位置。如果没有找到返回npos。

代码语言:javascript
代码运行次数:0
复制
string s("hello world!!");
//从下标为5的位置往前查找字符串"hella"
size_t ret1 = s.rfind("hella",5);
cout << "字符串hella在s中的位置:"<<ret1 << endl;

//从下标为5的位置往前查找字符串"hella"的前四个字符
size_t ret2 = s.rfind("hella", 5,4);
cout << "字符串hella的前四个字符在s中的位置:" << ret2 << endl;

string s1("llo wor");
//从默认位置往前查找s1
size_t ret3 = s.rfind(s1);
cout << "string对象s1在s中的位置:" << ret3 << endl;

//从下标为9的位置往前查找字串'r'
size_t ret4=s.rfind('r', 9);
cout << "字符r在s中的位置:" << ret4 << endl;

输出:

474419a335c64caebbd7ad5dfe678591.png
474419a335c64caebbd7ad5dfe678591.png
4.4.7、string substr ()

在str中从pos位置开始,截取n个字符,然后将其返回,如果截取位置超过当前字符串长度会抛出out of range异常

c339875bee99462086a8f58a94b10d1d.png
c339875bee99462086a8f58a94b10d1d.png
代码语言:javascript
代码运行次数:0
复制
string str("We think in generalities, but we live in details.");
//截取str前10个字符
string s1 = str.substr(0, 10);
cout << s1 <<  endl;

//截取str从下标5开始的15个字符
string s2 = str.substr(5, 15);
cout << s2 << endl;

//截取整个字符串
string s3 = str.substr();
cout << s3 << endl;

//截取长度超出字符串长度,会截取整个字符串
string s4 = str.substr(3,100);
cout << s4 << endl;

//如果截取位置超过当前字符串长度 会抛出out of range异常
//string s4 = str.substr(80,20);
//cout << s4 << endl;

输出:

b29afcf64ab844e7ab8def3443908337.png
b29afcf64ab844e7ab8def3443908337.png

5、vs下和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

5.1、vs下string的结构:

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间 当字符串长度小于16时,使用内部固定的字符数组来存放. 当字符串长度大于等于16时,从堆上开辟空间。

代码语言:javascript
代码运行次数:0
复制
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建 好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。 其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的 容量,还有一个指针做一些其他事情。 故总共占16+4+4+4=28个字节.

974d3abccd954e308679e13486975778.png
974d3abccd954e308679e13486975778.png

5.2、g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个 指针,该指针将来指向一块堆空间,内部包含了如下字段: 空间总大小 、字符串有效长度 、引用计数

代码语言:javascript
代码运行次数:0
复制
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
}

指向堆空间的指针,用来存储字符串。

6、string的模拟实现

6.1、经典的string类问题—深浅拷贝

代码语言:javascript
代码运行次数:0
复制
// 为了和标准库区分,此处使用String
class String
{
public:

	//以下为错误的string构造函数
	/*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 world!!!");
	String s2(s1);
}

大家观察一下上述代码有什么问题吗? 上述String类没有显实现义拷贝构造函数与赋值运算符重载,此时编译器会生成默认的,当 用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝

6.2、浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致

多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该

资源已被释放,所以当继续对资源进行操作时,就会发生发生了访问违规。

c1f379a9d6644d9380d6c3d2eb8252c9.png
c1f379a9d6644d9380d6c3d2eb8252c9.png

s1和s2指向同一块内存空间,管理同一片资源,Test函数结束时,需要将当前栈帧中的s1对象和s2对象销毁,先销毁s2,当s2对象析构时,会将这片内存空间里面的资源释放,s2成功销毁,s1中的_pStr就成为野指针,销毁s1时就会发生错误,s1无法正常析构。解决的问题就是采用深拷贝!

6.3、深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。 深拷贝是指在拷贝一个对象时,不仅仅复制对象本身的数据成员的值,对于对象中那些指向其他资源(如动态分配的内存的指针成员,会重新分配相应的资源,并将原对象所指向资源中的数据完整地复制到新分配的资源中,使得新对象和原对象拥有完全独立的、相同内容的资源副本,彼此之间对各自资源的操作不会相互影响。

113bacf856894fbda9374e471177ba5d.png
113bacf856894fbda9374e471177ba5d.png

当s1和s2都拥有自己的资源时,各自独立,析构时会不影响,就可以解决浅拷贝带来的问题!实现深拷贝,就需要我们自己实现拷贝构造函数和赋值重载!

6.4、传统写法的string

代码语言:javascript
代码运行次数:0
复制
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;
};

6.5、现代写法的string

代码语言:javascript
代码运行次数:0
复制
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& operator=(const String& s)
	{
		if(this != &s)
		{
			String strTmp(s);
			swap(_str, strTmp._str);
		}
		return *this;
	}
	
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
	const char* c_str() const
	{
		return _str;
	}
private:
	char* _str;
};

现代写法,用拷贝对象了一个临时对象strTmp,swap交换底层指针的方式来实现拷贝构造,避免了不必要的拷贝,临时对象出了函数就销毁,赋值重载也利用交换临时对象指针的方式实现,由于sawp交换的成本很低,大大提高了拷贝构造以及赋值重载的效率!

第二种赋值重载较于第一种的优点: 避免不必要拷贝:const String& 作为参数,避免了按值传递带来的额外拷贝开销。只有在确实需要进行数据交换时(即当前对象和传入对象不同时),才通过创建一个临时对象strTmp并进行swap操作来完成赋值,这种方式在性能上通常更优,尤其是对于较大的String类对象。 正确性保障:通过if (this!= &s)的判断,避免了对象自我赋值的情况。自我赋值如果处理不当,可能会导致资源释放错误、内存泄漏等问题,这里的判断确保了在自我赋值时不会执行不必要的操作。

6.6、完整的string类实现

头文件:

代码语言:javascript
代码运行次数:0
复制
#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace zwy
{
	class String {
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		/*	String()
				:_str(new char[1] {'\0'})
				, _size(0)
				, _capacity(0)
			{}

			String(char* str)
			{
				_size = strlen(str);
				_capacity = _size;
				_str = new char[_capacity+1];
				strcpy(_str, str);
			}*/

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}
		String(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		~String()
		{
			if (_str) 
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
			}
			
		}

		//ͳд
		//s1(s2)
	  /* String(const String& str)
		{
		   _str = new char [str._capacity + 1];
		   strcpy(_str,str._str);
		   _size = str._size;
		   _capacity = str._capacity;
		}*/
	   
	   void Swap(String& str)
	   {
		   std::swap(_str, str._str);
		   std::swap(_size, str._size);
		  std:: swap(_capacity, str._capacity);
	   }

	   //ִд
	   String(const String& str)
	   {
		   String tmp(str._str);
		   Swap(tmp);
	   }


	   //String& operator=(const String& str)
	   //{
		  // if (this != &str)
		  // {
			 //  delete[] _str;

			 //  _str = new char[str._capacity + 1];
			 //  strcpy(_str, str._str);
			 //  _size = str._size;
			 //  _capacity = str._capacity;
		  // }
		  // return *this;
	   //}


	   //s1=s2
	/*   String& operator=(const String& str)
	   {
		   if (this != &str)
		   {
			   String tmp(str._str);
			   Swap(tmp);
		   }
		   return *this;
	   }*/

	   String& operator=( String tmp)
	   {
			   Swap(tmp);
		   return *this;
	   }

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}
		const char* c_str() const
		{
			return _str;
		}


		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
	
		//bool empty()const;

		void reserve(size_t n);
		void push_back(char ch);
		String& operator+=(char ch);
		String& operator+=(const char* str);
		void append(const char* str);
		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		String substr(size_t pos = 0, size_t len = npos);
	private:
		char* _str=nullptr;
		size_t _size = 0;
		size_t _capacity=0;
		static const size_t npos;
	};
	ostream& operator<<(ostream& out, const String& str);
	istream& operator>>(istream& in, String& str);
	bool operator<(const String& s1, const String& s2);
	bool operator<=(const String& s1, const String& s2);
	bool operator>(const String& s1, const String& s2);
	bool operator>=(const String& s1, const String& s2);
	bool operator==(const String& s1, const String& s2);
	bool operator!=(const String& s1, const String& s2);
}

源文件:

代码语言:javascript
代码运行次数:0
复制
#include"String.h"

namespace zwy {
	const size_t String::npos = -1;
	void String::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void String::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';
	}

	String& String:: operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}


	String& String::operator+=(const char* str)
	{
		append(str);
		return *this;
	}


	void String::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

	void String::insert(size_t pos, char ch)
	{
		assert(pos < _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;
	}


	void String::insert(size_t pos, const char* str)
	{
		assert(pos < _size);
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		size_t end = _size + len;
		while (end>pos+len-1)
		{
			_str[end] = _str[end-len];
			--end;
	   }
		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}
		_size += len;
	}

	void String::erase(size_t pos, size_t len)

	{
		assert(pos < _size);
		if (len > _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else {
			for (size_t i = pos + len; i <= _size; i++)
			{
				_str[i - len] = _str[i];
			}
			_size -= len;
			}
	}

	size_t String::find(char ch, size_t pos)
	{
		assert(pos < _size);
		for (size_t i = 0; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}
	size_t String::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		return ptr - _str;
	}

	String String::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len > _size - pos)
		{
			len = _size - pos;
		}
		String sub;
		sub.reserve(len);
		for (size_t i=0; i < len; i++)

		{
			sub += _str[pos + i];
		}
		return sub;
	}

	bool operator<(const String& s1, const String& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}
	bool operator<=(const String& s1, const String& s2)
	{
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const String& s1, const String& s2)
	{
		return !(s1 <= s2);
	}
	bool operator>=(const String& s1, const String& s2)
	{
		return !(s1 < s2);
	}
	bool operator==(const String& s1, const String& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator!=(const String& s1, const String& s2)
	{
		return !(s1 == s2);
	}

	ostream& operator<<(ostream& out, const String& str)
	{
		for (auto ch : str)
		{
			out << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, String& str)
	{

		//
		str.clear();
		char ch;
		const int N = 256;
		char buff[N];
		int i = 0;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				str += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return in;
	}
}

6、写时拷贝

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。 引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

以下给大家分享两篇有关写实拷贝的文章:有兴趣的同学可以加餐阅读:

C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell

C++的std::string的“读时也拷贝”技术! | 酷 壳 - CoolShell

7、小结

有关string类的讲解到这里就结束了,string是C++中非常基础也非常重要的内容,希望大家都能掌握,接下来会给大家带来C++ STL中其他容器的深度讲解,创作不易,还请多多支持。关注博主,为你带来更多优质内容 !

如上讲解如有不足之处,还望各位大佬评论区赐教点拨,感激不尽!

2adf5b5352304c509df4369309aa4ad1.gif
2adf5b5352304c509df4369309aa4ad1.gif
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、STL简介
    • 1.1、STL 的组成部分:
    • 1.2、STL的优点
    • 1.3、必要性
  • 2、标准库中的string
  • 3、string的底层basic_string
  • 4、string的常用接口
    • 4.1、构造函数:constructor
      • 4.1.1、string()
      • 4.1.2、string(const char* s)
      • 4.1.3、string(size_t n, char c)
      • 4.1.4、string(const string& s)
    • 4.2、string的容量操作 Capacity
      • 4.2.1、size()
      • 4.2.2、length()
      • 4.2.3、capacity()
      • 4.2.4、empty()
      • 4.2.5、clear()
      • 4.2.6、reserve(size_t n)
      • 4.2.7、resize()
    • 4.3、string类对象的访问及遍历操作
      • 4.3.1、operator[]
      • 4.3.2、begin()和end()
      • 4.3.3、rbegin()和rend()
      • 4.4.4、C++11支持的范围for遍历
    • 4.4、string类对象的增删查改操作
      • 4.4.1、 push_back()
      • 4.4.2、append ()
      • 4.4.3、operator+=(常用)
      • 4.4.4、c_str()
      • 4.4.5、size_t find()
      • 4.4.6、size_t rfind()
      • 4.4.7、string substr ()
  • 5、vs下和g++下string结构的说明
    • 5.1、vs下string的结构:
    • 5.2、g++下string的结构
  • 6、string的模拟实现
    • 6.1、经典的string类问题—深浅拷贝
    • 6.2、浅拷贝
    • 6.3、深拷贝
    • 6.4、传统写法的string
    • 6.5、现代写法的string
    • 6.6、完整的string类实现
  • 6、写时拷贝
  • 7、小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档