【STL】C++ 开发者必学字符类详解析:std::string

发布于:2025-09-04 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言:为什么要学string类?

回顾C语言学习字符串,比如实现字符串拼接、查找、替换、比较等一些常用操作时,都需要我们手动实现。同时,我们就还需要手动地申请内存,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

C++ 的std::string是标准库提供的字符串类,封装了字符串的存储、操作和管理逻辑,相比 C 语言,string类可以实现自动内存管理;同时,std::string有丰富的成员函数,即封装了大量字符串操作接口,无需手动实现复杂逻辑。

在学习之前,先向大家推荐一个好用的文档,方便大家随时去查阅C/C++标准库中的库函数:cplusplus.com - The C++ Resources Network

cplusplus.com - The C++ Resources Network

string类的文档:http://www.cplusplus.com/reference/string/string/?kw=string

1、常用接口说明

1. string类对象的常见构造:

实现类对象的初始化

函数名称                                                功能
string()   构造空的string类对象,即空字符串
string(const char* s)  用字符串s来构造string类对象
string(const string&s) 拷贝构造函数
string(size_t n, char c) 用n个字符c初始化类对象
#include<string> // 头文件
using namespace std;

void Test()
{
	string st1; // 构造空字符串st1
	char ch[] = "Hello World!";

	string st2("Hello World!"); // 用"Hello World!"初始化st2
	string st3(st2); // 拷贝构造
	string st4(st2, 6, 6); // 用st2中第六个字符以后的字符拷贝构造st4
	string st5(st2, 6); // 用st2的前6个字符拷贝构造st5
	string st6(ch); // 等价于用"Hello World!"初始化st2
	string st7(ch, 5); // 用字符串ch的前5个字符初始化st7
	string st8(10, 'X'); // 用10和字符'X'初始化st8
}
// 输出:
st2:Hello World!
st3:Hello World!
st4:World!
st5:World!
st6:Hello World!
st7:Hello
st8:XXXXXXXXXX

2. string类对象的容量操作

函数                                         功能
   s.size() 返回字符串有效字符长度
   s.length() 返回字符串有效字符长度
   s.capacity() 返回空间总大小,不包含'\0'
   s.empty() 检测字符串释放为空串,是返回true,否则返回false
   s.clear() 清空有效字符
    s.reserve(n) 为字符串预留空间,实际预留空间一般大于n

    s.resize (size_t n)

resize (size_t n, char c)

将有效字符的个数改成n个,多出的空间用字符c填充
int main()
{
	string str("hello world");
	cout << str.size() << endl;
	cout << str.length() << endl;
	cout << str.capacity() << endl;
	return 0;
}

3. string类对象的访问及遍历操作

函数                                                  功能
operator[ ] 重载运算符[],通过访问数组元素的方式返回pos位置的字符
s.begin() begin获取一个字符的迭代器(地址),用于从前往后遍历字符串。
s.end() end获取最后一个字符下一个位置的迭代器(地址)
s.rbegin() rbegin()返回的是反向迭代器,指向字符串的最后一个字符,用于从后往前遍历字符串。
s.rend() end获取第一个字符前一个位置的迭代器(地址)
范围for C++11支持更简洁的范围for的新遍历方式,与auto关键字联用

begin()函数有两种形式,分别对应const和非conststd::string对象:

#include <string>

// 对于非const的std::string对象,返回指向首字符的正向迭代器(可读可写)
std::string::iterator begin(); 

// 对于const的std::string对象,返回指向首字符的常量正向迭代器(只读)
std::string::const_iterator begin() const; 

示例:

