C++ 标准库中的 std::string 是处理字符串的核心类,封装了字符串的存储、管理和操作,相比 C 风格的 char* 更安全、易用。

1、基本概念

1.1 基本特性

std::string 定义在 <string> 头文件中(属于 std 命名空间),本质是对动态字符数组的封装

  • 动态大小:自动扩容,无需手动管理内存(避免 C 风格字符串的缓冲区溢出问题)。
  • 值语义:赋值、传参时默认进行深拷贝(C++11 后支持移动语义,提升性能)。
  • 丰富接口:提供拼接、查找、替换等数十种字符串操作函数。
  • 与 C 兼容:可通过 c_str() 转换为 C 风格字符串(const char*)。

1.2 与 C 风格字符串的区别

// C 风格字符串
const char* cstr = "Hello";
char cstr_array[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // C++ std::string
std::string cppstr = "Hello";

主要区别

  • 内存管理std::string 自动管理内存,无需手动分配/释放
  • 安全性std::string 避免缓冲区溢出等安全问题
  • 功能性std::string 提供丰富的成员函数
  • 便利性:支持运算符重载(+, ==, < 等)

2、基本操作

2.1 构造与初始化

std::string 提供多种构造函数,支持从字符串字面量、字符数组、其他 string 等初始化

#include <string>
#include <iostream> int main() {
// 默认构造函数
std::string s1; // 从C风格字符串构造
std::string s2 = "Hello";
std::string s3("World"); // 从部分C风格字符串构造
const char* text = "Hello World";
std::string s4(text, 5); // "Hello"
std::string s5(text + 6, 5); // "World" // 重复字符构造
std::string s6(5, 'A'); // "AAAAA" // 拷贝构造
std::string s7 = s2; // "Hello" // 移动构造 (C++11)
std::string s8 = std::move(s2); // s2变为有效但未指定状态 // 初始化列表构造 (C++11)
std::string s9 = {'H', 'e', 'l', 'l', 'o'}; return 0;
}

2.2 赋值操作

std::string s1, s2;

// 赋值操作
s1 = "Hello"; // 从C风格字符串赋值
s2 = s1; // 从另一个string赋值
s1 = 'A'; // 从单个字符赋值 // assign() 方法
s1.assign("Hello"); // 等同于 s1 = "Hello"
s1.assign("Hello", 3); // "Hel"
s1.assign(5, 'A'); // "AAAAA"
s1.assign(s2, 1, 3); // 从s2的下标1开始,取3个字符 // 移动赋值 (C++11)
s1 = std::move(s2);

2.3 访问元素

std::string str = "Hello";

// 使用 [] 运算符(不检查边界)
char c1 = str[0]; // 'H'
str[0] = 'h'; // 修改第一个字符 // 使用 at() 方法(检查边界,越界抛出std::out_of_range)
char c2 = str.at(1); // 'e'
try {
char c3 = str.at(10); // 抛出异常
} catch (const std::out_of_range& e) {
std::cerr << "Out of range: " << e.what() << std::endl;
} // 访问第一个和最后一个字符 (C++11)
char first = str.front(); // 'H'
char last = str.back(); // 'o' // 获取C风格字符串
const char* cstr = str.c_str();
const char* data = str.data(); // C++17起,与c_str()相同

2.4 容量操作

std::string str = "Hello";

// 大小和容量
std::cout << "Size: " << str.size() << std::endl; // 5
std::cout << "Length: " << str.length() << std::endl; // 5 (与size()相同)
std::cout << "Capacity: " << str.capacity() << std::endl; // 当前分配的存储空间
std::cout << "Empty: " << str.empty() << std::endl; // 0 (false) // 调整容量
str.reserve(100); // 预分配至少100字符的空间
str.shrink_to_fit(); // 请求减少容量以适应大小 (C++11) // 调整大小
str.resize(3); // "Hel" (截断)
str.resize(8, '!'); // "Hel!!!!!" (扩展并用'!'填充)

2.5 修改操作

std::string str = "Hello";

// 追加
str.append(" World"); // "Hello World"
str += "!"; // "Hello World!"
str.push_back('!'); // "Hello World!!" // 插入
str.insert(5, " C++"); // "Hello C++ World!!" // 删除
str.erase(5, 4); // "Hello World!!" (删除从位置5开始的4个字符)
str.erase(str.begin() + 5); // 删除单个字符
str.clear(); // 清空字符串 // 替换
str = "Hello World";
str.replace(6, 5, "C++"); // "Hello C++" (从位置6开始,替换5个字符) // 交换
std::string other = "Other";
str.swap(other); // str = "Other", other = "Hello C++"

2.6 字符串操作

std::string str = "Hello World";

