当前位置:   article > 正文

【C++基础】模拟实现string类_请设计一个str类,能模拟string类的部分操作: str s1; //建立一个空字符串 st

请设计一个str类,能模拟string类的部分操作: str s1; //建立一个空字符串 str s2 (“abc”); //用常量建立一个初值为”abc”的字符串 str s3 ( s2);//执行拷贝构造函数,用s2的值作为s3的初值 cout<

C++中string类是个管理字符串的类,而在面试中,如何模拟实现string类也是一个考点,我在这里简单的实现一下如何模拟实现一个string类

模拟实现string类

  1. class String{
  2. public:
  3. //成员函数
  4. private:
  5. //成员变量
  6. char* _str;
  7. size_t _size;
  8. size_t _capacity;
  9. };

模拟实现string类,我们需要一个成员变量_size来表示字符串的长度(不包含'\0'),还需要一个_capacity来表示申请的内存空间的大小

一、String类的六个默认成员函数

1.构造函数

  1. //构造函数
  2. String(const char* str = "") {
  3.         _size = strlen(str);
  4.         //+1是因为要保存'\0'
  5.         _str = new char[_size + 1];
  6.         strcpy(_str, str);
  7.         _capacity = _size + 1;
  8.     }

构造函数是创建一个对象,然后给_str变量申请内存,再设置好_size和_capacity变量,示意图如下:

2.拷贝构造函数

  1. //拷贝构造函数
  2. String(const String& s)
  3.     :_str(new char[strlen(s._str) + 1]){
  4.     strcpy(_str, s._str);
  5.     _size = s._size;
  6.     _capacity = s._capacity;
  7. }

注意,这里的拷贝构造函数是深拷贝,给_str重新开辟一段内存空间,然后使用strcpy函数进行复制,示意图如下

3.赋值运算符重载

赋值运算符重载这里有好几种写法,有优有劣

①传统写法

  1. //传统写法
  2. String& operator=(const String& s) {
  3.     if (this != &s) {
  4.         //传统写法是规规矩矩的
  5.         //先把旧的内存空间释放
  6.         delete[] _str;
  7.         //然后再申请新的内存空间         
  8.         _str = new char[s._capacity];
  9.         //然后再拷贝过去
  10.         strcpy(_str, s._str);
  11.         _size = s._size;
  12.         _capacity = s._capacity;
  13.         return *this;
  14.     }
  15. }

传统写法先释放旧的内存空间,然后再申请内存,再复制过去,这种方法是比较蠢的,下面介绍一种新方法

②现代写法1

  1. String& operator=(const String& s) {
  2. if (this != &s) {
  3.     //创建一个临时变量,这里是用的拷贝构造函数,也可以用构造函数
  4.     String tmp(s);
  5.     //然后使用swap函数交换
  6.     swap(_str, tmp._str);
  7.     //tmp在函数结束时直接销毁
  8.     _size = tmp._size;
  9.     _capacity = tmp._capacity;
  10.     }
  11.     return *this;
  12. }
这里的方法是,创建一个临时对象,使用了 拷贝构造函数,创建成功后,如图

然后使用swap(一个库函数)函数交换临时对象和当前赋值的对象,最后再把_size和_capacity赋值过去

交换前:


交换后:

由于临时变量在函数结束时就直接销毁了(调用析构函数),所以不需要去对对象再手动释放,是比较简单的

③现代写法2

既然上面使用了拷贝构造函数构造一个临时对象,那么我们也可以使用构造函数来构造一个临时对象,代码如下:

  1. //现代写法1
  2. String& operator=(const String& s) {
  3. if (this != &s) {
  4.          //创建一个临时变量,使用构造函数
  5.     String tmp(s._str);
  6.     //然后使用swap函数交换
  7.     swap(_str, tmp._str);
  8.     //tmp在函数结束时直接销毁
  9.     _size = tmp._size;
  10.     _capacity = tmp._capacity;
  11.     }
  12. return *this;
  13. }

④现代写法3

上面两种现代写法,都是需要手动的创建一个临时变量,下面的方法不需要手动的去创建临时变量,代码如下:

  1. //现代写法2
  2. String& operator=(String s) {
  3. if (this != &s) {
  4.          //传入进来的参数是传值的,函数结束后会销毁,跟上面的临时变量一样
  5.     swap(_str, s._str);
  6.     _size = s._size;
  7.     _capacity = s._capacity;
  8.     }
  9.     return *this;
  10. }

这种方法,利用了c/c++中的函数传参时候是传值的原理,直接由编译器拷贝构造一份临时变量,拿来直接用就好了,很方便~

⑤现代写法4

我们可以自己实现一个Swap函数,只交换两个_str的指针

  1. void Swap(String& s) {
  2. char* tmp = s._str;
  3. s._str = _str;
  4. _str = tmp;
  5. }
  6. String& operator=(String& s) {
  7. if (this != &s) {
  8.     //这种方法也是先创建一个临时变量
  9.             String tmp(s._str);
  10.     //然后把两个对象的_str指针交换
  11.     Swap(tmp);
  12.     //然后更新_size和_capacity
  13.     _size = tmp._size;
  14.     _capacity = tmp._capacity;
  15.     }
  16.     return *this;
  17. }

这种方法就像是,两个人都去做一件事,比如写作业,写完之后,你把别人的作业拿来直接写上你的名字,然后这个作业就是你的了(如果人家不拍你的话)。

