赞
踩
C++中string类是个管理字符串的类,而在面试中,如何模拟实现string类也是一个考点,我在这里简单的实现一下如何模拟实现一个string类
- class String{
- public:
- //成员函数
- private:
- //成员变量
- char* _str;
- size_t _size;
- size_t _capacity;
- };
模拟实现string类,我们需要一个成员变量_size来表示字符串的长度(不包含'\0'),还需要一个_capacity来表示申请的内存空间的大小
1.构造函数
- //构造函数
- String(const char* str = "") {
- _size = strlen(str);
- //+1是因为要保存'\0'
- _str = new char[_size + 1];
- strcpy(_str, str);
- _capacity = _size + 1;
- }
构造函数是创建一个对象,然后给_str变量申请内存,再设置好_size和_capacity变量,示意图如下:
2.拷贝构造函数
- //拷贝构造函数
- String(const String& s)
- :_str(new char[strlen(s._str) + 1]){
- strcpy(_str, s._str);
- _size = s._size;
- _capacity = s._capacity;
- }
注意,这里的拷贝构造函数是深拷贝,给_str重新开辟一段内存空间,然后使用strcpy函数进行复制,示意图如下
3.赋值运算符重载
赋值运算符重载这里有好几种写法,有优有劣
①传统写法
- //传统写法
- String& operator=(const String& s) {
- if (this != &s) {
- //传统写法是规规矩矩的
- //先把旧的内存空间释放
- delete[] _str;
- //然后再申请新的内存空间
- _str = new char[s._capacity];
- //然后再拷贝过去
- strcpy(_str, s._str);
- _size = s._size;
- _capacity = s._capacity;
- return *this;
- }
- }
传统写法先释放旧的内存空间,然后再申请内存,再复制过去,这种方法是比较蠢的,下面介绍一种新方法
②现代写法1
- String& operator=(const String& s) {
- if (this != &s) {
- //创建一个临时变量,这里是用的拷贝构造函数,也可以用构造函数
- String tmp(s);
- //然后使用swap函数交换
- swap(_str, tmp._str);
- //tmp在函数结束时直接销毁
- _size = tmp._size;
- _capacity = tmp._capacity;
- }
- return *this;
- }
这里的方法是,创建一个临时对象,使用了
拷贝构造函数,创建成功后,如图
然后使用swap(一个库函数)函数交换临时对象和当前赋值的对象,最后再把_size和_capacity赋值过去
交换前:
交换后:
由于临时变量在函数结束时就直接销毁了(调用析构函数),所以不需要去对对象再手动释放,是比较简单的
③现代写法2
既然上面使用了拷贝构造函数构造一个临时对象,那么我们也可以使用构造函数来构造一个临时对象,代码如下:
- //现代写法1
- String& operator=(const String& s) {
- if (this != &s) {
- //创建一个临时变量,使用构造函数
- String tmp(s._str);
- //然后使用swap函数交换
- swap(_str, tmp._str);
- //tmp在函数结束时直接销毁
- _size = tmp._size;
- _capacity = tmp._capacity;
- }
- return *this;
- }
④现代写法3
上面两种现代写法,都是需要手动的创建一个临时变量,下面的方法不需要手动的去创建临时变量,代码如下:
- //现代写法2
- String& operator=(String s) {
- if (this != &s) {
- //传入进来的参数是传值的,函数结束后会销毁,跟上面的临时变量一样
- swap(_str, s._str);
- _size = s._size;
- _capacity = s._capacity;
- }
- return *this;
- }
这种方法,利用了c/c++中的函数传参时候是传值的原理,直接由编译器拷贝构造一份临时变量,拿来直接用就好了,很方便~
⑤现代写法4
我们可以自己实现一个Swap函数,只交换两个_str的指针
- void Swap(String& s) {
- char* tmp = s._str;
- s._str = _str;
- _str = tmp;
- }
- String& operator=(String& s) {
- if (this != &s) {
- //这种方法也是先创建一个临时变量
- String tmp(s._str);
- //然后把两个对象的_str指针交换
- Swap(tmp);
- //然后更新_size和_capacity
- _size = tmp._size;
- _capacity = tmp._capacity;
- }
- return *this;
- }

