【C++基础】模拟实现string类

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;
}


  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值