C++STL string简单实现


前言

    std::string是C++标准模板库中的一个类,用于处理字符串。与C语言的字符串相比,std::string 提供了更强大的功能和更安全的操作, 这次我们来简单实现一下

string接口介绍

构造函数

    std::string提供了多种构造函数,允许灵活地创建字符串对象, 下面举例几个最常用的

std::string();//默认构造函数, 创建一个空的字符串
std::string(const char* s);//使用C语言字符串创建
std::string(const std::string& str);//拷贝构造
std::string(const char* s, size_t n);//使用C语言字符串构造, 指定长度
std::string(size_t n, char c);//使用n个字符c构造
template <class InputIterator>
std::string(InputIterator first, InputIterator last);//迭代器构造
std::string(const std::string& str, size_t pos, size_t len = npos);//使用另一个string的部分构造

迭代器

    std::string也提供了迭代器来遍历和操作其内容

iterator begin();//返回字符串开头的迭代器
const_iterator begin() const;//常量迭代器
iterator end();//返回字符串结尾的迭代器
const_iterator end() const;
reverse_iterator rbegin();//返回字符串开头的反向迭代器
const_reverse_iterator rbegin() const;
reverse_iterator rend();//返回字符串结尾的反向迭代器
const_reverse_iterator rend() const;

    std::string::iterator 是string里封装的一个类型, 类似于指针
使用示例

std::string str = "123456789";
std::string::iterator it = str.begin();
while (it != str.end())
{
	std::cout << *it << " ";
	it++;
}
std::cout << std::endl;

    上面是一个使用迭代器遍历的例子, STL的容器几乎都能使用迭代器遍历
it++移动迭代器, *it获取当前迭代器的引用, 使用起来和指针差不多, 当然如果绝对这个类型太长, 也可以

auto it=str.begin();

反向迭代器使用一样, 不够是从字符串的末尾开始遍历。
至于const迭代器, 不能通过*it等方式修改迭代器指向的值

常用容量操作

size_t size() const;//获取字符串的字符个数
size_t length() const;//同上
void resize (size_t n);//开n个字符的空间
void resize (size_t n, char c);//开n个字符的空间, 并都初始化为 c
void clear();//清空字符串
bool empty() const;//判断字符串是否为空

    size和length没有区别, 都是获取字符串的字符个数, resize一般用于提前开空间, string的数据是一个动态管理内存开辟的, 会自动扩容(扩容是有代价的), 提前开取足够的空间可以减少扩容的次数, clear可以清空字符串为空串

元素访问操作

char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

    string实现了[]重载, 使得可以像使用数组一样访问该下标的元素

char& at (size_t pos);
const char& at (size_t pos) const;

    取pos位置的元素

char& back();
const char& back() const;
char& front();
const char& front() const;

    访问第一个位置的元素和最后一个位置的元素

常用操作

string& operator+= (const string& str);
string& operator+= (const char* s);
string& operator+= (char c);
string& operator+= (initializer_list<char> il);

    string也实现了+=操作符重载, 使得拼接字符串更方便

void push_back (char c);
void pop_back();

    尾插尾删操作, 经典

string& insert (size_t pos, const string& str);
...
iterator insert (const_iterator p, char c);
...
string& erase (size_t pos = 0, size_t len = npos);
iterator erase (const_iterator p);
iterator erase (const_iterator first, const_iterator last);

    insert/erase一系列操作, 指定位置插入/删除或者指定迭代器位置插入/删除

void swap (string& str);

    用于交换两个字符串的资源

static const size_t npos = -1;
size_t find (const string& str, size_t pos = 0) const noexcept;
size_t find (const char* s, size_t pos = 0) const;
size_t find (const char* s, size_t pos, size_type n) const;
size_t find (char c, size_t pos = 0) const noexcept;
size_t rfind (const string& str, size_t pos = npos) const noexcept;
size_t rfind (const char* s, size_t pos = npos) const;
size_t rfind (const char* s, size_t pos, size_t n) const;
size_t rfind (char c, size_t pos = npos) const noexcept;

    find系列函数, 返回找到了第一个匹配的下标, 找不到返回npos

string substr (size_t pos = 0, size_t len = npos) const;

    提取子字符串

char* c_str();
const char* c_str() const;

string简单实现

    我们已经认识了string的基本接口使用, 现在我们自己简单模拟实现一下吧
完整代码(Github)

