引言
在C 编程中,字符串操作是非常常见且重要的任务。标准库中的std::string类提供了丰富且强大的功能,使得字符串处理变得相对简单。然而,对于学习C 的开发者来说,深入理解std::string的内部实现原理是非常有益的。通过亲手实现一个类似的String类,不仅可以帮助我们掌握面向对象编程的基本概念,还能增强我们对内存管理和字符串操作的理解。
在这篇博客中,我们将从零开始,逐步实现一个自定义的C String类。我们的目标是构建一个功能完整且高效的字符串类,同时尽可能地模仿std::string的行为。我们将讨论类的基本结构、构造函数和析构函数的实现、基本成员函数的设计、运算符重载、内存管理,以及如何编写测试代码来验证我们的实现。
通过这篇文章,您将学到如何在C 中进行动态内存分配和管理,如何实现深拷贝和移动语义,如何重载运算符以提升类的易用性,等等。无论您是刚刚入门的C 学习者,还是希望深入理解C 底层实现的开发者,这篇文章都将为您提供宝贵的知识和实践经验。
让我们一起来探索C String类的实现之旅吧!
1.类的基本结构
1.1定义类
代码语言:javascript复制#include<iostream>
#include<assert.h>
using namespace std;
namespace lyrics
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//迭代器
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
//构造函数
string(const char* str = "");
string(const string& s);
//析构函数
~string();
//
const char* c_str() const;
//返回大小
size_t size() const;
//运算符重载
char& operator[](size_t pos);
const char& operator[](size_t pos)const;
//空间扩容
void reserve(size_t n);
//尾插一个字符
void push_back(char ch);
//尾插一个字符串
void append(const char* str);
//运算符重载 =操作
string& operator =(char ch);
string& operator =(const char* str);
//插入操作,插入一个字符串和插入一个字符
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
//删除某段字符
void erase(size_t = 0, size_t len = npos);
//查找某个字符串或者字符
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
//赋值拷贝
string& operator=(const string& s);
//交换函数
void swap(string& s);
//取子串
string substr(size_t pos = 0, size_t = npos);
//比较函数运算符重载
bool operator<(const string& s)const;
bool operator<=(const string& s)const;
bool operator>(const string& s)const;
bool operator>=(const string& s)const;
bool operator==(const string& s)const;
//清理
void clear();
private:
size_t _size;
size_t _capacity;
char* _str;
const static size_t npos;
};
//非成员函数,,重载流插入和流提取
istream& operator>>(istream& is, string& str);
ostream& operator<<(ostream& is, string& str);
}
用命名空间形成类域将其与全局作用域隔开,防止发生命名冲突 1.2私有成员变量
- size_t _size;
_size表示当前string的有效空间
- size_t _capacity;
_capaciity表示当前string的总的空间容量
- char _str;*
_str表示存储字符串的指针
- const static size_t npos;
npos表示一个静态变量
1.3公有成员函数
公有成员函数代码上有标识
2.构造函数和析构函数
2.1构造函数
这里我们直接将构造函数和拷贝构造写成一个函数
代码语言:javascript复制string::string(const char* str)//指定类域
//strlen的效率很低
//初始化列表 写在内部函数
:_size(strlen(str))
{
_str = new char[_size 1];
_capacity = _size;
strcpy(_str, str);
}
2.2赋值拷贝函数
注意:这里赋值拷贝函数由于我们不知道两个串到底有多长,所以我们直接将需要赋值拷贝的串给释放了,然后重新开一个空间,将s中的串拷贝给新的空间,这样虽然很暴力,但是少了很多不必要的讨论
代码语言:javascript复制string& string::operator=(const string& s)
{
char* tmp = new char[s._capacity 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
return *this;
}
2.3c_str函数
代码语言:javascript复制const char* string::c_str() const
{
return _str;
}
** 2.4析构函数**
代码语言:javascript复制由于str的空间是我们手动开辟的所以,需要我们用Delete来释放,这里释放之后将其置位空指针即可,然后重置我们的size和capacity
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
3.基本成员函数
3.1获取字符串长度
代码语言:javascript复制size_t string::size() const
{
return _size;
}
3.2operator[]重载
代码语言:javascript复制这里直接返回pos位置对应的元素即可
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];//返回pos位置的字符
}
3.3const版本的operator[]重载
代码语言:javascript复制//const版本的[]重载
const char& string::operator[](size_t pos)const
{
return _str[pos];
}
3.4预开辟空间
代码语言:javascript复制注意:这里预开辟的空间要是比实际空间小,则不进行操作,若预开辟的空间比实际空间大,则进行空间的开辟
void string::reserve(size_t n)
{
if (n > _capacity)
{
//开新空间
char* tmp = new char[n 1];
//拷贝数据
strcpy(tmp, _str);
//释放新空间
delete[] _str;
//指向新空间
_str = tmp;
//更新容量
_capacity = n;
}
}
3.5尾插
代码语言:javascript复制这里尾插一个字符也很简单,先检查一下空间是否允许再插入,如果空间不够则先开辟两倍的空间,如果以前的空间是0,则先预开辟4个空间
//尾插一个字符
void string::push_back(char ch)
{
if (_capacity == _size)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size ] = ch;
_str[_size] = '