目录1、STL的介绍1容器存数据就是各种装东西的盒子2迭代器遍历3算法功能函数2、string类(1)auto和范围for(2)string类的相关接口最常见的接口1.string常见的构造函数2.string类对象的容器操作函数3.string类对象的访问及遍历操作4.string类对象的修改操作5.string 类非成员函数1、STL的介绍STLC标准模板库。就是官方提前写好的一大堆现成容器算法你直接拿来用不用自己手写STL分为三个部分1容器存数据就是各种装东西的盒子string 字符串、vector 动态数组最常用、list 链表、stack 栈、queue 队列、map键值对。2迭代器遍历就是容器的通用指针统一遍历所有的容器3算法功能函数排序、查找、去重、交换等其中sort排序find查找都属于STL一句话:以前需要我们自己造轮子去实现数组的增删查改现在我们学习了STL之后直接用现成的一行代码就搞定。C语言自己造轮子C:现成的轮子全套配齐。2、string类为啥要学习string类呢C语言中字符串是以\0结尾的一些字符的集合为了操作方便C标准库中提供了一些str系列的库函数但是这些库函数与字符串是分离开的不太符合OOP的思想而且底层空间需要用户自己管理稍不留神可能还会越界访问。(1)auto和范围for一、前言在现代C11及以后语法中 auto 类型推导与范围for循环是日常开发、STL遍历、代码精简最常用两大语法。不管是遍历 string 、 vector 还是其他容器熟练使用这两个语法能极大简化代码也是校招笔试、日常写业务必备知识点。二、auto 关键字详解1. 什么是auto?auto 是自动类型推导关键字编译器根据右边赋值内容自动推导出变量真实类型无需手动写冗长类型。2.基础用法#define _CRT_SECURE_NO_WARNINGS 1 #includestdio.h #includeiostream #includestring using namespace std; int main() { //auto的使用自动推到类型 auto a1 10;//自动识别a1是int类型 auto a2 10.0;//自动识别a2是double类型 return 0; }2搭配STL中的迭代器STL迭代器中的类型极长直接用auto简化//传统的迭代器 string::iterator its.begin(); //auto推导 auto its.begin();auto使用注意定义必须初始化不给值无法推到类型。不能作为函数形参。三、范围for循环1、作用自动遍历整个容器无需下标无需迭代器极简遍历stringvector等容器所有STL。2、标准语法格式for(auto 类型 容器) { //逻辑代码 }比如我们想要遍历一个字符串我们可以看到这里我们没有写任何下标就用了一个auto和范围for循环遍历就能很好的将这个字符串一个个的打印出来。四、范围for的底层就是迭代器遍历的语法糖编译器底层自动转换成auto its.begin(); while(it !s.end()) { cout(*it) ; it; }(2)string类的相关接口最常见的接口1.string常见的构造函数string 构造空的string类现象即空字符串string s1();//无参构造stringconst char *s) 用c_str来构造string类string s2(xx);//带参数构造string(const string s)拷贝构造函数string s3(s2);//拷贝构造扩充1.对于拷贝构造函数来说还有一些接口是从第几个字符开始拷贝格式string(const string str,size_t pos,size_t lennpos);我们直接上例子这两种写法一样当我们不写后面的拷贝字符串的长度时它会自动拷贝后面所有的字符。2.拷贝字符串的前几个字符string(const char* str,size_t n);3.把字符串的前几个字符初始化string(size_t n,char c);2.string类对象的容器操作函数(1)size 和 length 返回字符串的有效长度不包括\0string s1hello world; couts1.size()endl; //打印出的结果是11 couts1.lengthendl; //打印出的结果也是112capacity 返回空间的总大小string s1 hello world; cout s1.capacity() endl; //输出的结果是15为啥是15呢?capacity()里面不包括\0它只能表示最多能存放多少个有效字符实际上应该是16多出来的一个位置存放\0.3empty 检测字符串释放为空串是返回true,否则false.string s1(); cout s1.empty() endl; //字符串为空true,输出1 string s2(hello world); cout s2.empty() endl; //字符串不为空false,输出04clear 清空有效字符string s1(hello world); cout s1.size() endl;//11 cout s1.capacity() endl;//15 cout s1.empty() endl;//0 s1.clear(); cout endl; cout s1.size() endl;//0 cout s1.capacity() endl;//15 cout s1.empty() endl;//1我们可以清晰的看到clear清除的是字符串的有效长度它的字符串的容量并没有改变。5)reserve 为字符串预留空间string s1(hello world); s1.reserve(50); cout s1.capacity() endl; //输出的结果是63为啥是63呢这是标准库为了性能做的内存对齐/按块分配优化我们所写的reserve(50)会触发分配标准库会直接分配一大块、对齐更友好的内存块比如64字节其中一个字节用来存放\0所以会显示63。6resize 将有效字符的个数改成n个多出来的空间用字符c填充string s1 hello world; cout s1.size() endl;//输出11 s1.resize(15); cout endl; cout s1 endl;//hello world,当我们没有填充字符时编译器默认补充\0ASCII为0 cout s1.size() endl;//输出15我们也可以添加几个字符比如我们想把原来的11个有效字符改成15个那么多出来的4个字符就要用字符填充下面的我们用字符x进行填充。string s1 hello world; cout s1.size() endl;//11 s1.resize(15,x); cout endl; cout s1 endl;//hello worldxxxx cout s1.size() endl;//153.string类对象的访问及遍历操作(1)operator[] 返回pos位置的字符const string 类对象的调用这个简单来说就是和数组的访问是一样的我们也同样做一个例子string s1 hello world; cout s1[0] endl;//输出h cout s1[1] endl;//输出e cout s1[2] endl;//输出l不应该是operator[]吗?operator[]是个运算符的名字而s1[]是它的实际调用s1[0]是s1.operator[](0)的语法糖所以它们是完全等价的。(2)beginend begin获取的是字符串的第一个字符end获取的是最后一个字符的下一个位置的迭代器也就是\0的位置。s我们直接举一个遍历字符串的例子来直观看一下这个用法string s1 hello world; string ::iterator it s1.begin(); while (it ! s1.end()) { cout *it ; it; } //输出h e l l o w o r l d我们在写这个迭代器的通用的指针类型的时候我们会好奇为啥要写这个类作用域的这是因为这个迭代器指针适用于所有的容器类型所以我们使用时需要标明这个指针是哪个类型的比如我们有的时候是vector中的迭代器有时候是list当中的迭代器所以我们使用的需要表明这个迭代器的出处。3rbegin和rend 这个和begin和rend恰恰相反rbegin指向的最后一个字符rend指的是第一个字符的前一个位置这个我们也可以用遍历来实现这个就是把这个字符串倒过来遍历一遍这个和正向遍历有所不同的是这个是反向遍历这里是反向迭代器需要使用reverse_iterator来实现遍历string s1 hello world; string::reverse_iterator it s1.rbegin(); while (it ! s1.rend()) { cout *it ; it; } //输出结果d l r o w o l l e h4.string类对象的修改操作1push_back 在字符串后面尾插字符cstring s1(hello world); s1.push_back(x); cout s1 endl; //打印出hello worldx2append 在字符串后面追加一个字符串写法一在s1后面追加字符串s2string s1 hello; string s2 world; cout s1.append(s2) endl; //输出helloworld写法二从s2下标是5后面的字符追加到s1的后面string s1 hello world; string s2 abcdefg; cout s1.append(s2, 5) endl; //输出结果hello worldfg写法三在字符串后面追加一个字符串string s1(hello world); cout s1.append(jiayou) endl; //输出hello worldjiayou写法四创建一个对象取一字符串的前三个追加到这个创建的对象当中string s2; cout s2.append(jiayou,3) endl; //输出jia写法五在字符串后面添加五个字符string s1(hello world); cout s1.append(5, x); /输出hello worldxxxxx3operator 在字符串后面追加字符串str,必须有一个是string类类型例子1string s1 hello; string s2 world; s1 s2; cout s1 endl; //输出helloworld例子2string s1 hello; s1 nihao; cout s1 endl; //输出hellonihao例子3;string s1(hello); s1 x; cout s1 endl; //输出hellox4c_str 返回C语言风格的字符串为啥需要引用c_str呢我们可以直接上一个例子来对比一下就知道了。因为在C语言当中输出的结果只认const char*,而string是类类型需要转换为C语言的风格所以需要转换一下。string s1(hello world); //错误写法 printf(%s, s1); cout endl; //正确写法 printf(%s, s1.c_str()); //输出 ?訅? hello world5findnpos 从字符串pos的位置开始往后找字符c,返回该字符在字符串当中的下标位置例子1在已知的字符串当中找另一个字符串有两种写法string s1 hello world; string s2 world; cout s1.find(s2) endl;//输出6string s1 hello world; cout s1.find(llo) endl;//输出2 cout s1.find(lo) endl;//输出3例子2从s1的下标为3开始找wo出现的位置string s1helloworld; cout s1.find(world,3,2) endl;//输出5例子3找一个字符string s1 helloworld; cout s1.find(l) endl; //输出2npos:本质是当在C当中没找到所在位置的下标就会返回-1static const size_t npos-16)rfind 从字符串pos位置开始往前找字符c返回该字符在字符串所在的位置。例子1已知两个字符串在一个字符串找另一个字符串出现的位置。写法1string s1(hello world); string s2(llo); int key s1.rfind(s2); cout key endl; //输出2写法2string s1(hello world); int key s1.rfind(llo); cout key endl; //输出2例子二在已知字符串当中找某个字符string s1(hello world); int key s1.rfind(w); cout key endl; //输出6例子三在已知的字符串第pos个位置开始找某个字符串的n个子字符串下面这个就是我们要找的字符串是llo,从s1的下标为3的位置开始找也就是倒着的下标为3也就是o找目标字符串的前两个字符串ll。string s1(hello world); int key s1.rfind(llo,3,2); cout key endl; //输出27substr 从str的pos位置开始截取n个字符让后将其返回string s1(hello world); string s2 s1.substr(5); cout s2 endl; //输出空格world //从空格开始往后打印这里没有写打印几个字符编译器默认打印到最后 string s3 s1.substr(5, 3); cout s3 endl; //输出:空格wo5.string 类非成员函数(1) operator 输入运算符重载string name; cout Please, enter your name: ; cin name; cout Hello, name !\n;我们可以在这里输入我们的名字比如我输入张三它就会输出 hello 张三 2 operator 输出运算符重载string str Hello world!; cout str \n; //输出:hello world!(3) getline 获取一行字符串string str; getline(cin, str); cout str endl; //当我们输入hello world //它就是输出hello world为啥我们不直接用上面的operator运算符重载呢我们可以将上面的getline这段代码替换成cinstr;看看会发生什么string str; cin str; cout str endl; //当我们输入hello world //它会输出hello为啥后面的world为啥没有打印出来呢下面我把两种的结束条件都说明一下getline(cin,str)和cin的区别1.getlinecin,str :结束条件遇到换行符\n就停止读取。空格处理会读取并保留所有的空格只有换行符会被丢弃。缓冲区残留会把结束用的换行符从缓冲区读走并丢弃。适用场景读取整行文本带空格的句子。2.cin结束条件当读取字符串时只要遇到空格换行符制表符这些空白字符就会立刻停止读取。空格处理不会读取空格会把空格当成结束标记。缓冲区残留结束标记空格/缓冲区会留在缓冲区适用场景读取单个单词无空格的输入(4) relational operator 大小比较比较字符串之间的大小需要注意的是比较的两个对象之间必须有一个是类类型。因为这里的操作符是类类型的运算符重载。我们可以简单的举几个类型1.string 和string之间的比较string s1 hello world; string s2 hello xorld; cout (s1 s2) endl; //这里应该是返回的是真,返回1这里前6个字符都相等第7个比较的是ASCII值x在w的后面比较大2.string 和char之间的比较string s1 hello world; cout (s1 hello abcd) endl; //这里同样是前6个字符都相等s1的第7个字符比下面要比较的字符串要大所以应该是s1s2所以是假返回03.char和string之间的比较string s1 hello world; cout (hello abcds1) endl; //这里同样是前6个字符都相等s1的第7个字符比下面要比较的字符串要大所以应该是s1s2所以是真返回1上面就是我们在日常常用的接口以及算法题的常用接口我们只掌握如何适用就可以了当然了当我们在面试过程当中面试官总是让我们手撕这些接口的底层逻辑我们也是需要掌握的那么我们在下次博客当中也会手写一下string的模拟实现。3、string类的模拟实现1string.h头文件#define _CRT_SECURE_NO_WARNINGS 1 #pragma once #includeiostream #includestring #includeassert.h using namespace std; namespace myself { class string { public: //这里是迭代器版本相当于把char*指针换了个名字 typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str _size; } /*string() :_str(new char[1]{\0}) ,_size(0) ,_capacity(0) {}*/ // 短小频繁调用的函数可以直接定义到类里面默认是inline string(const char* str ) { _size strlen(str); // _capacity不包含\0 _capacity _size; _str new char[_capacity 1]; strcpy(_str, str); } // 深拷贝问题 // // s2(s1) string(const string s) { _str new char[s._capacity 1]; strcpy(_str, s._str); _size s._size; _capacity s._capacity; } // s2 s1 // s1 s1 string operator(const string s) { if (this ! s) { delete[] _str; _str new char[s._capacity 1]; strcpy(_str, s._str); _size s._size; _capacity s._capacity; } return *this; } ~string() { delete[] _str; _str nullptr; _size _capacity 0; } const char* c_str() const { return _str; } void clear() { _str[0] \0; _size 0; } 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]; } //保留空间大小 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); //从pos位置开始删除字符 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; size_t _size; size_t _capacity; static const size_t npos; }; 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); ostream operator(ostream out, const string s); istream operator(istream in, string s); }2string.cpp文件#includestring.h namespace myself { const size_t string::npos -1; void string::reserve(size_t n) { if (n _capacity) { //cout reserve: n endl; 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 : _capacity * 2); } _str[_size] ch; _size; _str[_size] \0; } string string::operator(char ch) { push_back(ch); return *this; } void string::append(const char* str) { size_t len strlen(str); if (_size len _capacity) { // 大于2倍需要多少开多少小于2倍按2倍扩 reserve(_size len 2 * _capacity ? _size len : 2 * _capacity); } strcpy(_str _size, str); _size len; } 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 : _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* s) { assert(pos _size); size_t len strlen(s); if (_size len _capacity) { // 大于2倍需要多少开多少小于2倍按2倍扩 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] s[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 pos; 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; } else { return ptr - _str; } } string string::substr(size_t pos, size_t len) { assert(pos _size); // len大于剩余字符长度更新一下len 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 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; //in ch; ch in.get(); while (ch ! ch ! \n) { buff[i] ch; if (i N - 1) { buff[i] \0; s buff; i 0; } //in ch; ch in.get(); } if (i 0) { buff[i] \0; s buff; } return in; } }3test.cpp文件namespace myself { void test_string1() { string s1; string s2(hello world); cout s1.c_str() endl; cout s2.c_str() endl; for (size_t i 0; i s2.size(); i) { s2[i] 2; } cout s2.c_str() endl; for (auto e : s2) { cout e ; } cout endl; string::iterator it s2.begin(); while (it ! s2.end()) { //*it 2; cout *it ; it; } cout endl; } void test_string2() { string s1(hello world); s1 x; s1 #; cout s1.c_str() endl; s1 hello bit; cout s1.c_str() endl; s1.insert(5, $); cout s1.c_str() endl; s1.insert(0, $); cout s1.c_str() endl; string s2(hello world); cout s2.c_str() endl; s2.insert(5, $$$); cout s2.c_str() endl; s2.insert(0, $$$); cout s2.c_str() endl; } void test_string3() { string s1(hello world); s1.erase(6, 100); cout s1.c_str() endl; string s2(hello world); s2.erase(6); cout s2.c_str() endl; string s3(hello world); s3.erase(6, 3); cout s3.c_str() endl; } void test_string4() { string s(test.cpp.zip); size_t pos s.find(.); string suffix s.substr(pos); cout suffix.c_str() endl; string copy(s); cout copy.c_str() endl; s suffix; cout suffix.c_str() endl; cout s.c_str() endl; s s; cout s.c_str() endl; } void test_string5() { string s1(hello world); string s2(hello world); cout (s1 s2) endl; cout (s1 s2) endl; cout (hello world s2) endl; cout (s1 hello world) endl; //cout (hello world hello world) endl; cout s1 s2 endl; string s0; cin s0; cout s0 endl; } } int main() { myself::test_string5(); char str[] 牛马; cout strlen(str) endl; str[1]; cout str endl; str[3]--; cout str endl; str[1]; cout str endl; str[3]--; cout str endl; return 0; }
从零学STL:string类常用接口一篇吃透
目录1、STL的介绍1容器存数据就是各种装东西的盒子2迭代器遍历3算法功能函数2、string类(1)auto和范围for(2)string类的相关接口最常见的接口1.string常见的构造函数2.string类对象的容器操作函数3.string类对象的访问及遍历操作4.string类对象的修改操作5.string 类非成员函数1、STL的介绍STLC标准模板库。就是官方提前写好的一大堆现成容器算法你直接拿来用不用自己手写STL分为三个部分1容器存数据就是各种装东西的盒子string 字符串、vector 动态数组最常用、list 链表、stack 栈、queue 队列、map键值对。2迭代器遍历就是容器的通用指针统一遍历所有的容器3算法功能函数排序、查找、去重、交换等其中sort排序find查找都属于STL一句话:以前需要我们自己造轮子去实现数组的增删查改现在我们学习了STL之后直接用现成的一行代码就搞定。C语言自己造轮子C:现成的轮子全套配齐。2、string类为啥要学习string类呢C语言中字符串是以\0结尾的一些字符的集合为了操作方便C标准库中提供了一些str系列的库函数但是这些库函数与字符串是分离开的不太符合OOP的思想而且底层空间需要用户自己管理稍不留神可能还会越界访问。(1)auto和范围for一、前言在现代C11及以后语法中 auto 类型推导与范围for循环是日常开发、STL遍历、代码精简最常用两大语法。不管是遍历 string 、 vector 还是其他容器熟练使用这两个语法能极大简化代码也是校招笔试、日常写业务必备知识点。二、auto 关键字详解1. 什么是auto?auto 是自动类型推导关键字编译器根据右边赋值内容自动推导出变量真实类型无需手动写冗长类型。2.基础用法#define _CRT_SECURE_NO_WARNINGS 1 #includestdio.h #includeiostream #includestring using namespace std; int main() { //auto的使用自动推到类型 auto a1 10;//自动识别a1是int类型 auto a2 10.0;//自动识别a2是double类型 return 0; }2搭配STL中的迭代器STL迭代器中的类型极长直接用auto简化//传统的迭代器 string::iterator its.begin(); //auto推导 auto its.begin();auto使用注意定义必须初始化不给值无法推到类型。不能作为函数形参。三、范围for循环1、作用自动遍历整个容器无需下标无需迭代器极简遍历stringvector等容器所有STL。2、标准语法格式for(auto 类型 容器) { //逻辑代码 }比如我们想要遍历一个字符串我们可以看到这里我们没有写任何下标就用了一个auto和范围for循环遍历就能很好的将这个字符串一个个的打印出来。四、范围for的底层就是迭代器遍历的语法糖编译器底层自动转换成auto its.begin(); while(it !s.end()) { cout(*it) ; it; }(2)string类的相关接口最常见的接口1.string常见的构造函数string 构造空的string类现象即空字符串string s1();//无参构造stringconst char *s) 用c_str来构造string类string s2(xx);//带参数构造string(const string s)拷贝构造函数string s3(s2);//拷贝构造扩充1.对于拷贝构造函数来说还有一些接口是从第几个字符开始拷贝格式string(const string str,size_t pos,size_t lennpos);我们直接上例子这两种写法一样当我们不写后面的拷贝字符串的长度时它会自动拷贝后面所有的字符。2.拷贝字符串的前几个字符string(const char* str,size_t n);3.把字符串的前几个字符初始化string(size_t n,char c);2.string类对象的容器操作函数(1)size 和 length 返回字符串的有效长度不包括\0string s1hello world; couts1.size()endl; //打印出的结果是11 couts1.lengthendl; //打印出的结果也是112capacity 返回空间的总大小string s1 hello world; cout s1.capacity() endl; //输出的结果是15为啥是15呢?capacity()里面不包括\0它只能表示最多能存放多少个有效字符实际上应该是16多出来的一个位置存放\0.3empty 检测字符串释放为空串是返回true,否则false.string s1(); cout s1.empty() endl; //字符串为空true,输出1 string s2(hello world); cout s2.empty() endl; //字符串不为空false,输出04clear 清空有效字符string s1(hello world); cout s1.size() endl;//11 cout s1.capacity() endl;//15 cout s1.empty() endl;//0 s1.clear(); cout endl; cout s1.size() endl;//0 cout s1.capacity() endl;//15 cout s1.empty() endl;//1我们可以清晰的看到clear清除的是字符串的有效长度它的字符串的容量并没有改变。5)reserve 为字符串预留空间string s1(hello world); s1.reserve(50); cout s1.capacity() endl; //输出的结果是63为啥是63呢这是标准库为了性能做的内存对齐/按块分配优化我们所写的reserve(50)会触发分配标准库会直接分配一大块、对齐更友好的内存块比如64字节其中一个字节用来存放\0所以会显示63。6resize 将有效字符的个数改成n个多出来的空间用字符c填充string s1 hello world; cout s1.size() endl;//输出11 s1.resize(15); cout endl; cout s1 endl;//hello world,当我们没有填充字符时编译器默认补充\0ASCII为0 cout s1.size() endl;//输出15我们也可以添加几个字符比如我们想把原来的11个有效字符改成15个那么多出来的4个字符就要用字符填充下面的我们用字符x进行填充。string s1 hello world; cout s1.size() endl;//11 s1.resize(15,x); cout endl; cout s1 endl;//hello worldxxxx cout s1.size() endl;//153.string类对象的访问及遍历操作(1)operator[] 返回pos位置的字符const string 类对象的调用这个简单来说就是和数组的访问是一样的我们也同样做一个例子string s1 hello world; cout s1[0] endl;//输出h cout s1[1] endl;//输出e cout s1[2] endl;//输出l不应该是operator[]吗?operator[]是个运算符的名字而s1[]是它的实际调用s1[0]是s1.operator[](0)的语法糖所以它们是完全等价的。(2)beginend begin获取的是字符串的第一个字符end获取的是最后一个字符的下一个位置的迭代器也就是\0的位置。s我们直接举一个遍历字符串的例子来直观看一下这个用法string s1 hello world; string ::iterator it s1.begin(); while (it ! s1.end()) { cout *it ; it; } //输出h e l l o w o r l d我们在写这个迭代器的通用的指针类型的时候我们会好奇为啥要写这个类作用域的这是因为这个迭代器指针适用于所有的容器类型所以我们使用时需要标明这个指针是哪个类型的比如我们有的时候是vector中的迭代器有时候是list当中的迭代器所以我们使用的需要表明这个迭代器的出处。3rbegin和rend 这个和begin和rend恰恰相反rbegin指向的最后一个字符rend指的是第一个字符的前一个位置这个我们也可以用遍历来实现这个就是把这个字符串倒过来遍历一遍这个和正向遍历有所不同的是这个是反向遍历这里是反向迭代器需要使用reverse_iterator来实现遍历string s1 hello world; string::reverse_iterator it s1.rbegin(); while (it ! s1.rend()) { cout *it ; it; } //输出结果d l r o w o l l e h4.string类对象的修改操作1push_back 在字符串后面尾插字符cstring s1(hello world); s1.push_back(x); cout s1 endl; //打印出hello worldx2append 在字符串后面追加一个字符串写法一在s1后面追加字符串s2string s1 hello; string s2 world; cout s1.append(s2) endl; //输出helloworld写法二从s2下标是5后面的字符追加到s1的后面string s1 hello world; string s2 abcdefg; cout s1.append(s2, 5) endl; //输出结果hello worldfg写法三在字符串后面追加一个字符串string s1(hello world); cout s1.append(jiayou) endl; //输出hello worldjiayou写法四创建一个对象取一字符串的前三个追加到这个创建的对象当中string s2; cout s2.append(jiayou,3) endl; //输出jia写法五在字符串后面添加五个字符string s1(hello world); cout s1.append(5, x); /输出hello worldxxxxx3operator 在字符串后面追加字符串str,必须有一个是string类类型例子1string s1 hello; string s2 world; s1 s2; cout s1 endl; //输出helloworld例子2string s1 hello; s1 nihao; cout s1 endl; //输出hellonihao例子3;string s1(hello); s1 x; cout s1 endl; //输出hellox4c_str 返回C语言风格的字符串为啥需要引用c_str呢我们可以直接上一个例子来对比一下就知道了。因为在C语言当中输出的结果只认const char*,而string是类类型需要转换为C语言的风格所以需要转换一下。string s1(hello world); //错误写法 printf(%s, s1); cout endl; //正确写法 printf(%s, s1.c_str()); //输出 ?訅? hello world5findnpos 从字符串pos的位置开始往后找字符c,返回该字符在字符串当中的下标位置例子1在已知的字符串当中找另一个字符串有两种写法string s1 hello world; string s2 world; cout s1.find(s2) endl;//输出6string s1 hello world; cout s1.find(llo) endl;//输出2 cout s1.find(lo) endl;//输出3例子2从s1的下标为3开始找wo出现的位置string s1helloworld; cout s1.find(world,3,2) endl;//输出5例子3找一个字符string s1 helloworld; cout s1.find(l) endl; //输出2npos:本质是当在C当中没找到所在位置的下标就会返回-1static const size_t npos-16)rfind 从字符串pos位置开始往前找字符c返回该字符在字符串所在的位置。例子1已知两个字符串在一个字符串找另一个字符串出现的位置。写法1string s1(hello world); string s2(llo); int key s1.rfind(s2); cout key endl; //输出2写法2string s1(hello world); int key s1.rfind(llo); cout key endl; //输出2例子二在已知字符串当中找某个字符string s1(hello world); int key s1.rfind(w); cout key endl; //输出6例子三在已知的字符串第pos个位置开始找某个字符串的n个子字符串下面这个就是我们要找的字符串是llo,从s1的下标为3的位置开始找也就是倒着的下标为3也就是o找目标字符串的前两个字符串ll。string s1(hello world); int key s1.rfind(llo,3,2); cout key endl; //输出27substr 从str的pos位置开始截取n个字符让后将其返回string s1(hello world); string s2 s1.substr(5); cout s2 endl; //输出空格world //从空格开始往后打印这里没有写打印几个字符编译器默认打印到最后 string s3 s1.substr(5, 3); cout s3 endl; //输出:空格wo5.string 类非成员函数(1) operator 输入运算符重载string name; cout Please, enter your name: ; cin name; cout Hello, name !\n;我们可以在这里输入我们的名字比如我输入张三它就会输出 hello 张三 2 operator 输出运算符重载string str Hello world!; cout str \n; //输出:hello world!(3) getline 获取一行字符串string str; getline(cin, str); cout str endl; //当我们输入hello world //它就是输出hello world为啥我们不直接用上面的operator运算符重载呢我们可以将上面的getline这段代码替换成cinstr;看看会发生什么string str; cin str; cout str endl; //当我们输入hello world //它会输出hello为啥后面的world为啥没有打印出来呢下面我把两种的结束条件都说明一下getline(cin,str)和cin的区别1.getlinecin,str :结束条件遇到换行符\n就停止读取。空格处理会读取并保留所有的空格只有换行符会被丢弃。缓冲区残留会把结束用的换行符从缓冲区读走并丢弃。适用场景读取整行文本带空格的句子。2.cin结束条件当读取字符串时只要遇到空格换行符制表符这些空白字符就会立刻停止读取。空格处理不会读取空格会把空格当成结束标记。缓冲区残留结束标记空格/缓冲区会留在缓冲区适用场景读取单个单词无空格的输入(4) relational operator 大小比较比较字符串之间的大小需要注意的是比较的两个对象之间必须有一个是类类型。因为这里的操作符是类类型的运算符重载。我们可以简单的举几个类型1.string 和string之间的比较string s1 hello world; string s2 hello xorld; cout (s1 s2) endl; //这里应该是返回的是真,返回1这里前6个字符都相等第7个比较的是ASCII值x在w的后面比较大2.string 和char之间的比较string s1 hello world; cout (s1 hello abcd) endl; //这里同样是前6个字符都相等s1的第7个字符比下面要比较的字符串要大所以应该是s1s2所以是假返回03.char和string之间的比较string s1 hello world; cout (hello abcds1) endl; //这里同样是前6个字符都相等s1的第7个字符比下面要比较的字符串要大所以应该是s1s2所以是真返回1上面就是我们在日常常用的接口以及算法题的常用接口我们只掌握如何适用就可以了当然了当我们在面试过程当中面试官总是让我们手撕这些接口的底层逻辑我们也是需要掌握的那么我们在下次博客当中也会手写一下string的模拟实现。3、string类的模拟实现1string.h头文件#define _CRT_SECURE_NO_WARNINGS 1 #pragma once #includeiostream #includestring #includeassert.h using namespace std; namespace myself { class string { public: //这里是迭代器版本相当于把char*指针换了个名字 typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str _size; } /*string() :_str(new char[1]{\0}) ,_size(0) ,_capacity(0) {}*/ // 短小频繁调用的函数可以直接定义到类里面默认是inline string(const char* str ) { _size strlen(str); // _capacity不包含\0 _capacity _size; _str new char[_capacity 1]; strcpy(_str, str); } // 深拷贝问题 // // s2(s1) string(const string s) { _str new char[s._capacity 1]; strcpy(_str, s._str); _size s._size; _capacity s._capacity; } // s2 s1 // s1 s1 string operator(const string s) { if (this ! s) { delete[] _str; _str new char[s._capacity 1]; strcpy(_str, s._str); _size s._size; _capacity s._capacity; } return *this; } ~string() { delete[] _str; _str nullptr; _size _capacity 0; } const char* c_str() const { return _str; } void clear() { _str[0] \0; _size 0; } 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]; } //保留空间大小 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); //从pos位置开始删除字符 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; size_t _size; size_t _capacity; static const size_t npos; }; 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); ostream operator(ostream out, const string s); istream operator(istream in, string s); }2string.cpp文件#includestring.h namespace myself { const size_t string::npos -1; void string::reserve(size_t n) { if (n _capacity) { //cout reserve: n endl; 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 : _capacity * 2); } _str[_size] ch; _size; _str[_size] \0; } string string::operator(char ch) { push_back(ch); return *this; } void string::append(const char* str) { size_t len strlen(str); if (_size len _capacity) { // 大于2倍需要多少开多少小于2倍按2倍扩 reserve(_size len 2 * _capacity ? _size len : 2 * _capacity); } strcpy(_str _size, str); _size len; } 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 : _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* s) { assert(pos _size); size_t len strlen(s); if (_size len _capacity) { // 大于2倍需要多少开多少小于2倍按2倍扩 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] s[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 pos; 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; } else { return ptr - _str; } } string string::substr(size_t pos, size_t len) { assert(pos _size); // len大于剩余字符长度更新一下len 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 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; //in ch; ch in.get(); while (ch ! ch ! \n) { buff[i] ch; if (i N - 1) { buff[i] \0; s buff; i 0; } //in ch; ch in.get(); } if (i 0) { buff[i] \0; s buff; } return in; } }3test.cpp文件namespace myself { void test_string1() { string s1; string s2(hello world); cout s1.c_str() endl; cout s2.c_str() endl; for (size_t i 0; i s2.size(); i) { s2[i] 2; } cout s2.c_str() endl; for (auto e : s2) { cout e ; } cout endl; string::iterator it s2.begin(); while (it ! s2.end()) { //*it 2; cout *it ; it; } cout endl; } void test_string2() { string s1(hello world); s1 x; s1 #; cout s1.c_str() endl; s1 hello bit; cout s1.c_str() endl; s1.insert(5, $); cout s1.c_str() endl; s1.insert(0, $); cout s1.c_str() endl; string s2(hello world); cout s2.c_str() endl; s2.insert(5, $$$); cout s2.c_str() endl; s2.insert(0, $$$); cout s2.c_str() endl; } void test_string3() { string s1(hello world); s1.erase(6, 100); cout s1.c_str() endl; string s2(hello world); s2.erase(6); cout s2.c_str() endl; string s3(hello world); s3.erase(6, 3); cout s3.c_str() endl; } void test_string4() { string s(test.cpp.zip); size_t pos s.find(.); string suffix s.substr(pos); cout suffix.c_str() endl; string copy(s); cout copy.c_str() endl; s suffix; cout suffix.c_str() endl; cout s.c_str() endl; s s; cout s.c_str() endl; } void test_string5() { string s1(hello world); string s2(hello world); cout (s1 s2) endl; cout (s1 s2) endl; cout (hello world s2) endl; cout (s1 hello world) endl; //cout (hello world hello world) endl; cout s1 s2 endl; string s0; cin s0; cout s0 endl; } } int main() { myself::test_string5(); char str[] 牛马; cout strlen(str) endl; str[1]; cout str endl; str[3]--; cout str endl; str[1]; cout str endl; str[3]--; cout str endl; return 0; }