赋值运算符重载还有一种,例如s1 = "abcd",传入的不是一个对象,而是一个字符串,代码如下:

  1. //赋值运算符重载
  2. String& operator=(const char* str) {
  3.     //删除旧的字符
  4.     delete[] _str;
  5.     //计算出字符串大小
  6.     size_t str_size = strlen(str);
  7.     //开辟内存空间
  8.     _capacity = str_size + 1;
  9.     _str = new char[_capacity];
  10.     //复制
  11.     strcpy(_str, str);
  12.     _size = _capacity - 1;
  13.     return *this;
  14. }

也可以使用上面的现代写法

4.析构函数

  1. //析构函数
  2. ~String() {
  3.      delete[] _str;
  4.     _str = NULL;
  5.     _size = 0;
  6.     _capacity = 0;
  7.     cout << "~String" << endl;
  8. }

二、增删改查

增删改查不可避免的就需要改动字符串的大小,尤其是增,可能现有的_capacity不够,所以有时候需要对当前申请的内存空间进行扩容

扩容函数如下:

  1. //扩容
  2. void String::Expand(size_t n) {
  3. //先申请大小为n的内存空间
  4. char* tmp = new char[n];
  5. //然后复制
  6. strcpy(tmp, _str);
  7. delete[] _str;
  8. _str = tmp;
  9. _capacity = n;
  10. }
对现有内存空间进行扩容的话,需要重新申请足够的内存空间(设为n,n >= 0),再把数据拷贝过去,然后再把旧的内存空间释放,最后再让_str指向新申请的内存空间,这样扩容就完成了

接下来就看一下增删查改的具体函数:

1.增

①尾插一个字符

函数定义如下:

  1. //尾插一个字符
  2. void String::PushBack(char ch) {
  3. if (_size + 1 >= _capacity) {
  4. //直接扩大两倍
  5. Expand(_capacity * 2);
  6. }
  7. _str[_size++] = ch;
  8. _str[_size] = '\0';
  9. }

我们引入_size和_capacity这两个成员变量就是为了增删改查时使用的

_size指向字符数组的'\0'的位置,同事也代表了有多少这个数组有多少个字符

_capacity表示申请的内存空间的大小,由于数组是从0算起的,所以_capacity是指向申请的内存空间的最后 一个元素之后的

所以我们什么时候增容呢?

当_size + 1和_capacity相等时,即'\0'已经是申请的内存空间的最后一个元素时,如下图

把要插入的位置插在_size的位置,然后让_size指向下一个位置,向那个位置写入'\0'


②尾插一个字符串

函数代码如下:

  1. //尾插一个字符串
  2. void String::PushBack(const char* str) {
  3. size_t str_size = strlen(str);
  4. if (_size + str_size + 1 >= _capacity) {
  5. //如果空间不够就进行扩容
  6. _capacity += str_size;
  7. Expand(_capacity);
  8. //然后再拷贝
  9. while (*str != '\0') {
  10. _str[_size++] = *str++;
  11. }
  12. _str[_size] = '\0';
  13. }
  14. }

先算出要插入的字符串,然后再判断空间是否足够

如果不够,就进行扩容


头插字符和字符串是不划算的,最好也不要提供这样的接口,但是为了逻辑思维的完整,我在这里加上了

③头插一个字符

  1. //头插一个字符
  2. void String::PushFront(char ch) {
  3. if (_size + 1 >= _capacity) {
  4. //扩容
  5. Expand(_capacity * 2);
  6. }
  7. //要头插一个字符串,先把最后一个元素设置为\0
  8. _str[_size + 1] = '\0';
  9. for (int i = _size; i > 0; --i) {
  10. _str[i] = _str[i - 1];
  11. }
  12. _str[0] = ch;
  13. _size += 1;
  14. }
和上面一样,这个函数都是先判断是否需要扩容,如果觉得空间浪费的话,只需要扩容到_capacity + 1就可以了

④头插一个字符串

代码如下:

  1. //头插一个字符串
  2. void String::PushFront(const char* str) {
  1. //计算出要插入的字符串的长度
  2. size_t str_size = strlen(str);
  3. //如果要插入的字符串的长度加上原有的字符串长度大于已有的容量,就进行扩容
  4. if (str_size + _size + 1 >= _capacity) {
  5. _capacity = str_size + _size + 1;
  6. //扩容
  7. Expand(_capacity);
  8. }
  9. //_capacity表示容量,总共可以有多少个元素
  10. //事实上,我们可以认为_size是_str字符数组的最后一个元素了,因为它存的是'\0'
  11. _str[_size + str_size + 1] = '\0';
  12.         //所以,最后一个元素应该是在_capacity-2的位置
  13.         size_t index_prev = _size;
  14.         size_t index_last = _size + str_size - 1;
  15.         while (index_prev) {
  16.             _str[index_last--] = _str[--index_prev];
  17.         }
  18.         //index_prev当前一定是0
  19.         while (*str != '\0') {
  20.             _str[index_prev++] = *str++;
  21.         }
  22.         _size += str_size;
  23. }

由代码可以看出,

第一步:先判断是否需要扩容

第二步:给插入成功后字符串的末尾加上'\0'(不一定限于第二步)

第三步:把当前字符串向后移,距离是str_size(给前面腾出位置,有点类似于顺序表的头插),然后再插入字符串

如图:


这里的边界条件要注意清除,比如'\0'的位置,比如把字符统一往后移动

⑤在指定位置插入一个字符