// 子字符串
std::string sub = str.substr(6, 5); // "World" // 查找
size_t pos = str.find("World"); // 6
pos = str.find('o'); // 4
pos = str.find('x'); // std::string::npos // 反向查找
pos = str.rfind('o'); // 7 // 查找首次出现/未出现的字符
pos = str.find_first_of("aeiou"); // 1 ('e')
pos = str.find_first_not_of("Helo "); // 6 ('W') // 比较
int result = str.compare("Hello"); // > 0 (str > "Hello")
result = str.compare(6, 5, "World"); // 比较子字符串

2.7 输入输出

#include <string>
#include <iostream>
#include <sstream> int main() {
// 从标准输入读取
std::string input;
std::cout << "Enter a string: ";
std::getline(std::cin, input); // 输出到标准输出
std::cout << "You entered: " << input << std::endl; // 使用字符串流
std::stringstream ss;
ss << "Number: " << 42 << " Pi: " << 3.14;
std::string result = ss.str();
std::cout << result << std::endl; return 0;
}

3、高级特性

3.1 迭代器支持

std::string str = "Hello";

// 使用迭代器遍历
for (auto it = str.begin(); it != str.end(); ++it) {
std::cout << *it;
}
std::cout << std::endl; // 反向迭代器
for (auto rit = str.rbegin(); rit != str.rend(); ++rit) {
std::cout << *rit;
}
std::cout << std::endl; // 基于范围的for循环 (C++11)
for (char c : str) {
std::cout << c;
}
std::cout << std::endl; // 使用算法
#include <algorithm>
std::reverse(str.begin(), str.end()); // "olleH"
std::sort(str.begin(), str.end()); // "hlloe" (注意大小写)

3.2 数值转换

// 字符串到数值
std::string num_str = "123.45";
int i = std::stoi(num_str); // 123
long l = std::stol(num_str); // 123
double d = std::stod(num_str); // 123.45 // 数值到字符串
int value = 42;
double pi = 3.14159;
std::string s1 = std::to_string(value); // "42"
std::string s2 = std::to_string(pi); // "3.141590"

3.3 字符串视图 (C++17)

当函数只需要只读访问字符串而不需要拥有其所有权时,使用 std::string_view可以避免构造 std::string带来的拷贝开销,尤其适用于处理子串。

#include <string_view>
void process_string(std::string_view sv) { // 接受 string, string_view, char[] 等
std::cout << sv.substr(0, 5) << std::endl; // 操作轻量,无拷贝
}

4、底层原理

4.1 内存布局

std::string 通常包含三个核心成员(不同编译器实现可能不同,但逻辑一致):

  • 指向字符数组的指针char*):存储字符串数据(以 \0 结尾,兼容 C 风格)。
  • 大小(size):当前字符串的字符数(不含 \0)。
  • 容量(capacity):当前内存可容纳的最大字符数(不含 \0)。

4.2 扩容机制

当字符串需要增长(如拼接、插入)且 size 超过 capacity 时,std::string 会自动扩容:

  1. 分配一块更大的内存(通常是当前 capacity 的 1.5 倍或 2 倍,取决于编译器)。
  2. 将原数据拷贝到新内存。
  3. 释放旧内存,更新指针。

注意:扩容会导致迭代器、指针、引用失效(因为内存地址改变)。

4.3. 小字符串优化(SSO,Small String Optimization)

为提升短字符串性能,现代编译器(如 GCC、Clang)对 std::string 实现了 SSO:

  • 当字符串较短(如长度 ≤ 15 字符)时,数据直接存储在 std::string 对象内部(无需动态分配内存)。
  • 当字符串较长时,才使用动态内存分配(通过指针指向堆内存)。

优势:避免短字符串的动态内存分配开销(malloc/free 成本),提升性能。