这种方法就像是,两个人都去做一件事,比如写作业,写完之后,你把别人的作业拿来直接写上你的名字,然后这个作业就是你的了(如果人家不拍你的话)。
赋值运算符重载还有一种,例如s1 = "abcd",传入的不是一个对象,而是一个字符串,代码如下:
- //赋值运算符重载
- String& operator=(const char* str) {
- //删除旧的字符
- delete[] _str;
- //计算出字符串大小
- size_t str_size = strlen(str);
- //开辟内存空间
- _capacity = str_size + 1;
- _str = new char[_capacity];
- //复制
- strcpy(_str, str);
- _size = _capacity - 1;
- return *this;
- }
也可以使用上面的现代写法
4.析构函数
- //析构函数
- ~String() {
- delete[] _str;
- _str = NULL;
- _size = 0;
- _capacity = 0;
- cout << "~String" << endl;
- }
增删改查不可避免的就需要改动字符串的大小,尤其是增,可能现有的_capacity不够,所以有时候需要对当前申请的内存空间进行扩容
扩容函数如下:
- //扩容
- void String::Expand(size_t n) {
- //先申请大小为n的内存空间
- char* tmp = new char[n];
- //然后复制
- strcpy(tmp, _str);
- delete[] _str;
- _str = tmp;
- _capacity = n;
- }
对现有内存空间进行扩容的话,需要重新申请足够的内存空间(设为n,n >= 0),再把数据拷贝过去,然后再把旧的内存空间释放,最后再让_str指向新申请的内存空间,这样扩容就完成了
接下来就看一下增删查改的具体函数:
1.增
①尾插一个字符
函数定义如下:
- //尾插一个字符
- void String::PushBack(char ch) {
- if (_size + 1 >= _capacity) {
- //直接扩大两倍
- Expand(_capacity * 2);
- }
- _str[_size++] = ch;
- _str[_size] = '\0';
- }
我们引入_size和_capacity这两个成员变量就是为了增删改查时使用的
_size指向字符数组的'\0'的位置,同事也代表了有多少这个数组有多少个字符
_capacity表示申请的内存空间的大小,由于数组是从0算起的,所以_capacity是指向申请的内存空间的最后 一个元素之后的
所以我们什么时候增容呢?
当_size + 1和_capacity相等时,即'\0'已经是申请的内存空间的最后一个元素时,如下图
把要插入的位置插在_size的位置,然后让_size指向下一个位置,向那个位置写入'\0'
②尾插一个字符串
函数代码如下:
- //尾插一个字符串
- void String::PushBack(const char* str) {
- size_t str_size = strlen(str);
- if (_size + str_size + 1 >= _capacity) {
- //如果空间不够就进行扩容
- _capacity += str_size;
- Expand(_capacity);
- //然后再拷贝
- while (*str != '\0') {
- _str[_size++] = *str++;
- }
- _str[_size] = '\0';
- }
- }
先算出要插入的字符串,然后再判断空间是否足够
如果不够,就进行扩容
头插字符和字符串是不划算的,最好也不要提供这样的接口,但是为了逻辑思维的完整,我在这里加上了
③头插一个字符
- //头插一个字符
- void String::PushFront(char ch) {
- if (_size + 1 >= _capacity) {
- //扩容
- Expand(_capacity * 2);
- }
- //要头插一个字符串,先把最后一个元素设置为\0
- _str[_size + 1] = '\0';
- for (int i = _size; i > 0; --i) {
- _str[i] = _str[i - 1];
- }
- _str[0] = ch;
- _size += 1;
- }
和上面一样,这个函数都是先判断是否需要扩容,如果觉得空间浪费的话,只需要扩容到_capacity + 1就可以了
④头插一个字符串
代码如下:
- //头插一个字符串
- void String::PushFront(const char* str) {
- //计算出要插入的字符串的长度
- size_t str_size = strlen(str);
- //如果要插入的字符串的长度加上原有的字符串长度大于已有的容量,就进行扩容
- if (str_size + _size + 1 >= _capacity) {
- _capacity = str_size + _size + 1;
- //扩容
- Expand(_capacity);
- }
- //_capacity表示容量,总共可以有多少个元素
- //事实上,我们可以认为_size是_str字符数组的最后一个元素了,因为它存的是'\0'
- _str[_size + str_size + 1] = '\0';
- //所以,最后一个元素应该是在_capacity-2的位置
- size_t index_prev = _size;
- size_t index_last = _size + str_size - 1;
- while (index_prev) {
- _str[index_last--] = _str[--index_prev];
- }
- //index_prev当前一定是0
- while (*str != '\0') {
- _str[index_prev++] = *str++;
- }
- _size += str_size;
- }

