• 【C++】string详细介绍及模拟实现string类


    【C++】string详细介绍及模拟实现string类

    1.什么是string

    提到string,我们先回头看下C语言中的字符串

    C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,
    但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可
    能还会越界访问

    OJ中,有关字符串的题目基本以string类的类型出现,而且在常规学习中,为了简单、方便、快捷、基本都在使用string类,很少有人去使用C库中的字符串操作函数。

    让我们一起来认识下string类

    image-20221126185518478

    1.字符串是表示字符序列的类。

    2.标准的字符串类提供了对此类对象的支持,其接口类似于标准字符器的接口,但添加了专门用于操作单字节字符字符串的设计特性。

    3.string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型 )。

    4.string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数。

    总结:

    1)string是表示字符串的字符串类

    2)该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作

    3) string在底层实际是:basic_string模板类的别名,typedef basic_stringstring;

    4)不能操作多字节或者变长字符的序列

    在使用string类时,必须包含#include头文件以及using namespace std;

    若想了解更详细内容,请查询正规文档: string - C++ Reference (cplusplus.com)

    2.string常用接口介绍

    以下内容均根据官方文档中对string类的详细介绍进行整合编写,如有错误,还望各位大佬指出,多多包涵

    2.1string类对象的常见构造

    1)string() 功能:构造空的string类对象,即空字符串

    2)string(const char* s) 功能:用C-string来构造string类对象

    3)string(size_t n,char c) 功能:string类对象中包含n个字符c

    4)string(const string& s) 功能:拷贝构造函数

    image-20221126193404045

    图中以四种不同的方式创建了5个string类对象,接下来让我们分析一下各种创建方式

    首先,我们可以看到s1打印在屏幕是一串空字符,而string s1创建时正好没有给s1内容,为一个空字符串;

    s2很明显,以Hello world为内容初始化了;s3以5个X来进行初始化。 s4和s5通过拷贝构造函数对s2和s3进行拷贝,存放的字符串内容与其一样。

    2.2string类对象的容量操作

    1) size 功能:返回字符串有效字符长度

    image-20221126200248863

    此接口功能简单,不做过多赘述。

    2)length 功能:返回字符串有效字符长度

    image-20221126200605424

    PS: length与size接口功能一样,都是返回字符串有效字符长度。

    3)capacity 功能:返回空间总大小

    image-20221126201957948

    这里我们可以看出size接口返回的值与capacity接口返回的值不同,往后我们再详细分析原因。

    4)empty 功能:检测字符串是否为空串

    image-20221126203407529

    图中getline不懂的同学可以暂时理解为cin/scanf

    让我们测试一下,该接口的功能。

    1

    我们可以得知,empty接口功能,当字符串为空返回真,不为空则返回假。

    5)clear 功能:清空有效字符

    image-20221126204255732

    很明显,使用clear接口之后,s1中的字符串内容全部清理干净了。

    我们可以将clear理解为一个大扫除,会将目标对象中的字符串全部清理干净,“一个不剩”,但是不会改变空间大小。

    6)reserve 功能:为字符串预留空间

    image-20221126210243747

    第63行中,我们使用reserve接口将s1空间扩大到30,但m2的值却是31,这是为什么?

    image-20221126213331143

    这里为什么m2的值没有发生改变,明明用过了reserve接口?

    接下来,让我们一探究竟

    image-20221126213437469

    仔细阅读,我们会得知,只有当n大于当前的capacity时,才会对其进行预留空间的操作,并且预留空间是否正好为n也是未知数,可能会比n大(正如我上面用的测试用例,n为30,预留空间却成了31);当n小于当前的capacity,reserve接口不会做任何事情。

    7)resize 功能:将有效字符的个数改成n个,多出的空间用字符c填充

    image-20221126214643435

    resize接口与reserve接口就不一样了

    在n小于capacity情况下,都将直接修改你字符串的个数(不是空间大小),并且截断你的字符;

    在n大于capcacity情况下,resize也会帮你扩容空间大小,并且你可以选择是否填充字符来补满剩余的空间大小。

    image-20221126215121911

    string类对象的容量操作相关接口就介绍到这。

    注意:

    1.size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

    2.clear()只是将string中有效字符清空,不改变底层空间大小。

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

    4.reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

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

    1)operator[] 功能:返回pos位置的字符,const string类对象调用

    image-20221126220640472

    string类中对[]进行重载,让其返回pos位置的字符。 让string[i]变成数组一样(char[i])可以直接通过下标访问

    让我们看下调试展示,是否能到达我们所说的效果

    1

    很显然,string[i]正如一个数组一样可以通过下标i逐一访问。

    2)begin+end

    image-20221126223316133

    begin接口获得第一个字符的迭代器,end接口获取最后一个字符下一个位置的迭代器。

    用a记录下第一个迭代器的位置,用b记录下最后一个字符下一个位置的迭代器的位置

    然后计算b-a得出的结果是否为该string类字符串的长度呢? 结果为是,这样看来迭代器与指针有点异曲同工之妙。

    再看下面一部分

    image-20221126223657875

    正如你想象的一样,将str里面的内容逐一打印出来了。

    用for循环把迭代器的位置从begin位置逐渐向end位置移动,并将其对应解引用的字符打印出来,

    这样看来越来越像指针了 :)

    3)rbegin+rend

    这两个接口与 begin+end 属于“逆行”关系。

    rbegin接口返回一个逆向迭代器,指向最后一个字符;rend接口返回一个逆向迭代器,指向第一个元素的前一个位置。

    image-20221127190740632

    上图应该能让大家更清楚的理解 “逆行” 关系。

    2.4string类对象的修改操作

    1) push back 功能:在字符串后尾插入字符c

    image-20221127191840112

    这与我们之前学过的数据结构,栈,队列里面的pushback一样,同样是在数据尾部插入一个对应的数据。

    image-20221127192017447

    也可以配着循环玩玩 😃

    需要注意的是,push back在字符串空间不够的时候会自动扩容

    image-20221127193157013

    2)append 功能:在字符串后追加一个字符串

    image-20221127195814278

    往字符串后追加字符时使用append,可以有多种方式来进行操作

    image-20221127200243139

    3)operator+= 功能:在字符串后追加字符串str

    在对于C语言中内置类型,我们知道 a+=b,等价于a=a+b

    对于string类,要想往后增加字符或字符串是否只能使用上面介绍的两个接口呢?

    那我们是否能将字符串也实现该功能,str1+=str2 ,答案是可以的

    image-20221127203542491

    4)c_str 功能:返回C格式字符串

    image-20221128183146833

    image-20221128183240073

    5)find+npos 功能:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

    find即为查找功能,我们若想在某个字符串中查找某个字符用find即可。

    image-20221128183739052

    有一点需要注意的是,find接口默认从字符串首个字符开始查询,我们也可以对其修改第一次开始查询的位置。

    image-20221128183913464

    我们对s1第一次查询的位置进行了修改,在这个位置后面没有‘y’字符,这个时候编译器返回了一个随机值

    这个时候我们需要知道,在string类中,存在着npos这样一个值。

    image-20221128184426018

    “until the end of the string”

    6)rfind 功能:从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置

    rfind和find同样是属于“逆行”的关系,rfind就是从字符串末尾开始查询字符c,与find不同的仅仅就是查询方向。

    image-20221128185134803

    7)substr 功能:在str中从pos位置开始,截取n个字符,然后将其返回

    image-20221128185506923

    注意:

    1.在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

    2.对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好 。

    2.5string类非成员函数

    1)operator+ 功能:将各个字符串连接在一起

    这个接口可以将“支离破碎”的多个字符串合在一起

    image-20221128191549372

    可以是定义好的字符串,也可以是未定义的字符串。

    2)getline 功能:获取一行字符串

    在C++中cin常来作为输入,但我们在输入字符串时,难免会遇到输入空格的情况,但cin遇到空格/回车时会自动结束,将\n存放在缓冲区,所以为了避免这个问题,string类中就有getline接口,在输入时即使碰到空格也不会停止,会将空格存放在字符串中。

    PS:上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。

    string类以及相关常用接口就结束到这里。

    接下来让我们尝试模拟string类的实现。**

    3.string类的模拟实现

    为了与库里的某些函数命名冲突,我们在自己的命名空间里完成模拟实现string类

    PS.以下标出的函数需要特别注意

    3.1构造函数

    image-20221128200706268

    需要注意的是这里我们不使用malloc,选择用new,需要手动将str拷贝到_str中,开辟的空间内存也需要多一个用来存放’\0’,string不会自动以’\0’结束。

    3.2重载=

    image-20221128203305617

    记得先进行判断_str是否还留有空间哦,空间不足,需要开辟新的空间,剩余细节与上述构造函数相似。

    3.3重载[],reserve,resize

    image-20221129104139243

    image-20221128204842610

    3.4push_back,append

    image-20221129104212983

    3.5迭代器

    image-20221129105607336

    image-20221129105635356

    范围for循环

    image-20221129110155584

    以前觉得迭代器1好像很神秘,但是实现起来就是这么简单。

    3.6insert

    插入字符

    image-20221129114812307

    插入字符串

    image-20221129114825766

    3.7find

    查找字符

    image-20221129124400430

    查找字符串

    image-20221129124516137

    3.8流提取,流输入

    image-20221129125217229

    4.string模拟实现代码

    最后给出全部代码,供大家参考

    #pragma once
    #include
    #include
    namespace YYz
    {
    	class string
    	{
    	public:
    		typedef char* iterator;
    		iterator begin()
    		{
    			return _str;
    		}
    		iterator end()
    		{
    			return _str + _size;
    		}
    		string(const char* str="")//构造函数
    		{
    			_size = strlen(str);
    			_capacity = _size;
    			_str = new char[_capacity + 1];
    			strcpy(_str, str);
    		}
    		string(const string& s)//拷贝构造
    		{
    			_str = new char[s._capacity + 1];
    			_size = s._size;
    			_capacity = s._capacity;
    			strcpy(_str, s._str);
    		}
    
    		string& operator=(const string& s)
    		{
    			if (_capacity == _size)
    			{
    				char* tmp = new char[s._capacity + 1];
    				strcpy(tmp,s._str);
    				delete[]_str;
    				_str = tmp;
    				_size = s._size;
    				_capacity = s._capacity;
    			}
    			return *this;
    		}
    		char& operator[](size_t pos)
    		{
    			assert(_size < pos);
    			return _str[pos];
    		}
    
    		void reserve(size_t n)
    		{
    			if (_capacity < n)
    			{
    				char* tmp = new char[n + 1];
    				strcpy(tmp, _str);
    				delete[]_str;
    				_str = tmp;
    				_capacity = n;
    			}
    		}
    		void resize(size_t n,char ch='\0')
    		{
    			if (n > _size)
    			{
    				reserve(n);
    				for (size_t i = _size; i < n; ++i)
    				{
    					_str[i] = ch;
    				}
    			}
    			else
    			{
    				_size = n;
    				_str[n] = '\0';
    			}
    		}
    		void push_back(char ch)
    		{
    			if (_size == _capacity)
    			{
    				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
    				reserve(newcapacity);
    			}
    			_str[_size] = ch;
    			_size++;
    			_str[_size] = '\n';
    		}
    		void append(const char* str)
    		{
    			size_t len = strlen(str);
    			if (_size + len > _capacity)
    			{
    				reserve(_size + len);
    			}
    			strcpy(_size + _str, str);
    			_size += len;
    		}
    		string& operator+=(char ch)
    		{
    			push_back(ch);
    			return *this;
    		}
    		string& operator+=(const char* ch)
    		{
    			append(ch);
    			return *this;
    		}
    		string& insert(size_t pos, char ch)
    		{
    			assert(pos <= _size);
    			if (_size == _capacity)
    			{
    				size_t newcapacity = _capacity == 0 ? 4: 2 * _capacity;
    				reserve(newcapacity);
    			}
    			size_t end = _size+1;
    			while (end > pos)
    			{
    				_str[end] = _str[end - 1];
    				end--;
    			}
    			_str[pos] = ch;
    			_size++;
    			return *this;
    		}
    		string& insert(size_t pos, const char* str)
    		{
    			int len = strlen(str);
    			if (_size + len < _capacity)
    			{
    				reserve(_size + len);
    			}
    			size_t end = _size + len;
    			while (end > pos + len - 1)
    			{
    				_str[end] = _str[end - 1];
    				end--;
    			}
    			strncpy(_str + pos, str, len);
    			_size += len;
    			return *this;
    		}
    
    		const char* c_str()
    		{
    			return _str;
    		}
    		size_t size() const
    		{
    			return _size;
    		}
    
    		size_t capacity() const
    		{
    			return _capacity;
    		}
    		~string()//析构函数
    		{
    			delete[] _str;
    			_str = nullptr;
    			_size = _capacity = 0;
    		}
    		string& erase(size_t pos, size_t len = npos)
    		{
    			assert(pos <= _size);
    			if (len == npos || pos + len >= _size)
    			{
    				_str[pos] = '\0';
    				_size = pos;
    			}
    			else
    			{
    				strcpy(_str + pos, _str + pos+len);
    				_size -= len;
    			}
    			return *this;
    		}
    		size_t find(char ch, size_t pos = 0) const
    		{
    			assert(pos < _size);
    			while (pos < _size)
    			{
    				if (_str[pos] == ch)
    				{
    					return pos;
    				}
    
    				++pos;
    			}
    
    			return npos;
    		}
    		size_t find(const char* str, size_t pos = 0) const
    		{
    			assert(pos < _size);
    			const char* ptr = strstr(_str + pos, str);
    			if (ptr == nullptr)
    			{
    				return npos;
    			}
    			else
    			{
    				return ptr - _str;
    			}
    		}
    		void clear()
    		{
    			_size = 0;
    			_str[0] = '\0';
    		}
    	private:
    		char* _str;
    		size_t _size;
    		size_t _capacity;
    		const static size_t npos = -1;
    	};
    }
    ostream& operator<<(ostream& out, const string& s)
    {
    	for (size_t i = 0; i < s.size(); ++i)
    	{
    		out << s[i];
    	}
    
    	return out;
    }
    istream& operator>>(istream& in, string& s)
    {
    	s.clear();
    	char buff[128] = { '\0' };
    	size_t i = 0;
    	char ch = in.get();
    	while (ch != ' ' && ch != '\n')
    	{
    		if (i == 127)
    		{
    			// 满了
    			s += buff;
    			i = 0;
    		}
    
    		buff[i++] = ch;
    
    		ch = in.get();
    	}
    
    	if (i > 0)
    	{
    		buff[i] = '\0';
    		s += buff;
    	}
    
    	return in;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
  • 相关阅读:
    复盘:图像有哪些基本属性?关于图像的知识你知道哪些?图像的参数有哪些
    stm32 - 中断/定时器
    机器学习-8-超参数寻优的常用算法
    图形验证码
    工业通讯领域的总线、协议、规范、接口、数据采集与控制系统
    (跨模态)AI作画——使用stable-diffusion生成图片
    搭建自己的文件服务器
    HTML+CSS期末大作业 中国传统美食网站设计 节日美食13页 html5网页设计作业代码 html制作网页案例代码 html大作业网页代码
    MySQL-底层设置
    【蓝桥杯专项】初识0/1背包问题(Java)
  • 原文地址:https://blog.csdn.net/m0_62023005/article/details/128096031