欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 建筑 > C++的STL标准模板库容器--string类

C++的STL标准模板库容器--string类

2024/10/25 5:11:45 来源:https://blog.csdn.net/2401_82376751/article/details/141965586  浏览:    关键词:C++的STL标准模板库容器--string类

目录

浅浅介绍一下什么是STL:

 string类需要知道的小知识

auto和范围for:

string类的常用接口:

实现一个string类:

1. 成员变量和构造函数,拷贝构造,析构函数

2. string类对象的容量操作

<1>size,capacity,empty,clear(初始化)

<2>reserve(扩容),resize

3. string类对象的访问和遍历

4. string类对象的修改操作

<1>push_back(尾插), append, operator+=

<2>insert(插入),erase(删除),operator=(赋值)

<3>c_str(返回C格式字符串)和find(查找)

<4>substr(复制子字符串)

5. string类非成员函数


浅浅介绍一下什么是STL:

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。STL有六大组件:仿函数,算法,迭代器,空间适配器,容器,配接器
本篇讲的就是STL容器部分的string类

 string类需要知道的小知识

 auto和范围for:

(1)在本篇的学习中将使用到关键字auto,它的作用是自动识别类型

注意:

1.使用auto声明指针类型,auto和auto*没有任何区别,但如果修饰引用类型则必须加上&

2.auto在对同一行声明多个变量时,变量必须是相同的类型,否则会报错,因为编译器实际只能对第一个类型进行推导,然后用推导出来的类型定义其他变量

//会出现编译报错:在声明符列表中,auto必须始终推导为同一类型
auto a = 1, b = 2.1;//在这之前已经推导出了int

3.auto不能够作为函数的参数,但可以作为返回值,想设为返回值请谨慎使用

4.auto不能设为数组

(2)范围for

C++11中引入了基于范围的for循环,for循环后的括号由冒号“ :”分为两部分:第一部分是范围
内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

1.对于一个知道范围的集合而言,就可以使用范围for

2.范围for可以作用到数组和容器上进行遍历

3.范围for的底层实际上就是替换为迭代器

int main()
{int a[]={1,2,3,4,5};for(auto& a1:a){cout<<a1<<" ";}return 0;
}

string类的常用接口:

string是一个比较早的容器所以在部分设计上会出现冗余的情况,我只会讲一些比较常用的接口,其他的接口请自行查看文档

string类接口详细文档传送门:string - C++ 参考 (cplusplus.com)

1.string类对象的常见构造

string()        //构造出一个空的string对象

string(const char* ch)        //使用一个字符串进行构造对象

string  (size_t n,char c)           //string类对象中包含n个字符c,这个基本不实现

string  (const string& st)        //拷贝构造

2.string类对象的容量操作

size                   //返回字符串有效长度

capacity           //返回空间总大小

empty               //判断字符串是否为空            

clear                 //清空字符串       

reserve             //空间扩容        

resize               ​​​​​​//将字符串大小调整为n个字符的长度,多出的空间用字符c填充

3.string类对象的访问和遍历

operator[]         //返回pos位置的字符引用                      

begin                //获取字符串起始位置字符的迭代器

end                   //获取字符串最后一位的下一位置的迭代器

4.string类对象的修改操作

operator=             //将一个对象赋值给另一个对象

push_back         //尾插一个字符或者字符串

append               //在字符串后追加一个字符串

operator+=         //在字符串后面追加一个字符或者字符串

c_str                   //返回C格式字符串

find+pos             //在起始位置加上pos后的下标开始往后查找字符或者字符串出现的起始位置

substr                 //从对象的字符串的pos位置开始向后截取n个字符,然后返回

insert                   //在pos位置上插入一个字符或者字符串

5.string类非成员函数

operator>>          //输入重载

operator<<          //输出重载

实现一个string类:

这里都是我自己实现各项功能,实现参数部分可能会和文档有差异,请见谅

1. 成员变量和构造函数,拷贝构造,析构函数
string类主要有三个成员变量,还会设置一个静态成员常量,分别是指向数组的指针char*,记录有效字符数的_size,和容量_capacity,静态成员常量npos

构造函数上我们不使用初始化列表,参数设成一个带有缺省值的char*指针,这样可以同时解决单字符初始化,字符串初始化,空字符串对象三种初始化

注意:

1.npos固定为-1,因为在参数里会使用它作为缺省值,无符号整形的符号位无效所以当缺省值有效时它的大小是:4294967295。它的作用就是给一个很大的值,在后面的函数实现中erase(删除)和substr(复制子字符串)会使用到

2.字符串的结尾都会跟上一个'\0'它也是占空间的,但记录容量的_capacity并不会记录这个空间,所以在new开空间的时候要额外增加一个空间给'\0',如果不开这个空间就有两个结果:

