STL -- C++ string 类 模拟实现

标准库的 std::string 用起来很方便,但你真的了解它背后的内存管理、深拷贝、写时拷贝、迭代器、动态扩容吗?

为了彻底搞懂这些底层机制,我造了一个自己的 string 类 ------ 取名为 jmp::string

从构造、析构、拷贝、赋值,到增删改查、迭代器、流输入输出

这篇文章会完整记录我的实现细节、踩过的坑(都已经在代码注释里标注出来了),以及最终跑通测试时的快感。希望也能帮你把 C++ 对象的生命周期和运算符重载看得更通透。

一、基础管理(构造、析构、拷贝、赋值、交换)

|----------|----------------------------------------------------------|
| 构造函数 | string(const char* str = ""):分配足够空间,复制字符串。支持缺省参数(空字符串) |

cpp 复制代码
	string::string(const char* str )//初始化的内容
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_size+1];//+1为\0腾出一块空间 
		strcpy(_str, str);//char * strcpy ( char * destination, const char * source );
	}

|----------|--------------------------------|
| 析构函数 | ~string():释放动态内存,指针置空,大小容量归零 |

cpp 复制代码
	string::~string()
	{
		delete[] _str;//匹配使用
		_str = nullptr;
		_size = _capacity = 0;//析构函数的作用不能忘
	}

|-----------|--------------------------------------------------------|
| 赋值运算符 | operator=(string s):传值(copy-and-swap 惯用法),通过交换实现安全赋值 |

|--------|-------------------------------------------------|
| 交换 | swap(string& s):交换 _str_size_capacity |

我这里配合了 内部的swap实现 代码一并贴出

cpp 复制代码
	void string::swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}

	string& string::operator=(string s)
	{
		swap(s);
		return *this;
	}

operator= 利用传值生成临时副本,然后交换,天然处理自赋值且异常安全

二、容量与大小

|----------|------------|
| size() | 返回 _size |

cpp 复制代码
	size_t string::size() const
	{
		return _size;
	}

|---------------------|--------------------------------------|
| reserve(size_t n) | 仅扩容(不缩小),保留原内容,保证至少有 n 容量(不含 \0) |

cpp 复制代码
	void string::reserve(size_t n)//只放大不缩小
	{
		if (n <= _capacity) return;//只放大不缩小 并不意味着要报错 只是没有强制性;				
		char* new_str = new char[n + 1];//多给一个空间给'\0'
		if (_str)
		{
			memcpy(new_str, _str, _size + 1); // 拷贝原有内容(包括\0'))
			delete[] _str;
		}
		_str = new_str;//可以采用先移动 再销毁 然后再次指向移动的新位置 从而达到改变this内容的效果
		_capacity = n;
	}

|-----------|---------------------------------------------|
| clear() | 清空内容,仅将 _size0,并在 _str[0]'\0' |

cpp 复制代码
	void string::clear()
	{
		_size = 0;
		_str[_size] = '\0';
	}

三、元素访问与迭代器

|--------------|----------------------------|
| operator[] | 返回指定下标字符的引用(提供 const 版本) |

cpp 复制代码
	char& string::operator[](size_t i)
	{
		assert(i < _size);
		return *(_str + i);
	}
	const char& string::operator[](size_t i) const
	{
		assert(i < _size);
		return *(_str + i);
	}

|---------------------|--------------------------------|
| begin() / end() | 返回 char* 迭代器(普通与 const 版本) |

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;//表示最后一个有效数据的后一个位置
}

四、修改操作(增、删、改)

|----------------------|----------------------------------|
| push_back(char ch) | 尾部添加字符,容量不足时翻倍扩容(注意已修正内存分配 +1) |

cpp 复制代码
void string::push_back(char ch)
{
	if (_size == _capacity)
	{
		size_t _newcapacity = (_capacity == 0 ? 4 : _capacity * 2);
		char* new_str = new char[_newcapacity+1];
		if (_str) //有字符就得拷贝到新的空间内
		{
			strcpy(new_str, _str); // 拷贝原数据(包括结尾的\0)
			delete[] _str;
		}
		_str = new_str;
		_capacity = _newcapacity;
	}
	_str[_size] = ch;//把\0的位置填上ch
	_str[_size + 1] = '\0';//ch后面把\0加上
	_size++;
}