函数代码如下:

  1. //在指定位置插入一个字符
  2. void String::Insert(size_t pos, char ch) {
  3. if (pos > _size) {
  4. //可以等于_size,表示尾插了一个字符
  5. //为了让Insert更通用,就不调用pushback尾插了
  6. printf("pos位置传入错误!\n");
  7. return;
  8. }
  9. else {
  10. //pos的位置是正常的,可以插入
  11. if (_size + 1 >= _capacity) {
  12. //扩容
  13. Expand(_capacity*2);
  14. }
  15. //先把'\0'加上
  16. _str[_size + 1] = '\0';
  17. //先把pos之后的位置全部向后挪一个位置(包括pos)
  18. for (int i = _size; i > (int)pos; --i) {
  19. _str[i] = _str[i - 1];
  20. }
  21. //然后再在pos的位置插入字符
  22. _str[pos] = ch;
  23. _size++;
  24. }
  25. }

第一步先判断传入位置是否正确

第二步,如果正确了就开始插入,插入之前先判断是否需要扩容,然后把pos位置之后的字符都向后挪一位,注意:

  1. for (int i = _size; i > (int)pos; --i) {
  2. _str[i] = _str[i - 1];
  3. }

注意这里的for循环,判断条件那里,i不能设置为size_t类型,因为如果在开头位置(pos = 0)插入的话,会成为一个死循环

但是把i设置为int,这里又会出现隐式类型转换,i还是会被转换为size_t类型,还是会出错,所以最好写成这样

⑥在指定位置插入一个字符串

  1. //在指定位置插入一个字符串
  2. void String::Insert(size_t pos, const char* str) {
  3. //先判断参数是否正确
  4. if (pos > _size) {
  5. printf("pos位置传入错误!\n");
  6. return;
  7. }
  8. //求出要插入字符串的长度
  9. size_t str_size = strlen(str);
  10. if (str_size == 0) {
  11. //表示要插入的字符串是空字符串,直接返回
  12. return;
  13. }
  14. //然后判断_capaciy是否足够
  15. if (_size + str_size + 1 > _capacity) {
  16. //原本字符串中字符的个数加上要插入的字符串字符的个数再加上'\0'就是我们需要的空间
  17. _capacity = _size + str_size + 1;
  18. Expand(_capacity);
  19. }
  20. //把'\0'先加上
  21. int last_index = _size + str_size;
  22. _str[last_index--] = '\0';
  23. //然后把pos之后的字符串都向后挪str_size个位置
  24. for (int i = _size - 1; i >= (int)pos; --i) {
  25. _str[last_index--] = _str[i];
  26. }
  27. //然后从pos位置开始插入要插入的字符串
  28. while (*str != '\0') {
  29. _str[pos++] = *str++;
  30. }
  31. //更新size
  32. _size += str_size;
  33. }
这个和上面插入一个字符的区别不大~

2.删

①尾删一个字符

  1. //尾删
  2. void String::PopBack() {
  3. //判断字符串是否为空字符串
  4. if (_size == 0) {
  5. printf("字符串已为空!\n");
  6. return;
  7. }
  8. _size--;
  9. _str[_size] = '\0';
  10. }
只需要更改_size就好

②在指定位置之后删除长度为n的字符

  1. //在指定位置之后删除长度为n的字符
  2. void String::Erase(size_t pos, size_t n) {
  3. //判断参数的合法性
  4. //pos等于_size是"合法"的,但是没有意义,'\0'是不能删除的
  5. //所以这里直接就判断为不合法了
  6. if (pos >= _size) {
  7. //传入的位置pos不合法
  8. printf("pos位置传入不合法!\n");
  9. return;
  10. }
  11. //删除pos之后的n个元素
  12. if (pos + n < _size) {
  13. //删除pos之后n个字符
  14. size_t index_erase = pos + n;
  15. while (index_erase != _size) {
  16. _str[pos++] = _str[index_erase++];
  17. }
  18. }
  19. //当pos + n大于等于_size时,都是删除pos之后的所有元素
  20. _str[pos] = '\0';
  21. _size = pos;
  22. }

这里有两种情况

第一种,pos位置之后的字符个数大于n,这时候就全删了

第二种,pos位置之后的字符个数小于n,则我们需要在pos之后删除n个元素

删除pos之后n个元素,只需要把n个元素之后的剩下的元素挪到前面来,然后再给末尾加上'\0'

最后更新_size

3.改

  1. //[]运算符重载
  2. char& String::operator[](size_t pos) {
  3. return _str[pos];
  4. }

返回字符数组的字符的引用,这样就可以改了

4.查

①查找字符

  1. //查找字符
  2. size_t String::Find(char ch) {
  3. for (size_t i = 0; i < _size; ++i) {
  4. if (_str[i] == ch) {
  5. //找到了就返回下标
  6. return i;
  7. }
  8. }
  9. return -1;
  10. }

②查找字符串

  1. //查找字符串
  2. size_t String::Find(const char* str) {
  3. size_t index_str = 0;
  4. //循环退出条件,要么查找到了,要么就是查找到了结尾也没有找到
  5. while (_str[index_str] != '\0') {
  6. if (_str[index_str] == *str) {
  7. //开头的字符相等了
  8. //可以匹配的查找了
  9. size_t find_index = index_str;
  10. size_t str_index = 0;
  11. while (1) {
  12. //如果遍历完了字符串str,就表示找到了
  13. if (str[str_index] == '\0') {
  14. //当str为NUL的时候,就表示匹配,直接返回下标
  15. return index_str;
  16. }
  17. //如果不相等就结束循环
  18. if (_str[find_index] != str[str_index]) {
  19. break;
  20. }
  21. find_index++;
  22. str_index++;
  23. }//循环结束
  24. }//表示不匹配了
  25. //如果不相等就继续向前查找
  26. index_str++;
  27. }
  28. return -1;
  29. }

