广告位联系
返回顶部
分享到

C++实现String类的方法

C语言 来源:互联网 作者:佚名 发布时间:2022-08-21 18:22:27 人浏览
摘要

前言 在C语言中,没有专门用来表示字符串的类型。C语言的字符串是一系列以\0为结尾的字符的集合。虽然C语言为这样的字符串提供了一系列的库函数如strcpy, strcmp等等,但这些函数与

前言

在C语言中,没有专门用来表示字符串的类型。C语言的字符串是一系列以’\0’为结尾的字符的集合。虽然C语言为这样的字符串提供了一系列的库函数如strcpy, strcmp等等,但这些函数与字符串这个类型是分开的,这不太符合C++中面试对象的思想,所以在C++中封装了一个string类,来帮助我们操作字符串。string该如何使用,我这里就不做赘述了,大家可以去看看官方文档呀

string - C++ Reference (cplusplus.com)

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

class string

{

private:

    char *_str;

public:

    string(const char *str)

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

    {

        strcpy(_str, str);

    }

 

    string(const string &str)

        : _str(new char[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_t pos)

{

    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

class string

{

private:

    char *_str;

    size_t _size;

    size_t _capacity;

 

public:

    string(const char *str = "")

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

    {

        _str = new char[_capacity + 1];

        strcpy(_str, str);

    }

 

    string(const string &str)

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

    {

        _str = new char[_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

//关系运算符的重载

bool operator>(const string &s)

{

    return strcmp(_str, s.c_str());

}

 

bool operator==(const string &s)

{

    return strcmp(_str, s.c_str()) == 0;

}

 

bool operator!=(const string &s)

{

    return !(*this == s);

}

 

bool operator>=(const string &s)

{

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

}

 

bool operator<(const string &s)

{

    return !(*this >= s);

}

 

bool operator<=(const string &s)

{

    return !(*this > s);

}

//操作运算符的重载

string &operator=(string& str)

{

    if(*this != str)

    {

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

        strcpy(tmp,str._str);

        delete[] _str;

        _str = tmp;

        _size = str._size;

        _capacity = str._capacity;

    }

    return *this;

}

 

char &operator[](size_t pos)

{

    assert(pos < _size);

 

    return *(_str + pos);

}

 

const char &operator[](size_t pos) 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_t size() const

{

    return _size;

}

 

size_t capacity() const

{

    return _capacity;

}

 

bool empty() const

{

    return 0 == _size;

}

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

void clear()

{

    _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

void reserve(size_t n)

{

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

    {

        char *tmp = new char[n + 1];

        strcpy(tmp, _str);

        delete[] _str;

        _str = tmp;

        _capacity = n;

    }

}

 

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

void resize(size_t n, char c = '\0')

{

    if (n > _capacity)

    {

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

    }

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

    {

        _str[i] = c;

    }

    _size = n;

    _str[_size] = '\0';

}

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

1

2

3

4

5

6

7

8

9

void push_back(char n)

{

    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_t pos, const char *str)

{

    //检查空间是否足够

    assert(pos <= _size);

    size_t len = strlen(str);

    if (len + _size > _capacity)

    {

        reserve(len + _size);

    }

 

    //挪动后面的数据

    size_t end = _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_t pos, size_t len = npos) //npos为静态变量,值为-1

{

    assert(pos < _size);

     

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

    {

        _str[pos] = '\0';

        _size = pos;

    }

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

    {

        size_t begin = 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

typedef char *iterator;

typedef const char *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+=(const char *str)

{

    append(str);

}

 

string &operator+=(char n)

{

    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

void swap(string& str)

{

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

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

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

}

 

string(const string &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会去调用析构函数进行一个空间释放。

完整代码

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

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

class string

{

public:

    typedef char *iterator;

    typedef const char *const_iterator;

 

    const_iterator begin() const

    {

        return _str;

    }

 

    const_iterator end() const

    {

        return _str + _size;

    }

 

    iterator begin()

    {

        return _str;

    }

 

    iterator end()

    {

        return _str + _size;

    }

 

    string(const char *s = "")

        : _size(strlen(s)),

          _capacity(_size)

    {

        _str = new char[_capacity + 1];

        strcpy(_str, s);

    }

 

    string(const string &s)

        : _str(nullptr),

          _size(0),

          _capacity(0)

    {

        string tmp(s._str);

        swap(tmp);

    }

 

    ~string()

    {

        delete[] _str;

        _str = nullptr;

        _size = _capacity = 0;

    }

 

    string &operator=(string s)

    {

        swap(s);

        return *this;

    }

 

    char &operator[](size_t pos)

    {

        assert(pos < _size);

 

        return *(_str + pos);

    }

 

    const char &operator[](size_t pos) const

    {

        assert(pos < _size);

        return *(_str + pos);

    }

 

    const char *c_str() const

    {

        return _str;

    }

 

    void reserve(size_t n)

    {

        if (n > _capacity)

        {

            char *tmp = new char[n + 1];

            strcpy(tmp, _str);

            delete[] _str;

            _str = tmp;

            _capacity = n;

        }

    }

 

    void push_back(char n)

    {

        if (_size == _capacity)

        {

            reserve(_capacity == 0 ? 4 : _capacity * 2);

        }

        _str[_size++] = n;

        _str[_size] = '\0';

    }

 

    string &operator+=(char n)

    {

        push_back(n);

        return *this;

    }

 

    void append(const char *str)

    {

        size_t len = _size + strlen(str);

        if (len > _capacity)

        {

            reserve(len);

        }

        strcpy(_str + _size, str);

        _size = len;

    }

 

    string &operator+=(const char *str)

    {

        append(str);

    }

 

    void resize(size_t n, char c = '\0')

    {

        if (n > _capacity)

        {

            reserve(n);

        }

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

        {

            _str[i] = c;

        }

        _size = n;

        _str[_size] = '\0';

    }

 

    size_t size() const

    {

        return _size;

    }

 

    size_t capacity() const

    {

        return _capacity;

    }

 

    bool empty()

    {

        return 0 == _size;

    }

 

    bool operator>(const string &s)

    {

        return strcmp(_str, s.c_str());

    }

 

    bool operator==(const string &s)

    {

        return strcmp(_str, s.c_str()) == 0;

    }

 

    bool operator!=(const string &s)

    {

        return !(*this == s);

    }

 

    bool operator>=(const string &s)

    {

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

    }

 

    bool operator<(const string &s)

    {

        return !(*this >= s);

    }

 

    bool operator<=(const string &s)

    {

        return !(*this > s);

    }

 

    string &insert(size_t pos, const char *str)

    {

        assert(pos <= _size);

        size_t len = strlen(str);

        if (len + _size > _capacity)

        {

            reserve(len + _size);

        }

 

        size_t end = _size + len;

        while (end != pos + len - 1)

        {

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

            --end;

        }

 

        strncpy(_str + pos, str, len);

        _size += len;

        return *this;

    }

 

    string &eraser(size_t pos, size_t len = npos)

    {

        assert(pos < _size);

 

        if (len == npos || pos + len >= _size)

        {

            _str[pos] = '\0';

            _size = pos;

        }

        else

        {

            size_t begin = pos + len;

            while (begin <= _size)

            {

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

                begin++;

            }

            _size = _size - len;

        }

        return *this;

    }

 

    void clear()

    {

        _size = 0;

        _str[0] = '\0';

        _capacity = 0;

    }

 

    void swap(string &s)

    {

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

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

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

    }

 

    size_t find(char c, size_t pos = 0) const

    {

        while (pos < _size)

        {

            if (_str[pos] == c)

            {

                return pos;

            }

            ++pos;

        }

        return npos;

    }

 

    size_t find(char *s, size_t pos = 0) const

    {

        const char *p = strstr(_str + pos, s);

        if (p == nullptr)

        {

            return npos;

        }

        else

        {

            return p - _str;

        }

    }

 

private:

    char *_str;

    size_t _size;

    size_t _capacity;

    const static size_t npos;

};

 

const size_t string::npos = -1;


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/m0_60447315/article/details/126448202
相关文章
  • C++中类的六大默认成员函数的介绍

    C++中类的六大默认成员函数的介绍
    一、类的默认成员函数 二、构造函数Date(形参列表) 构造函数主要完成初始化对象,相当于C语言阶段写的Init函数。 默认构造函数:无参的构
  • C/C++实现遍历文件夹最全方法总结介绍

    C/C++实现遍历文件夹最全方法总结介绍
    一、filesystem(推荐) 在c++17中,引入了文件系统,使用起来非常方便 在VS中,可以直接在项目属性中调整: 只要是C++17即以上都可 然后头文件
  • C语言实现手写Map(数组+链表+红黑树)的代码

    C语言实现手写Map(数组+链表+红黑树)的代码
    要求 需要准备数组集合(List) 数据结构 需要准备单向链表(Linked) 数据结构 需要准备红黑树(Rbtree)数据结构 需要准备红黑树和链表适配策略
  • MySQL系列教程之使用C语言来连接数据库

    MySQL系列教程之使用C语言来连接数据库
    写在前面 知道了 Java中使用 JDBC编程 来连接数据库了,但是使用 C语言 来连接数据库却总是连接不上去~ 立即安排一波使用 C语言连接 MySQL数
  • 基于C语言实现简单学生成绩管理系统

    基于C语言实现简单学生成绩管理系统
    一、系统主要功能 1、密码登录 2、输入数据 3、查询成绩 4、修改成绩 5、输出所有学生成绩 6、退出系统 二、代码实现 1 2 3 4 5 6 7 8 9 10 11
  • C语言实现共享单车管理系统

    C语言实现共享单车管理系统
    1.功能模块图; 2.各个模块详细的功能描述。 1.登陆:登陆分为用户登陆,管理员登陆以及维修员登录,登陆后不同的用户所执行的操作
  • C++继承与菱形继承的介绍

    C++继承与菱形继承的介绍
    继承的概念和定义 继承机制是面向对象程序设计的一种实现代码复用的重要手段,它允许程序员在保持原有类特性的基础上进行拓展,增加
  • C/C++指针介绍与使用介绍

    C/C++指针介绍与使用介绍
    什么是指针 C/C++语言拥有在程序运行时获得变量的地址和操作地址的能力,这种用来操作地址的特殊类型变量被称作指针。 翻译翻译什么
  • C++进程的创建和进程ID标识介绍
    进程的ID 进程的ID,可称为PID。它是进程的唯一标识,类似于我们的身份证号是唯一标识,因为名字可能会和其他人相同,生日可能会与其他
  • C++分析如何用虚析构与纯虚析构处理内存泄漏

    C++分析如何用虚析构与纯虚析构处理内存泄漏
    一、问题引入 使用多态时,如果有一些子类的成员开辟在堆区,那么在父类执行完毕释放后,没有办法去释放子类的内存,这样会导致内存
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计