|--------------|-------------------------------|
| pop_back() | 删除尾部字符,仅将 _size-- 并置 '\0' |

cpp 复制代码
	void string::pop_back()
	{
		assert(_size > 0);//删除的话要确保有内容可删 否则断言报错
		_size--;
		_str[_size] = '\0';
	}

|---------------------------|---------------|
| append(const char* str) | 追加 C 字符串,动态扩容 |

cpp 复制代码
void string::append(const char* str)
{
	if (str == nullptr) return;//这些都是细节
	size_t len = strlen(str);
	if (len == 0) return;//这些都是细节
	if (_size + len > _capacity)
	{
		size_t new_capacity = (_size + len) * 2;// 或简单_size + len
		char* new_str = new char[new_capacity + 1];//倘若空间不够的问题要记得考虑
		if (_str)
			memcpy(new_str, _str, _size);
		delete[] _str;
		_str = new_str;
		_capacity = new_capacity;
	}
	memcpy(_str + _size, str, len + 1);
	_size += len;
}

|-------------------------------------------------------|---------------------------|
| operator+=(char ch) / operator+=(const char* str) | 复用 push_back / append |

cpp 复制代码
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}

|-------------------------------|------------------|
| insert(size_t pos, char ch) | 在指定位置插入字符,后移后续字符 |

cpp 复制代码
string& string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size + 1 > _capacity)
		reserve(_size + 1);
	//memmove(_str + pos + 1,_str + pos,_size - pos + 1);// 后移包括 \0'
	//应该从后往前挪动 否则会造成覆盖
	for (size_t i = _size; i > pos; i--)
	{
		_str[i + 1] = _str[i];
	}
	_str[pos] = ch;
	_size++;
	return *this;
}

|---------------------------------------|----------------------------|
| insert(size_t pos, const char* str) | 插入 C 字符串,使用 memmove 批量后移 |

cpp 复制代码
	string& string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity)
			reserve(_size + len + 1);//为\0留空间
		memmove(_str + pos + len, _str + pos, _size - pos + 1);// 后移包括 \0'
		//把_str+pos 后面的所有字符(包括\0) 都从_str + pos + len这个位置开始填充(移动覆盖)
		memcpy(_str + pos, str, len);
		_size+=len;
		return *this;
	}

|---------------------------------|-------------------------------------------|
| erase(size_t pos, size_t len) | 删除从 pos 开始的 len 个字符(支持 npos 表示删到末尾) |

cpp 复制代码
string& string::erase(size_t pos, size_t len)
{
	assert(pos <= _size);
	if (npos == len || pos + len > _size)
	{
		len = _size - pos;
	}
	if (len == 0) return *this;
	memmove(_str + pos, _str + pos + len, _size - pos-len + 1);
	_size -= len;
	return *this;
}

五、查找与子串

|-----------------------------|-----------------------------|
| find(char ch, size_t pos) | 从 pos 开始查找字符,返回下标或 npos |

cpp 复制代码
size_t string::find(char ch, size_t pos) const
{
	assert(pos <= _size);
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
			return i;
	}
	return npos;
}

|-------------------------------------|----------------------------------|
| find(const char* str, size_t pos) | 从 pos 开始查找子串,利用 strncmp 逐位比较 |

cpp 复制代码
size_t string::find(const char* str, size_t pos)const
{
	if (str == nullptr) return npos;
	assert(pos <= _size);
	size_t len = strlen(str);
	if (len == 0) return pos;
	if (pos + len > _size) return npos;
	for (size_t i = pos; i <= _size - len; ++i)
	{
		if (strncmp(_str + i, str, len) == 0)//比较前n个字符是否相同
		{
			return i;
		}
	}
	return npos;
}

|----------------------------------|----------------------|
| substr(size_t pos, size_t len) | 提取子串,自动处理长度溢出(截断至末尾) |

