什么是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):分为三种类型
- 普通迭代器(iterator)
- const迭代器(const_iterator)
- 反向迭代器(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;
}