如图,

要查找字符串,先定义一个变量index_str,用它来遍历_str来对比查找

如果遇到某个字符和要查找的字符串的首字符相等了,就再进一步判断,如何进一步判断呢?

  1. size_t find_index = index_str;
  2. size_t str_index = 0;

定义两个变量,find_index表示_str指向的要查找的开头部分,str_index表示要查找字符串str的开头部分

接下来循环遍历两个字符串,有两种情况

第一种,如果要查找的字符串str[str_index]走到了末尾,就表示完全匹配,直接返回下标,函数结束

第二种,字符不相等了,就结束循环

然后使用index_str继续遍历_str,如果遍历完毕循环结束,那么就返回-1表示查找失败

三、运算符重载

运算符重载,有的是添加字符或字符串的,如+,+=

1.+号运算符重载

①加一个字符

  1. String String::operator+(char ch) {
  2. //重新开辟一块内存空间,然后加上去再返回
  3. String tmp(_str);
  4. tmp.Insert(_size, ch);
  5. return tmp;
  6. }

②加一个字符串

  1. //字符串
  2. String String::operator+(const char* str) {
  3. String tmp(_str);
  4. tmp.Insert(_size, str);
  5. return tmp;
  6. }

2.+=运算符重载

①+=一个字符

  1. //字符
  2. String& String::operator+=(char ch) {
  3. Insert(_size, ch);
  4. return *this;
  5. }

②+=一个字符串

  1. //字符串
  2. String& String::operator+=(const char* str) {
  3. Insert(_size, str);
  4. return *this;
  5. }

注意,+=因为是给自身加,所以这里的返回值类型是String&引用

3.>运算符重载

  1. //比较
  2. bool String::operator>(const String& s) {
  3.     int i = 0;
  4.     while (_str[i] == s._str[i] && i < _size) {
  5.         //当两个字符串字符相等时进入循环
  6.         i++;
  7.     }
  8.     //不相等或遍历完了_str时退出循环
  9.     if (i == _size) {
  10.         //表示_str遍历完了,则肯定不大于
  11.         return false;
  12.     }
  13.     return _str[i] > s._str[i] ? true : false;
  14. }

这个大于需要分条件来讨论

第一种,遍历时要查找的字符串先走完,无论是否大雨,属于正常情况

第二种,遍历时_str先走完或者一起走完,这种情况都可以算作不大于,返回false,需要用一个变量监视是否是_str已遍历完

如上代码中就定义变量i就用来判断是否_str已遍历完,如果不进行判断的话,如果两个字符串相等,则会一直遍历,造成越界

4.==运算符重载

  1. bool String::operator==(const String& s) {
  2. int i = 0;
  3. while (_str[i] == s._str[i] && i < _size) {
  4. i++;
  5. }
  6. //遍历两个字符串,如果遍历完了,则表示相等
  7. if (i == _size && s._str[i] == '\0') {
  8. return true;
  9. }
  10. else {
  11. return false;
  12. }
  13. }

等于运算符重载是比较简单的,如果两个字符串一直相等,同时遍历结束,就表示两个相等,其他情况都是不相等

有了上面两个运算符重载,我们写其他的运算符重载时就可以调用它们了

5.其他的运算符重载

  1. bool String::operator>=(const String& s) {
  2. if (*this > s || *this == s) {
  3. return true;
  4. }
  5. return false;
  6. }
  7. bool String::operator<(const String& s) {
  8. if (!(*this >= s)) {
  9. return true;
  10. }
  11. return false;
  12. }
  13. bool String::operator<=(const String& s) {
  14. if (*this > s) {
  15. return false;
  16. }
  17. return true;
  18. }
  19. bool String::operator!=(const String& s) {
  20. if (*this == s) {
  21. return false;
  22. }
  23. return true;
  24. }

模拟实现String类的基本操作就这些了,然而深拷贝有时候是有些麻烦的,比如我需要一个字符串但是我不去修改它,那么我不必去再重新开辟一段内存空间,因为这样是很麻烦的

于是就有了写时拷贝的方法,具体请看我下一篇博客

我把模拟实现String类的源代码放在下面

