资讯动态

C++STL【string】常用接口函数以及模拟实现

发布时间:2026/11/6 7:44:45 来源:尧图企业网站定制
本篇博客给大家带来第一个学习的STL容器建议大家对照着C的文档进行学习string - C Reference一.为什么学string在C语言中字符串是以/0结尾的一些字符的集合为了方便操作C标准库里提供了一些str系列的库函数但是这些库函数和要操作的字符串是分离的不符合OOP的思想而且空间要使用者自己管理很不方便所以我们要学习string以便可以更好的操作管理字符串二.string类对象的常用默认成员函数1.构造函数(包括拷贝构造)default (1) string(); copy (2) string (const string str); substring (3) string (const string str, size_t pos, size_t len npos); from c-string (4) string (const char* s); from sequence (5) string (const char* s, size_t n); fill (6) string (size_t n, char c); range (7) template class InputIterator string (InputIterator first, InputIterator last);①是构造空的string类对象即空字符串(注意并不是给空指针而是容量为0的已经开好了的字符串)②拷贝构造用一个string类的对象去创建一个string的对象。③用目标str的字符串的pos下标开始包括和后面len个字符去创建写的string对象。④用字符串s去创建string类型的对象⑤用字符串s的前n个去创建string类型的对象⑥用n个c字符去创建string的对象⑦是用一个迭代器区间去构造string的对象迭代器是什么以及怎么用会在下面进行讲解使用演示void Test1() { string s0(this is a str); string s1; string s2(s0); string s3(s0, 8, 5); string s4(hello world); string s5(hello world,5); string s6(5, x); cout s1 endl; cout s2 endl; cout s3 endl; cout s4 endl; cout s5 endl; cout s6 endl; } int main() { Test1(); return 0; }输出结果2.析构函数~string();只有一个函数会自动释放string类对象/3.赋值运算符重载string (1) string operator (const string str); c-string (2) string operator (const char* s); character (3) string operator (char c);①把一个string类的对象赋值给另一个string类的对象②把一个字符串赋值给一个string类对象③吧一个字符赋值给string类对象使用演示void Test2() { string s1(hello); string s2; s2 s1; cout s2 endl; s1 c; cout s1 endl; s2 world; cout s2 endl; }输出结果三.string类的迭代器及其相关函数• 迭代器是类似指针的东西有的可以就是指针是用来遍历容器内的数据和对其进行访问的它是在类内进行过typedef的使用迭代器是因为好多容器不能使用指针来完成这样的遍历操作但都能使用迭代器如list类使用一个类去完成迭代器的工作极大的提升了代码的可读性。这是四个迭代器前两个为正向迭代器后面两个为反向迭代器而加不加const在于是否想对容器内的数据进行修改如果要访问要修改就要使用普通迭代器如果只访问不修改就可以使用const迭代器。我们先看一个使用演示再接下来看各个函数void Test3() { string s1(1234 5678); string::iterator it s1.begin(); while (it ! s1.end()) { cout *it /; it; } }1.begin()和end()iterator begin(); const_iterator begin() const;这两个都是返回指向第一个数据的迭代器第一个是给普通迭代器使用的第二个是给const迭代器使用的iterator end(); const_iterator end() const;end()返回最后一个数据的下一个位置的迭代器2.rbegin()和rend()rbegin()和rend()这两个函数的返回值类型为反向迭代器它和正向迭代器的功能是相反的先来一个反向迭代器的使用演示void Test4() { string s1(hello world); string::reverse_iterator rit s1.rbegin(); while (rit ! s1.rend()) { cout *rit /; rit; } }输出结果在这里我依然是对迭代器但是输出的顺序却反过来了reverse_iterator rbegin(); const_reverse_iterator rbegin() const;rbegin()返回的是最后一个元素位置的迭代器reverse_iterator rend(); const_reverse_iterator rend() const;rend()返回的是第一个位置的前一个位置的迭代器。• 反向迭代器是倒过来遍历数据反向迭代器的就是正向迭代器的--rbeginrend和beginend的返回位置也不同。3.范围forC11支持• 其底层也是通过指针实现的但使用起来更加方便使用演示void Test5() { string s1(hello world); for (auto ch : s1) { cout ch; } }• auto是一个自动类型其会根据所传入的数据类型自己推导变量应该是什么类型• auto ch s1 这里的意思就是把容器s1里的存的数据给ch这个变量一次取一个从第一个按顺序往下取在循环中使用ch去进行遍历。•写成auto ch类似于传值传参ch和底部s1的数据不是应该东西若想通过改写ch进而改变s1中的数据可以写成auto ch让ch称为每个元素的别名四.string的容量与大小操作函数1.size、length、capacity和max_size①size和length其实这两个的功能完全相同都是返回string内存储的字符串的长度也就是sizesize_t size() const; size_t length() const;为什么会设计两个一样的其实是因为string出现的太早了它最开始使用的是length而后面的容器使用的都是size于是它也加了个size②capacity返回的是现在string开了多少空间即总共可以放入多少字符size_t capacity() const;③max_size其实这个函数没有用它是返回理论上这个string对象能开空间的最大值size_t max_size() const;2.resize和reserve①reserve作用是把你调用reserve函数的对象的空间capacity开成n个如果n小于原来的空间不会缩小可以不会完全按照你给的数去开因为其开空间的机制可能开大于你给的n的空间void reserve (size_t n 0);使用演示void Test6() { string s1(hello world); cout s1.capacity() endl; s1.reserve(20); cout s1.capacity() endl; }输出结果②resize这个函数就复杂了功能是对size进行操作一定会把size变成你给的大小可以放大可以缩小因此存在三种形式当然如果你给的size大于当前的capacity了string会自己扩容的void resize (size_t n); void resize (size_t n, char c);第一种你给的n大于当前的size并且没给char c编译器会用\0把大于原来size的部分给填上第二种你给的n大于当前的size并且给了char c:编译器会用你给的字符c把大于原来size的部分给填上第三种你给的n小于当前的size编译器不会动capacity但是会直接把size变成你给的大小数据会被截断对应的例子void Test7() { string s1(12345); s1.resize(20); cout s1 endl; string s2(66666); s2.resize(20, x); cout s2 endl; string s3(hello world); s3.resize(5); cout s3 endl; }输出结果3.empty和clear①empty判空如果string是空返回true非空返回falsebool empty() const;②clear清除字符串把_str的第一个字符设置成\0把size设置成0但capacity不变。void clear();使用实例void Test8() { string s1(hello world); cout s1 endl; cout s1.size() endl; cout s1.capacity() endl; s1.clear(); cout s1 endl; cout s1.size() endl; cout s1.capacity() endl; }输出结果五.容器的访问函数operator[]和at• operator[]是string中最常用的函数之一其允许string的对象可以像数组一样访问大大的提高了代码的可读性。char operator[] (size_t pos); const char operator[] (size_t pos) const;使用实例operator[] size() 的组合是string里最常用的遍历方式但对于其他容器并不通用void Test9() { string s1(123456789); for (int i 0; i s1.size(); i) { s1[i] - 1; } cout s1 endl; }• at的功能和operator[]完全相同但是两者的使用方式不同对于错误的处理方式不同operator[]面对越界行为未定义要看各个编译器自己咋实现的而at面对越界会抛出异常char at (size_t pos); const char at (size_t pos) const;使用实例void Test10() { string s1(12345678); for (int i 0; i s1.size(); i) { s1.at(i) 1; } cout s1 endl; }六.字符串修改函数1.push_backappend和operator①push_back尾插一个字符void push_back (char c);②append在尾部添加一个字符串这里和string的构造函数是类似的。string (1) string append (const string str); substring (2) string append (const string str, size_t subpos, size_t sublen); c-string (3) string append (const char* s); buffer (4) string append (const char* s, size_t n); fill (5) string append (size_t n, char c); range (6) template class InputIterator string append (InputIterator first, InputIterator last);③operator在尾部添加字符或字符串比push_back和append使用的更多可读性更好。string (1) string operator (const string str); c-string (2) string operator (const char* s); character (3) string operator (char c);使用实例这里仅展示部分调用void Test11() { string s1; string s2(345); s1.push_back(1); s1.append(s2); s1 789; cout s1 endl; }2.inserterase①insert在指定位置插入字符或者字符串会产生数据挪动效率低要谨慎使用。这里的参数比构造函数和上面的append相比多了个pos或者iterator p这个pos是控制要把字符或者字符串插入到调用insert的对象的哪个下标位置pos对应的是下标迭代器指针插入是插入到迭代器指向的位置这里还会涉及到迭代器失效但是我们不做深入研究迭代器失效在vector详细讲解。string (1) string insert (size_t pos, const string str); substring (2) string insert (size_t pos, const string str, size_t subpos, size_t sublen); c-string (3) string insert (size_t pos, const char* s); buffer (4) string insert (size_t pos, const char* s, size_t n); fill (5) string insert (size_t pos, size_t n, char c); void insert (iterator p, size_t n, char c); single character (6) iterator insert (iterator p, char c); range (7) template class InputIterator void insert (iterator p, InputIterator first, InputIterator last);使用实例void Test12() { string s1(hello world); s1.insert(5, 123); cout s1 endl; }②erase删除对应区间或者是指定位置的值会产生数据挪动效率较低要谨慎使用。这里的pos是对应的下标缺省值npos是一个超级大的数意思是如果你没传第二个值就会默认从pos位置开始全部删除。sequence (1) string erase (size_t pos 0, size_t len npos); character (2) iterator erase (iterator p); range (3) iterator erase (iterator first, iterator last);使用实例void Test13() { string s1(hello world); s1.erase(5, 4); cout s1 endl; }3.swapvoid Test14() { string s1(hello world); string s2(123 456); string s3(welcome); s1.swap(s3); cout s1 endl; cout s3 endl; swap(s2, s3); cout s2 endl; cout s3 endl; }这个是string里的std::string::swapvoid swap (string str);这个是专门写给string类的交换函数但是因为使用方式和其他的交换函数不一样怕大家不会调用所以在全局也还有一个专门写给string的swap函数这是定义在全局的std::swapvoid swap (string x, string y);使用实例void Test14() { string s1(hello world); string s2(123 456); string s3(welcome); s1.swap(s3); cout s1 endl; cout s3 endl; swap(s2, s3); cout s2 endl; cout s3 endl; }4.replace和assign• assign() 用于重置字符串通常直接用 代替replace() 用于替换指定区间通常用 eraseinsert 或拼接 substr 代替。了解即可日常开发中出场率较低。这里就直接跳过了七.字符串的提取、查找与比较操作1.c_str和data这两个函数的功能都是获取std::string内部底层字符数组的指针通常返回const char*。主要的目的是为了把内部存的字符串去给一些只能通过指针调用的函数。const char* c_str() const;const char* data() const;使用实例void Test15() { string s1(12345); cout strlen(s1.c_str()) endl; }2.find、rfind这两个都是找完整的字串在调用这个函数的string对象中找“一模一样的连续字符串”。• find从左往右找返回子串第一次出现的首字符下标。例子在 abcbca 中找 bc返回 1。• rfindr 代表 reverse反向。从右往左找返回子串最后一次出现的首字符下标。例子在 abcbca 中找 bc返回 3。• 这两者若没找到都会返回nposfind第一个参数都是要找什么第二个参数pos是从那个下标开始找。第三个重载的‘n’是只找第一个参数const char* s的前‘n’个其实没啥用直接用第二个重载就好了。string (1) size_t find (const string str, size_t pos 0) const; c-string (2) size_t find (const char* s, size_t pos 0) const; buffer (3) size_t find (const char* s, size_t pos, size_t n) const; character (4) size_t find (char c, size_t pos 0) const;rfind与其的参数和返回值都一一对应这里就不展示了。使用实例void Test16() { string s1(abcbc); cout s1.find(bc,2) endl; string s2(hello world); string s3(hello); cout s2.find(s3) endl; }3.find_first_of,find_last_of,find_first_not_of,find_last_not_of这里的四个函数可以一起学习参数、行为都类似只是细节不同• 这四个函数的参数通常是一个字符集合比如 abc。注意它们不是找整个字符串 abc而是找 a、b 或 c 其中的任意一个字符• find_first_of从左往右找找第一个属于该集合的字符。例子str hello找 find_first_of(xyol)。行为h 不在里面指不在要找的xyol这几个字符里e 不在l 在里面返回 l 的下标 2。• find_last_of从右往左找找最后一个属于该集合的字符。例子str hello找 find_last_of(xyol)。行为从右往左看o 在里面返回 o 的下标 4。• find_first_not_of从左往右找找第一个不属于该集合的字符找异类。例子str hello找 find_first_not_of(he)。行为h 在集合里e 在集合里l 不在返回 l 的下标 2。• find_last_not_of从右往左找找最后一个不属于该集合的字符。例子str hello找 find_last_not_of(lo)。行为从右往左o 在l 在l 在e 不在返回 e 的下标 1。使用实例void Test17() { string s1(hello world); cout s1.find_first_of(wdr) endl; cout s1.find_first_not_of(helowd) endl; }4.substr和compare①substr其作用是从原字符串中提取一部分返回一个新的子字符串不会影响原字符串pos是开始取的初始下标len是取多长string substr (size_t pos 0, size_t len npos) const;使用实例void Test18() { string s1(hello world); string s2 s1.substr(2,5); cout s2 endl; }②compare这个用的也不多就是比较字符串的大小大于就返回正数等于返回0小于返回负数使用实例void Test18() { string s1(12345); string s2(23456); cout s2.compare(s1) endl; }八.非成员函数这部分主要包括operator各种关系运算符重载上面说过的全局的swap函数流插入流提取重载和getline函数。在这里就简单说一下operator和getline函数。①operator就是会返回把两部分合并到一起的新string对象不会改变参与运算的字符串使用实例void Test19() { string s1(12345); string s2(23456); cout s1s2hello c endl; }②getline函数和cin一样是用来读取的函数但是getline更加灵活。istream getline (istream is, string str, char delim); istream getline (istream is, string str);第一个是一直读取你在键盘上输入的字符串直到遇到你给定的字符 delim 为止不会把他读进去第二个一直读取直到遇到换行符\n为止这个最常用比cin好的地方在于遇到空格不会停止也会把空格读进去了。使用实例:void Test20() { string s1; std::getline(cin,s1); cout s1 endl; string s2; std::getline(cin, s2, l); cout s2 endl; }九.string类的模拟实现//string.h #pragma once #define _CRT_SECURE_NO_WARNINGS 0 #includestring #includeiostream #includeutility #includecstring #includeclimits #includeassert.h using namespace std; namespace lgf { class string { public: //迭代器,迭代的是容器内部储存的数据所以其类型应和储存的数据的类型相同 typedef char* iterator; typedef const char* const_iterator; //返回指向第一个数据的指针 iterator begin() { return _str; } const_iterator begin()const { return _str; } //返回指向最后一个数据的下一个指针 iterator end() { return _str _size; } const_iterator end()const { return _str _size; } //构造拷贝构造赋值运算符重载析构 string(); string(const char* str); string(const string s); string operator(const string s); string operator(string s); ~string(); //操作函数 void reserve(size_t n); void push_back(char ch); void append(const char* str); string operator(char ch); string operator(const char* str); void insert(size_t pos, char ch); void insert(size_t pos, const char* str); void erase(size_t pos 0, size_t len string::npos); size_t find(char ch, size_t pos 0) const; size_t find(const char* str, size_t pos 0) const; string substr(size_t pos 0, size_t len npos) const; void swap(string s); const char* c_str() const { return _str; } // 下标访问 size_t size() const { return _size; } size_t capacity() const { return _capacity; } char operator[](size_t pos) { assert(pos _size); return _str[pos]; } const char operator[](size_t pos) const { assert(pos _size); return _str[pos]; } static const size_t npos -1; private: char* _str; size_t _size; size_t _capacity; }; string operator (const string lhs, const string rhs); string operator (const string lhs, const char* rhs); string operator (const char* lhs, const string rhs); bool operator (const string lhs, const string rhs); bool operator! (const string lhs, const string rhs); bool operator(const string lhs, const string rhs); bool operator(const string lhs, const string rhs); bool operator (const string lhs, const string rhs); bool operator (const string lhs, const string rhs); ostream operator(ostream out, const string s); istream operator(istream in, string s); istream getline(istream in, string s); } //string.cpp #includeString.h namespace lgf { //无参默认构造 string::string() : _str(new char[1]), _size(0), _capacity(0) { _str[0] /0; }; //用一个字符串构造 string::string(const char* str) : _size(strlen(str)), _capacity(_size) { _str new char[_size 1]; strcpy(_str, str); } //拷贝构造传统写法 string::string(const string s) : _str(new char[s._capacity 1]), _size(s._size), _capacity(s._capacity) { strcpy(_str, s._str); } ////拷贝构造现代写法 //string::string(const string s) // :_str(nullptr) //{ // string tmp(s._str); // swap(tmp); //} //赋值运算符重载传统写法 string string::operator(const string s) { if(s ! *this) { delete[] _str; _str new char[s._capacity 1]; _size s._size; _capacity s._capacity; strcpy(_str, s._str); } return *this; } //赋值运算符重载现代写法 string string::operator(string s) { swap(s); return *this; } //析构函数 string::~string() { _size 0; _capacity 0; delete[] _str; _str nullptr; } //操作函数 void string::reserve(size_t n) { if (n _capacity) { //tmp去开一个和_str一样大的空间去存内部数据 char* tmp new char[n 1]; //把_str的数据给tmp中间值然后把_str的空间释放 strcpy(tmp, _str); delete[] _str; //这里是把tmp的地址直接给str让str直接指向新空间 _str tmp; //capacity更新一下size没变不用改 _capacity n; } } void string::push_back(char ch) { if (_size _capacity) { reserve(_capacity 0 ? 4 : 2 * _capacity); } _str[_size] ch; _size; //要在最后把’/0‘给补上 _str[_size] /0; } void string::append(const char* str) { size_t len strlen(str); if (_capacity _size len) { reserve(_size len); } strcpy(_str _size, str); _size len; } string string::operator(char ch) { push_back(ch); return *this; } string string::operator(const char* str) { append(str); return *this; } void string::insert(size_t pos, char ch) { assert(pos _size); if (_size _capacity) { reserve(_capacity 0 ? 4 : 2 * _capacity); } //在这里挪动数据要注意i和pos的边界问题因为pos为size_t对负数处理不了 //在这里让i size1是因为size指向的是最后一个元素的下一个也就是‘/0’但是/0本身也要往后挪动 //error挪动数据,i是主动移动的最后会导致i 0i--i0了 /*int end _size; while (end (int)pos) { _str[end 1] _str[end]; --end; }*/ size_t i _size 1; while (i pos) { //在这里要记住str[i]不要去主动移动要让i的关系式移动保证i本身的安全 _str[i] _str[i - 1]; --i; } _str[pos] ch; _size; } void string::insert(size_t pos, const char* str) { size_t len strlen(str); if (_capacity len _size) { reserve(len _size); } size_t i _size len; while (i pos len - 1) { _str[i] _str[i - len]; i--; } for (size_t i 0; i len; i) { _str[i pos] str[i]; } _size len; } void string::erase(size_t pos, size_t len) { //情况①pos后的全删除 if (len npos || len _size - pos) { _str[pos] /0; _size pos; } //情况②删除一部分要挪动数据 else { size_t i pos; while (i _size - len) { _str[i] _str[i len]; i; } _size - len; } } size_t string::find(char ch, size_t pos) const { 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* str, size_t pos) const { assert(pos _size); const char* ptr strstr(_str pos, str); if (ptr) { return ptr - _str; } return npos; } string string::substr(size_t pos, size_t len) const { if (len npos || 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; } string operator (const string lhs, const string rhs) { string tmp(lhs); tmp rhs.c_str(); return tmp; } string operator (const string lhs, const char* rhs) { string tmp(lhs); tmp rhs; return tmp; } string operator (const char* lhs, const string rhs) { string tmp(lhs); tmp rhs.c_str(); return tmp; } bool operator (const string lhs, const string rhs) { return strcmp(lhs.c_str(), rhs.c_str()) 0; } bool operator! (const string lhs, const string rhs) { return !(lhs rhs); } bool operator(const string lhs, const string rhs) { return strcmp(lhs.c_str(), rhs.c_str()) 0; } bool operator(const string lhs, const string rhs) { return strcmp(lhs.c_str(), rhs.c_str()) 0; } bool operator (const string lhs, const string rhs) { return !(lhs rhs); } bool operator (const string lhs, const string rhs) { return !(lhs rhs); } ostream operator(ostream out, const string s) { for (auto ch : s) { out ch ; } return out; } istream operator(istream in, string s) { char ch; ch in.get(); while (ch ! ch ! \n) { s ch; ch in.get(); } return in; } istream getline(istream in, string s) { char ch; ch in.get(); while (ch ! \n) { s ch; ch in.get(); } return in; } void string::swap(string s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //这个就是怕使用的人不知道要s1.swap(s2);这样写所以复用了一下 void swap(lgf::string s1, lgf::string s2) { s1.swap(s2); } }完string因为是我们认识的第一个容器各种函数使用都说的比较细致而后面的vector和list或多或少都和它有相似之处对于函数的使用自然会过的快很多但是因为底层的实现更为复杂对于底层的理解即模拟实现又会称为大头。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