void Test02() {
	string st2("Hello World!");

	string::iterator it1 = st2.begin();
	while (it1 != st2.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
	string::iterator it2 = st2.begin();
	// it指针指向的内容可以改变
	while (it2 != st2.end())
	{
		*it2 += 2;
		cout << *it2 << " ";
		++it2;
	}
}


void test01() {
	const string st("Hello World!");// 需要使用const_iterator类型

	string::const_iterator it1 = st.begin();
	while (it1 != st.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
	//// 错误示范
	//string::const_iterator it2 = st.begin();
	//// st被const修饰,it指针指向的内容不可以改变
	//while (it2 != st.end())
	//{
	//	*it2 += 2;
	//	cout << *it2 << " ";
	//	++it2;
	//}
}

auto + 范围for:

int main()
{	
    string str("Hello World!");
	for (auto& e : str)
	{
		cout << e << " ";
	}
	return 0;
}

4. string类对象的修改操作

函数                                                 功能
push_back 尾插一个字符ch
append 尾插一个字符串
operator+= 尾插一个字符串
insert 在指定位置插入字符ch,或者字符串str
c_str 返回字符串首地址
find 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置,找不到返回npos(整型的最大值)
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的 位置,找不到返回npos(整型的最大值)
substr 在str中从pos位置开始,截取n个字符,然后将其返回
// push_back
string str("hello world");
str.push_back('!');

// append
str.append("!!!!");

str.append("xxxx",2); // 将字符串"xxxx"中的两个字符尾插
	
str.append(5, 'y'); // 尾插5个字符'y'

str.append("xxxx", 0, 4); // 将字符串"xxxx"从第一个开始往后数四个尾插

// +=
str += '!'; //尾插'!'

// insert
string str("hello world");
str.insert(0, "xxx");

string str1("hds");
str.insert(0, str1);

2、string类的模拟实现

说明:我们依旧采用多文件的方式来模拟实现string类,头文件string.h创建一个string类,并在类里面实现一些需要经常调用的成员函数(如拷贝构造,析构等)以及完成函数的声明;string.cpp文件完成仅在string类中声明的成员函数的实现以及全局函数的实现;test.cpp文件完成对实现的string类的测试。同时,我们将所有的实现过程都放在一个我们自己的命名空间中。

class string {
	public:
        // ...
	private:// 成员变量
		char* _str;    // 指向字符数组的指针
		size_t _size;  // 字符串的字符个数
		size_t _capacity;// 空间大小(不包含'\0')
        static const size_t npos = -1; // 整型的最大值
	};

2.1、类对象的初始化

1. 构造函数

string(const char* str = "")
{
	_size = strlen(str);
	_capacity = _size; //_capacity不包括'\0'
	_str = new char[_capacity + 1]; //需要为'\0'开辟一个空间
	strcpy(_str, str);
}

使用全缺省参数,当不传参数时,用缺省值(空字符串)来初始化,同时,空字符串默认含有一个 '\0',可以防止将_str初始化为nullptr而对空指针的解引用。

2.拷贝构造函数

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

拷贝构造,用一个已经存在的对象来初始化一个正在创建的对象。最重要的是对于一些指向了资源的内置类型需要完成深拷贝。

下面我们再来介绍拷贝构造的现代写法:

void swap(string& tmp)
{
	std::swap(_str, tmp._str);
	std::swap(_size, tmp._size);
	std::swap(_capacity, tmp._capacity);
}

string(const string& s)
{
	string tmp(s.c_str());
	swap(tmp);
}

3.析构函数

~string()
{
	if(_str)
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
}

2.2、功能函数接口

1.获取有效数据个数——_size

// 获取有效数据个数
size_t size()const
{
	return _size;
}

2.空间大小——_capacity

// 获取空间大小
size_t capacity()const
{
	return _capacity;
}

3.访问——重载[ ]

我们要访问pos位置的元素,就要检查pos的合法性,所以,先对pos进行断言。

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

对于const对象,我们只能访问而不能修改,所以,对const对象单独重载一个[ ]运算符。

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

4.清理数据——clear

void clear()
{
	_str[0] = '\0'; 
	_size = 0;
}

5.访问_str——c_str

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

2.3、尾插——push_back&append&+=

// 头文件函数声明
void reserve(size_t n); // 申请空间
void push_back(char ch); // 尾插一个字符
void append(const char* str); // 尾插字符串
string& operator+=(char ch); // 尾插字符
string& operator+=(const char* str); // 尾插字符串

1、我们要插入数据,首先要检查空间是否足够,如果空间不足,就需要我们扩容。所以,我们先实现string类中的reserve函数,同时,reserve函数能够提前申请空间,减少扩容,提高效率。

void string:: reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1]; // 需要为'\0'开辟一个空间
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

2、push_back

void string::push_back(char ch)
{
	if (_size == _capacity)
	{
		size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(capacity); // 扩容
		_capacity = capacity;
	}
	_str[_size] = ch; // 尾插
	++_size; 
	_str[_size] = '\0'; // 手动添加'\0'
}

3、append:由于是插入一个字符串,所以在扩容时要考虑字符串长度

void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
		reserve(capacity);
		_capacity = capacity;
	}
	strcpy(_str + _size, str);
	_size += len;
}

4、+=

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

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

2.4、指定位置插入——insert

首先检查pos的合法性,然后检查空间是否足够,最后移动字符串为要插入的字符或者字符串腾出合适的空间并插入。

void string::insert(size_t pos, char ch) 
{
	assert(pos <= _size);
	if (_size == _capacity) 
    {
		size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(capacity);
		_capacity = capacity;
	}
    // 将pos位置之后的字符串整体向后移动
	size_t end = _size + 1; //将'\0'也向后移动
	while (end > pos) {
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
}
void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
		reserve(capacity);
		_capacity = capacity;
	}
    // 将pos位置的字符串整体向后移动
	size_t end = _size+1;
	while (end > pos)
	{
		_str[end+len-1] = _str[end-1];
		--end;
	}
    // 插入字符串
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}
	_size += len;
}

