【c++】std::string

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

std::string 是 C++ 标准库(<string> 头文件)提供的字符串处理类,封装了字符序列的存储、操作和内存管理,是替代 C 风格字符数组(char[])的核心工具。

一、核心特性(与 C 风格字符串的对比)

二、内存模型(底层实现核心)

std::string 的底层实现依赖小字符串优化(SSO, Small String Optimization),这是理解其性能和行为的关键:

1. 小字符串优化(SSO)

  • 核心思想:短字符串直接存在 string 对象内部的静态缓冲区,避免堆内存分配;长字符串使用独立堆内存。
  • 阈值:主流编译器(GCC/Clang/MSVC)默认阈值为 15 字节(15 个字符 + 1 个 '\0',共 16 字节,对齐优化);部分编译器为 22/23 字节。
// 简化版内存结构
struct string {
    union {
        struct { // 小字符串(≤15字节)
            char buf[16]; // 内置缓冲区,含 '\0'
            size_t size;  // 实际长度
        };
        struct { // 大字符串(>15字节)
            char* ptr;    // 堆内存指针
            size_t size;  // 实际长度
            size_t cap;   // 总容量(不含 '\0')
        };
    };
};

2. 容量(capacity)与大小(size)

  • size/length:字符串的实际有效字符数(不含末尾的 '\0'),size()length() 是等价的(历史原因保留两个接口)。
  • capacity:已分配内存的总容量(可存储的最大字符数,不含 '\0'),capacity() ≥ size()
  • reserve(n):预分配至少 n 字节的容量,避免频繁扩容(提升性能)。
  • shrink_to_fit():将容量缩减至与大小一致(释放多余内存,C++11 引入)。

示例:容量与大小操作

string s = "hello";
cout << s.size();     // 5(有效字符数)
cout << s.capacity(); // GCC下:15(SSO阈值,小字符串容量=15)
cout << s.length();   // 5(等价于size())

s.reserve(100);       // 预分配100字节容量
cout << s.capacity(); // 100(转为大字符串,堆内存)
s.shrink_to_fit();    // 容量缩减为5
cout << s.capacity(); // 5

3. 扩容规则

当字符串长度超过当前容量时,string 会自动扩容,主流编译器的扩容因子为 2 倍(部分为 1.5 倍):

  • 示例:初始容量 15 → 扩容后 30 → 再扩容 60 → ...
  • 注意:扩容会触发「内存重新分配 + 数据拷贝」,频繁扩容影响性能,可通过 reserve() 提前预分配。

三、基础用法

1. 构造与初始化

std::string 提供多种构造方式,覆盖不同场景:

示例:构造函数

// 基础构造
string s1;                // 空字符串
string s2 = "hello";      // 拷贝初始化
string s3("world", 3);    // "wor"
string s4(4, 'x');        // "xxxx"
string s5(s2, 1, 3);      // 从s2的索引1取3个字符 → "ell"

// 移动构造(s1变为空,s6接管资源)
string s6 = std::move(s2);
cout << s2.empty(); // true
cout << s6;         // "hello"

2. 元素访问

提供 4 种访问方式,各有适用场景:

示例:元素访问

string s = "hello";
cout << s[0];      // 'h'(无越界检查)
cout << s.at(1);   // 'e'(越界抛异常)
s[4] = 'o';        // 修改为 'o' → "hello"
s.back() = '!';    // 修改尾字符 → "hell!"

// 兼容C接口
printf("%s\n", s.c_str()); // 输出 "hell!"

3. 赋值与拼接

(1)赋值操作

string s;
s = "hello";       // 赋值C风格字符串
s = 'a';           // 赋值单个字符
s.assign("world", 3); // 赋值前3个字符 → "wor"
s.assign(5, 'x');  // 赋值5个'x' → "xxxxx"

(2)拼接操作

4. 插入与删除

(1)插入(insert)
string s = "hello";
s.insert(2, "xx");    // 在索引2插入 → "hexxllo"
s.insert(0, 3, 'y');  // 在开头插入3个'y' → "yyyhexxllo"
s.insert(s.end(), 'z');// 在末尾插入 → "yyyhexxlloz"
(2)删除(erase)
string s = "hello world";
s.erase(5);          // 从索引5删除到末尾 → "hello"
s.erase(1, 2);       // 从索引1删除2个字符 → "hlo"
s.clear();           // 清空字符串,size=0,capacity不变

5. 查找与替换

(1)查找(find 系列)

find 是最常用的查找方法,返回 size_t 类型的索引,未找到返回 string::npossize_t(-1),无效位置)。

查找方法功能
find(str, pos)从 pos 开始查找 str,返回首次出现的索引
rfind(str)反向查找,返回最后一次出现的索引
find_first_of(str)查找 str 中任意字符的首次出现索引
find_last_of(str)查找 str 中任意字符的最后出现索引
find_first_not_of(str)查找第一个不在 str 中的字符索引
示例:查找
string s = "hello world hello";
size_t pos = s.find("hello");    // 0(首次出现)
pos = s.find("hello", 1);       // 12(从索引1开始找)
pos = s.rfind("hello");         // 12(最后一次出现)
pos = s.find("test");           // string::npos(未找到)

// 查找任意字符
pos = s.find_first_of("abc");   // string::npos(无a/b/c)
pos = s.find_first_of("wor");   // 6('w'的索引)
(2)替换(replace)
string s = "hello world";
s.replace(6, 5, "cpp"); // 从索引6替换5个字符为"cpp" → "hello cpp"
s.replace(s.find("cpp"), 3, "c++"); // 替换"cpp"为"c++" → "hello c++"

