【C++修炼之路】C++string的用法

🏝️专栏: 【C++修炼之路】

🌅主页: f狐o狸x

"不为盛名而来,不为低估而去"


hello!大家好久不见呀~从今天开始我将继续整理关于C++的学习心得,欢迎大家一起学习

在 C 语言中,我们处理字符串依赖于字符数组和一系列 C 标准库函数(如strcpystrcatstrcmp),不仅需要手动管理内存,还容易出现缓冲区溢出等问题。而 C++ 标准库提供的std::string(简称string),封装了字符串的所有操作,提供了安全、便捷、高效的字符串处理方案,是 C++ 开发中处理字符串的首选。

本文将从基础到实用,全面讲解string的核心用法,适合 C++ 初学者快速上手,也可作为日常开发的参考手册。

一、前期准备:使用 string 的前提

要使用string,必须满足两个基本条件(缺一不可):

  1. 包含string头文件(注意无后缀.h
  2. 引用std命名空间(string是标准库中的类,定义在std命名空间下)
cpp 复制代码
#include <iostream>
// 1. 包含string头文件
#include <string>

// 2. 引用std命名空间(两种方式二选一)
// 方式1:全局引用(简洁,适合小程序/示例代码)
using namespace std;

// 方式2:局部引用(更安全,适合大型项目,避免命名冲突)
// 后续使用时写 std::string 即可

int main() {
    // 后续string操作写在这里
    return 0;
}

二、string 的基本初始化:多种创建方式

string提供了多种初始化方式,满足不同场景的需求,以下是最常用的几种:

2.1. 空字符串初始化

创建一个长度为 0 的空字符串,后续可通过赋值、拼接等操作填充内容。

cpp 复制代码
// 两种等价写法
string str1;          // 默认构造函数,空字符串
string str2 = "";     // 赋值空字符串常量

2.2. 直接用字符串常量初始化

最常用的方式,直接将双引号包裹的字符串常量赋值给string对象。

cpp 复制代码
string str3 = "Hello, C++ string!";  // 拷贝初始化
string str4("Welcome to C++ world"); // 直接初始化(构造函数传参)

2.3. 用另一个 string 对象初始化

复制已有string对象的内容,创建一个新的字符串对象。

cpp 复制代码
string str5 = str3;  // 拷贝初始化,str5与str3内容完全一致
string str6(str4);   // 直接初始化,str6与str4内容完全一致

2.4. 用指定字符和长度初始化

创建一个由n个相同字符组成的字符串。

cpp 复制代码
int n = 5;
char ch = 'a';
string str7(n, ch);  // str7 = "aaaaa"

2.5. 截取其他字符串的部分内容初始化

从已有字符串的指定索引开始,截取指定长度的内容(后续substr方法会详细讲解)。

cpp 复制代码
string str8 = "Hello, World!";
string str9(str8, 7, 5);  // 从索引7开始,截取5个字符,str9 = "World"

三、string 的核心基础操作

3.1. 获取字符串长度 / 判断是否为空

string提供了两种获取长度的方法,功能完全一致,还有一个专门判断是否为空的高效方法:

  • size():返回字符串的字符个数(推荐,语义更清晰,与其他 STL 容器保持一致)
  • length():返回字符串的字符个数(历史遗留方法,与size()等价)
  • empty():判断字符串是否为空,为空返回true,否则返回false(比size() == 0更高效)

示例代码:

cpp 复制代码
string str = "Hello, C++";
cout << "字符串长度(size()):" << str.size() << endl;
cout << "字符串长度(length()):" << str.length() << endl;

if (str.empty()) {
    cout << "str 是空字符串" << endl;
} else {
    cout << "str 不是空字符串" << endl;
}

// 清空字符串后再判断
string empty_str;
if (empty_str.empty()) {
    cout << "empty_str 是空字符串" << endl;
}

运行结果:

3.2. 字符串的访问:下标 [] vs at ()

string支持两种访问单个字符的方式,均通过索引获取:

(1)下标运算符 []

  • 语法:str[index]
  • 特点:不做越界检查,若索引超出范围(index >= str.size()index < 0),会导致未定义行为(程序可能崩溃、输出乱码等)
  • 适用场景:确定索引不会越界时(如循环遍历),效率更高

(2)at() 成员方法

  • 语法:str.at(index)
  • 特点:会做越界检查,若索引超出范围,会抛出std::out_of_range异常
  • 适用场景:索引可能不确定(如用户输入、动态计算),需要安全校验时

示例代码:

cpp 复制代码
string str = "Hello";

// 1. 下标[]访问
cout << "通过[]访问第0个字符:" << str[0] << endl;
cout << "通过[]访问第3个字符:" << str[3] << endl;

// 2. at()方法访问
cout << "通过at()访问第1个字符:" << str.at(1) << endl;
cout << "通过at()访问第4个字符:" << str.at(4) << endl;

// 3. 循环遍历所有字符(两种方式均可)
cout << "循环遍历字符串([]):";
for (int i = 0; i < str.size(); ++i) {
    cout << str[i] << " ";
}
cout << endl;

// 4. 越界访问演示(注释掉,避免程序崩溃)
// str[10];  // 未定义行为,可能崩溃
// str.at(10); // 抛出out_of_range异常

运行结果:

3.3. 字符串的修改与拼接

string提供了多种便捷的修改和拼接方式,告别 C 语言的strcatstrcpy

(1)直接赋值(覆盖原有内容)

使用=运算符,直接将新字符串覆盖原有字符串的内容。

cpp 复制代码
string str = "Old string";
cout << "赋值前:" << str << endl;

str = "New string";  // 直接赋值,覆盖原有内容
cout << "赋值后:" << str << endl;

(2)拼接操作(追加内容,不覆盖原有内容)

  • 方式 1:+= 运算符(简洁高效,推荐日常使用),支持拼接string对象、字符串常量、单个字符
  • 方式 2:append() 方法(功能更强大,支持拼接部分字符串、多个重复字符等)

示例代码:

cpp 复制代码
string str1 = "Hello";
string str2 = " World";

// 1. += 运算符拼接
str1 += str2;  // 拼接string对象
cout << "拼接后1:" << str1 << endl;

str1 += "!";   // 拼接字符串常量
cout << "拼接后2:" << str1 << endl;

str1 += '@';   // 拼接单个字符
cout << "拼接后3:" << str1 << endl;

// 2. append() 方法拼接
string str3 = "I love";
str3.append(" C++");  // 拼接字符串常量
cout << "append拼接后1:" << str3 << endl;

str3.append(3, '!');  // 拼接3个重复字符
cout << "append拼接后2:" << str3 << endl;

str3.append(str1, 0, 5);  // 拼接str1的前5个字符(从索引0开始,长度5)
cout << "append拼接后3:" << str3 << endl;

运行结果:

3.4. 字符串的比较

string支持直接使用比较运算符进行字符串比较,也提供了compare()方法,比较规则与 C 语言的strcmp一致:按字符的 ASCII 码值逐字符比较,直到出现不同字符或到达字符串末尾

(1)比较运算符(推荐,简洁直观)

支持的运算符:==(相等)、!=(不相等)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)