String.h

  1. #pragma once
  2. //深浅拷贝
  3. #include <iostream>
  4. using namespace std;
  5. class String {
  6. public:
  7. //构造函数
  8. //函数的作用是构造一个String对象
  9. String(const char* str = "") {
  10. _size = strlen(str);
  11. //+1是因为要保存'\0'
  12. _str = new char[_size + 1];
  13. strcpy(_str, str);
  14. _capacity = _size + 1;
  15. }
  16. //拷贝构造函数
  17. String(const String& s)
  18. :_str(new char[strlen(s._str) + 1]){
  19. strcpy(_str, s._str);
  20. _size = s._size;
  21. _capacity = s._capacity;
  22. }
  23. //赋值运算符重载
  24. 传统写法
  25. //String& operator=(const String& s) {
  26. // if (this != &s) {
  27. // //传统写法是规规矩矩的
  28. // //先把旧的内存空间释放
  29. // delete[] _str;
  30. // //然后再申请新的内存空间
  31. // _str = new char[s._capacity];
  32. // //然后再拷贝过去
  33. // strcpy(_str, s._str);
  34. // _size = s._size;
  35. // _capacity = s._capacity;
  36. // return *this;
  37. // }
  38. //}
  39. 现代写法1
  40. //String& operator=(const String& s) {
  41. // if (this != &s) {
  42. // //创建一个临时变量,这里是用的拷贝构造函数,也可以用构造函数
  43. // String tmp(s);
  44. // //然后使用swap函数交换
  45. // swap(_str, tmp._str);
  46. // //tmp在函数结束时直接销毁
  47. // _size = tmp._size;
  48. // _capacity = tmp._capacity;
  49. // }
  50. // return *this;
  51. //}
  52. //
  53. 现代写法2
  54. //String& operator=(String s) {
  55. // if (this != &s) {
  56. // //传入进来的参数是传值的,函数结束后会销毁,跟上面的临时变量一样
  57. // swap(_str, s._str);
  58. // _size = s._size;
  59. // _capacity = s._capacity;
  60. // }
  61. // return *this;
  62. //}
  63. //
  64. //现代写法4
  65. void Swap(String& s) {
  66. char* tmp = s._str;
  67. s._str = _str;
  68. _str = tmp;
  69. }
  70. String& operator=(String& s) {
  71. if (this != &s) {
  72. //这种方法也是先创建一个临时变量
  73. String tmp(s._str);
  74. //然后把两个对象的_str指针交换
  75. Swap(tmp);
  76. //然后更新_size和_capacity
  77. _size = tmp._size;
  78. _capacity = tmp._capacity;
  79. }
  80. return *this;
  81. }
  82. //赋值运算符重载
  83. String& operator=(const char* str) {
  84. //删除旧的字符
  85. delete[] _str;
  86. //计算出字符串大小
  87. size_t str_size = strlen(str);
  88. //开辟内存空间
  89. _capacity = str_size + 1;
  90. _str = new char[_capacity];
  91. //复制
  92. strcpy(_str, str);
  93. _size = _capacity - 1;
  94. return *this;
  95. }
  96. //析构函数
  97. ~String() {
  98. delete[] _str;
  99. _str = NULL;
  100. _size = 0;
  101. _capacity = 0;
  102. cout << "~String" << endl;
  103. }
  104. //增删改查
  105. //扩容
  106. void Expand(size_t n);
  107. //增
  108. //尾插一个字符
  109. void PushBack(char ch);
  110. //尾插一个字符串
  111. void PushBack(const char* str);
  112. //头插一个字符
  113. void PushFront(char ch);
  114. //头插一个字符串
  115. void PushFront(const char* str);
  116. //尾删
  117. void PopBack();
  118. //在指定位置插入一个字符
  119. void Insert(size_t pos, char ch);
  120. //在指定位置插入一个字符串
  121. void Insert(size_t pos, const char* ch);
  122. //在指定位置之后删除长度为n的字符
  123. void Erase(size_t pos, size_t n = 1);
  124. char& operator[](size_t pos);
  125. //查找字符
  126. size_t Find(char ch);
  127. //查找字符串
  128. size_t Find(const char* str);
  129. //运算符重载
  130. //+
  131. //字符
  132. String operator+(char ch);
  133. //字符串
  134. String operator+(const char* str);
  135. //+=
  136. //字符
  137. String& operator+=(char ch);
  138. //字符串
  139. String& operator+=(const char* ch);
  140. //比较
  141. bool operator>(const String& s);
  142. bool operator>=(const String& s);
  143. bool operator<(const String& s);
  144. bool operator<=(const String& s);
  145. bool operator==(const String& s);
  146. bool operator!=(const String& s);
  147. //返回_size
  148. size_t String_size() {
  149. return _size;
  150. }
  151. //返回_capacity
  152. size_t String_capacity() {
  153. return _capacity;
  154. }
  155. //返回字符串
  156. char* String_str() {
  157. return _str;
  158. }
  159. //打印字符串
  160. void Show() {
  161. printf("%s\n", _str);
  162. }
  163. private:
  164. char* _str;
  165. size_t _size;
  166. size_t _capacity;
  167. };

