什么是string类

string类是C++STL中提供的字符串类,用于高效的处理字符串类型数据,其包含在头文件<string>中

string的简单实现

构造函数和析构函数

为了防止与STL中的string类冲突(当我们引入命名空间std时),我们将模拟string置于命名空间namespace X中,如下所示

namespace X {
	class string {
	public:
		string(const char* str = "") {

		}
		~string() {

		}
	private:
		char* _str;	
    	// 定义一个char*数组用以存储字符串数据
		size_t _capacity;	
        // 记录最大容量
		size_t _size;	
        // 记录字符串长度
	};
}

下面我们将实现简单的构造函数和析构函数:

string(const char* str = ""){	
  // 以const char*为初始值进行的构造函数
	_str = new char[strlen(str) + 1];
	strcpy(_str, str);
	this->_size = strlen(str);
	_capacity = _size;
}

string(const string& s)	
  // 以string为初始值进行的拷贝构造函数
	:_str(nullptr)	
  // 赋予初始值
	,_size(0)
	,_capacity(0)
{
	char* temp = new char[strlen(s._str) + 1];	
    // 开辟一个新内存,存储临时变量
	strcpy(temp , s._str);																	
	if (_str) {
		delete[]_str;	
    // 释放_str指向的原有空间,并指向新空间
	}
	_str = temp;	
    // 重新初始化数据,使之与新的string保持一致
	this->_capacity = s._capacity;													
	this->_size = s._size;
}		// 注意,这里的拷贝需要使用深拷贝,否则会导致delete错误

~string() {	
  // 析构函数,用于释放内存,防止内存泄露
	delete[]_str;
	_str = nullptr;
}

常见函数接口和运算符重载

实现了构造和析构函数之后,我们将开始实现一些string类常见的函数接口:

push_back

void push_back(const char data) {
	if (_size == _capacity) {
		size_t new_capacity = _capacity == 0 ? 2 : _capacity * 2;	
        // 判断_capacity是否为0,并进行扩容
		char* temp = new char[new_capacity + 1];	
    	// 开辟新空间

		strcpy(temp, this->_str);	
        // 如果超过容量,拷贝数据到临时空间中
		if (_str) {													
			delete[]_str;	
           	// 释放原有空间
		}
		_str = temp;	
        // 将临时空间指向_str
		_capacity = new_capacity;
	}
	_str[_size] = data;	
    // 进行数据插入
	_size++;	
    // 数据量递增
}

同拷贝构造函数类似,都是通过在堆上开辟一个临时空间,用来对原来空间进行扩容,如果本身足够大,直接进行插入即可

append

同push_back,只不过从字符变成了字符串,一样先检查是否要扩容,只不过检查条件改变,其他类似,不予赘述

string& append(const char* str) {
	size_t len = strlen(str);
	if (_size + len >= _capacity) {
		size_t new_capacity = _size + len;
		char* temp = new char[new_capacity + 1];
		strcpy(temp, _str);
		if (this->_str) {
			delete[]_str;
		}
		_str = temp;
		_capacity = new_capacity;

	}
	strcpy(_str + _size, str);
	_size += strlen(str);
	return *this;
}

这里说明一下+=的运算符重载,运算符重载的关键字为operator,下面实现一下+=的运算符重载:

分为两个重载版本,字符串和string类型

string类型:

string& operator+=(const string& str) {
	this->append(str._str);	
  // 调用了append函数,使用了string类中的_str成员,该成员为char*类型
	return *this;
}

字符串类型:

string& operator+=(const char* s) {
	this->append(s);	
  // 依然使用了append函数,这次直接通过const char* s进行传参
	return *this;
}

这里说一下为什么类中的函数和函数之间可以互相调用,因为编译器在编译时,会先处理函数的声明,而不会去管它的定义,所以当进入函数内部实现定义时,就不会出现某个函数内部的函数未声明的情况

resize

不同于上述的函数,这里的resize分为两种情况:

声明的size大于0还是小于0:

void resize(size_t target) {
	if (target <= this->_size) {	
    // 如果小于当前的_size,只需要将该位置改成'\0'即可
		_str[target] = '\0';
		_size = target;	// 重置_size的值
	}
	else {
		char* temp = new char[target + 1]();	
        // 否则,就需要重新申请空间来重新放置数据
		strcpy(temp, _str);	
        // 操作同上述的内存申请,这里不予赘述
		if (_str) {
			delete[]_str;
		}
		_str = temp;
		_capacity = target;
	}
}

insert

同上面的思路类似,稍微改变即可

string& insert(size_t pos, const char* s) {
	char* temp = new char[_size]();
	strcpy(temp, _str + pos);
	this->resize(pos);
	*this += s;	
  // 在进行了+=运算符重载后,建议使用+=
	*this += temp;	
  // append 函数还要考虑参数的类型,不推荐
	return *this;
}

erase

string& erase(size_t pos = 0 , size_t len = std::string::npos) {
	char* temp = new char[_capacity]();
	strcpy(temp, _str + pos + len - 1);
	this->resize(pos);
	*this += temp;
	this->_size = _size - len;
	return *this;
}

赋值运算符的重载(operator=)