(2)compare() 方法(功能更强大,返回整数值)

  • 返回值:0(两个字符串相等)、正数(当前字符串大于目标字符串)、负数(当前字符串小于目标字符串)

示例代码:

cpp 复制代码
string str1 = "Apple";
string str2 = "Banana";
string str3 = "Apple";

// 1. 比较运算符
if (str1 == str3) {
    cout << "str1 与 str3 相等" << endl;
}

if (str1 < str2) {
    cout << "str1 小于 str2" << endl;
}

if (str2 != str1) {
    cout << "str2 与 str1 不相等" << endl;
}

// 2. compare() 方法
int result = str1.compare(str2);
if (result < 0) {
    cout << "str1.compare(str2) 返回负数,str1 < str2" << endl;
}

result = str1.compare(str3);
if (result == 0) {
    cout << "str1.compare(str3) 返回0,str1 == str3" << endl;
}

// 比较部分字符串(str1从索引0开始,长度3 与 str2从索引0开始,长度3比较)
result = str1.compare(0, 3, str2, 0, 3);
cout << "str1前3个字符与str2前3个字符比较结果:" << result << endl;

运行结果:

四、string 的常用实用操作

4.1. 字符串查找:find ()

find() 方法用于在当前字符串中查找指定的子串或字符,返回首次出现的索引 ;若查找失败,返回string::npos(一个静态常量,代表无效索引,通常被定义为-1,但实际是无符号整数类型)。