框架

    只实现了常用的接口, 这里只介绍一些重点方法实现, 其他的可以去我的GitHub仓库看源码

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& str);
	~string();
    //空间
	size_t size()const;
	void reserve(size_t capacity);//这个用来扩容
	void resize(size_t size);
	size_t capacity()const;
	void swap(string& str);
	void clear();
    //插入删除
	void push_back(char c);
	string& append(const string str);
	string& insert(size_t pos, const string& str);
	string& insert(size_t pos, char c);
	string& erase(size_t pos, size_t n = npos);
	string& repalce(size_t pos, size_t n, const string& str);
    //获得C风格的字符串
	char* c_str();
	const char* c_str() const;
    //查找
	size_t find(const string& str, size_t pos=0);
    //提取子字符串
	string substr(size_t pos, size_t len = string::npos);
    //运算符重载
	char& operator[](size_t pos);
	const char& operator[](size_t pos) const;
	string& operator+= (const string& str);
	string& operator+= (char c);
	string operator+(const string& str) const;
	string& operator= (string str);
	static size_t npos;
private:
	char* _str;//字符数组
	size_t _size;//字符个数
	size_t _capacity;//容量
};
size_t string::npos = -1;
std::ostream& operator<<(std::ostream& out, M::string& str);
std::istream& operator>>(std::istream& in, M::string& str);

构造/析构函数

string(const char* str = "")
{
	_capacity = strlen(str);
	_str = new char[_capacity + 1] {0};
	_size = _capacity;
	strcpy(_str, str);
}
string(const string& str)
{
	string s(str.c_str());
	swap(s);
}
~string()
{
	delete[] _str;
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}

注意

  • 这里设定capacity和sized都为不包含’\0’的大小, 所有要多开一个空间给’\0’
  • 拷贝构造里面的s是一个临时变量, 函数栈帧销毁后自动释放, 所有我们可以直接交换和它的资源, 既能拿取它的资源, 还能让自己之前的资源释放
  • 构造使用了new[],析构也要相应匹配

迭代器

typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str+_size;
}
const_iterator begin()const
{
	return _str;
}
const_iterator end()const
{
	return _str + _size;
}
  • 这里直接使用char* 可以完成迭代器的功能

操作符重载

[]+=重载

char& operator[](size_t pos)
{
	assert(pos <= _size);
	return _str[pos];
}
string& operator+= (const string& str)
{
	if (_size + str.size() > _capacity)
	{
		reserve(_size + str.size());
	}
	strcpy(_str + _size, str.c_str());
	_size += str.size();
	return *this;
}
string& operator+= (char c)
{
	push_back(c);
	return *this;
}
string operator+(const string& str) const
{
	string s(*this);
	s += str;
	return s;
}
string& operator= (string str)
{
	swap(str);
	return *this;
}

注意

  • +=操作符重载要支持连续+=
  • +操作符的返回值是两个字符串拼接的结果, 但原本的字符串不能改变
  • =操作符要支持连接=

流插入/提取操作符重载

std::ostream& operator<<(std::ostream& out, M::string& str)
{
	for (int i = 0; i < str.size(); i++)
	{
		out << str.c_str()[i];
	}
	return out;
}
std::istream& operator>>(std::istream& in, M::string& str)
{
	char ch=in.get();
	char strbuff[128];
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		strbuff[i++]=ch;
		if (i == 127)
		{
			strbuff[i] = '\0';
			str += strbuff;
			i = 0;
		}
		ch = in.get();
	}
	if (i != 0)
	{
		strbuff[i] = '\0';
		str += strbuff;
	}
	return in;
}

注意

  • 流插入/提取操作符第一个参数必须是i/ostream对象, 这些函数不能写在类里面, 如果把这些函数写在类里面, 第一个参数会被this指针占用
  • 流提取使用了缓冲区, 避免多次调用+=

常用操作

插入

void push_back(char c)
{
	if (_size == _capacity)
	{
		size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
		reserve(new_capacity);
	}
	_str[_size] = c;
	_size++;
}
string& insert(size_t pos, const string& str)
{
	if (_size + str.size() > _capacity)
	{
		reserve(_size + str.size());
	}
	size_t end = str.size()+_size;
	while (end > pos)
	{
		_str[end] = _str[end - str.size()];
		--end;
	}
	strncpy(_str + pos, str.c_str(),str.size());
	_size += str.size();
	return *this;
}
string& insert(size_t pos, char c)
{
	if (_size + 1 > _capacity)
	{
		reserve(_size + 1);
	}
	size_t end = 1 + _size;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = c;
	_size ++;
	return *this;
}
string& append(const string str)
{
	(*this)+= str;//直接偷懒复用+=
	return *this;
}

注意

  • 满了需要扩容, 我这里选择的第一次开辟4个空间, 接下面都是2倍扩容
  • insert也要提前计算空间大小, 然后移动位置插入

删除

string& erase(size_t pos, size_t n = npos)
{
	if (n == npos || n + pos >= _size)
	{
		_str[pos] = '\0';
	}
	else
	{
		strcpy(_str + pos, _str + pos + n);
	}
	return *this;
}

注意

  • 删除的数量大于数据数直接将后面全部清空, 删除不缩容
  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值