由代码可以看出,
第一步:先判断是否需要扩容
第二步:给插入成功后字符串的末尾加上'\0'(不一定限于第二步)
第三步:把当前字符串向后移,距离是str_size(给前面腾出位置,有点类似于顺序表的头插),然后再插入字符串
如图:
这里的边界条件要注意清除,比如'\0'的位置,比如把字符统一往后移动
⑤在指定位置插入一个字符
函数代码如下:
- //在指定位置插入一个字符
- void String::Insert(size_t pos, char ch) {
- if (pos > _size) {
- //可以等于_size,表示尾插了一个字符
- //为了让Insert更通用,就不调用pushback尾插了
- printf("pos位置传入错误!\n");
- return;
- }
- else {
- //pos的位置是正常的,可以插入
- if (_size + 1 >= _capacity) {
- //扩容
- Expand(_capacity*2);
- }
- //先把'\0'加上
- _str[_size + 1] = '\0';
- //先把pos之后的位置全部向后挪一个位置(包括pos)
- for (int i = _size; i > (int)pos; --i) {
- _str[i] = _str[i - 1];
- }
- //然后再在pos的位置插入字符
- _str[pos] = ch;
- _size++;
- }
- }

第一步先判断传入位置是否正确
第二步,如果正确了就开始插入,插入之前先判断是否需要扩容,然后把pos位置之后的字符都向后挪一位,注意:
- for (int i = _size; i > (int)pos; --i) {
- _str[i] = _str[i - 1];
- }
注意这里的for循环,判断条件那里,i不能设置为size_t类型,因为如果在开头位置(pos = 0)插入的话,会成为一个死循环
但是把i设置为int,这里又会出现隐式类型转换,i还是会被转换为size_t类型,还是会出错,所以最好写成这样
⑥在指定位置插入一个字符串
- //在指定位置插入一个字符串
- void String::Insert(size_t pos, const char* str) {
- //先判断参数是否正确
- if (pos > _size) {
- printf("pos位置传入错误!\n");
- return;
- }
- //求出要插入字符串的长度
- size_t str_size = strlen(str);
- if (str_size == 0) {
- //表示要插入的字符串是空字符串,直接返回
- return;
- }
- //然后判断_capaciy是否足够
- if (_size + str_size + 1 > _capacity) {
- //原本字符串中字符的个数加上要插入的字符串字符的个数再加上'\0'就是我们需要的空间
- _capacity = _size + str_size + 1;
- Expand(_capacity);
- }
- //把'\0'先加上
- int last_index = _size + str_size;
- _str[last_index--] = '\0';
- //然后把pos之后的字符串都向后挪str_size个位置
- for (int i = _size - 1; i >= (int)pos; --i) {
- _str[last_index--] = _str[i];
- }
- //然后从pos位置开始插入要插入的字符串
- while (*str != '\0') {
- _str[pos++] = *str++;
- }
- //更新size
- _size += str_size;
- }

这个和上面插入一个字符的区别不大~
2.删
①尾删一个字符
- //尾删
- void String::PopBack() {
- //判断字符串是否为空字符串
- if (_size == 0) {
- printf("字符串已为空!\n");
- return;
- }
- _size--;
- _str[_size] = '\0';
- }
只需要更改_size就好
②在指定位置之后删除长度为n的字符
- //在指定位置之后删除长度为n的字符
- void String::Erase(size_t pos, size_t n) {
- //判断参数的合法性
- //pos等于_size是"合法"的,但是没有意义,'\0'是不能删除的
- //所以这里直接就判断为不合法了
- if (pos >= _size) {
- //传入的位置pos不合法
- printf("pos位置传入不合法!\n");
- return;
- }
- //删除pos之后的n个元素
- if (pos + n < _size) {
- //删除pos之后n个字符
- size_t index_erase = pos + n;
- while (index_erase != _size) {
- _str[pos++] = _str[index_erase++];
- }
- }
- //当pos + n大于等于_size时,都是删除pos之后的所有元素
- _str[pos] = '\0';
- _size = pos;
- }

这里有两种情况
第一种,pos位置之后的字符个数大于n,这时候就全删了
第二种,pos位置之后的字符个数小于n,则我们需要在pos之后删除n个元素
删除pos之后n个元素,只需要把n个元素之后的剩下的元素挪到前面来,然后再给末尾加上'\0'
最后更新_size
3.改
- //[]运算符重载
- char& String::operator[](size_t pos) {
- return _str[pos];
- }
返回字符数组的字符的引用,这样就可以改了
4.查
①查找字符
- //查找字符
- size_t String::Find(char ch) {
- for (size_t i = 0; i < _size; ++i) {
- if (_str[i] == ch) {
- //找到了就返回下标
- return i;
- }
- }
- return -1;
- }
②查找字符串
- //查找字符串
- size_t String::Find(const char* str) {
- size_t index_str = 0;
- //循环退出条件,要么查找到了,要么就是查找到了结尾也没有找到
- while (_str[index_str] != '\0') {
- if (_str[index_str] == *str) {
- //开头的字符相等了
- //可以匹配的查找了
- size_t find_index = index_str;
- size_t str_index = 0;
- while (1) {
- //如果遍历完了字符串str,就表示找到了
- if (str[str_index] == '\0') {
- //当str为NUL的时候,就表示匹配,直接返回下标
- return index_str;
- }
- //如果不相等就结束循环
- if (_str[find_index] != str[str_index]) {
- break;
- }
- find_index++;
- str_index++;
- }//循环结束
- }//表示不匹配了
- //如果不相等就继续向前查找
- index_str++;
- }
- return -1;
- }