语法(常用):

  • str.find(substr):查找子串substr首次出现的索引
  • str.find(ch):查找字符ch首次出现的索引
  • str.find(substr, pos):从索引pos开始,查找子串substr首次出现的索引

示例代码:

cpp 复制代码
string str = "Hello, World! Hello, C++!";
string substr = "Hello";
char ch = 'W';

// 1. 查找子串首次出现的索引
size_t index1 = str.find(substr);
if (index1 != string::npos) {
    cout << "子串 \"" << substr << "\" 首次出现的索引:" << index1 << endl;
}

// 2. 查找字符首次出现的索引
size_t index2 = str.find(ch);
if (index2 != string::npos) {
    cout << "字符 \'" << ch << "\' 首次出现的索引:" << index2 << endl;
}

// 3. 从指定索引开始查找子串
size_t index3 = str.find(substr, 7);
if (index3 != string::npos) {
    cout << "从索引7开始,子串 \"" << substr << "\" 首次出现的索引:" << index3 << endl;
}

// 4. 查找不存在的子串
size_t index4 = str.find("Java");
if (index4 == string::npos) {
    cout << "未找到子串 \"Java\"" << endl;
}

运行结果:

注意:find()的返回值类型是size_t(无符号整数类型),不要用int接收,避免负数转换问题。

4.2. 字符串截取:substr ()

substr() 方法用于截取字符串的一部分,返回一个新的string对象,不会修改原字符串。

语法:

  • str.substr(pos, len):从索引pos开始,截取长度为len的字符串
  • len省略,或pos + len超出字符串长度,则截取到字符串末尾

示例代码:

cpp 复制代码
string str = "Hello, World! Hello, C++!";

// 1. 截取指定长度的字符串
string sub1 = str.substr(7, 5);  // 从索引7开始,截取5个字符
cout << "截取结果1:" << sub1 << endl;

// 2. 截取到字符串末尾
string sub2 = str.substr(19);  // 从索引19开始,截取到末尾
cout << "截取结果2:" << sub2 << endl;

// 3. 索引超出范围(抛出out_of_range异常)
// string sub3 = str.substr(100);

运行结果:

4.3. 字符串替换:replace ()

replace() 方法用于将字符串中的指定部分替换为新的字符串,会修改原字符串

语法(常用):

  • str.replace(pos, len, new_str):从索引pos开始,将长度为len的字符串替换为new_str

示例代码:

cpp 复制代码
string str = "I love Java!";
cout << "替换前:" << str << endl;

// 将从索引7开始的4个字符("Java")替换为"C++"
str.replace(7, 4, "C++");
cout << "替换后:" << str << endl;

// 替换为更长的字符串
str.replace(7, 3, "C++ Programming");
cout << "再次替换后:" << str << endl;

运行结果:

4.4. 清空字符串:clear ()

clear() 方法用于清空字符串的所有内容,清空后字符串变为空字符串,size()返回 0。

示例代码:

cpp 复制代码
string str = "Hello, C++!";
cout << "清空前长度:" << str.size() << endl;