2.5、查找——find

从pos指向的位置向后查找对应的字符或者字符串是否存在,先对pos断言检查,对于单个字符直接遍历字符串,对于字符串,通过strstr函数来查找。

size_t string::find(char ch, size_t pos)
{
	assert(pos < _size);
	for (size_t i = 0; i < _size; i++)
	{
		if (_str[i] == ch)
			return i;
	}
	return npos;
}

size_t string::find(const char* str, size_t pos)
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
		return ptr - _str;
}

2.6、比较——<&<=&>&>=&==&!=

我们先实现<和==,然后复用来实现其他的。

bool operator < (const string& str1, const string& str2)
{
	return strcmp(str1.c_str(), str2.c_str()) < 0;
}
bool operator <= (const string& str1, const string& str2)
{
	return str1 < str2 || str1 == str2;
}
bool operator > (const string& str1, const string& str2)
{
	return !(str1 < str2);
}
bool operator >= (const string& str1, const string& str2)
{
	return str1 > str2 || str1 == str2;
}
bool operator == (const string& str1, const string& str2)
{
	return strcmp(str1.c_str(), str2.c_str()) == 0;
}
bool operator != (const string& str1, const string& str2)
{
	return !(str1 == str2);
}

2.7、流提取<< 和流插入>>

流提取<<:通过范围遍历str对象

ostream& operator<<(ostream& out, const string& str)
{
	for (auto ch : str)
	{
		out << ch;
	}
    return out;
}

流插入>>:为了减少在不断提取的过程中的扩容次数和拷贝构造的次数,提高效率,我们先创建一个一定空间大小数组,将输入的字符先存放到数组中,当字符数组获取到足够的字符后,一次性将字符数组中的元素拷贝到str对象 。

istream& operator>>(istream& in, string& str)
{
	str.clear();

	const int N = 256; // 创建数组,减少扩容的次数
	char buff[N];
	int i = 0;

	char ch;
	ch = in.get(); // 获取输入的第一个字符
	while (ch != ' ' && ch != '\n') //字符串分隔符
	{
		buff[i++] = ch;
		if (i == N - 1) //当字符数组获取到足够的元素后,一次性拷贝到str对象
		{
			buff[i] = '\0';
			str += buff;
			i = 0;
		}

		ch = in.get(); // 逐个获取剩下的的字符
	}
    // 处理还没有提取到str对象中的字符
	if (i > 0)
	{
		buff[i] = '\0';
		str += buff;
	}
	return in;
}

3、完整源码

// 头文件:string.h
#include<iostream>
#include<assert.h>
using namespace std;