String.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"String.h"
  3. //增删改查
  4. //扩容
  5. void String::Expand(size_t n) {
  6. //先申请大小为n的内存空间
  7. char* tmp = new char[n];
  8. //然后复制
  9. strcpy(tmp, _str);
  10. delete[] _str;
  11. _str = tmp;
  12. _capacity = n;
  13. }
  14. //增
  15. //尾插一个字符
  16. void String::PushBack(char ch) {
  17. if (_size + 1 >= _capacity) {
  18. //直接扩大两倍
  19. Expand(_capacity * 2);
  20. }
  21. _str[_size++] = ch;
  22. _str[_size] = '\0';
  23. }
  24. //尾插一个字符串
  25. void String::PushBack(const char* str) {
  26. size_t str_size = strlen(str);
  27. if (_size + str_size + 1 >= _capacity) {
  28. //如果空间不够就进行扩容
  29. _capacity += str_size;
  30. Expand(_capacity);
  31. //然后再拷贝
  32. while (*str != '\0') {
  33. _str[_size++] = *str++;
  34. }
  35. _str[_size] = '\0';
  36. }
  37. }
  38. //头插一个字符
  39. void String::PushFront(char ch) {
  40. if (_size + 1 >= _capacity) {
  41. //扩容
  42. Expand(_capacity * 2);
  43. }
  44. //要头插一个字符串,先把最后一个元素设置为\0
  45. _str[_size + 1] = '\0';
  46. for (int i = _size; i > 0; --i) {
  47. _str[i] = _str[i - 1];
  48. }
  49. _str[0] = ch;
  50. _size += 1;
  51. }
  52. //头插一个字符串
  53. void String::PushFront(const char* str) {
  54. //计算出要插入的字符串的长度
  55. size_t str_size = strlen(str);
  56. //如果要插入的字符串的长度加上原有的字符串长度大于已有的容量,就进行扩容
  57. if (str_size + _size + 1 >= _capacity) {
  58. _capacity = str_size + _size + 1;
  59. //扩容
  60. Expand(_capacity);
  61. }
  62. //_capacity表示容量,总共可以有多少个元素
  63. //事实上,我们可以认为_size是_str字符数组的最后一个元素了,因为它存的是'\0'
  64. _str[_size + str_size] = '\0';
  65. //所以,最后一个元素应该是在_capacity-2的位置
  66. size_t index_prev = _size;
  67. size_t index_last = _size + str_size - 1;
  68. while (index_prev) {
  69. _str[index_last--] = _str[--index_prev];
  70. }
  71. //index_prev当前一定是0
  72. while (*str != '\0') {
  73. _str[index_prev++] = *str++;
  74. }
  75. _size += str_size;
  76. }
  77. //尾删
  78. void String::PopBack() {
  79. //判断字符串是否为空字符串
  80. if (_size == 0) {
  81. printf("字符串已为空!\n");
  82. return;
  83. }
  84. _size--;
  85. _str[_size] = '\0';
  86. }
  87. //在指定位置插入一个字符
  88. void String::Insert(size_t pos, char ch) {
  89. if (pos > _size) {
  90. //可以等于_size,表示尾插了一个字符
  91. //为了让Insert更通用,就不调用pushback尾插了
  92. printf("pos位置传入错误!\n");
  93. return;
  94. }
  95. else {
  96. //pos的位置是正常的,可以插入
  97. if (_size + 1 >= _capacity) {
  98. //扩容
  99. Expand(_capacity*2);
  100. }
  101. //先把'\0'加上
  102. _str[_size + 1] = '\0';
  103. //先把pos之后的位置全部向后挪一个位置(包括pos)
  104. for (int i = _size; i > (int)pos; --i) {
  105. _str[i] = _str[i - 1];
  106. }
  107. //然后再在pos的位置插入字符
  108. _str[pos] = ch;
  109. _size++;
  110. }
  111. }
  112. //在指定位置插入一个字符串
  113. void String::Insert(size_t pos, const char* str) {
  114. //先判断参数是否正确
  115. if (pos > _size) {
  116. printf("pos位置传入错误!\n");
  117. return;
  118. }
  119. //求出要插入字符串的长度
  120. size_t str_size = strlen(str);
  121. if (str_size == 0) {
  122. //表示要插入的字符串是空字符串,直接返回
  123. return;
  124. }
  125. //然后判断_capaciy是否足够
  126. if (_size + str_size + 1 > _capacity) {
  127. //原本字符串中字符的个数加上要插入的字符串字符的个数再加上'\0'就是我们需要的空间
  128. _capacity = _size + str_size + 1;
  129. Expand(_capacity);
  130. }
  131. //把'\0'先加上
  132. int last_index = _size + str_size;
  133. _str[last_index--] = '\0';
  134. //然后把pos之后的字符串都向后挪str_size个位置
  135. for (int i = _size - 1; i >= (int)pos; --i) {
  136. _str[last_index--] = _str[i];
  137. }
  138. //然后从pos位置开始插入要插入的字符串
  139. while (*str != '\0') {
  140. _str[pos++] = *str++;
  141. }
  142. //更新size
  143. _size += str_size;
  144. }
  145. //在指定位置之后删除长度为n的字符
  146. void String::Erase(size_t pos, size_t n) {
  147. //判断参数的合法性
  148. //pos等于_size是"合法"的,但是没有意义,'\0'是不能删除的
  149. //所以这里直接就判断为不合法了
  150. if (pos >= _size) {
  151. //传入的位置pos不合法
  152. printf("pos位置传入不合法!\n");
  153. return;
  154. }
  155. //删除pos之后的n个元素
  156. if (pos + n < _size) {
  157. //删除pos之后n个字符
  158. size_t index_erase = pos + n;
  159. while (index_erase != _size) {
  160. _str[pos++] = _str[index_erase++];
  161. }
  162. }
  163. //当pos + n大于等于_size时,都是删除pos之后的所有元素
  164. _str[pos] = '\0';
  165. _size = pos;
  166. }
  167. //[]运算符重载
  168. char& String::operator[](size_t pos) {
  169. return _str[pos];
  170. }
  171. //查找字符
  172. size_t String::Find(char ch) {
  173. for (size_t i = 0; i < _size; ++i) {
  174. if (_str[i] == ch) {
  175. //找到了就返回下标
  176. return i;
  177. }
  178. }
  179. return -1;
  180. }
  181. //查找字符串
  182. size_t String::Find(const char* str) {
  183. size_t index_str = 0;
  184. //循环退出条件,要么查找到了,要么就是查找到了结尾也没有找到
  185. while (_str[index_str] != '\0') {
  186. if (_str[index_str] == *str) {
  187. //开头的字符相等了
  188. //可以匹配的查找了
  189. size_t find_index = index_str;
  190. size_t str_index = 0;
  191. while (1) {
  192. //如果遍历完了字符串str,就表示找到了
  193. if (str[str_index] == '\0') {
  194. //当str为NUL的时候,就表示匹配,直接返回下标
  195. return index_str;
  196. }
  197. //如果不相等就结束循环
  198. if (_str[find_index] != str[str_index]) {
  199. break;
  200. }
  201. find_index++;
  202. str_index++;
  203. }//循环结束
  204. }//表示不匹配了
  205. //如果不相等就继续向前查找
  206. index_str++;
  207. }
  208. return -1;
  209. }
  210. //运算符重载
  211. //+
  212. //字符
  213. String String::operator+(char ch) {
  214. //重新开辟一块内存空间,然后加上去再返回?
  215. String tmp(_str);
  216. tmp.Insert(_size, ch);
  217. return tmp;
  218. }
  219. //字符串
  220. String String::operator+(const char* str) {
  221. String tmp(_str);
  222. tmp.Insert(_size, str);
  223. return tmp;
  224. }
  225. //+=
  226. //字符
  227. String& String::operator+=(char ch) {
  228. Insert(_size, ch);
  229. return *this;
  230. }
  231. //字符串
  232. String& String::operator+=(const char* str) {
  233. Insert(_size, str);
  234. return *this;
  235. }
  236. //比较
  237. bool String::operator>(const String& s) {
  238. int i = 0;
  239. while (_str[i] == s._str[i] && i < _size) {
  240. //当两个字符串字符相等时进入循环
  241. i++;
  242. }
  243. //不相等或遍历完了_str时退出循环
  244. if (i == _size) {
  245. //表示_str遍历完了,则肯定不大于
  246. return false;
  247. }
  248. return _str[i] > s._str[i] ? true : false;
  249. }
  250. bool String::operator==(const String& s) {
  251. int i = 0;
  252. while (_str[i] == s._str[i] && i < _size) {
  253. i++;
  254. }
  255. //遍历两个字符串,如果遍历完了,则表示相等
  256. if (i == _size && s._str[i] == '\0') {
  257. return true;
  258. }
  259. else {
  260. return false;
  261. }
  262. }
  263. bool String::operator>=(const String& s) {
  264. if (*this > s || *this == s) {
  265. return true;
  266. }
  267. return false;
  268. }
  269. bool String::operator<(const String& s) {
  270. if (!(*this >= s)) {
  271. return true;
  272. }
  273. return false;
  274. }
  275. bool String::operator<=(const String& s) {
  276. if (*this > s) {
  277. return false;
  278. }
  279. return true;
  280. }
  281. bool String::operator!=(const String& s) {
  282. if (*this == s) {
  283. return false;
  284. }
  285. return true;
  286. }