str.clear();
cout << "清空后长度:" << str.size() << endl;
cout << "是否为空:" << (str.empty() ? "是" : "否") << endl;

运行结果:

五、string 与 C 语言字符数组的转换

在实际开发中,我们可能需要与 C 语言接口交互(如某些函数要求传入const char*类型参数),此时需要进行string与 C 语言字符数组的转换。

5.1. string 转 const char*(两种方法)

  • c_str():返回一个以\0结尾的const char*(C 风格字符串),是最常用的方法
  • data():C++11 及以后,data()c_str()功能一致,均返回const char*;C++11 之前,data()不保证返回的字符串以\0结尾

注意:

  1. 返回的const char*是只读的,不能修改其内容
  2. 其有效期与原string对象一致,若原string对象被修改(如赋值、拼接)或销毁,返回的指针将失效

示例代码:

cpp 复制代码
string str = "Hello, C language!";

// 1. c_str() 转换
const char* c_str1 = str.c_str();
cout << "c_str() 转换结果:" << c_str1 << endl;

// 2. data() 转换
const char* c_str2 = str.data();
cout << "data() 转换结果:" << c_str2 << endl;

// 3. 注意:修改原字符串后,指针失效
str = "Modified string";
// cout << c_str1 << endl;  // 未定义行为,输出结果不可预期

运行结果:

5.2. const char* 转 string

直接赋值即可,非常简洁,string会自动处理\0结尾,无需手动管理长度。

示例代码:

cpp 复制代码
const char* c_str = "Hello, C++ string!";

// 直接赋值转换
string str = c_str;
cout << "转换后的string:" << str << endl;

// 也可以在初始化时直接赋值
string str2(c_str);
cout << "初始化转换后的string:" << str2 << endl;

运行结果:

六、完整可运行示例代码

将以上知识点整合为一个完整程序,可直接复制编译运行:

cpp 复制代码
#include <iostream>
#include <string>

using namespace std;

int main() {
    // 1. 初始化
    string str = "Hello, C++!";
    string str_copy = str;
    string str_char(5, 'a');

    // 2. 输出基本信息
    cout << "原始字符串:" << str << endl;
    cout << "拷贝字符串:" << str_copy << endl;
    cout << "字符填充字符串:" << str_char << endl;
    cout << "字符串长度:" << str.size() << endl;
    cout << "是否为空:" << (str.empty() ? "是" : "否") << endl;

    // 3. 访问与修改
    cout << "第0个字符:" << str[0] << endl;
    str[0] = 'h';  // 小写化第一个字符
    cout << "修改后字符串:" << str << endl;

    // 4. 拼接
    str += " Welcome!";
    cout << "拼接后字符串:" << str << endl;

    // 5. 查找与截取
    size_t index = str.find("Welcome");
    if (index != string::npos) {
        string sub = str.substr(index);
        cout << "找到的子串:" << sub << endl;
    }

    // 6. 替换
    str.replace(index, 7, "Hello World");
    cout << "替换后字符串:" << str << endl;

    // 7. 转换为C风格字符串
    const char* c_str = str.c_str();
    cout << "C风格字符串:" << c_str << endl;

    // 8. 清空
    str.clear();
    cout << "清空后字符串长度:" << str.size() << endl;

    return 0;
}

运行结果:

七、模拟实现string

下面是我根据string的主要功能模拟实现的部分string的函数,大家可以参考一下

7.1. string.h

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1;

#include <iostream>
#include <string>
#include <assert.h>

using namespace std;

namespace fox
{
	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;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// 析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

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

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

		size_t size()
		{
			return _size;
		}

		size_t capacity()
		{
			return _capacity;
		}

		char& operator[](size_t pos)
		{
			//检查pos合法性
			assert(pos < _size);
			return *(_str + pos);
		}