如图,
要查找字符串,先定义一个变量index_str,用它来遍历_str来对比查找
如果遇到某个字符和要查找的字符串的首字符相等了,就再进一步判断,如何进一步判断呢?
- size_t find_index = index_str;
- size_t str_index = 0;
定义两个变量,find_index表示_str指向的要查找的开头部分,str_index表示要查找字符串str的开头部分
接下来循环遍历两个字符串,有两种情况
第一种,如果要查找的字符串str[str_index]走到了末尾,就表示完全匹配,直接返回下标,函数结束
第二种,字符不相等了,就结束循环
然后使用index_str继续遍历_str,如果遍历完毕循环结束,那么就返回-1表示查找失败
运算符重载,有的是添加字符或字符串的,如+,+=
1.+号运算符重载
①加一个字符
- String String::operator+(char ch) {
- //重新开辟一块内存空间,然后加上去再返回
- String tmp(_str);
- tmp.Insert(_size, ch);
- return tmp;
- }
②加一个字符串
- //字符串
- String String::operator+(const char* str) {
- String tmp(_str);
- tmp.Insert(_size, str);
- return tmp;
- }
2.+=运算符重载
①+=一个字符
- //字符
- String& String::operator+=(char ch) {
- Insert(_size, ch);
- return *this;
- }
②+=一个字符串
- //字符串
- String& String::operator+=(const char* str) {
- Insert(_size, str);
- return *this;
- }
注意,+=因为是给自身加,所以这里的返回值类型是String&引用
3.>运算符重载
- //比较
- bool String::operator>(const String& s) {
- int i = 0;
- while (_str[i] == s._str[i] && i < _size) {
- //当两个字符串字符相等时进入循环
- i++;
- }
- //不相等或遍历完了_str时退出循环
- if (i == _size) {
- //表示_str遍历完了,则肯定不大于
- return false;
- }
- return _str[i] > s._str[i] ? true : false;
- }
这个大于需要分条件来讨论
第一种,遍历时要查找的字符串先走完,无论是否大雨,属于正常情况
第二种,遍历时_str先走完或者一起走完,这种情况都可以算作不大于,返回false,需要用一个变量监视是否是_str已遍历完
如上代码中就定义变量i就用来判断是否_str已遍历完,如果不进行判断的话,如果两个字符串相等,则会一直遍历,造成越界
4.==运算符重载
- bool String::operator==(const String& s) {
- int i = 0;
- while (_str[i] == s._str[i] && i < _size) {
- i++;
- }
- //遍历两个字符串,如果遍历完了,则表示相等
- if (i == _size && s._str[i] == '\0') {
- return true;
- }
- else {
- return false;
- }
- }
等于运算符重载是比较简单的,如果两个字符串一直相等,同时遍历结束,就表示两个相等,其他情况都是不相等
有了上面两个运算符重载,我们写其他的运算符重载时就可以调用它们了
5.其他的运算符重载
- bool String::operator>=(const String& s) {
- if (*this > s || *this == s) {
- return true;
- }
- return false;
- }
-
- bool String::operator<(const String& s) {
- if (!(*this >= s)) {
- return true;
- }
- return false;
- }
-
- bool String::operator<=(const String& s) {
- if (*this > s) {
- return false;
- }
- return true;
- }
-
- bool String::operator!=(const String& s) {
- if (*this == s) {
- return false;
- }
- return true;
- }