5、常见问题

  1. std::stringsize()capacity() 有什么区别?

    • size():返回字符串当前包含的字符数(实际存储的有效字符,不含结尾 \0)。

    • capacity():返回当前已分配内存可容纳的最大字符数(不包含 \0),即无需扩容可存储的最大字符数。

      举例:若 string s = "abc",则 s.size() = 3s.capacity() 可能为 15(GCC 下 SSO 对短字符串的默认容量)。

  2. reserve(n)resize(n) 的区别?

    • reserve(n):仅调整 capacity(至少为 n),不改变 size(字符串内容不变)。用于预分配内存,避免后续操作频繁扩容。
    • resize(n):调整size为n(改变字符串长度):
      • n > 当前 size:用指定字符(默认 \0)填充新增位置。
      • n < 当前 size:截断字符串至前 n 个字符。

        使用场景reserve 用于性能优化(预分配),resize 用于实际修改字符串长度。
  3. std::string 的拷贝构造函数是深拷贝还是浅拷贝?为什么?

    是深拷贝。std::string 封装了动态内存,拷贝构造时会复制底层字符数组的内容(而非仅复制指针),确保两个字符串相互独立(修改一个不会影响另一个)。

    string a = "hello";
    string b = a; // 深拷贝:b 拥有独立的 "hello" 副本
    b[0] = 'H';
    cout << a << endl; // 仍为 "hello"(不受 b 影响)

    C++11 优化:若源字符串是临时对象(右值),会触发移动构造(string(string&&)),直接接管源字符串的内存(避免拷贝,提升性能)。

  4. 什么是小字符串优化(SSO)?其目的是什么?

    SSO 是 std::string 的一种优化策略:对于短字符串(如长度 ≤ 15),数据直接存储在 std::string 对象内部(栈内存),无需分配堆内存;长字符串才使用堆内存。

    目的:减少短字符串的内存分配开销(堆内存的 malloc/free 成本较高),提升访问速度(栈内存访问比堆内存快)。

    实现std::string 对象内部预留一块固定大小的缓冲区(如 16 字节),短字符串直接存在这里;长字符串则用指针指向堆内存。

  5. 如何将 std::string 转换为 C 风格字符串(const char*)?为什么需要这样做?

    通过 c_str()data() 方法(C++11 后二者等价),返回指向字符串的 const char* 指针(以 \0 结尾)。

    原因:很多 legacy 函数(如 C 库函数 printf、系统调用 open 等)要求传入 C 风格字符串(const char*),c_str() 提供了兼容性接口。

    注意:返回的指针有效期与 std::string 一致,若字符串被修改(如扩容、赋值),指针可能失效。

  6. std::string 是线程安全的吗?

    不是。C++ 标准规定,多个线程同时读写同一个 std::string 对象会导致未定义行为(如数据竞争)。

    线程安全场景

    • 多个线程同时读取同一个 const std::string 对象(安全)。
    • 多个线程操作不同的 std::string 对象(安全)。
  7. std::stringoperator[]at() 有什么区别?

    • 越界检查:operator[] 不做越界检查(越界访问导致未定义行为,可能崩溃);at() 会做越界检查,越界时抛出 out_of_range 异常。
    • 性能:operator[] 略快(无检查开销);at() 因检查略慢,但更安全。

      使用场景:确定索引有效时用 [](追求性能);索引可能无效时用 at()(需捕获异常)。
  8. 为什么 std::stringlength()size() 是等价的?

    历史原因。std::string 起源于早期 C++ 的 basic_string 模板,size() 是容器类的通用接口(如 vector::size()),而 length() 是为了与 C 风格字符串的 strlen() 保持命名习惯。为兼容两种习惯,标准规定二者等价,返回值相同。

  9. 什么情况下使用 std::string::reserve()

    当你知道字符串将增长到特定大小时,使用 reserve() 预分配足够的内存,可以避免多次重新分配和拷贝,提高性能。

  10. 为什么 std::string 的拼接操作可能效率低下?

    多次使用 +=+ 进行拼接可能导致多次内存重新分配。更高效的方式是使用 reserve() 预分配足够空间,或者使用 std::ostringstream

    // 低效的方式
    std::string result;
    for (int i = 0; i < 1000; ++i) {
    result += "string"; // 可能导致多次重新分配
    } // 高效的方式
    std::string result;
    result.reserve(6000); // 预分配足够空间
    for (int i = 0; i < 1000; ++i) {
    result += "string";
    } // 或者使用 ostringstream
    std::ostringstream oss;
    for (int i = 0; i < 1000; ++i) {
    oss << "string";
    }
    std::string result = oss.str();
  11. std::string_view 是什么?有什么优势?

    • std::string_view (C++17) 是一个非拥有式的字符串视图
    • 它提供对字符串数据的只读访问,不管理内存
    • 优势:避免不必要的字符串拷贝,提高性能
    • 注意:必须确保底层字符串的生命周期比 string_view
  12. 移动语义如何影响 std::string

    • C++11 引入移动语义,允许高效地转移字符串所有权
    • 移动操作通常只是复制指针和大小信息,时间复杂度为 O(1)
    • 这大大提高了返回字符串和传递字符串的性能