6. 子串(substr)

string s = "hello world";
string sub1 = s.substr(6);      // 从索引6到末尾 → "world"
string sub2 = s.substr(0, 5);   // 从索引0取5个字符 → "hello"

 注意:substr(pos, len) 中,若 len 超过剩余字符数,会自动截取到末尾;若 pos 越界,抛 out_of_range 异常。

7. 比较(compare)

支持直接用 ==/!=/>/< 运算符,也可通过 compare() 方法(返回 int,兼容 C 风格):

string s1 = "abc", s2 = "abd";
cout << (s1 < s2);  // true(按字典序比较)
cout << s1.compare(s2); // -1(s1 < s2)
cout << s1.compare(1, 2, "bc"); // 0(s1[1:2] = "bc")

四、高级操作(C++11+ 特性)

1. 移动语义(C++11)

std::string 实现了移动构造和移动赋值,避免深拷贝,提升性能:

// 移动构造:s1 转移资源给 s2,s1 变为空
string s1 = "hello";
string s2 = std::move(s1);

// 移动赋值
string s3;
s3 = std::move(s2);

2. 数值转换(C++11)

<string> 提供数值与字符串的双向转换,替代 atoi/itoa

转换方向函数示例
数值→字符串to_string(val)to_string(123) → "123"
字符串→整数stoi()/stol()/stoll()stoi("123") → 123
字符串→浮点数stof()/stod()/stold()stod("3.14") → 3.14
示例:数值转换
string s = to_string(123.45); // "123.45"
int i = stoi("456");          // 456
double d = stod("78.9");      // 78.9

// 处理异常(如转换失败)
try {
    stoi("abc"); // 抛 invalid_argument 异常
} catch (const exception& e) {
    cout << e.what() << endl;
}

3. 范围 for 循环(C++11)

string s = "hello";
for (char c : s) {
    cout << c << " "; // h e l l o
}
// 修改字符
for (char& c : s) {
    c = toupper(c); // 转为大写 → "HELLO"
}

4. 非成员函数

(1)getline:读取整行(含空格)
string line;
// 读取一行(直到换行,忽略换行符),替代 cin >> s(遇空格停止)
getline(cin, line);
cout << line;
(2)swap:交换两个字符串(O (1))
string s1 = "hello", s2 = "world";
swap(s1, s2); // s1="world", s2="hello"(仅交换指针/缓冲区,无拷贝)

五、实现细节(深入理解)

1. 写时拷贝(COW)

  • 概念:多个 string 对象共享同一块堆内存,仅当其中一个对象修改时,才拷贝内存(Copy On Write)。
  • 现状:C++11 后已废弃(因线程安全问题),现代编译器(GCC 5+、MSVC)的 string 均为深拷贝 + SSO。

2. 线程安全

  • const 成员函数(如 size()c_str())是线程安全的(多个线程只读);
  • const 成员函数(如 +=erase())不是线程安全的,多线程修改需加锁;
  • 不同 string 对象的操作互不影响。

3. 空字符串

  • 空字符串的 size()=0capacity() 通常为 SSO 阈值(15);
  • 空字符串的 c_str() 返回指向 "\0" 的指针,非空指针。

六、避坑要点(高频错误)

1. 索引越界

  • s[i] 越界是未定义行为(UB),可能崩溃;
  • 优先用 s.at(i)(抛异常),或先检查 i < s.size()

2. npos 的使用

  • string::npossize_t(无符号整数),不能用 int 接收返回值:
    // 错误
    int pos = s.find("test");
    if (pos == string::npos) { ... } // 隐式转换导致错误
    
    // 正确
    size_t pos = s.find("test");
    if (pos == string::npos) { ... }
    

3. c_str ()/data () 的生命周期

  • c_str() 返回的指针仅在 string 未修改时有效,修改后可能失效:
    string s = "hello";
    const char* p = s.c_str();
    s += " world"; // s 扩容,p 变为野指针
    cout << p;     // 未定义行为
    

4. 扩容导致迭代器失效

  • 修改字符串(如 +=insert)可能触发扩容,导致原有迭代器 / 指针失效:
    string s = "hello";
    char* p = &s[0];
    s.reserve(100); // 扩容,p 失效
    cout << *p;     // 未定义行为
    

5. 与 C 风格字符串的混用

  • 避免将 stringdata() 传给需要 \0 结尾的 C 函数(string 内部已保证 \0,但修改后需确认);
  • 不要用 delete[] s.c_str()string 管理内存,手动释放会重复释放)。

七、总结(核心知识点回顾)

  1. 核心优势:自动内存管理、SSO 性能优化、丰富的操作接口、类型安全;
  2. 核心概念size()(实际长度)、capacity()(总容量)、npos(无效位置)、SSO(小字符串优化);
  3. 关键操作:元素访问([]/at())、查找(find)、替换(replace)、子串(substr)、数值转换(to_string/stoi);
  4. 避坑重点:迭代器失效、npos 类型匹配、c_str () 生命周期、越界访问;
  5. 性能优化:提前 reserve() 避免扩容、使用移动语义减少拷贝、优先用 += 而非 + 拼接。

std::string 是 C++ 开发中最常用的容器之一,掌握其底层实现和接口用法,能大幅提升字符串处理的效率和安全性,避免 C 风格字符串的各种坑。

下一章会发的mystd::mystring,帮助我自己加深印象。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值