cpp 复制代码
string string::substr(size_t pos, size_t len) const
{
	assert(pos <= _size);
	size_t count = len;
	if (count == npos || pos + count > _size)
	{
		count = _size - pos;//考虑没有传值或者传值过头的情况 以便正常遍历
	}
	string new_str;
	new_str.reserve(count);
	for (size_t i = 0; i < count; i++)
	{
		new_str += _str[pos+i];
	}
	return new_str;
}

六、比较运算符(全部内联)

所有比较运算符均基于 strcmp 实现:

==, !=, <, <=, >, >=

cpp 复制代码
bool  jmp::string::operator<(const string& s) const
{
	return strcmp(_str, s._str) < 0;
}
bool jmp::string::operator<=(const string& s) const
{
	return strcmp(_str, s._str) <= 0;

}
bool jmp::string:: operator>(const string& s) const
{
	return strcmp(_str, s._str) > 0;

}
bool jmp::string::operator>=(const string& s) const
{
	return strcmp(_str, s._str) >=0;

}
bool jmp::string::operator==(const string& s) const
{
	return strcmp(_str, s._str) == 0;
}
bool jmp::string::operator!=(const string& s) const
{
	return strcmp(_str, s._str) != 0;
}

七、输入输出流

|--------------|-----------------|
| operator<< | 输出 c_str() 内容 |

|--------------|-----------------------------|
| operator>> | 简单实现(读取单词到临时缓冲区再赋值,生产中应更健壮) |

cpp 复制代码
ostream& jmp::operator<<(ostream& out, const string& s)
{
	out << s.c_str();//输出就直接输出字符串类型即可
	return out;
}
istream& jmp::operator>>(istream& in, string& s)
{
    // 先清空,然后读入到临时缓冲区,再赋值给 s
    // 简单实现:in >> s._str 不安全。可以用 getline 或临时 string
	char buf[1024];
	in >> buf; // 可能越界
	s = buf;
	return in;
}

|-----------|---------------------------------|
| getline | 读取一行直到分隔符,使用 is.get(ch) 逐字符添加 |

cpp 复制代码
istream& jmp::getline(istream& is, string& str, char delim)
{
	str.clear();
	char ch;
	while (is.get(ch))
	{
		if (ch == delim)
		{
			break;
		}
		str.push_back(ch);//每次读取一个字符都尾插上去
	}
	return is;
}

八、辅助工具

c_str():返回底层 char*,兼容 C 接口。

cpp 复制代码
	const char* string::c_str() const
	{
		return _str;//本质是为了调用函数来得到_str的内容 因为正常不能访问私有内容
	}

swap(x, y) 全局函数:调用成员 swap,通过 ADL 匹配。

这里使用了内部的匹配使用

cpp 复制代码
void jmp::swap(string& x, string& y)
{
	x.swap(y);
}

完结

九、我的gitee仓库

https://gitee.com/jiangmingpeng0716/c-learning-process

这里已经具备一个入门级 string 类的核心功能 其他的 功能暂且不做实现

好的 我的string 就是这样

相关推荐
ZHOUPUYU1 小时前
PHP 开发实战:从零搭建一个高性能的 RESTful API 服务
运维·开发语言·后端·html·php
·心猿意码·2 小时前
OCCT源码解析(六):TKG3d 模块——三维曲面体系
c++·3d
身如柳絮随风扬2 小时前
除了 JWT,你还用过哪些认证方案?Spring Security 中如何集成 JWT?
java·后端·spring
吴声子夜歌2 小时前
Java——Apache Commons CSV
java·csv
Anastasiozzzz2 小时前
万字深度实战!AI Agent 接入万物的底层密码:MCP 协议传输机制与开发指南(下篇)
java·开发语言·数据库·人工智能·ai·架构
JAVA面经实录9172 小时前
完整版JVM 深度学习体系(一)
java·jvm
兰令水2 小时前
topcode【随机算法题】【2026.5.17打卡-java版本】
java·算法·leetcode
会开花的二叉树2 小时前
Qt初体验-第一个窗口程序踩的坑
开发语言·c++·qt
灰色人生qwer2 小时前
python 中 BaseModel 在这里有什么用?
开发语言·python·状态模式