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

C++实现String类的方法详解

string模拟实现

string简单实现

首先我们不考虑string类的增删查改,只是先给string类搭建一个最简单的框架出来。

和C语言中相同,为了存储一个字符串,我们的string类需要一个char*的指针来指向字符像这个对象。作为一个对象,string还需要有构造函数,析构函数和拷贝构造。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

classstring

{

private:

char*_str;

public:

string(constchar*str)

: _str(newchar[strlen(str) + 1])// +1 是给'\0'留出位置

{

strcpy(_str, str);

}

string(conststring &str)

: _str(newchar[strlen(str._str) + 1])

{

strcpy(_str, str._str);

}

~string()

{

if(_str)

{

delete[] _str;

_str = nullptr;

}

}

};

有的朋友可能会疑惑,这里的构造函数和拷贝构造函数为什么不用编译器自动生成的,直接将_str指向原本的字符串就可以了,为什么还要开辟空间呢?

这是因为我们在日常使用中,假如有两个string类 a 和 b,b是由a拷贝构造而来,一般情况下我们在修改b的同时不希望a也被改。此外,如果直接将_str指向原本的字符串会导致的问题是当 a 和 b用完被销毁时,会对同一片空间调用两次析构函数,对同一片空间释放两次。所以在这里,我们需要重新开辟一片空间来给这个string。这也就是所谓的深拷贝。

然后,为了访问string类中的元素,我们需要对运算符[]进行重载。

1

2

3

4

5

char& operator[](size_tpos)

{

assert(pos <strlen())

return_str[pos];

}

这样我们就实现了一个简单的string类。

string完整实现

构造函数,析构函数,拷贝构造

之前我们实现的一个string类是一个最简单的string类,它没有办法进行增删查改,接下来我们就来一点一点完善它。

要实现增删查改,我们还需要两个变量,一个记录string类当前长度,一个记录string类的容量大小。加入这两个变量后,我们原本的构造函数,拷贝构造和析构函数需要发生一点点变化。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

classstring

{

private:

char*_str;

size_t_size;

size_t_capacity;

public:

string(constchar*str ="")

: _size(strlen(str)), _capacity(_size)

{

_str =newchar[_capacity + 1];

strcpy(_str, str);

}

string(conststring &str)

: _size(str._size), _capacity(str._capacity)

{

_str =newchar[_size + 1];

strcpy(_str, str._str);

}

~string()

{

if(_str)

{

delete[] _str;

_str = nullptr;

_size = _capacity = 0;

}

}

};

运算符重载

接下来我们来实现一下,string类的运算符。在实现运算符重载时,我们需要做的只是实现少数几个运算符即可,其他的运算符可复用前面实现的运算符来达到我们想要的效果。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

//关系运算符的重载

booloperator>(conststring &s)

{

returnstrcmp(_str, s.c_str());

}

booloperator==(conststring &s)

{

returnstrcmp(_str, s.c_str()) == 0;

}

booloperator!=(conststring &s)

{

return!(*this== s);

}

booloperator>=(conststring &s)

{

return*this> s || *this== s;

}

booloperator<(conststring &s)

{

return!(*this>= s);

}

booloperator<=(conststring &s)

{

return!(*this> s);

}

//操作运算符的重载

string &operator=(string& str)

{

if(*this!= str)

{

char*tmp =newchar[str._capacity + 1];

strcpy(tmp,str._str);

delete[] _str;

_str = tmp;

_size = str._size;

_capacity = str._capacity;

}

return*this;

}

char&operator[](size_tpos)

{

assert(pos < _size);

return*(_str + pos);

}

constchar&operator[](size_tpos)const

{

assert(pos < _size);

return*(_str + pos);

}

string接口实现

首先是比较简单的size(),empty(),capacity(),clear()。这些接口大部分直接访问string类的成员变量就可以得到结果。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

size_tsize()const

{

return_size;

}

size_tcapacity()const

{

return_capacity;

}

boolempty()const

{

return0 == _size;

}

//后面添加const的目的是为了让所有对象都可以进行访问

voidclear()

{

_str[0] ='\0';

_size = 0;

_capacity = 0;

}

因为后面的接口大部分都需要进行空间的调整,所以首先我们将调整空间的接口,reserve和resize实现。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

voidreserve(size_tn)

{

if(n > _capacity)//判断是否需要扩容

{

char*tmp =newchar[n + 1];

strcpy(tmp, _str);

delete[] _str;

_str = tmp;

_capacity = n;

}

}

//resize和reserve的区别在于,reserve只是开空间,而resize还要进行初始化

voidresize(size_tn,charc ='\0')

{

if(n > _capacity)

{

reserve(n);//开空间复用reserve

}

for(size_ti = _size; i < n; ++i)

{

_str[i] = c;

}

_size = n;

_str[_size] ='\0';

}

接下来是插入的实现,首先是push_back,这个比较简单,找到尾部进行插入即可。

1

2

3

4

5

6

7

8

9

voidpush_back(charn)

{

if(_size == _capacity)

{

reserve(_capacity == 0 ? 4 : _capacity * 2);//开空间复用reserve

}

_str[_size++] = n;

_str[_size] ='\0';

}

接下来是insert,这个较push_back而言要麻烦一些,因为除了尾插,其他地方去插入数据你都需要挪动后面数据的位置。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

string &insert(size_tpos,constchar*str)