模拟实现String类的基本操作就这些了,然而深拷贝有时候是有些麻烦的,比如我需要一个字符串但是我不去修改它,那么我不必去再重新开辟一段内存空间,因为这样是很麻烦的
于是就有了写时拷贝的方法,具体请看我下一篇博客
我把模拟实现String类的源代码放在下面
String.h
- #pragma once
-
- //深浅拷贝
- #include <iostream>
-
- using namespace std;
-
- class String {
- public:
- //构造函数
- //函数的作用是构造一个String对象
- String(const char* str = "") {
- _size = strlen(str);
- //+1是因为要保存'\0'
- _str = new char[_size + 1];
- strcpy(_str, str);
- _capacity = _size + 1;
- }
- //拷贝构造函数
- String(const String& s)
- :_str(new char[strlen(s._str) + 1]){
- strcpy(_str, s._str);
- _size = s._size;
- _capacity = s._capacity;
- }
-
- //赋值运算符重载
-
- 传统写法
- //String& operator=(const String& s) {
- // if (this != &s) {
- // //传统写法是规规矩矩的
- // //先把旧的内存空间释放
- // delete[] _str;
- // //然后再申请新的内存空间
- // _str = new char[s._capacity];
- // //然后再拷贝过去
- // strcpy(_str, s._str);
- // _size = s._size;
- // _capacity = s._capacity;
- // return *this;
- // }
- //}
-
-
-
- 现代写法1
- //String& operator=(const String& s) {
- // if (this != &s) {
- // //创建一个临时变量,这里是用的拷贝构造函数,也可以用构造函数
- // String tmp(s);
- // //然后使用swap函数交换
- // swap(_str, tmp._str);
- // //tmp在函数结束时直接销毁
- // _size = tmp._size;
- // _capacity = tmp._capacity;
- // }
- // return *this;
- //}
- //
-
-
- 现代写法2
- //String& operator=(String s) {
- // if (this != &s) {
- // //传入进来的参数是传值的,函数结束后会销毁,跟上面的临时变量一样
- // swap(_str, s._str);
- // _size = s._size;
- // _capacity = s._capacity;
- // }
- // return *this;
- //}
- //
-
- //现代写法4
- void Swap(String& s) {
- char* tmp = s._str;
- s._str = _str;
- _str = tmp;
- }
- String& operator=(String& s) {
- if (this != &s) {
- //这种方法也是先创建一个临时变量
- String tmp(s._str);
- //然后把两个对象的_str指针交换
- Swap(tmp);
- //然后更新_size和_capacity
- _size = tmp._size;
- _capacity = tmp._capacity;
- }
- return *this;
- }
-
- //赋值运算符重载
- String& operator=(const char* str) {
- //删除旧的字符
- delete[] _str;
-
- //计算出字符串大小
- size_t str_size = strlen(str);
- //开辟内存空间
- _capacity = str_size + 1;
- _str = new char[_capacity];
- //复制
- strcpy(_str, str);
- _size = _capacity - 1;
- return *this;
- }
-
- //析构函数
- ~String() {
- delete[] _str;
- _str = NULL;
- _size = 0;
- _capacity = 0;
- cout << "~String" << endl;
- }
-
- //增删改查
- //扩容
- void Expand(size_t n);
- //增
- //尾插一个字符
- void PushBack(char ch);
- //尾插一个字符串
- void PushBack(const char* str);
- //头插一个字符
- void PushFront(char ch);
- //头插一个字符串
- void PushFront(const char* str);
- //尾删
- void PopBack();
- //在指定位置插入一个字符
- void Insert(size_t pos, char ch);
- //在指定位置插入一个字符串
- void Insert(size_t pos, const char* ch);
- //在指定位置之后删除长度为n的字符
- void Erase(size_t pos, size_t n = 1);
-
- char& operator[](size_t pos);
-
- //查找字符
- size_t Find(char ch);
- //查找字符串
- size_t Find(const char* str);
-
- //运算符重载
- //+
- //字符
- String operator+(char ch);
- //字符串
- String operator+(const char* str);
- //+=
- //字符
- String& operator+=(char ch);
- //字符串
- String& operator+=(const char* ch);
-
- //比较
- bool operator>(const String& s);
- bool operator>=(const String& s);
- bool operator<(const String& s);
- bool operator<=(const String& s);
- bool operator==(const String& s);
- bool operator!=(const String& s);
-
-
- //返回_size
- size_t String_size() {
- return _size;
- }
- //返回_capacity
- size_t String_capacity() {
- return _capacity;
- }
- //返回字符串
- char* String_str() {
- return _str;
- }
- //打印字符串
- void Show() {
- printf("%s\n", _str);
- }
- private:
- char* _str;
- size_t _size;
- size_t _capacity;
- };