test.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include "String.h"
  3. #define TESTHEAD printf("---------------------%s-------------------\n", __FUNCTION__)
  4. //测试构造函数
  5. void Test1() {
  6. TESTHEAD;
  7. //构造函数
  8. //正常构造
  9. String s("hello");
  10. s.Show();
  11. printf("expect 6, actual: _size = %d, _capacity = %d\n",
  12. s.String_size(), s.String_capacity());
  13. //缺省构造
  14. String s2;
  15. s2.Show();
  16. printf("expect 1, actual: _size = %d, _capacity = %d\n",
  17. s2.String_size(), s2.String_capacity());
  18. //拷贝构造函数
  19. //正常拷贝构造
  20. String s3 = s;
  21. s3.Show();
  22. printf("expect 6, actual: _size = %d, _capacity = %d\n",
  23. s3.String_size(), s3.String_capacity());
  24. //拷贝构造一个空字符串
  25. String s4(s2);
  26. s4.Show();
  27. printf("expect 1, actual: _size = %d, _capacity = %d\n",
  28. s4.String_size(), s4.String_capacity());
  29. String s5 = "hello world";
  30. s5.Show();
  31. String s6 = "";
  32. s6.Show();
  33. }
  34. //测试赋值运算符重载
  35. void Test2() {
  36. TESTHEAD;
  37. String s1("hello world");
  38. s1.Show();
  39. String s2("hello");
  40. s2 = s1;
  41. s2.Show();
  42. }
  43. //测试尾插
  44. void Test3() {
  45. TESTHEAD;
  46. //尾插字符
  47. String s1("hello");
  48. s1.Show();
  49. s1.PushBack(' ');
  50. s1.PushBack('w');
  51. s1.PushBack('o');
  52. s1.PushBack('r');
  53. s1.PushBack('l');
  54. s1.PushBack('d');
  55. s1.Show();
  56. //尾插字符串
  57. s1.PushBack(" hello world!");
  58. s1.Show();
  59. //尾插一个空字符串
  60. s1.PushBack("");
  61. s1.Show();
  62. }
  63. //测试头插
  64. void Test4() {
  65. TESTHEAD;
  66. //头插一个字符
  67. String s1("ello");
  68. s1.Show();
  69. s1.PushFront('h');
  70. s1.Show();
  71. s1.PushFront('e');
  72. s1.Show();
  73. //头插一个字符串
  74. String s2("world!");
  75. s2.Show();
  76. s2.PushFront("hello ");
  77. s2.Show();
  78. s2.PushFront("");
  79. s2.Show();
  80. }
  81. //测试尾删
  82. void Test5() {
  83. TESTHEAD;
  84. String s1("hello");
  85. s1.Show();
  86. s1.PopBack();
  87. s1.PopBack();
  88. s1.PopBack();
  89. s1.Show();
  90. s1.PopBack();
  91. s1.PopBack();
  92. s1.Show();
  93. s1.PopBack();
  94. }
  95. //测试insert函数
  96. void Test6() {
  97. TESTHEAD;
  98. //测试插入一个字符
  99. String s1("ello worl");
  100. s1.Show();
  101. //在开头位置插入
  102. s1.Insert(0, 'h');
  103. s1.Show();
  104. //在中间位置插入
  105. s1.Insert(5, '~');
  106. s1.Show();
  107. //在末尾插入
  108. s1.Insert(11, 'd');
  109. s1.Show();
  110. //测试插入字符串
  111. String s2("adefg");
  112. s2.Show();
  113. //在开头插入一个字符串
  114. s2.Insert(0, "hehe ");
  115. s2.Show();
  116. //在末尾插入一个字符串
  117. size_t pos = s2.String_size();
  118. s2.Insert(pos, "higklmnopq");
  119. s2.Show();
  120. //在中间插入一个字符串
  121. s2.Insert(6, "bc");
  122. s2.Show();
  123. //插入一个空字符串
  124. s2.Insert(0, "");
  125. s2.Insert(5, "");
  126. pos = s2.String_size();
  127. s2.Insert(pos, "");
  128. s2.Show();
  129. //传入非法的pos位置
  130. pos = 200;
  131. s2.Insert(pos, "hehe");
  132. }
  133. //测试删除一个元素
  134. void Test7() {
  135. TESTHEAD;
  136. String s1("hello world!");
  137. s1.Show();
  138. //传入pos位置正确,n小于pos之后的字符串长度
  139. s1.Erase(0, 6);
  140. s1.Show();
  141. //传入pos位置正确,n大于pos之后的字符串长度
  142. s1.Erase(0, 10);
  143. s1.Show();
  144. //传入pos位置正确,n等于pos之后的字符串长度
  145. String s2("hello");
  146. s2.Show();
  147. s2.Erase(2, 3);
  148. s2.Show();
  149. //传入pos的位置正确,n缺省
  150. s2.Erase(1);
  151. s2.Show();
  152. //传入pos的位置错误
  153. s2.Erase(2);
  154. }
  155. //测试Find
  156. void Test8() {
  157. TESTHEAD;
  158. //测试用例1,有多种只匹配前几个的
  159. String s1("abcbcdbcdef");
  160. char* find = "bcde";
  161. size_t pos = s1.Find(find);
  162. printf("expect 6, actual:%lu\n", pos);
  163. //测试用例2,要查找的刚好是源字符串
  164. String s2("abcd");
  165. char* find2 = "abcd";
  166. size_t pos2 = s2.Find(find2);
  167. printf("expecr 0, acutla:%lu\n", pos2);
  168. //测试用例3,查找的字符串刚好在_str的末尾
  169. String s3("asebcde");
  170. char* find3 = "bcde";
  171. size_t pos3 = s3.Find(find3);
  172. printf("expect 3, actual:%lu\n", pos3);
  173. //测试用例4,没有可查找的字符串
  174. String s4("abdfdsg");
  175. char* find4 = "saf";
  176. size_t pos4 = s4.Find(find4);
  177. printf("expect -1, actual: %d", (int)pos4);
  178. }
  179. //测试+ +=
  180. void Test9() {
  181. TESTHEAD;
  182. String s1("hello worl");
  183. s1.Show();
  184. String s2 = s1 + 'd';
  185. s2.Show();
  186. String s3;
  187. s3 = s2 + " hello world!";
  188. s3.Show();
  189. s1 += 'd';
  190. s1.Show();
  191. s1 += " hello world";
  192. s1.Show();
  193. }
  194. //测试 > >= < <= == !=
  195. void Test10() {
  196. TESTHEAD;
  197. String s1("hello world");
  198. String s2("hello worle");
  199. //测试>
  200. if (s2 > s1) {
  201. printf("s2 > s1\n");
  202. }
  203. s1 = "hello";
  204. s1.Show();
  205. s2 = "hello";
  206. s2.Show();
  207. if (s2 > s1 || s1 > s2) {
  208. //打印不出来是正常的
  209. printf("错误\n");
  210. }
  211. //测试==
  212. if (s1 == s2) {
  213. printf("s1 == s2\n");
  214. }
  215. s1 = "hellop";
  216. if (s1 == s2) {
  217. printf("错误\n");
  218. }
  219. //测试>=
  220. //测试<
  221. //测试<=
  222. //测试!=
  223. }
  224. void Test() {
  225. TESTHEAD;
  226. String s1("hehehe");
  227. String s2("hehehe");
  228. if (s2 > s1) {
  229. printf("right");
  230. }
  231. }
  232. int main() {
  233. //Test1();
  234. //Test2();
  235. //Test3();
  236. //Test4();
  237. //Test5();
  238. //Test6();
  239. //Test7();
  240. //Test8();
  241. //Test9();
  242. //Test10();
  243. //char* p1 = "";
  244. //char* p2 = "abcdefg";
  245. //swap(p1, p2);
  246. //cout << "p1:";
  247. //cout << p1 << endl;
  248. Test();
  249. system("pause");
  250. return 0;
  251. }


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/490421
推荐阅读
相关标签