		const char& operator[](size_t pos) const
		{
			//检查pos合法性
			assert(pos < _size);
			return *(_str + pos);
		}

		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; // string指针
		size_t _size; // 大小 
		size_t _capacity; // 容量
		static const size_t npos;
	};
	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
}

7.2. string.cpp

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1;

#include "string.h"

using namespace std;

namespace fox
{
	const size_t string::npos = -1;

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

	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);//二倍扩容
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

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

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

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

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);//二倍扩容
		}

		//挪动数据
		size_t end = _size + 1;
		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 == _capacity)
		{
			reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);
		}

		//挪动数据
		size_t end = _size +len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		for (int 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)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			for (size_t i = pos + len; i < _size; i++)
			{
				_str[i - len] = _str[i];
			}

			_size -= len;
		}
	}

	size_t string::find(char ch, size_t pos)
	{
		assert(pos <= _size);

		for (size_t i = pos; i < _size; i++)
		{
			if (ch == _str[i])
				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);

		// len大于剩余字符长度,更新一下len
		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& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}

	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

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

		return out;
	}

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

		const int N = 256;
		char buff[N];
		int i = 0;

		char ch;
		//in >> ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;

				i = 0;
			}

			//in >> ch;
			ch = in.get();
		}

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

		return in;
	}
}

void TestString1()
{
	fox::string s1("hello!");
	cout << s1 << endl;

	fox::string s2;
	cin >> s2;

	cout << s2 << endl;
}

int main()
{
	
	TestString1();
	return 0;
}

八、总结

  1. string是 C++ 标准库的字符串类,使用前需包含<string>头文件并引用std命名空间。
  2. 初始化方式灵活,支持空字符串、常量赋值、对象拷贝等多种形式。
  3. 基础操作:size()/length()获取长度、empty()判断为空、[]/at()访问字符、+=/append()拼接、比较运算符进行比较。
  4. 实用操作:find()查找、substr()截取、replace()替换、clear()清空。
  5. 与 C 语言字符数组转换:c_str()/data()const char*,直接赋值实现const char*string

进阶拓展

  1. string的内存管理:string会自动管理内存,动态扩容,也可通过reserve()预分配内存,提升效率。
  2. 迭代器遍历:string支持 STL 迭代器,可用于遍历、排序等操作。
  3. 与算法库结合:可使用<algorithm>中的函数(如sort()reverse())操作string
  4. 宽字符字符串:wstring用于处理中文、日文等宽字符,用法与string基本一致。

掌握string的基本用法,能满足绝大多数日常开发的字符串处理需求,后续可深入学习其进阶特性,进一步提升 C++ 字符串处理的效率和灵活性。

相关推荐
阿豪只会阿巴1 小时前
【多喝热水系列】从零开始的ROS2之旅——Day9 初识话题通信:基本命令
c++·笔记·python·ubuntu·ros2
cjp5602 小时前
018.C#管道服务,本机两软件间通讯交互
开发语言·c#
猫天意2 小时前
YOLOv11魔改高效涨点 | 注意力篇 | 坐标注意力CoordAttention:将位置信息硬核嵌入通道,精准捕获长程空间依赖,即插即用,涨点神器!!!
开发语言·人工智能·深度学习·神经网络·yolo·目标检测·低光照增强
黎雁·泠崖2 小时前
Java面向对象:this关键字+构造方法+标准JavaBean
java·开发语言·python
码小猿的CPP工坊2 小时前
C++弱引用智能指针std::weak_ptr使用介绍
开发语言·c++
sheji34162 小时前
【开题答辩全过程】以 基于Java的智慧环卫垃圾收运管理系统设计与实现为例,包含答辩的问题和答案
java·开发语言
暮色_年华2 小时前
随想3:关于语音采集线程 使用 CFS 调度或者 SCHED_FIFO 的思考
c++
Flash.kkl2 小时前
Linux——线程的同步和互斥
linux·开发语言·c++
sunfove2 小时前
Python 面向对象编程:从过程式思维到对象模型
linux·开发语言·python