String.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include"String.h"
-
- //增删改查
- //扩容
- void String::Expand(size_t n) {
- //先申请大小为n的内存空间
- char* tmp = new char[n];
- //然后复制
- strcpy(tmp, _str);
- delete[] _str;
- _str = tmp;
- _capacity = n;
- }
- //增
- //尾插一个字符
- void String::PushBack(char ch) {
- if (_size + 1 >= _capacity) {
- //直接扩大两倍
- Expand(_capacity * 2);
- }
- _str[_size++] = ch;
- _str[_size] = '\0';
- }
-
- //尾插一个字符串
- void String::PushBack(const char* str) {
- size_t str_size = strlen(str);
- if (_size + str_size + 1 >= _capacity) {
- //如果空间不够就进行扩容
- _capacity += str_size;
- Expand(_capacity);
- //然后再拷贝
- while (*str != '\0') {
- _str[_size++] = *str++;
- }
- _str[_size] = '\0';
- }
- }
-
- //头插一个字符
- void String::PushFront(char ch) {
- if (_size + 1 >= _capacity) {
- //扩容
- Expand(_capacity * 2);
- }
- //要头插一个字符串,先把最后一个元素设置为\0
- _str[_size + 1] = '\0';
- for (int i = _size; i > 0; --i) {
- _str[i] = _str[i - 1];
- }
- _str[0] = ch;
- _size += 1;
- }
-
- //头插一个字符串
- void String::PushFront(const char* str) {
- //计算出要插入的字符串的长度
- size_t str_size = strlen(str);
- //如果要插入的字符串的长度加上原有的字符串长度大于已有的容量,就进行扩容
- if (str_size + _size + 1 >= _capacity) {
- _capacity = str_size + _size + 1;
- //扩容
- Expand(_capacity);
- }
- //_capacity表示容量,总共可以有多少个元素
- //事实上,我们可以认为_size是_str字符数组的最后一个元素了,因为它存的是'\0'
- _str[_size + str_size] = '\0';
- //所以,最后一个元素应该是在_capacity-2的位置
- size_t index_prev = _size;
- size_t index_last = _size + str_size - 1;
- while (index_prev) {
- _str[index_last--] = _str[--index_prev];
- }
- //index_prev当前一定是0
- while (*str != '\0') {
- _str[index_prev++] = *str++;
- }
- _size += str_size;
- }
-
- //尾删
- void String::PopBack() {
- //判断字符串是否为空字符串
- if (_size == 0) {
- printf("字符串已为空!\n");
- return;
- }
- _size--;
- _str[_size] = '\0';
- }
-
- //在指定位置插入一个字符
- void String::Insert(size_t pos, char ch) {
- if (pos > _size) {
- //可以等于_size,表示尾插了一个字符
- //为了让Insert更通用,就不调用pushback尾插了
- printf("pos位置传入错误!\n");
- return;
- }
- else {
- //pos的位置是正常的,可以插入
- if (_size + 1 >= _capacity) {
- //扩容
- Expand(_capacity*2);
- }
- //先把'\0'加上
- _str[_size + 1] = '\0';
- //先把pos之后的位置全部向后挪一个位置(包括pos)
- for (int i = _size; i > (int)pos; --i) {
- _str[i] = _str[i - 1];
- }
- //然后再在pos的位置插入字符
- _str[pos] = ch;
- _size++;
- }
- }
-
- //在指定位置插入一个字符串
- void String::Insert(size_t pos, const char* str) {
- //先判断参数是否正确
- if (pos > _size) {
- printf("pos位置传入错误!\n");
- return;
- }
- //求出要插入字符串的长度
- size_t str_size = strlen(str);
- if (str_size == 0) {
- //表示要插入的字符串是空字符串,直接返回
- return;
- }
- //然后判断_capaciy是否足够
- if (_size + str_size + 1 > _capacity) {
- //原本字符串中字符的个数加上要插入的字符串字符的个数再加上'\0'就是我们需要的空间
- _capacity = _size + str_size + 1;
- Expand(_capacity);
- }
- //把'\0'先加上
- int last_index = _size + str_size;
- _str[last_index--] = '\0';
- //然后把pos之后的字符串都向后挪str_size个位置
- for (int i = _size - 1; i >= (int)pos; --i) {
- _str[last_index--] = _str[i];
- }
- //然后从pos位置开始插入要插入的字符串
- while (*str != '\0') {
- _str[pos++] = *str++;
- }
- //更新size
- _size += str_size;
- }
-
- //在指定位置之后删除长度为n的字符
- void String::Erase(size_t pos, size_t n) {
- //判断参数的合法性
- //pos等于_size是"合法"的,但是没有意义,'\0'是不能删除的
- //所以这里直接就判断为不合法了
- if (pos >= _size) {
- //传入的位置pos不合法
- printf("pos位置传入不合法!\n");
- return;
- }
- //删除pos之后的n个元素
- if (pos + n < _size) {
- //删除pos之后n个字符
- size_t index_erase = pos + n;
- while (index_erase != _size) {
- _str[pos++] = _str[index_erase++];
- }
- }
- //当pos + n大于等于_size时,都是删除pos之后的所有元素
- _str[pos] = '\0';
- _size = pos;
- }
-
- //[]运算符重载
- char& String::operator[](size_t pos) {
- return _str[pos];
- }
-
- //查找字符
- size_t String::Find(char ch) {
- for (size_t i = 0; i < _size; ++i) {
- if (_str[i] == ch) {
- //找到了就返回下标
- return i;
- }
- }
- return -1;
- }
-
- //查找字符串
- size_t String::Find(const char* str) {
- size_t index_str = 0;
- //循环退出条件,要么查找到了,要么就是查找到了结尾也没有找到
- while (_str[index_str] != '\0') {
- if (_str[index_str] == *str) {
- //开头的字符相等了
- //可以匹配的查找了
- size_t find_index = index_str;
- size_t str_index = 0;
- while (1) {
- //如果遍历完了字符串str,就表示找到了
- if (str[str_index] == '\0') {
- //当str为NUL的时候,就表示匹配,直接返回下标
- return index_str;
- }
- //如果不相等就结束循环
- if (_str[find_index] != str[str_index]) {
- break;
- }
- find_index++;
- str_index++;
- }//循环结束
- }//表示不匹配了
- //如果不相等就继续向前查找
- index_str++;
- }
- return -1;
- }
-
- //运算符重载
- //+
- //字符
- String String::operator+(char ch) {
- //重新开辟一块内存空间,然后加上去再返回?
- String tmp(_str);
- tmp.Insert(_size, ch);
- return tmp;
- }
-
- //字符串
- String String::operator+(const char* str) {
- String tmp(_str);
- tmp.Insert(_size, str);
- return tmp;
- }
-
- //+=
- //字符
- String& String::operator+=(char ch) {
- Insert(_size, ch);
- return *this;
- }
-
- //字符串
- String& String::operator+=(const char* str) {
- Insert(_size, str);
- return *this;
- }
-
- //比较
- bool String::operator>(const String& s) {
- int i = 0;
- while (_str[i] == s._str[i] && i < _size) {
- //当两个字符串字符相等时进入循环
- i++;
- }
- //不相等或遍历完了_str时退出循环
- if (i == _size) {
- //表示_str遍历完了,则肯定不大于
- return false;
- }
- return _str[i] > s._str[i] ? true : false;
- }
-
- bool String::operator==(const String& s) {
- int i = 0;
- while (_str[i] == s._str[i] && i < _size) {
- i++;
- }
- //遍历两个字符串,如果遍历完了,则表示相等
- if (i == _size && s._str[i] == '\0') {
- return true;
- }
- else {
- return false;
- }
- }
-
- bool String::operator>=(const String& s) {
- if (*this > s || *this == s) {
- return true;
- }
- return false;
- }
-
- bool String::operator<(const String& s) {
- if (!(*this >= s)) {
- return true;
- }
- return false;
- }
-
- bool String::operator<=(const String& s) {
- if (*this > s) {
- return false;
- }
- return true;
- }
-
- bool String::operator!=(const String& s) {
- if (*this == s) {
- return false;
- }
- return true;
- }
-
-