namespace hds
{
	class string
	{
	public:
		// 迭代器
		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;
		}

		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size; //_capacity不包括'\0'
			_str = new char[_capacity + 1]; //需要为'\0'开辟一个空间
			strcpy(_str, str);
		}

		//深拷贝
		/*string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}*/

		//深拷贝现代写法
		void swap(string& tmp)
		{
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}

		string(const string& s)
		{
			string tmp(s.c_str());
			swap(tmp);
		}
		// 写法一:
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				delete[] this->_str;
				this->_str = new char[s._capacity + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/
		// 写法二:
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s._str);
				swap(tmp);
			}
			return *this;
		}*/
		// 写法三:
		string& operator=(string& tmp)
		{
			swap(tmp);
			return *this;
		}

		~string()
		{
			if(_str)
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
			}
		}
		// 获取有效数据个数
		size_t size()const
		{
			return _size;
		}
		// 获取空间大小
		size_t capacity()const
		{
			return _capacity;
		}
		// 重载[]
		char& operator[](size_t pos)
		{
			assert(pos < _size);

			return _str[pos];
		}
		// const对象
		const char& operator[](size_t pos)const 
		{
			assert(pos < _size);
			return _str[pos];
		}
		//清理_str中的数据
		void clear()
		{
			_str[0] = '\0'; 
			_size = 0;
		}

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

		// 尾插
		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 pos, size_t len = npos);

		//查找
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		string substr(size_t pos = 0, size_t len = npos);

	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static const size_t npos = -1;
	};

	//声明为全局函数
	//比较
	bool operator<(const string& str1, const string& str2);
	bool operator<=(const string& str1, const string& str2);
	bool operator>(const string& str1, const string& str2);
	bool operator<=(const string& str1, const string& str2);
	bool operator==(const string& str1, const string& str2);
	bool operator!=(const string& str1, const string& str2);

	//流提取&流插入
	ostream& operator<<(ostream& out, const string& str);
	istream& operator>>(istream& in, string& str);
}
// 功能实现文件:string.cpp
#include"string.h"
namespace hds
{
	void string:: reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1]; // 需要为'\0'开辟一个空间
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(capacity);
			_capacity = capacity;
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';
	}
	
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
			reserve(capacity);
			_capacity = capacity;
		}
		strcpy(_str + _size, str);
		_size += len;
	}

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

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			size_t capacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(capacity);
			_capacity = capacity;
		}
		size_t end = _size + 1; //将'\0'也向后移动
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		_size++;
	}

	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t capacity = (_size + len) > 2 * _capacity ? (_size + len) : 2 * _capacity;
			reserve(capacity);
			_capacity = capacity;
		}

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

		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}
		_size += len;
	}

	void string::erase(size_t pos, size_t len) //缺省值只在声明时加
	{
		assert(pos < _size);
		if (len >= _size - pos)
		{
			_size = pos;
			_str[_size] = '\0';
		}
		else
		{
			for (size_t i = 0; i <= _size-pos-len; i++)
			{
				_str[i + pos] = _str[pos+len + i];
			}
		}
	}

	size_t string::find(char ch, size_t pos)
	{
		assert(pos < _size);
		for (size_t i = 0; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}

	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		const char* ptr = strstr(_str + pos, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
			return ptr - _str;
	}

	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len > _size - pos)
		{
			len = _size - pos;
		}
		string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		return sub;
	}

	bool operator < (const string& str1, const string& str2)
	{
		return strcmp(str1.c_str(), str2.c_str()) < 0;
	}
	bool operator <= (const string& str1, const string& str2)
	{
		return str1 < str2 || str1 == str2;
	}
	bool operator > (const string& str1, const string& str2)
	{
		return !(str1 < str2);
	}
	bool operator >= (const string& str1, const string& str2)
	{
		return str1 > str2 || str1 == str2;
	}
	bool operator == (const string& str1, const string& str2)
	{
		return strcmp(str1.c_str(), str2.c_str()) == 0;
	}
	bool operator != (const string& str1, const string& str2)
	{
		return !(str1 == str2);
	}

	ostream& operator<<(ostream& out, const string& str)
	{
		for (auto ch : str)
		{
			out << ch;
		}

		return out;
	}
	istream& operator>>(istream& in, string& str)
	{
		str.clear();

		const int N = 256; // 创建数组,减少扩容的次数
		char buff[N];
		int i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n') //字符串分隔符
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}

		return in;
	}
}
// 测试文件:test.cpp
#include"string.h"

namespace hds
{
	void test01()
	{
		hds::string str("hello world");
		cout << str.c_str() << endl;

		cout << str.size() << endl;

		str.push_back('!');
		cout << str.c_str() << endl;
	}

	void test02()
	{
		hds::string str("hello");
		str.append("world");
		cout << str.c_str() << endl;
	}

	void test03()
	{
		hds::string str("hello");
		str += "world";
		cout << str.c_str() << endl;
	}

	void test04()
	{
		hds::string str("hello world");
		str.insert(0, 'x');
		str.insert(7, 'y');
		str.insert(13, '!');
		cout << str.c_str() << endl;
	}

	void test05()
	{
		hds::string str("hello world");
		str.insert(0, "***");
		str.insert(9, "hds");
		str.insert(17, "!!!");
		cout << str.c_str() << endl;
	}

	void test06()
	{
		hds::string s1("hello world");
		s1.erase(5, 10);
		cout << s1.c_str() << endl;
		hds::string s2("hello world");
		s2.erase(5);
		cout << s2.c_str() << endl;
		hds::string s3("hello world");
		s3.erase(6, 3);
		cout << s3.c_str() << endl;
	}

	void test07()
	{
		hds::string s1("hello world");
		cout << s1.find('o', 0) << endl;
		cout << s1.find("wor", 0) << endl;

		string tmp = s1.substr(6, 10);
		cout << tmp.c_str() << endl;
	}

	void test08()
	{
		hds::string s1("hello world");
		hds::string s2("hello world");
		cout << (s1 <= s2) << endl;
	}

	void test09()
	{
		string s1("hello world");
		string s2("hello world");
		cout << s1 << s2 << endl;

		string s3;
		cin >> s3;
		cout << s3 << endl;
	}

	void test()
	{
		string s1("hello world");
		string s2(s1);
		cout << s1 << endl;
		cout << s2 << endl;

		string s3 ("hds");
		s3 = s1;
		cout << s3 << endl;
	}
}
int main()
{
	//hds::test01();
	//hds::test02();
	//hds::test03();
	//hds::test04();
	//hds::test05();
	//hds::test06();
	hds::test();
	return 0;
}

本期的分享就到此结束,大家快去上手练习吧!!!如果觉得还不错,请大家点个赞支持一下吧(笔芯笔芯)。