C++中string类是个管理字符串的类,而在面试中,如何模拟实现string类也是一个考点,我在这里简单的实现一下如何模拟实现一个string类
模拟实现string类
class String{
public:
//成员函数
private:
//成员变量
char* _str;
size_t _size;
size_t _capacity;
};
模拟实现string类,我们需要一个成员变量_size来表示字符串的长度(不包含'\0'),还需要一个_capacity来表示申请的内存空间的大小
一、String类的六个默认成员函数
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;
}