test.c
- #define _CRT_SECURE_NO_WARNINGS 1
- #include "String.h"
-
- #define TESTHEAD printf("---------------------%s-------------------\n", __FUNCTION__)
-
- //测试构造函数
- void Test1() {
- TESTHEAD;
- //构造函数
- //正常构造
- String s("hello");
- s.Show();
- printf("expect 6, actual: _size = %d, _capacity = %d\n",
- s.String_size(), s.String_capacity());
- //缺省构造
- String s2;
- s2.Show();
- printf("expect 1, actual: _size = %d, _capacity = %d\n",
- s2.String_size(), s2.String_capacity());
-
- //拷贝构造函数
- //正常拷贝构造
- String s3 = s;
- s3.Show();
- printf("expect 6, actual: _size = %d, _capacity = %d\n",
- s3.String_size(), s3.String_capacity());
- //拷贝构造一个空字符串
- String s4(s2);
- s4.Show();
- printf("expect 1, actual: _size = %d, _capacity = %d\n",
- s4.String_size(), s4.String_capacity());
- String s5 = "hello world";
- s5.Show();
- String s6 = "";
- s6.Show();
- }
-
- //测试赋值运算符重载
- void Test2() {
- TESTHEAD;
- String s1("hello world");
- s1.Show();
- String s2("hello");
- s2 = s1;
- s2.Show();
- }
-
- //测试尾插
- void Test3() {
- TESTHEAD;
- //尾插字符
- String s1("hello");
- s1.Show();
- s1.PushBack(' ');
- s1.PushBack('w');
- s1.PushBack('o');
- s1.PushBack('r');
- s1.PushBack('l');
- s1.PushBack('d');
- s1.Show();
- //尾插字符串
- s1.PushBack(" hello world!");
- s1.Show();
- //尾插一个空字符串
- s1.PushBack("");
- s1.Show();
- }
-
- //测试头插
- void Test4() {
- TESTHEAD;
- //头插一个字符
- String s1("ello");
- s1.Show();
- s1.PushFront('h');
- s1.Show();
- s1.PushFront('e');
- s1.Show();
- //头插一个字符串
- String s2("world!");
- s2.Show();
- s2.PushFront("hello ");
- s2.Show();
- s2.PushFront("");
- s2.Show();
- }
-
- //测试尾删
- void Test5() {
- TESTHEAD;
- String s1("hello");
- s1.Show();
- s1.PopBack();
- s1.PopBack();
- s1.PopBack();
- s1.Show();
- s1.PopBack();
- s1.PopBack();
- s1.Show();
- s1.PopBack();
- }
-
- //测试insert函数
- void Test6() {
- TESTHEAD;
- //测试插入一个字符
- String s1("ello worl");
- s1.Show();
- //在开头位置插入
- s1.Insert(0, 'h');
- s1.Show();
- //在中间位置插入
- s1.Insert(5, '~');
- s1.Show();
- //在末尾插入
- s1.Insert(11, 'd');
- s1.Show();
-
- //测试插入字符串
- String s2("adefg");
- s2.Show();
- //在开头插入一个字符串
- s2.Insert(0, "hehe ");
- s2.Show();
- //在末尾插入一个字符串
- size_t pos = s2.String_size();
- s2.Insert(pos, "higklmnopq");
- s2.Show();
- //在中间插入一个字符串
- s2.Insert(6, "bc");
- s2.Show();
- //插入一个空字符串
- s2.Insert(0, "");
- s2.Insert(5, "");
- pos = s2.String_size();
- s2.Insert(pos, "");
- s2.Show();
- //传入非法的pos位置
- pos = 200;
- s2.Insert(pos, "hehe");
- }
-
- //测试删除一个元素
- void Test7() {
- TESTHEAD;
- String s1("hello world!");
- s1.Show();
- //传入pos位置正确,n小于pos之后的字符串长度
- s1.Erase(0, 6);
- s1.Show();
- //传入pos位置正确,n大于pos之后的字符串长度
- s1.Erase(0, 10);
- s1.Show();
- //传入pos位置正确,n等于pos之后的字符串长度
- String s2("hello");
- s2.Show();
- s2.Erase(2, 3);
- s2.Show();
- //传入pos的位置正确,n缺省
- s2.Erase(1);
- s2.Show();
- //传入pos的位置错误
- s2.Erase(2);
-
- }
-
- //测试Find
- void Test8() {
- TESTHEAD;
- //测试用例1,有多种只匹配前几个的
- String s1("abcbcdbcdef");
- char* find = "bcde";
- size_t pos = s1.Find(find);
- printf("expect 6, actual:%lu\n", pos);
-
- //测试用例2,要查找的刚好是源字符串
- String s2("abcd");
- char* find2 = "abcd";
- size_t pos2 = s2.Find(find2);
- printf("expecr 0, acutla:%lu\n", pos2);
-
- //测试用例3,查找的字符串刚好在_str的末尾
- String s3("asebcde");
- char* find3 = "bcde";
- size_t pos3 = s3.Find(find3);
- printf("expect 3, actual:%lu\n", pos3);
-
- //测试用例4,没有可查找的字符串
- String s4("abdfdsg");
- char* find4 = "saf";
- size_t pos4 = s4.Find(find4);
- printf("expect -1, actual: %d", (int)pos4);
- }
-
- //测试+ +=
- void Test9() {
- TESTHEAD;
- String s1("hello worl");
- s1.Show();
- String s2 = s1 + 'd';
- s2.Show();
- String s3;
- s3 = s2 + " hello world!";
- s3.Show();
-
- s1 += 'd';
- s1.Show();
-
- s1 += " hello world";
- s1.Show();
- }
-
- //测试 > >= < <= == !=
- void Test10() {
- TESTHEAD;
- String s1("hello world");
- String s2("hello worle");
- //测试>
- if (s2 > s1) {
- printf("s2 > s1\n");
- }
- s1 = "hello";
- s1.Show();
- s2 = "hello";
- s2.Show();
- if (s2 > s1 || s1 > s2) {
- //打印不出来是正常的
- printf("错误\n");
- }
- //测试==
- if (s1 == s2) {
- printf("s1 == s2\n");
- }
- s1 = "hellop";
- if (s1 == s2) {
- printf("错误\n");
- }
- //测试>=
- //测试<
- //测试<=
- //测试!=
- }
-
- void Test() {
- TESTHEAD;
- String s1("hehehe");
- String s2("hehehe");
- if (s2 > s1) {
- printf("right");
- }
- }
-
- int main() {
- //Test1();
- //Test2();
- //Test3();
- //Test4();
- //Test5();
- //Test6();
- //Test7();
- //Test8();
- //Test9();
- //Test10();
- //char* p1 = "";
- //char* p2 = "abcdefg";
- //swap(p1, p2);
- //cout << "p1:";
- //cout << p1 << endl;
- Test();
- system("pause");
- return 0;
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。