【C++世界之string&模拟实现】

发布于:2025-09-11 ⋅ 阅读:(20) ⋅ 点赞:(0)

string介绍

C++ string:在C语言基础上,提供了一个 类的封装,内部通常包含:
一个指向动态内存的 char*(保存字符串内容);
一个记录当前字符串长度的 _size;
一个记录已分配空间大小的 _capacity(一般比 _size 大,用于减少频繁扩容)

string 会 自动管理内存:当字符串变长时,会重新分配更大的空间,并把原来的内容复制过去。
为了效率,string 通常采用 按需扩容:容量不足时,不是只增加 1 个,而是成倍增加(比如翻倍)。

std::string 提供了很多便利的功能:
构造:直接用字面量初始化(string s = “hello”;)。
访问:下标 [],迭代器,at()。
修改:append、insert、erase、replace。
比较:==、!=、< 等重载运算符。
拼接:支持 +、+=。
安全性:自动管理内存,避免手动 new/delete。

string用法及常用函数

string的文档

初始化

int  main()
{
	string s1;
	string s2("张三");
	string s3("hello world");
	string s4(10, '*');
	string s5(s2);
	string s6(s3, 6, 5);//区间初始化 6:pos 5:len
}

赋值

int main()
{
  s1 = s2;
  s1 = "1111";
  s1 = '2';
}

增(各种插入)

push_back

string s1("hello");
//尾插一个字符
s1.push_back(' ');

append

//尾插一个字符串
s1.append("world");
//插入一个字符串前几个字符
s1.append("hello world",5);  //插入字符串的前五个字符
//插入一个string区间
s1.append(s2,3,6);// pos: 3   len: 6

重载+=

s1 += ' ';
s1 += "world";
s1 += s1;

insert

  s1.insert(0, "hello");//pos + 字符串(指定位置插入字符串)
  s1.insert(0,"hello world",5);// pos + 字符串 + len(插入字符串区间)
  s1.insert(0,s2);//pos + string
  s1.insert(0, s2,2,5);//pos + string + string的区间
  s1.insert(0,10,'x');// pos + n(个数) + char
  s1.insert(s1.beign(),10,'x'); //迭代器

erase

int main()
{
	string s1("hello world");

	s1.erase(5, 1);
	cout << s1 << endl;

	s1.erase(5);
	cout << s1 << endl;

	string s2("hello world");

	s2.erase(0,1);
	cout << s2 << endl;

	s2.erase(s2.begin());
	cout << s2 << endl;

	s2.erase(s2.begin(),s2.end());//全删
	s2 = '0';
	cout << s2 << endl;
}

实际效果:
在这里插入图片描述

迭代器

介绍:
迭代器(Iterator)就是 用来遍历容器元素的一种对象。
它相当于一个“指针”,但比普通指针更灵活、更安全,能屏蔽底层容器的具体实现方式。

比如 vector、list、map 这些 STL 容器,内部结构不一样,但都能用迭代器来统一遍历。

功能:
输入迭代器:只读,单向移动(常见于 istream_iterator)。
输出迭代器:只写,单向移动(常见于 ostream_iterator)。
前向迭代器:可读可写,单向遍历。
双向迭代器:可读可写,能前进和后退(如 list 的迭代器)。
随机访问迭代器:支持 + - [] < > 等操作(如 vector、deque 的迭代器)。

常见几种迭代器
//iterator
//const_iterator
//reverse_iterator(反向)
//const_reverse_iterator

打印

string::iterator it = s1.begin();
	while (it != s1.end())
	{
		
		(*it)--;
		++it;
	}
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	

范围for

范围for底层就是迭代器,不过比较简单
打印:

	for (auto ch : s1)//for(char ch:s1)
	{
		cout << ch << " ";
	}
	cout << endl;

修改:(需要引用)

for (auto& ch : s1)
	{
		ch++;
	}

迭代器+算法容器

反转:

	reverse(v.begin(), v.end());
	reverse(lt.begin(), lt.end());

排序:

sort(s1.begin(),s1.end());

…………