{

//检查空间是否足够

assert(pos <= _size);

size_tlen =strlen(str);

if(len + _size > _capacity)

{

reserve(len + _size);

}

//挪动后面的数据

size_tend = _size + len;

while(end != pos + len - 1)

{

_str[end] = _str[end - len];

--end;

}

//数据插入

strncpy(_str + pos, str, len);

_size += len;

return*this;

}

写完了插入,接下来当然就是删除接口:eraser

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

string &eraser(size_tpos,size_tlen = npos)//npos为静态变量,值为-1

{

assert(pos < _size);

if(len == npos || pos + len >= _size)//将位置后的元素全部删除

{

_str[pos] ='\0';

_size = pos;

}

else//删除位置后的部分元素

{

size_tbegin = pos + len;

while(begin <= _size)

{

_str[begin - len] = _str[begin];

begin++;

}

_size = _size - len;

}

return*this;

}

迭代器的实现

C++中的迭代器和指针类似。为什么要有迭代器呢?因为C++中有各种各样的容器,每个容器它背后的存储方式不同,访问方式也不同,为了让使用者的使用成本降低,使大部分容器可以以相同的方式去访问,就有了迭代器的产生。

接下来我们来实现string的迭代器,其实string的迭代器就是一个指针。并不用去封装特别的东西。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

typedefchar*iterator;

typedefconstchar*const_iterator;

const_iterator begin()const

{

return_str;

}

const_iterator end()const

{

return_str + _size;

}

iterator begin()

{

return_str;

}

iterator end()

{

return_str + _size;

}

部分函数优化和完善

前面在写运算符重载时,还有部分运算符未重载在此加上

1

2

3

4

5

6

7

8

9

10

string &operator+=(constchar*str)

{

append(str);

}

string &operator+=(charn)

{

push_back(n);

return*this;

}

同时增加拷贝构造和operator=的现代写法,之前我们写拷贝构造和operator=时都需要自己去重新开空间,那么这个活可不可以让其他人帮我做呢?

我们来看看下面这一段代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

voidswap(string& str)

{

std::swap(_str, str._str);

std::swap(_size, str._size);

std::swap(_capacity, str._capacity);

}

string(conststring &s)

: _str(nullptr), _size(0), _capacity(0)

{

string tmp(s._str);

swap(tmp);

}

string &operator=(string s)

{

swap(s);

return*this;

}

上述代码同样可以帮我们完成拷贝构造和operator= ,原理如下:

1.首先是拷贝构造,我们在拷贝构造中使用构造函数去创建一个临时对象,这个临时对象在创建时,就帮我们开辟了空间。然后我们将临时对象和此对象的所有成员进行一个交换,这样此对象就可以接管临时对象创建的那块空间,我们的拷贝构造也就成功了

2.在operator=这,我们使用的是传值传参。好处在于由于我们的string类是自定义对象,所以在传参时会去调用拷贝构造,这样传过来的str参数也拥有了自己的空间,此时我们和拷贝构造一样,将str所开辟的那块空间接管,同时由于str是函数参数,当函数结束时,str会去调用析构函数进行一个空间释放。

http://www.jsqmd.com/news/723962/

相关文章:

  • 技术访问者的操作扩展与元素分离
  • 爬虫进阶:用Playwright拦截并分析动态页面请求,精准获取数据源
  • 测试说明文章
  • 【2026最新收藏版】AI Agent详解:从入门到实战,小白程序员必看的大模型智能体学习指南
  • 2026年佛山地区裁断机选购指南,裁断机定制生产的品牌推荐 - 工业设备
  • LeetCode 接雨水:python 题解
  • 如何为Windows系统安装macOS风格鼠标指针:完整配置指南
  • 支付宝上线AI付,让众多“龙虾”实现收钱,详细开通步骤
  • 聊聊2026年浙江性价比高的不锈钢雕塑来图定制企业,哪家值得选 - 工业推荐榜
  • MAUI 嵌入式 Web 架构实战(一) 在 MAUI 应用中嵌入 PicoServer 构建本地 HTTP 服务
  • GitHub中文插件:3分钟实现GitHub界面全面汉化
  • 3分钟掌握ncmdump:网易云音乐NCM文件终极转换指南
  • 合成数据质量评估:SDQM框架解析与应用实践
  • 终极指南:如何在Windows上轻松玩转经典Flash游戏与存档管理
  • Ozon向中国卖家降门槛:零保证金、零佣金拉新,俄罗斯电商蓝海如何用数据精准破局?
  • 2026年4月AI智能体平台推荐:口碑好的产品解决企业AI落地场景碎片化痛点
  • 2.4 静态链表
  • Go语言WebSocket实时聊天后端架构设计与实现指南
  • 智慧树刷课插件终极指南:3分钟实现学习自动化,效率提升300% ⚡
  • Microchip PIC64GX:64位RISC-V多核微处理器解析与应用
  • 飞函如何帮助金融机构把敏感群聊、会议纪要和文件共享纳入合规视野
  • 安海 ADA080N120 碳化硅MOSFET 技术简析
  • 论文阅读:ICLR 2026 A Guardrail for Safety Preservation: When Safety-Sensitive Subspace Meets Harmful-Res
  • 别再手动改Word了!用docxtemplater的{{#each}}和{{#if}}语法,5分钟搞定批量合同生成
  • 软件决策树管理中的选择路径分析者
  • 视觉语言导航技术:挑战、方案与SeeNav-Agent框架解析
  • 深圳中南实验室建设|黑灯实验室公司厂家:人类科研更好还是更糟
  • 立创3D模型快速下载
  • 基于Netty与WebSocket构建高性能物联网推送服务:从原理到实践
  • AI数据分类分级系统赋能金融行业数据治理提质增效