C++ std::string的更多相关文章

  1. QString 和std::string互转

    std::string cstr; QString qstring; //****从std::string 到QString qstring = QString(QString::fromLocal8 ...

  2. std::string的split函数

    刚刚要找个按空格分离std::string的函数, 结果发现了stackoverflow上的这个问题. 也没仔细看, 直接拿来一试, 靠, 不对啊, 怎么分离后多出个空字符串, 也就是 "a ...

  3. could not deduce template argument for 'const std::_Tree<_Traits> &' from 'const std::string'

    VS2008, 写一个简单的demo的时候出现了这个: 1>------ Build started: Project: GetExportTable, Configuration: Relea ...

  4. 源码阅读笔记 - 3 std::string 与 Short String Optimization

    众所周知,大部分情况下,操作一个自动(栈)变量的速度是比操作一个堆上的值的速度快的.然而,栈数组的大小是在编译时确定的(不要说 C99 的VLA,那货的 sizeof 是运行时计算的),但是堆数组的大 ...

  5. CString std::string相互转换

    CString->std::string 例子: CString strMfc=“test“; std::string strStl; strStl=strMfc.GetBuffer(0); s ...

  6. 计算std:string的字节长度

    如果项目本身是使用 Unicode 字符集和utf8编码,std::string的length(),size()甚至是c的strLen取到的都是字节长度了,比如三个汉字,就是9, 以上情况不满足的话, ...

  7. 【原】error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'std::string'

    今天遇到一个非常难以排查的BUG,谷歌度娘都问过了依旧无解,最后自己重新尝试之后找到解决方案: 先看一下报错信息: 1>.\lenz.cpp(2197)  error C2679: binary ...

  8. 类型安全且自动管理内存的返回 std::string 的 sprintf 实现

    在这篇博文里,我提到了一个例子,说的是使用C++实现类型安全的printf.这个例子很惊艳,但是在我写程序的时候,并非那么"迫切"地需要它出现在我的工具箱中,因为它并不比普通的pr ...

  9. VC++ 中使用 std::string 转换字符串编码

    目录 第1章说明    1 1.1 代码    1 1.2 使用    4 第1章说明 VC++中宽窄字符串的相互转换比较麻烦,借助std::string能大大减少代码量. 1.1 代码 函数声明如下 ...

  10. std::string::npos mean

    std::string::npos 表示 no position, 没位置, 没找到

随机推荐

  1. C# 中使用线程、Task和 ThreadPool 的并发性

    C# 中的并发性涉及使用线程和任务等功能在程序中同时执行任务.这就像让多个工人同时完成不同的工作.这在现代应用程序中至关重要,因为它使它们更快.响应更迅速.并发性可确保我们的应用程序平稳运行,快速响应 ...

  2. openWrt安装三方插件

    前言 openWrt是一款开源的路由器系统,其最大的优点就是 支持第三方扩展插件. 新增的插件基本都会在左侧的服务菜单中展现,通过此入口就可以使用插件功能. 大部分openWrt固件都帮你装好了ope ...

  3. PDD 笔试反思记录

     一个二叉树的有2016个节点  最多有几个拥有两个子节点的 应该是1007  我擦写错了 第二个求一个直线段的长度  不知道对错 没有参考意义 第三个是输出旋转数组  源码 #include< ...

  4. PLY 模型文件简析

    PLY 模型文件简析 参考链接 wiki需要FFFQQQ TIPS 主要是一些英文的简析,但是一句话,网上讲的不清楚,特此说明 property list uchar int vertex_indic ...

  5. unable to find string literal operator ‘operator""format’ with ‘const char [10]’, ‘long unsigned int’ arguments

    简介 遇到问题 unable to find string literal operator 'operator""format' with 'const char [10]', ...

  6. java 拖拽矩形

    简介 java 拖拽矩形 code import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.ut ...

  7. 【乐观锁实现】StampedLock 的乐观读机制

    StampedLock 的乐观读机制主要解决了读多写少场景下,传统读写锁(如 ReentrantReadWriteLock)可能存在的写线程饥饿或性能瓶颈问题.它通过一种"乐观"的 ...

  8. 探索 MCP C# SDK:实现大语言模型与应用的无缝对接

    探索 MCP C# SDK:实现大语言模型与应用的无缝对接 在当今人工智能快速发展的时代,大语言模型(LLMs)已经成为众多应用的核心驱动力.然而,如何让这些强大的模型与各种数据源和工具进行有效集成, ...

  9. 复杂文件格式如何通过ETL工具一步处理

    企业在数字化转型过程中,会面临数据孤岛及格式异构的双重挑战.传统方法处理JSON.XML.CSV等数十种混合格式时,常受限于解析效率低.转换逻辑复杂及数据质量参差等问题.而ETL工具凭借系统化数据治理 ...

  10. [转载]量子力学的Copenhagen学派Copenhagen Interpretation of Quantum Mechanics

    Origin: https://plato.stanford.edu/entries/qm-copenhagen/ Copenhagen Interpretation of Quantum Mecha ...