reserve & resize

reserve 开空间

	//开空间
  s1.reserve(100);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

resize 开空间+初始值

//开空间+填值初始化
	s1.resize(200,'x');
	cout << s1.size() << endl;
  cout << s1.capacity() << endl;//扩容

	s1.resize(20);//可以删除数据,不缩容
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

上述为基本常用功能·,其他接口自行去文档查找。

模拟实现

前提准备

namespace bit
{
class string
{
 private:
size_t _size;
size_t _capacity;
char* _str;
static size_t npos;
 public:
………………
}
size_t string:: npos = -1; 
}

构造函数

字符串构造

string(const char* str = "")//相当于\0
	:_size(strlen(str)),_capacity(_size)
{
	_str = new char[_capacity + 1];
	memcpy(_str, str,_size+1);
	// 如果 只拷贝_size 则 _str[_size] = '\0';
}

我们也可以使用for循环逐个拷贝进去

for (size_t i = 0; i < _size; i++)
		  {
				_str[i] = s[i];
		  }
_str[_size] = '\0';

字符构造

string(const char* str, size_t n)
	:_size(n),_capacity(_size)
{
	_str = new char[_capacity + 1];

	memcpy(_str, str, n);
	_str[_size] = '\0';//注意添加
}

PS1:为什么一个是_size + 1 一个是 _size。
二者差异在memcpy的拷贝区间,因为s自带’\0’,所以拷贝到_size + 1.
但是字符没有‘\0’,所以要手动添加。

PS2: 为何不推荐使用strcpy?
1. 若字符串中包含’\0’字符(虽然这种情况较为少见),拷贝过程会提前终止,导致无法完整复制。
2. 为保持代码一致性及美观性,建议避免使用该函数。

拷贝构造函数

传统写法:

string(const string& str)
	: _size(str._size), _capacity(str._capacity)
{
	_str = new char[_capacity + 1];
	memcpy(_str, str._str, _size + 1);
}

现代写法:

string(const string& str)
	:_str(nullptr),_size(0),_capacity(0)
{
	string tmp(str._str);
	swap(tmp);
}

PS:
若 _str、_size 和 _capacity 未被初始化,可能导致以下问题:
访问非法内存地址(野指针)
_size 和 _capacity 值异常,引发数组越界
将对象初始化为空状态可确保:
swap 操作时数据始终有效
避免未初始化值带来的风险

重载=

传统写法:

string& operator=(const string& str)
		{
			if (this == &str) return *this;
			delete[]_str;

			_str = new char[str._capacity + 1];
		  memcpy(_str, str._str, str._size + 1);

			_size = str._size;
			_capacity = str._capacity;
			return *this;
		}

现代写法:

void swap(string& str)
{
	std::swap(_str, str._str);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);//不能直接交换,会无限循环
}

string& operator=(string str)//形参(传值)拷贝了一个str
{
	if (this == &str)  return *this;
	swap(str);//this->swap(tmp);
	return *this;
}

PS1: 为什么不能直接交换(this)和(str)?
由于std::swap在交换this和str时会涉及赋值操作,这会触发重载的=运算符,导致无限递归。
PS2: 为什么要传递形参str?
形参str是实参的副本,通过交换这个副本既能实现赋值目的,又不会影响原始实参的值。

析构函数

~string()
{

	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

重载[]

char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

const char& operator[](size_t pos)const
{
	assert(pos < _size);
	return _str[pos];
}

字符串输出

const char* c_str()const { return _str;}

PS:
采用字符串形式输出,便于使用字符串处理函数。
直接支持打印功能,无需专门重载流输出和流插入操作符。

cout << s.c_str() << endl;

开空间

reserve

void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		memcpy(tmp, _str, n+1);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

PS:
调用reserve扩容时,需要先将原有数据拷贝到新空间,且仅当n大于当前_capacity时执行才有实际意义。

resize

void resize(size_t n,char ch = '\0')
{
	if (n < _size)
	{
		_size = n;
		_str[_size] = '\0';
	}
	else
	{
		reserve(n);
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = ch;
		}
		_size = n;
		_str[_size] = '\0';
	}
}