<1>不在字符串尾部增添'\0'导致循环越界访问直至遇到'\0';

<2>增添'\0'的位置已经被别的变量申请走了,越界增添'\0'导致破坏了其他资源的完整性        

class string
{
public:string(const char*ch="")    //注意这里给的缺省值是没有有效字符的哦{size_t size=strlen(ch);_str=new char[size+1];  //加1为'\0'开空间strcpy(_str,ch);_size=_capacity=size;}
private:char* _str;size_t _size;size_t _capacity;static const int npos = -1;
}

拷贝构造现在也不应该是浅拷贝,因为出现了空间申请,需要使用深拷贝,现在我们实现一个swap(交换函数)来辅助进行拷贝构造的实现。

注意:直接使用库里的swap对对象(参数是对象)也可以完成交换,但是和我自己实现交换过程有很大差别,库里的实现是对对象的拷贝再交换,这样会导致频繁的触发拷贝构造(深拷贝)导致降低效率,所以不直接交换对象,交换成员变量

出现了资源申请析构函数也不能使用自动生成的了,需要自己实现

void swap(string& ch)
{std::swap(_str, ch._str);            //交换指针std::swap(_size, ch._size);          //交换大小std::swap(_capacity, ch._capacity);  //交换容量
}
string(const string& str)
{//std::swap(*this,str);   频繁触发拷贝构造会出现效率降低的问题string temp(str._str);    //这里是用str的字符串初始化一个string对象swap(temp);               //现在交换temp和this指针指向的对象
}
~string()
{if(!empty()){delete[] _str;_str = nullptr;_size = _capacity = 0;}
}
2. string类对象的容量操作
<1>size,capacity,empty,clear(初始化)

这四个都是比较简单的成员函数,直接给大家看实现,一看很快就能理解了

size_t size()        //返回有效字符个数
{return _size;
}
size_t capacity()    //返回容量大小
{return _capacity;
}
bool empty()         //判空
{return _size==0;
}
void clear()
{_str[0]='\0';    //这里要重载[]返回当前位置的引用才能直接修改_size=0;
}
<2>reserve(扩容),resize

剩下的这两个,reserve负责扩容比较耳熟能详;resize它能调整字符串的长度,如果给的n大于当前字符串长度,则通过在末尾插入所需数量的字符来扩展,直到达到n的长度,如果给的n小于当前长度,那么缩短为当前n个字符,并且删除第n个字符(包括n)以外的字符

下面直接看实现来理解更方便:

void reserve(size_t n)
{if(n>_capacity){char* newstr=new char[n+1];    //这里也不要忘了'\0'的位置strcpy(newstr,_str);           //拷贝一下旧空间delete[] _str;                 //删除旧空间_str=newstr;                   //指向新空间_capacity=n;                   //更新容量 }
}
void resize(size_t n, char c='0')    
{if (n > _capacity)                 //如果容量不够就扩容{reserve(_capacity * 2);}if (n < size())                    {_str[n] = '\0';                //小于的话直接在n位置加'\0'_size = n;                     //更新有效字符数}else{for (int i = size(); i <= n; i++)    {_str[i] = c;     }_size=n;            //更新_size_str[_size]='\0';   //不要忘了'\0'}
}
3. string类对象的访问和遍历

这里就会涉及到迭代器的使用了。迭代器的主要目的是为了屏蔽底层细节,让代码有更好封装性和模块化,能够让使用者不要直接面对复杂的底层内部实现,只需要通过简化后的接口进行操作,使其更容易使用和理解,它就是封装的一种体现方式

class string
{
public:typedef char* iterator;    //自定义命名char*iterator begin()    //返回字符串起始位置的迭代器{return _str;    }iterator end()     //返回字符串末尾下一位的迭代器{return _str+_size;}const iterator begin()const    //const迭代器{return _str;}const iterator end()const{return _str + _size;}
}

operator[]:重载方括号方便字符串的访问和修改

char& operator[](size_t pos)    //运算符重载
{assert(pos < _size);        //当前下标必须有效return _str[pos];           //返回当前下标字符的引用 
}
4. string类对象的修改操作
<1>push_back(尾插), append, operator+=

这三个成员函数在实现上都有着十分相似的情况,甚至可以相互复用所以直接放到一起讲

void push_back(const char ch)
{if (_size == _capacity)    //判断容量是否足够{reserve(_capacity == 0 ? 4 : _capacity * 2);}	_str[_size++] = ch;    //尾插一个字符_str[_size] = '\0';
}string& operator+=(const char ch)    //复用push_back完成尾插一个字符
{push_back(ch);        return *this;
}string& operator+=(const char* ch)
{size_t len = strlen(ch);        //计算字符串长度if (_size+len > _capacity)      //判断容量是否足够{reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);}memcpy(_str + _size, ch, sizeof(char) * len);    //拷贝空间_size += len;                                    //更新_size_str[_size] = '\0';                              //尾部增添'\0'  return *this;        
}void append(const char* str)      //在字符串后面增添字符串
{*this+=str;    //调用operator+=
}void push_back(const char* ch)    //这个和append就一样
{*this += ch;    //调用operator+=
}

在实现完后就可以发现这些函数的功能重叠了好多,append和push_back尾插字符串功能就重叠了,这就是因为比较早出现的原因出现的设计冗余

<2>insert(插入),erase(删除),operator=(赋值)

insert(插入)在代码上容量扩容方面和上面基本一致,但注意不要把insert和前面的功能搞混,insert是插入字符串中的有效字符,上面的三个都是尾插需要在末尾手动添加'\0',假设调用insert时pos位置是尾巴,在代码段末端增加'\0'的代码那就和上面的功能无异,那pos在中间时那不就暴毙了,搁中间加'\0'那就错了,这也是为什么尾插无法复用insert,所以单独拉出来讲

void insert(size_t pos, char ch)    //在pos位置插入一个字符
{assert(pos <= _size);      //pos位置必须有效if (_size == _capacity)     //判断容量是否足够{reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;    //找到'\0'位置的下一个位置while (end >= pos)         //把pos位置后的字符(包括'\0')全部后移{_str[end] = _str[end - 1];end--;}_str[pos] = ch;            //插入字符_size++;                   //_size加1 
}void insert(size_t pos, const char* ch)
{assert(pos <= _size);            //pos位置必须有效size_t len = strlen(ch);         //计算插入字符串长度if (_size + len > _capacity)     //容量大小是否足够{reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);}int end = _size + len;           //'\0'位置加上字符串长度  while (end >= pos + len )        //将包括'\0'的字符全部后移{_str[end] = _str[end - len];end--;}memcpy(_str + pos, ch, sizeof(char) * len);    //直接拷贝进去_size += len;                    //_size增加插入字符串长度
}

erase(删除)的主要作用:要求在pos位置开始向后删除len个字符,len有缺省值,当缺省值有效时删除pos位置包括pos位置往后的所有字符,因为len此时超级大

void erase(size_t pos, int len=npos)
{assert(pos < _size);        //pos位置必须有效if (len >= _size - pos)     //如果删除字符数大于剩下字符数{_str[pos] = '\0';       //直接pos位置给'\0'_size = pos;            //更新_size}else{for (size_t i = pos + len; i <= _size; i++)    //将pos+len的数据前移{ _str[i - len] = _str[i];}_size -= len;    //更新_size}
}

operator=(赋值):这个就很简单了,使用赋值对象构造一个对象,然后交换被赋值对象和构造出来的对象即可

string& operator=(string temp)    //这里不用引用直接传参触发拷贝构造
{swap(temp);                   //交换(形参不影响实参)return *this;
}
<3>c_str(返回C格式字符串)find(查找)

在没有重载输出(cout)之前 cout<<st1(string类对象) 肯定是不行的,因为它是一个对象并不是字符串,c_str就是专门解决这个问题,它会返回字符串的起始位

const char* c_str()
{return _str;
}
//没重载cout之前 cout<<st1<<endl; 无法打印字符串
//cout<<st1.c_str()<<endl; 这样就可以了

find(查找):返回在pos位置往后查找字符或者字符串出现的第一位置的下标,

查找一个字符:直接for循环遍历查找,找到返回下标

查找字符串:调用strstr函数直接查找字符串出现第一个位置的指针,通过这个指针减去起始位就是下标

下面看实现:

size_t find(char ch, size_t pos)
{assert(pos < _size);        //pos位置必须有效for (size_t i = pos; i < _size; i++){if(_str[i]==ch){return i;          //返回下标}}
}size_t find(const char* ch, size_t pos)
{assert(pos < _size);const char* fin = strstr(_str + pos, ch);  //找到并返回出现的第一个位置的指针if (fin == nullptr)        //空代表没找到{return npos;           //返回npos}else{return fin - _str;     //当前指针减去起始位置得出下标返回}
}
<4>substr(复制子字符串)

substr的主要作用:要求指定位置开始往后截取n个字符并返回,如果n没有给指定的长度那么缺省值有效,默认从指定位置开始向后截取全部字符返回,下面看实现理解更方便

string substr(size_t pos, size_t len=npos)
{assert(pos < _size);        //依旧是pos位置必须有效//len大于剩余长度,更新lenif (len > _size-pos)        {len = _size-pos;}string sub;                        //构造一个新的string类对象sub.reserve(len);                  //给len长度的空间for (size_t i = 0; i < len; i++)   //从pos位置开始向后截取len次字符 {sub += _str[pos + i];          //尾插}return sub;    //返回当前类对象
}
5. string类非成员函数

比较主要的就是重载输入和输出了,这个比较简单直接看实现来理解

ostream& operator<<(ostream& out, const string& s)
{for(auto ch:s)    //使用范围for自动识别类型,自动迭代,自动判断停止{cout<<ch;}out << endl;return out;
}
istream& operator>>(istream& in, string& s)
{s.clear();        //输入自然要对当前对象进行初始化char ch;ch=in.get()while(ch != ' ' && ch != '\n')    //这个是判断什么时候停下来,我假设{                                 //遇到空格和换行就停止s+=ch;ch=in.get();}return in; 
}

本篇文章到这里主要内容已经讲完了,希望能够对你产生帮助,感谢阅读

 需要看全部实现可以往下翻下面有完整实现


我自己实现的一个string类 :

//头文件
#include <iostream>
#include <assert.h>
using namespace std;namespace cool
{class string{public://简易迭代器typedef char* iterator;iterator begin()               //普通迭代器{return _str;}iterator end(){return _str+_size;}const iterator begin()const    //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()                      //析构     {delete[] _str;_str = nullptr;_capacity = _size=0;}void swap(string& str)         //交换函数{std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}string(const string& str)      //拷贝构造{string temp(str._str);swap(temp);}const char* c_str()const       //返回C格式字符串{return _str;}size_t size()const             //返回有效个数{return _size;}bool empty()                   //判空{return _size==0;}void clear()                   //初始化{_str[0] = '\0';_size = 0;}void reserve(size_t n)         //扩容{if (n > _capacity){char* temp = new char[n+1];strcpy(temp, _str);delete[]_str;_str = temp;_capacity = n;}}void push_back(char ch);                  //尾插void push_back(const char* ch);          //尾插的函数重载string& operator+=(const char ch);        //重载+=string& operator+=(const char* ch);       //+=的函数重载string& operator=(string temp);           //重载=char& operator[](size_t pos)              //重载[]const char& operator[](size_t pos)const;  //const类型的[]重载void append(const char* str);             //字符串后增加一个字符串void insert(size_t pos, char ch);         //插入void insert(size_t pos, const char *ch);  //插入的函数重载  void erase(size_t pos,int len=npos);      //删除  size_t find(char ch, size_t pos = 0);           //查找 size_t find(const char* ch, 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=-1;};//重载输入输出ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);void test();void test1();void test2();void test3();void test4();
}
//实现文件
namespace cool
{void string::push_back(const char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}void string::push_back(const char* ch){*this += ch;}string& string::operator+=(const char ch){push_back(ch);return *this;}string& string::operator+=(const char* ch){size_t len = strlen(ch);if (_size+len > _capacity){reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);}memcpy(_str + _size, ch, sizeof(char) * len);_size += len;_str[_size] = '\0';return *this;}string& string::operator=(string temp){swap(temp);return *this;}char& string::operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& string::operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void string::append(const char* str){*this+=str;    } void string::insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}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* ch){assert(pos <= _size);size_t len = strlen(ch);if (_size + len > _capacity){reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);}size_t end = _size + len;while (pos + len <= end){_str[end] = _str[end - len];end--;}memcpy(_str + pos, ch, sizeof(char) * len);_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 = pos; i < _size; i++){if (_str[i] == ch) {return i;}}return npos;}size_t string::find(const char* ch, size_t pos){assert(pos < _size);const char* ptr = strstr(_str + pos, ch);if (ptr == nullptr){return npos;}else{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;}ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();const int N = 256;char buff[N];int i=0;char ch;ch = in.get();while (ch != '\n'){buff[i++] = ch;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;i = 0;}return in;}  //测试用例void test( ){string s1;string s2("h");string s3("hello");cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;}//尾插测试void test1(){string s1;s1.push_back('h');cout << s1 << endl;s1.push_back("ello");cout << s1 << endl;s1.append(" world");cout << s1 << endl;}//插入删除测试void test2(){string s1("hello world");s1.insert(5, 'x');cout << s1 << endl;s1.insert(0, "xxxx");cout << s1 << endl;s1.erase(0, 4);cout << s1 << endl;}//拷贝和赋值测试void test3(){string s1("hello world");string s2 = s1;string s3;s3 = s2;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;}//查找和返回字符串测试void test4(){string st("hello.cpp");size_t fin = st.find('.');string st1 = st.substr(fin);cout << st1 << endl;}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com