目录
引言
在 C++——string的了解和使用 中,我们学习了string的一些基础用法。接下来我们可以试着模拟实现string。
在C++中,std::string是一个功能强大且广泛使用的类,用于处理字符串。然而,了解其内部实现原理对于深入理解C++和编写高效代码至关重要。通过模拟实现一个简单的string类,我们可以更好地理解字符串的存储、管理以及操作。这不仅有助于我们更好地使用std::string,还能让我们在遇到特定需求时,能够自定义字符串类来满足这些需求。
成员变量
1.基本框架
为了与STL库中的string区分开来,我们要使用命名空间namespace进行封装。
char* _str:指向字符数组的指针,用于存储字符串的实际内容。
size_t _size:表示字符串中有效字符的数量。
size_t _capacity:表示字符数组的容量,即可以存储的最大字符数量(包括结尾的空字符\0)。
namespace My_string
{
class string
{
public:
// ...
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
成员函数
老规矩,我们在 string.h 中,声明函数;在 string.cpp 中,实现函数的功能。
1.构造函数和析构函数
构造函数:接受一个C风格字符串作为参数,计算其长度,分配足够的内存来存储该字符串及其结尾的空字符,并复制字符串内容。
析构函数:释放分配给字符串的内存,并将指针设置为nullptr,以避免悬挂指针问题。同时,将_size和_capacity设置为0,表示对象已销毁。
string.h:
namespace My_string
{
class string
{
public:
string(const char* str);//构造函数
~string(); //析构函数
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
string.cpp:
#include"string.h"
namespace My_string
{
// 构造函数
string::string(const char* str)
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1]; // +1用于储存'\0'
strcpy(_str, str);
}
// 析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
我们可以测试一下:
通过调试观察一下:
调用构造函数:
调用析构函数:
2.拷贝构造函数
拷贝构造函数:接受一个string对象作为参数,分配足够的内存来存储原对象的字符串内容,并复制该内容。
这里提供了三种实现方式,包括直接复制、使用临时对象进行深拷贝以及使用swap函数进行资源转移。
string.h:
string(const string& str); //拷贝构造函数
string.cpp:
// 拷贝构造函数(1)
string::string(const string& str)
{
_str = new char[str._capacity + 1]; //额外多给一个空间,用于存放'/0'
strcpy(_str, str._str); //拷贝数据
_capacity = str._capacity; //设置容量
_size = str._size; //设置有效数据个数
}
我们在这里也有其他的方法可以实现拷贝构造:
// 拷贝构造函数(2)
string::string(const string& str)
{
string tmp(str._str);
std::swap(tmp._str, _str);
std::swap(tmp._size, _size);
std::swap(tmp._capacity, _capacity);
}
以上代码还可以接着简化:
string::string(const string& str)
{
string tmp(str._str);
swap(tmp); // 这里的swap我们接下来会定义
}
3.容量操作函数
3.1 有效长度和容量大小
我们先写这两个函数:
size()和capacity():分别返回字符串的有效长度和字符数组的容量。
string.h:
size_t size() const; // size()函数
size_t capacity() const; // capacity()函数
string.cpp:
// size()函数
size_t string::size() const
{
return _size;
}
// capacity()函数
size_t string::capacity() const
{
return _capacity;
}
3.2 容量操作
c_str():返回一个指向以空字符结尾的字符数组的指针,该数组包含与string对象相同的字符序列。这允许将string对象与接受C风格字符串的函数一起使用。
empty():检查字符串是否为空(即长度为0)。
erase():删除字符串中指定位置的字符或子字符串。
string.h:
const char* c_str() const; // c_str()函数
bool empty() const; // empty()函数
void erase(size_t pos = 0, size_t len = npos); // erase()函数
string.cpp:
// c_str()函数
const char* string::c_str() const
{
return _str;
}
// empty()函数
bool string::empty() const
{
return _size == 0;
}
// erase()函数
void string::erase(size_t pos,size_t len)
{
assert(pos < _size);
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0'; // 位置pos置为'\0'
_size = pos; // 有效元素个数为pos个
}
else // len小于后面的字符个数
{
// 将后面的字符拷贝到pos位置
strcpy(_str + pos, _str + pos + len);
_size -= len; // 更新有效元素
}
}
接下来再来实现扩容函数reserve()和resize():
reserve():增加字符数组的容量,以确保可以存储至少n个字符。如果当前容量不足,则分配新的内存并复制现有内容。
resize():改变字符串的大小。如果新大小大于当前大小,则添加新字符(默认为\0);如果新大小小于当前大小,则删除多余的字符。
string.h:
// 预留空间
void reserve(size_t n);
// resize()函数
void resize(size_t n, char ch = '\0');
string.cpp:
// 预留空间
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
// resize()函数
void string::resize(size_t n, char ch)
{
if (n > _size)
{
if (n > _capacity)
{
reserve(n);
}
// 使用 memset 函数将字符 ch
// 填充到新添加的空间中
memset(_str + _size, ch, n - _size);
}
_size = n;
_str[n] = '\0';
}
3.3 访问操作
(1)operator[]函数
operator[]函数的功能:返回pos位置的字符
string.h:
// 非const版本
char& operator[](size_t pos); //operator[]函数
// const版本
const char& operator[](size_t pos)const;
string.cpp:
// operator[]函数
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
// const版本
const char& string::operator[](size_t pos)const
{
assert(pos < _size);
return _str[pos];
}
来个简单的代码测试一下:
(2)iterator迭代器
迭代器:提供begin()和end()函数来返回指向字符串开头和结尾的迭代器。这里简化了迭代器的实现,将其视为指向字符数组的指针。然而,在实际应用中,迭代器通常是一个更复杂的类,提供了更多的功能和安全性检查。
string.h:
//const版本的iterator
const_iterator begin() const; //提供const_iterator begin()函数
const_iterator end() const; //提供const_iterator end()函数
//非const版本的iterator
iterator begin(); //提供iterator begin()函数
iterator end(); //提供iterator end()函数
string.cpp:
string::iterator string::begin()
{
return _str;
}
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::begin() const
{
return _str;
}
string::const_iterator string::end() const
{
return _str + _size;
}
还是老样子,我们使用一个简单的函数测试一下:
void test3()
{
My_string::string str("hello");
for (auto i : str)
{
cout << i << " ";
}
cout << endl;
My_string::string::iterator it1 = str.begin();
while (it1 != str.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
My_string::string::iterator it2 = str.end();
if (it2 != str.begin())
{ // 检查避免直接解引用 end()
--it2; // 先移动到一个有效的位置
while (it2 != str.begin())
{
std::cout << *it2 << " ";
--it2;
}
std::cout << *it2 << " "; // 输出最后一个字符(begin() 之前的字符)
}
std::cout << std::endl;
}
输出结果为:
3.4 修改操作
(1)push_back()和append()
string.h:
// 尾插一个字符
void push_back(char ch);
// 尾插一个字符串
void append(const char* str);
string.cpp:
//尾插一个字符
void string::push_back(char ch)
{
if (_capacity == _size)
{
size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
reserve(newcapacity);
}
_str[_size] = ch;
_str[_size + 1] = '\0';
_size++;
}
//尾插一个字符串
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str+_size, str);
_size += len;
}
(2)operator+=函数
我们可以借助上面两个函数实现operator+=函数。
string.h:
//operator+=函数可以构成重载,函数名相同,参数不同
string& operator+=(char ch); // 字符相加
string& operator +=(const char* str); // 字符串相加
string.cpp:
string& string::operator+=(char ch)
{
// 调用push_back()函数
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
来测试一下:
operator+=函数返回的是对象本身。
内置类型的+=运算符返回值的副本。
自定义类型的+=运算符通常返回对象的引用(即*this),以支持链式操作和避免复制。
———————————————————————————————————————————
以上为string模拟实现的第一篇
求点赞收藏评论关注!!!
感谢各位大佬!!!
第二篇链接:C++——string的模拟实现(下)