C++ STL ——string的使用讲解及其底层实现
文章目录
- 前言:
- 首先我想问string类是什么?我们为什么要学习它?
- 1.string类的接口使用
- 1.1string的构造函数
- 1.2遍历string
- 1.2.1运算符重载下标+[] 来遍历string
- 1.2.2使用迭代器遍历
- 1.2.3范围for遍历
- 1.2.3.1首先我们要先介绍一下auto关键字(先做了解,这里重点在范围for)
- 1.2.3.2范围for
- 1.3capacity
- 1.3.1size和length
- 1.3.2capacity
- 1.3.3 clear和empty
- 1.3.4 缩容shrink_to_fit,扩容reserve
- 1.3.4.1 shrink_to_fit
- 1.3.4.2 reserve
- 1.3.5 resize
- 1.4 Modifier(修饰符)
- 1.4.1append
- 1.4.2赋值重载operator+=
- 1.4.3 assign
- 1.4.4 insert和erase删除
- 1.4.4.1insert插入
- 1.4.4.2erase删除
- 补充知识:nops
- 1.4.5 replace替换
- 1.5 String operations
- 1.5.1 c_str
- 1.5.2 copy
- 1.5.3 substr
- 2.利用接口
- 3.底层实现
- 3.1 头文件的描述
- 3.2 文件的讲解
- 3.2.1构造`string(); string(const char* str);`
- 3.2.2 析构`string::~string()`
- 3.2.3 交换`void string::swap(string& s)`
- 3.2.4 拷贝构造 `string::string(const string& s)`
- 3.2.5 取字符串`const char* string::c_str()const`字符长度`size_t string::size()const`
- 3.2.6 下标 `char& string::operator[](size_t i)`
- 3.2.7 迭代器
- 3.2.8 扩容`void string::reserve(size_t n)`
- 3.2.9尾插字符`void string::push_back(char ch)`尾插字符串`void string::append(const char* str)`
- 3.2.10 `operator+=`
- 3.2.11 输入输出流`<< >>` 整行输入`istream& getline(istream& in, string& s, char delim)`
- 3.2.12 指定位置插入数据`string& string::insert(size_t pos, const char* str)`
- 3.2.13指定位置删除`string& string::erase(size_t pos, size_t len)`
- 3.2.14尾删`void string::pop_back()`
- 3.2.15查找size_t
- 3.2.16 从pos返回len个字符`string string::substr(size_t pos, size_t len) const`
- 3.2.17 赋值重载
- 结语
前言:
- string类的学习不仅可以让我们更加深刻地掌握C++语法,还可以让我们在以后的算法题中游刃有余。
- 本文小编将从文档入手这个文档很经典建议大家学习时配合使用,string类接口的的基础使用入手,先带大家会在做题和日常训练中方便高效地使用string的接口,然后带大家理解string的底层逻辑并自己实现一个简单的string。
首先我想问string类是什么?我们为什么要学习它?
我们可以先回顾一下C语言中的字符串
- C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP(Object-Oriented Programming,面向对象编程)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
string类是啥?
- 定义:在C++中,std::string是 标准模板库(STL) 中用于处理字符串的一个 非常重要的类。 它封装了字符串的存储、操作和管理功能,使得字符串的使用更加方便、安全和高效。(简单概述)
为啥在STL中要重点学它?
- 学习std::string类在C++编程中是非常重要的,它不仅能够提升你的编程效率,还能帮助你写出更安全、更易维护的代码。
总之,std::string是C++中非常重要的一个类,学习它不仅可以提升你的编程能力,还可以帮助你写出更安全、更高效、更易维护的代码。
1.string类的接口使用
在使用之前我先介绍一下string的基本结构
class string {public:
//.......成员函数
//.......
private:char* str;//字符指针,当初字符数组存储字符数据size_t _size;//string的真实大小,不包含’\0‘size_t _capacity;//string的空间容量,不包含’\0‘
}
1.1string的构造函数
构造函数
对于类来说构造函数必不可少,库里的string类也重载了很多个版本。大家后来在使用的过程中会自动记住那些比较常用的。
//第一个空构造
string s1;//构造空的string,打印也什么都没有//用常量字符串构造
//string(const char* s)
string s2("hello world");//拷贝构造
//string(const string& str);
//用一个已有对象去构造另外一个对象
string s3(s2);//用已有的s2去构造s3//从一个对象str的pos位置取len长度的数据来构造
//string(const string& str,size_t pos,size_t len=npos);
//半缺省,且npos为整数最大值
string s4(s2,1,5);//从s2中下标为1的地方开始拷贝5个字节
string s5(s2, 1, 60);//len是拷贝到'\0'停止的
string s6(s2, 1);//半缺省,不传第三个参数//从常量字符串s中取n个字符来构造
//string(const char*s,size_t n);
const char*s={"hello world"};
string s7(s,5);//取n个字符c来构造
//string(size_t n,char c);
string s8(100,'#');cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
cout << s8 << endl;
看看输出结果:
1.2遍历string
这里介绍三个string遍历方法:
1.2.1运算符重载下标+[] 来遍历string
operator[]
string s1("hello world");
//1.遍历+修改->下标+[]
const string s2("hello world");//常量string
s1[0]++;// s1.operator[](0)++ ————->运算符重载
//s2[0]++;——>报错常量不能改变
cout<<s1<<endl;//s1的每个字符都++
for (size_t i = 0; i < s1.size(); i++)//size()和length()都行;
//最开始只有length()后来增加size(),length:长度 不适用于树等容器,因为没有树的长度;这一说法
//所以后来统一size()->通用性;
{s1[i]++;
}
cout << s1 << endl
1.2.2使用迭代器遍历
什么是迭代器?
在C++中,迭代器(Iterator)是一种非常重要的概念,它是一种抽象的数据访问方式,用于在容器(如数组、链表、向量等)中遍历和操作元素。迭代器的设计灵感来源于指针但,它提供了一种统一的方式来访问容器中的元素,而不需要直接操作容器的内部实现细节。
注:迭代器可能由指针实现但不一定是指针。
string的迭代器就有不少以begin()和end()为例
,begin()指向容器第一个字符,end()指向最后一个字符的下一个字符。
string::iterator() it1=s1.begin();//返回开始位置迭代器
while(it1!=s1.end())
{(*it1)--;it1++;
}
cout<<s1<<endl;
这时候可能就会有同学要问:我都有[]+下标了为啥还要用迭代器呢?
迭代器是所有容器的主流遍历方式。
虽然在string中推荐用[],但是迭代器是主流的,是通用的。
后面要学的树,链表都没有[]给我们用。
假如写个逆置算法vector和string就很相似—算法->reverse,这也是我们常说的解耦-----相同的代码形式不太关心对象类型
reverse(s1.begin(),s1.end());
cout << s1 << endl;vector<int> v1{1,2,3,4,5};
reverse(v1.begin(), v1.end());
for (size_t i = 0; i < v1.size(); i++)
{cout <<"逆置"<< v1[i] << ' ';
}
cout << endl;
string s1("hello world");
//const iterator——>迭代器本身不能修改,实际问题中无意义
//const_iterator——>迭代器指向的数据不能修改string::const_iterator it1=s1.begin();//也可用cbegin()更明显
//还有反向迭代器——>反向迭代
//rebegin指向最后一个位置,rend()指向第一个数据的前一个数据
string::reverse_iterator it2 = s1.rbegin();
while (it2 != s1.rend())
{cout << *it2 << ' ';it2++;
}
cout << endl;string::const_reverse_iterator it3 =s1.rbegin();
//指向数据不能修改
1.2.3范围for遍历
范围for ,C++11加入 , 由于用法方便简单被c叫做语法糖 , 所有容器都支持。
范围for就是迭代器的替换(底层是迭代器)
1.2.3.1首先我们要先介绍一下auto关键字(先做了解,这里重点在范围for)
- 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
- 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
- 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
- auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
- auto不能直接用来声明数组
来看下面例子:
#include<iostream>
using namespace std;
int func1()
{return 10;
}
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{return 3;
}
int main()
{int a = 10;auto b = a;auto c = 'a';auto d = func1();// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项auto e;cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x;auto* z = &x;auto& m = x;cout << typeid(x).name() << endl;cout << typeid(y).name() << endl;cout << typeid(z).name() << endl;auto aa = 1, bb = 2;// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型auto cc = 3, dd = 4.0;// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型auto array[] = { 4, 5, 6 };return 0;
}
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"} };// auto的用武之地//std::map<std::string, std::string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end())
{cout << it->first << ":" << it->second << endl;++it;
}return 0;
}
1.2.3.2范围for
- 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。
for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
- 范围for可以作用到数组和容器对象上进行遍历
- 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
//不仅仅支持容器还支持数组auto x = 10;//自动推导类型auto y = 10.1;cout << x <<' ' << y << endl;//自动取容器数据赋值给ch//自动判断结束//自动迭代//本质底层会替换为迭代器,auto ch=*迭代器//for (auto ch : s1)这样不会修改值for(auto& ch:s1){ch--;}for (auto ch : s1){ cout << ch << ' ';}cout << endl;//vector也可以for (auto ch : v1){cout << ch << ' ';}cout << endl;
}
1.3capacity
capacity(进入文档)
1.3.1size和length
- 首先在size和length功能都是计算string的真实长度,但是推荐用size(length:长度 , 不适用于树等容器)。
size和length(文档)
补充说明:max_size()(不常用)
//string::max_size()----->这个接口意义不大,因为它不是返回真实的大小
string s1("1234567");
cout << s1.size() << endl;
cout << s1.max_size() << endl;//返回:2147483647——>2G,但是真实开不了2G
1.3.2capacity
- capacity:告诉我们容量多大
- 一开始编译器会给定一些容量,当_size赶上_capacity时会有扩容操作,这样调用s1.capacity()就可以查看s1的空间容量。
我们来检测一下扩容机制。
string s2;
size_t old=s2.capacity();
for(size_t i=0;i<100;i++)
{s2.push_back('x');if(s2.capacity()!=old){cout<<'capacity:'<<old<<endl;old=s2.capacity();}//结论除了第一次都是1.5被扩容,不同平台不一样cout << "capacity:" << old << endl;
}
除了第一次都是1.5被扩容,不同平台不一样(这里用的是VS编译器,在g++中一般是两倍扩容)。
1.3.3 clear和empty
clear(文档)
注意上方英文,是将length也就是size置为空,不动capacity。(由于string类出的时间较早,所以这英文描述的是length,像顺序表中用到就是size了)
//clear->动size()不动capacity()
cout << s1 << endl;
s1.clear();
cout << s1 << endl;
empty是判空的意思,主要判断size是否为空。其使用较为简单就不过多赘述,就那文档上的例子给大家看。
1.3.4 缩容shrink_to_fit,扩容reserve
1.3.4.1 shrink_to_fit
shrink_to_fit(文档):将其容量减少以适应其大小。
由于使用clear还有空间开辟不均等原因,string出现了缩容接口(解决空间浪费问题)。
但是shrink_to_fit操作的代价其实很大:
- 不能释放空间的一部分
- 缩容是异地缩容:需要重新开空间,然后把数据深拷贝到新空间中,再释放旧空间。(是以时间换空间,不是很建议用)
cout <<"size:"<< s2.size() << endl;
cout << "capacity:"<< s2.capacity() << endl;
s2.clear();
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;//此时空间大小没动
s2.shrink_to_fit();//一般不调用
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;//此时空间大小改变
注意:VS编译器下的缩容只是缩到了一个 “合适” 的大小,并不是刚刚好的,就以上图为例,我们的size都为空了但是capacity并不会缩为空。
1.3.4.2 reserve
reserve
格式为:void reserve (size_t n = 0)
;
当我们传的值n>capacity时,毫无疑问编译器会给string扩容,但是考虑到空间对齐capacity可能大于扩大的空间。
但是当n<capacity时,编译器会缩容吗?
答案是:不会。(看文档解释:下方)
在所有其他情况下,这被视为一个非约束性的请求,要求缩小字符串的容量。容器的实现可以自由地进行优化,可能会使字符串的容量保持大于n。也就是说,当n小于当前容量时,这个请求只是希望减少容量,但容器的实现可能会根据自己的优化策略,不完全按照请求减少到n,而是保留更大的容量。例如,容器可能考虑到频繁调整容量会导致性能问题,所以即使请求减少容量,也可能保留一定的额外容量以应对后续可能的字符串长度变化。
//扩容接口 reserve
string s1("1234567");
cout << s1.max_size() << endl;
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
s1.reserve(100);//如果n>capacity函数就会增长到大于新capacity但是考虑到空间对齐capacity可能大于扩大的空间
//可以先开部分空间=来避免扩容
//很实用可以提高效率
cout << "size:" << s1.size() << endl;
cout << "capacity:" << s1.capacity() << endl;
s1.reserve(10);//会不会缩容呢?->看文档
cout << "size:" << s1.size() << endl;//不会,不具有约束
cout << "capacity:" << s1.capacity() << endl;//只能保证扩容
1.3.5 resize
resize
- resize()用的不多,对size改变,不传第二个参数就传‘/0’(空字符)
string s2("123456");
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;// 插入数据让size到n个,capacity扩容
// n>capacity>size
s2.resize(20, 'x');//扩容加插入
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
cout << s2 << endl;
//n<size
s2.resize(5, 'x');//删除,size减小
cout << "size:" << s2.size() << endl;
cout << "capacity:" << s2.capacity() << endl;
cout << s2 << endl;
1.4 Modifier(修饰符)
Modifier:在这里它指的是用于修改字符串内容的成员函数
1.4.1append
append:末尾追加字符串或字符。
- 从s2中从位置0开始截取5个字符,然后追加到s1的末尾
//string& append (const string& str, size_t subpos, size_t sublen);s1.append(s2, 0, 5);
- 下面是两个append分别是直接在结尾追加字符串和连续追加十个n(追加时空间不够会自动扩容)
1.4.2赋值重载operator+=
operator+=此处的赋值重载用起来非常简单方便
可以直接将两个string中的_str连接起来,也可以在对象后面加一个常量字符串或字符。
//operator+=
s1 = "haha";
s1+="hallow world";
s1 += 'a';
string m("bb");
s1 += m;
cout << s1 << endl<<endl;
1.4.3 assign
assign:它的作用是将字符串对象的内容替换为新的内容。它有多种重载形式,可以接受不同的参数类型,比如另一个字符串、一个C风格字符串、一个字符数组、一个字符等,从而实现不同的赋值操作。(不常用)
//assign一种赋值
string s2;
s2.assign(1,'x');
cout << s1 << endl;
1.4.4 insert和erase删除
1.4.4.1insert插入
insert:插入操作,可以头插,但是谨慎使用——>底层数据挪动效率低下时间复杂度:O(N)
直接看个简单的例子:
三种重要的插入
string s3("hello word");
s3.insert(0, "yyyy");//在下标为0的位置插入“yyyy”
cout << s3<< endl;
s3.insert(0, 1, 'z');//在下标为0的位置插入1个z
cout << "s3: " << s3 << endl;
//迭代器版本
s3.insert(s3.begin(), 'k');
cout << "s3: " << s3 << endl;
1.4.4.2erase删除
erase:是 C++ 标准库中 std::string 类的一个成员函数,用于删除字符串中的某些字符。
最常用的是那个带缺省值的结构
补充知识:nops
size_t
是一个无符号整形,nops
的值为-1
,那么-1
从也就是其最大值。
由于计算机存储数据时采用原码,反码,补码的形式。
原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
我们size_t npos=-1
转换为补码就是:11111111 11111111 11111111 11111111
,计算机就会识别成最大值。
//erase删除
//也谨慎使用——>挪动效率低下
string s4("hello word");
s4.erase(5.1);//从第五个位置删除一个数据‘ ’
cout << "s4: " << s4 << endl;
s4.erase(5);
//有缺省值string& erase (size_t pos = 0, size_t len = npos);
//从第五个位置开始删除npos个数据
cout << "s4: " << s4 << endl<<endl;
1.4.5 replace替换
replace:用新的内容替换掉字符串从位置 pos 开始的、长度为 len 的部分(或者替换掉字符串在 [i1,i2) 范围内的部分)。
总体来说不常用,介绍一下主要用法。
string s5("hello word");
s5.replace(5, 2, "##");
//最后参数是字符串,将s5从的下标为5位置开始的两个数据换成"##"
//替换多个数据时挪动数据效率低
cout << s5 << endl;
我们再配合find写一个例子将所有空格换掉的例子
//将所有空格换掉
string s6("hello world hellow bit");
size_t pos = s6.find(' ');//find默认从pos位置开始查找(还有第二个参数,缺省值为0 :从下标0开始)
while (pos!=string::npos)
{s6.replace(pos, 1, "%%");pos = s6.find(' ',pos+1);//从pos+1的位置开始查找
}
cout << s6 << endl;//优化一下
s6="hello world hellow bit";
string s7;
s7.reserve(s6.size());
for (auto ch : s6)
{if (ch == ' ')s7 += "##";elses7 += ch;
}
cout << s7 << endl;
1.5 String operations
String operations:表示接下来列出的是一些与字符串操作相关的成员函数。
1.5.1 c_str
c_str:c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址。由于在C语言中没有string类,所以要用c_str将其转换为C中字符串。
data和c_str高度类似不讲解了
1.5.2 copy
copy接口->拷贝
size_t copy (char* s, size_t len, size_t pos = 0) const;
s:目标字符数组,必须有足够的空间。
len:指定要复制的字符数量,实际复制数量取决于字符串长度和起始位置。
pos:指定从字符串中开始复制的位置,从0开始计数,超出范围会抛出异常。
1.5.3 substr
substr
string substr (size_t pos = 0, size_t len = npos) const;
第一个参数可缺省
它会返回一个新构造的字符串对象,其值初始化为这个对象的一个子字符串的副本。这个子字符串是从对象的第pos个字符位置开始,长度为len个字符(或者直到字符串的末尾,以先到者为准)。
2.利用接口
在学习了以上接口后,我们就可以利用它们进行一些实用操作。
在此小编就给大家介绍一个切分网址 的操作。
将协议,域名和路径分开。
string url1 = "http://legacy.cplusplus.com/reference/string/string/";
string url2 = "https://yuanbao.tencent.com/chat/naQivTmsDa/43735652-b5e3-11ef-bcaa-c6162ee89a56?yb_channel=3003";
string url3 = "https://legacy.cplusplus.com/reference/vector/vector/";
例如url1中的http
就是协议,legacy.cplusplus.com
就是域名,/reference/string/string/
就是路径。
如何将三者分开呢?
//切分网址
void split_url(const string& url)
{//find的第二个参数可以指定开始地点size_t i1 = url.find(':');if (i1 != string::npos){cout << url.substr(0, i1)<<endl;//[0,i1)}//加三然后找中间的“/”size_t i2 = i1 + 3;// 加3的目的是为了跳过“//”找到。com后面的‘/’size_t i3=url.find('/', i2);//从i2开始找‘/’if (i3 != string::npos){cout << url.substr(i2, i3-i2)<<endl;//从i2开始取i3-i2个字符cout << url.substr(i3+1) << endl;//再将后面的域名一次性取出}
}
3.底层实现
注意:在基本了解了string的接口后小编将带着大家手把手地自己实现一个string。
在这里我会先直接将string的头文件(自己实现的)抛给大家,在对头文件结构进行简单描述后,我会逐一带大家底层实现头文件中的相关函数。
3.1 头文件的描述
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
//因为库里也有string,不能起冲突,所以我们要用命名空间把我们的string包起来
namespace dhb
{class string {public://先写个构造string();string(const char* str);//以上两个构造可以用全缺省代替/*string(const char* str="");*///c_str 辅助构造const char* c_str()const;//析构函数~string();//拷贝构造string(const string& s);//下标访问size_t size()const;char& operator[](size_t i);const char& operator[](size_t i)const;//迭代器的简单实现typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();//end返回最后一个字符的下一个位置const_iterator begin() const;//后面加const是指this指针不能修改,修饰函数参数const_iterator end() const;//输入输出扩容void reserve(size_t n);void push_back(char ch);void append(const char* str);//+=string& operator+=(char ch);string& operator+=(const char* ch);//插入,删除,找string& insert(size_t pos, char ch);string& insert(size_t pos, const char* str);string& erase(size_t pos, size_t len = npos);void pop_back();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, size_t len = npos) const;//比较大小bool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;bool operator==(const string& s) const;bool operator!=(const string& s) const;//清理void clear();//赋值重载string& operator=(const string& s);//交换void swap(string& s);private:char* _str;size_t _size;//不包含'\0'size_t _capacity;//不包含'\0'public:static const size_t npos;//静态成员变量不存在对象上,存在静态区上};ostream& operator<< (ostream& out, const string& s);istream& operator>> (istream& in,string& s);//不能给const,因为不支持拷贝构造,所以给引用istream& getline(istream& in, string& str, char delim);
}
3.2 文件的讲解
3.2.1构造string(); string(const char* str);
string::string()//不带参的构造:_str(new char[1]{'\0'}),_size=0,_capacity(0){}
string::string(const char* str)//调用三次strlen效率太低//:_str(new char[strlen(str)+1]) //不能这样写_str(str),涉及到权限的放大//, _size(strlen(str))//, _capacity(strlen(str))//sizeof是在编译时计算,strlen是在运行时计算_size=strlrn(str);//先初始化一个_size降低耦合{_str=new char[_str+1];_capacity=_size;//不要忘记拷贝数据strcpy(_str,str)//这里会自动拷贝‘\0’}
上面两种构造形式也可以用一种构造来替换-----》全缺省形式
string::string(const char* str)//这里结合我给的头文件观看,缺省值给在头文件那里:_size(strlen(str))
{_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);//会自动拷贝‘\0’
}
3.2.2 析构string::~string()
string::~string()
{delete _str;_size=_capacity=0;//释放空间后别忘记分配_str的指向_str=nullptr;
}
3.2.3 交换void string::swap(string& s)
void string::swap(string& s)
{std::swap(_str,s._str);//不能这样写:std::swap(_str,s)//s是string类型不是一个字符串std::swap(_size,s,_size);std::swap(_capacity,s._capacity);
}
3.2.4 拷贝构造 string::string(const string& s)
string::string(const string&s)
{string tmp(s);swap(tmp);
}
3.2.5 取字符串const char* string::c_str()const
字符长度size_t string::size()const
const char* string::c_str()const
{return _str;
}
3.2.6 下标 char& string::operator[](size_t i)
const char& string::operator[](size_t i)const{assert(i < _size);return _str[i];}
3.2.7 迭代器
string::iterator string::begin()
{return _str;
}
string::iterator string::end()//end返回最后一个字符的下一个位置
{return _str + _size;
}
string::const_iterator string::begin() const//后面加const是指this指针不能修改,修饰函数参数
{return _str;
}
string::const_iterator string::end() const
{return _str + _size;
}
3.2.8 扩容void string::reserve(size_t n)
void string::reserve(size_t n)
{if(n>_capacity){//手动扩容char* str=new char[n];memcpy(str,_str,_size+1);//向str中拷贝_str中的_size+1个数据delete[]_str;_str=str;_capacity=n;}
}
3.2.9尾插字符void string::push_back(char ch)
尾插字符串void string::append(const char* str)
void string::push_back(char ch)
{//先检查空间是否足够if(_size>=_capacity)//此时要扩容{int newcapacity=_capacity==0?4:2*_capacity;_capacity=newcapacity;reserve(_capacity);}_str[_size]=ch;_size++;//不要忘了把‘\0’重新摆一下_str[_size] = '\0';
}
void string::append(const char* str)
{int len=strlen(str);//插入字符串的扩容和字符的扩容不一样//插入字符串时扩容扩两倍可能还是不够用if(_size+len>capacity){int newcapacity=2*capacity>_size+len?2*_capacity:_size+len;reserve(newcapacity);}memcpy(_str+_size,str,len+1);//注意要拷贝len+1个数据把‘\0’也拷贝过来_size=_size+len;}
3.2.10 operator+=
string& string::operator+=(char ch)
{push_back(ch);return *this;}
string& string::operator+=(const char* str)
{append(str);return *this;
}
3.2.11 输入输出流<< >>
整行输入istream& getline(istream& in, string& s, char delim)
//补充
void string::clear()
{_str[0] = '\0';_size = 0;
}
ostream& operator<<(ostream& out, const string& s)
{for (size_t i = 0; i < s.size(); i++){out << s[i];}return out;
}
iostream& operator>>(istream& in,string& s)
{char buffer[128];s.clear();int i=0;char ch=get();//手动输入一个数给chwhile(ch!=' '&&ch!='\n')//每当buffer满了以后在一次性加给s--->提高效率{buffer[i++]=ch;if(i=127){buffer[127]='\0';s+=buffer;i=0;}ch=get();}if(i>0){buffer[i]='\0';s+=buffer;}return in;
}
istream& getline(istream& in, string& s, char delim)
{s.clear();char ch = in.get();while (ch != delim){s += ch;ch = in.get();}return in;
}
3.2.12 指定位置插入数据string& string::insert(size_t pos, const char* str)
string& string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;reserve(newcapacity);}size_t end = _size + len;//先挪数据,把pos后的数据后移while (end >= pos + len){_str[end] = _str[end - len];--end;}//开始插入数据for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;return *this;
}
补充:const size_t string::npos=-1;
3.2.13指定位置删除string& string::erase(size_t pos, size_t len)
string& string::erase(size_t pos, size_t len)
{// 要删除的数据,大于pos后面的字符个数// pos后面全删assert(pos <= _size);if (len == npos || len >= (_size - pos)){_size = pos;_str[pos] = '\0';}else{size_t i = pos + len;memmove(_str + pos, _str + i,_size-i+1);//+1省略//_str[_size + 1] = '\0';_size -= len;//_str[_size + 1] = '\0';}return *this;
}
3.2.14尾删void string::pop_back()
void string::pop_back()
{assert(_size > 0);--_size;_str[_size] = '\0';
}
3.2.15查找size_t
size_t string::find(char ch, size_t pos )const
{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
{const char* p1 = strstr(_str + pos, str);//在一个串中找另外一个串if (p1 == nullptr){return npos;}else{return p1 - _str;}
}
3.2.16 从pos返回len个字符string string::substr(size_t pos, size_t len) const
string string::substr(size_t pos, size_t len) const
{if (len == npos || len >= (_size - pos)){len = _size - pos;}string ret;ret.reserve(len);for (size_t i = 0; i < len; i++){ret += _str[pos+i];}return ret;
}
3.2.17 赋值重载
//赋值重载我们只需要写两个就行了其他的都可以复用
bool string::operator<(const string& s) const
{size_t i1=0, i2=0;while (i1 < _size && i2 <s._size){if (_str[i1] < s._str[i2]){return true;}else if(_str[i1] > s._str[i2]){return false;}else {i1++;i2++;}}return i2 <s._size;//上面的第三种情况
}
bool string::operator<=(const string& s) const
{return *this < s || *this == s;
}
bool string::operator>(const string& s) const
{return !(*this <= s);
}
bool string::operator>=(const string& s) const
{return !(*this < s);
}
bool string::operator==(const string& s) const
{size_t i1 = 0, i2 = 0;while (i1 < _size && i2 < s._size){if (_str[i1] !=s._str[i2]){return false;}else {i1++;i2++;}}return i1 == _size && i2 == s._size;
}
bool string::operator!=(const string& s) const
{return !(*this == s);
}string& string::operator=(const string& s)
{if (this != &s){/*char* tmp = new char[s._capacity+1];memcpy(tmp, s._str, s._size + 1);delete[]_str;_str = tmp;_size = s._size;_capacity = s._capacity;*/string tmp(s);swap(tmp); }return *this;
}
结语
小编最近会持续跟新STL的相关知识,如果觉得本文对你有用,欢迎点赞+关注。