PS:使用resize缩小空间时需手动添加’\0’进行截断。

push_back

void push_back(char c)
{
	if (_size == _capacity)
	{
		//2倍扩容
		size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
		reserve(newcapacity);
	}
	_str[_size++] = c;
	_str[_size] = '\0';//插入字符需要手动补充'\0'
}

append

void append(const char* s)
{
	size_t len = strlen(s);

	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	memcpy(_str + _size, s,len+1);
	_size += len;
}
void append(const string& str) {  append(str.c_str());}

PS:插入流程如下:首先检查空间是否合适,接着通过reserve调整空间大小,最后执行数据插入操作。

+=

string& operator+=(char c)
{
	push_back(c);
	return *this;
}

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

string& operator+=(string& str)
{
	append(str);
	return *this;
}

PS:实现+=比较简单,主要是函数push_back 和 append 的复用。

erase

void erase(size_t pos, size_t len = npos)
{
	assert(pos < size());
	if (len == npos || len + pos >= _size)
	{
		_size = pos;
		_str[_size] = '\0';

	}
	else
	{
		size_t end = pos + len;

		while (end <= _size)
		{
			_str[pos++] = _str[end++];
		}
		_size -= len;
	}
}

PS:
检查位置:assert(pos < size()); 确保删除起点合法。
判断删除范围:
如果删到末尾(len == npos 或者超出范围),直接把 _size 截到 pos。
否则就把后面的内容往前搬。
更新 _size:调整字符串长度并补 ‘\0’ 结尾(end==_size 可以添加到’\0’)。

迭代器

begin

end

iterator begin(){ 	return _str;}

iterator end(){ return _str+size();}

const_iterator begin()const  {  return _str;}

const_iterator end()const{  return _str+size();}

PS:STL中的end()返回的是容器末尾元素的下一个位置,因此这里使用_str+_size表示。
PS:STL迭代器分为只读迭代器和可读写迭代器两种类型,因此需要区分这两类。

find

size_t find(const char* s, size_t pos = 0)const
{
	const char* ptr = strstr(_str + pos, s);

	return ptr ? (ptr - _str) : npos;
}

size_t find(string& str, size_t pos = 0)const
{
	return find(str.c_str(), pos);
}

字符串匹配

size_t find(const char* s, size_t pos = 0)const
		{
			size_t len1 = _size;
			size_t len2 = strlen(s);

			if (len2 == 0) return pos;

			if (len1 < pos || len1 < len2 + pos) return npos;

			for (int i = pos; i < len1; i++)
			{

				size_t j = 0;
				while (j < len2 && _str[i + j] == s[j])
					j++;

				if (j == len2) return i;
			}

PS:

合法性检查:
assert(pos < _size); 确保起始位置不越界。
确定实际长度 n:
如果 len == npos 或者 pos + len > _size,就截到末尾。
否则用传入的 len。
创建新字符串 tmp:
预留空间 reserve(n),避免多次扩容。
逐个拷贝字符:
从 _str[pos] 开始,拷贝 n 个字符到 tmp。

返回结果:
返回新的子串 tmp。

字串提取

substr

string substr(size_t pos = 0, size_t len = npos) const
{
	assert(pos < _size);
	size_t n = len;

	if (len == npos || pos + len > _size)
	{
		n = _size - pos;
	}
	string tmp;
	tmp.reserve(n);
	for (size_t i = 0 ; i < n; i++)
	{
		tmp += _str[pos + i];
	}

	return tmp;  
}

PS:
检查位置合法:assert(pos < _size);
确定实际截取长度:
如果 len == npos 或超范围,就取 _size - pos。
否则用传入的 len。
创建结果字符串:预留空间 reserve(n)。
逐个字符拷贝:把 _str[pos ~ pos+n-1] 复制进 tmp。
返回子串。

总结

下文是vector的讲解和模拟实现,如果有余力将完成string的其余实现和部分题目。


网站公告

今日签到

点亮在社区的每一天
去签到