string& operator=(const string& str) {
	char* temp = new char[str._capacity + 1];
	strcpy(temp, str._str);
	if (this->_str) {
		delete[]_str;
	}
	this->_str = temp;
	this->_capacity = str._capacity;
	this->_size = str._size;
	return *this;
}

其他一些函数的重载

char operator[](size_t i) {
	assert(i < _size && i >= 0);
	return _str[i];
}

size_t size() {
	return _size;
}

size_t capacity() {
	return _capacity;
}

迭代器的实现

迭代器(iterator):分为三种类型

  1. 普通迭代器(iterator)
  2. const迭代器(const_iterator)
  3. 反向迭代器(reverse_iterator)

在string类中,我们可以使用指针来实现这一操作(只实现前两个):

typedef char* iterator;
typedef const char* const_iterator;

iterator还包含几个成员函数,下面分别实现一下:

typedef char* iterator;
typedef const char* const_iterator;

iterator begin() {
	return _str;
}
iterator end() {
	return _str + _size;
}

const_iterator const_begin() const {
	return _str;
}
const_iterator const_end() const {
	return _str + _size;
}

完整代码 + 测试用例

#define _CRT_SECURE_NO_WARNINGS							
// 我使用的Visual Studio 2022 会对strcpy报错,加上上述代码即可

#include <iostream>
#include <string.h>
#include <assert.h>


namespace X {
	class string {
		friend std::ostream& operator<<(std::ostream& out, X::string s);

	public:

		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin() {
			return _str;
		}
		iterator end() {
			return _str + _size;
		}

		const_iterator const_begin() const {
			return _str;
		}
		const_iterator const_end() const {
			return _str + _size;
		}

		string(const char* str = ""){
			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
			this->_size = strlen(str);
			_capacity = _size;
		}

		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			char* temp = new char[strlen(s._str) + 1];
			strcpy(temp , s._str);
			if (_str) {
				delete[]_str;
			}
			_str = temp;
			this->_capacity = s._capacity;
			this->_size = s._size;
		}

		~string() {
			delete[]_str;
			_str = nullptr;
		}

		char operator[](size_t i) {
			assert(i < _size && i >= 0);
			return _str[i];
		}

		size_t size() {
			return _size;
		}

		size_t capacity() {
			return _capacity;
		}

		void push_back(const char data) {
			if (_size == _capacity) {
				size_t new_capacity = _capacity == 0 ? 2 : _capacity * 2;
				char* temp = new char[new_capacity + 1];

				strcpy(temp, this->_str);
				if (_str) {
					delete[]_str;
				}
				_str = temp;
				_capacity = new_capacity;
			}
			_str[_size] = data;
			_size++;
		}

		string& append(const char* str) {
			size_t len = strlen(str);
			if (_size + len >= _capacity) {
				size_t new_capacity = _size + len;
				char* temp = new char[new_capacity + 1];
				strcpy(temp, _str);
				if (this->_str) {
					delete[]_str;
				}
				_str = temp;
				_capacity = new_capacity;

			}
			strcpy(_str + _size, str);
			_size += strlen(str);
			return *this;
		}

		string& operator=(const string& str) {
			char* temp = new char[str._capacity + 1];
			strcpy(temp, str._str);
			if (this->_str) {
				delete[]_str;
			}
			this->_str = temp;
			this->_capacity = str._capacity;
			this->_size = str._size;
			return *this;
		}

		string& operator+=(const string& str) {
			this->append(str._str);
			return *this;
		}

		string& operator+=(const char* s) {
			this->append(s);
			return *this;
		}

		void resize(size_t target) {
			if (target <= this->_size) {
				_str[target] = '\0';
				_size = target;
			}
			else {
				char* temp = new char[target + 1]();
				strcpy(temp, _str);
				if (_str) {
					delete[]_str;
				}
				_str = temp;
				_capacity = target;
			}
		}

		string& insert(size_t pos, const char* s) {
			char* temp = new char[_size]();
			strcpy(temp, _str + pos);
			this->resize(pos);
			*this += s;
			*this += temp;
			return *this;
		}

		string& erase(size_t pos = 0 , size_t len = std::string::npos) {
			char* temp = new char[_capacity]();
			strcpy(temp, _str + pos + len - 1);
			this->resize(pos);
			*this += temp;
			this->_size = _size - len;
			return *this;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
	std::ostream& operator<<(std::ostream& out, X::string s) {
		for (int i = 0; i < s._size; ++i) {
			out << s._str[i];
		}
		return out;
	}

}


int main(void) {
	X::string s1("Hello World");
	std::cout << s1 << std::endl;
	X::string s2("C++!");
	s1 += s2;
	s1.insert(11, ",");
	std::cout << s1 << std::endl;
	std::cout << s1.size() << std::endl;

	s1.resize(20);
	std::cout << s1.size() << std::endl;
	std::cout << s1 << std::endl;

	s1 += "!";
	std::cout << s1 << std::endl;

	s1.erase(16, 1);
	std::cout << s1 << std::endl;

	X::string s3 = s1;
	std::cout << "s3 : " << s3 << std::endl;

	X::string::const_iterator it = s3.const_begin();
	std::cout << *it << std::endl;

	return 0;
}