当前位置: 首页 > news >正文

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(文档)
    在这里插入图片描述
    在这里插入图片描述

7个
补充说明: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操作的代价其实很大

  1. 不能释放空间的一部分
  2. 缩容是异地缩容:需要重新开空间,然后把数据深拷贝到新空间中,再释放旧空间。(是以时间换空间,不是很建议用)
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的相关知识,如果觉得本文对你有用,欢迎点赞+关注。


文章转载自:
http://bronchia.apjjykv.cn
http://battik.apjjykv.cn
http://abirritant.apjjykv.cn
http://blinkered.apjjykv.cn
http://amphibious.apjjykv.cn
http://category.apjjykv.cn
http://anthelion.apjjykv.cn
http://bedbug.apjjykv.cn
http://articulacy.apjjykv.cn
http://autosemantic.apjjykv.cn
http://camphorate.apjjykv.cn
http://cachucha.apjjykv.cn
http://balt.apjjykv.cn
http://caseidin.apjjykv.cn
http://belie.apjjykv.cn
http://chateaubriand.apjjykv.cn
http://ametabolic.apjjykv.cn
http://brahmanical.apjjykv.cn
http://cadwallader.apjjykv.cn
http://accountantship.apjjykv.cn
http://bruin.apjjykv.cn
http://arhus.apjjykv.cn
http://arrhythmically.apjjykv.cn
http://burry.apjjykv.cn
http://belying.apjjykv.cn
http://cardioscope.apjjykv.cn
http://admittedly.apjjykv.cn
http://aerophyte.apjjykv.cn
http://apocalyptician.apjjykv.cn
http://aggrieve.apjjykv.cn
http://www.dtcms.com/a/260411.html

相关文章:

  • react-嵌套路由 二级路由
  • 128K 长文本处理实战:腾讯混元 + 云函数 SCF 构建 PDF 摘要生成器
  • Spring Cloud Config动态刷新实战指南
  • CyberGlove触觉反馈手套遥操作机器人灵巧手解决方案
  • java中的anyMatch和allMatch方法
  • [3D-portfolio] 版块包装高阶组件(封装到HOC) | Email表单逻辑 | 链式调用
  • 【Electron】electron+react的插件@electron/remote用法,在渲染进程直接调用主进程的API
  • 码蹄集:MT2045斐波那契,但是是字符串
  • Java设计模式->责任链模式的介绍
  • Java 编程之备忘录模式
  • SQL学习笔记3
  • LeetCode--37.解数独
  • 使用 Nordic Connect SDK (NCS) 而不是标准 Zephyr 的优势是什么
  • AI+时代已至|AI人才到底该如何培育?
  • ubuntu部署woodpecker依赖gitea
  • 6月26日星期四今日早报简报微语报早读
  • [论文阅读]RaFe: Ranking Feedback Improves Query Rewriting for RAG
  • GraphQL注入 -- GPN CTF 2025 Real Christmas
  • 通过对 NIDS 自适应黑盒对抗性攻击披露漏洞
  • 攻防世界-MISC-4-2
  • 力扣网C语言编程题:搜索插入位置
  • Python 数据分析与可视化 Day 8 - Pandas 高级操作技巧
  • 表单数据收集实现分析
  • Python 中 `bytes` 与 `str` 的核心差异及注意事项
  • python中学物理实验模拟:斜面受力分析
  • IDEA + Spring Boot + javadoc 实例应用
  • Java底层原理:深入理解JVM性能调优与监控
  • 腾讯云产品都有哪些
  • 永磁无刷电机旋转原理
  • 大脑感官:视觉系统中将感观信息转换为神经信号