C++ 中string的初始化和使用方法

一、先弄清楚:C++ 里有哪些“字符串”?

在 C++ 中,和“字符串”相关的常见类型主要有:

  1. C 风格字符串(C-style string)
    • 本质:以 '\0' 结尾的 char 数组
    • 例:char s[] = "hello";
  2. std::string(C++ 标准库字符串)
    • 本质:封装好的动态字符串类,自动管理内存
    • 例:std::string s = "hello";
  3. 宽字符/Unicode 相关的:
    • wchar_tchar16_tchar32_tstd::wstringstd::u16stringstd::u32string
    • 这些在处理多语言/Unicode 时会用到,这里先不展开。

日常开发中,绝大部分情况都用 std::string,只有与底层 API、C 接口打交道时,才不得不使用 C 风格字符串。

你看到的代码片段:

string groups[6][6]{};for(auto& s : allowed){ groups[s[0]-'A'][s[1]-'A']+= s[2];}

这里用的是 std::string 的二维数组std::string groups[6][6]。在 C++ 中,stringstd::string 的别名(using std::string; 之后)。


二、std::string 的常见初始化方式

1. 默认初始化 / 值初始化

std::string s1;// 默认初始化,内容为空字符串 "" std::string s2{};// 值初始化,效果同上,也是 ""

你看到的:

string groups[6][6]{};

这行的含义是:

  • 声明一个 6×6 的 std::string 数组
  • {}; 表示值初始化,把每个元素都初始化为“空字符串”

等价于:

string groups[6][6];// 在这种情况下,同样会默认构造出 36 个空字符串

加不加 {}std::string 来说差别不大(都是默认构造为 “”),但 {} 是一种更明确的“值初始化”写法。


2. 用 C 字符串字面量初始化

std::string s1 ="hello"; std::string s2("world"); std::string s3{"hi"};// 列表初始化

这些写法都可以,把右边的 C 字符串字面量拷贝到 std::string 里。


3. 用重复字符构造

std::string s4(5,'a');// "aaaaa"

4. 用另一个字符串 / 子串初始化

std::string src ="abcdefg"; std::string s5 = src;// 拷贝整个字符串 std::string s6(src,2);// 从下标 2 开始到末尾:"cdefg" std::string s7(src,2,3);// 从下标 2 开始取 3 个字符:"cde"

5. 用迭代器区间初始化

std::string src ="abcdef"; std::string s8(src.begin()+1, src.begin()+4);// "bcd"

三、std::string 的基本用法

只要理解:std::string 就像一个会自动扩容的字符数组 + 常用操作方法,用起来就很自然。

1. 访问字符

std::string s ="hello";// 下标操作char c1 = s[0];// 'h',不做越界检查 s[1]='A';// s 变成 "hAllo"// at() 会做越界检查,越界会抛出异常char c2 = s.at(1);

在你看到的代码里:

groups[s[0]-'A'][s[1]-'A']+= s[2];

这里也在用下标访问 std::string 的字符:

  • s[0]:第一个字符
  • s[1]:第二个字符
  • s[2]:第三个字符

再解释一下这个表达式:

s[0]-'A'

假设 s[0] 是一个大写字母 'A''F'(因为数组大小是 6),例如:

  • 'A' - 'A' == 0
  • 'B' - 'A' == 1
  • 'C' - 'A' == 2

所以 s[0] - 'A' 被当作 行索引s[1] - 'A' 当作 列索引
s[2] 是一个字符,被 追加groups[i][j] 这个字符串里(下面会讲 “追加” 的用法)。


2. 获取长度

std::string s ="hello"; size_t len1 = s.size(); size_t len2 = s.length();// 和 size() 等价

3. 拼接字符串

++=
std::string s1 ="hello"; std::string s2 ="world"; std::string s3 = s1 + s2;// "helloworld" std::string s4 = s1 +" ";// "hello " s1 +=" world";// s1 变成 "hello world" s2 +='!';// s2 变成 "world!"

现在再看你那行代码:

groups[s[0]-'A'][s[1]-'A']+= s[2];

等价含义是:

// 取出这个单元格里的字符串 std::string& cell = groups[s[0]-'A'][s[1]-'A'];// 把 s[2] 这个字符追加到 cell 的末尾 cell.push_back(s[2]);// 等价于 cell += s[2];

groups 是一个 6×6 的字符串数组,初始全是空串 ""
当遍历 allowed 里的每一个 s 时,把 s[2] 这个字符分类放到 groups[s[0]-'A'][s[1]-'A'] 里。

一个类比:

  • groups 看作 6×6 的“格子”,每个格子里是一个字符串篮子
  • 对于字符串 s = "ABC"
    • 'C' 扔进 ( 'A'-'A' , 'B'-'A' ) = (0, 1) 这个格子的篮子里

最终,每个格子会存放一个由多个字符拼起来的字符串。


4. 插入 / 删除 / 替换

std::string s ="hello";// 插入 s.insert(1,"ABC");// "hABCello"// 擦除 s.erase(1,3);// 从下标 1 起删 3 个,变回 "hello"// 替换 s.replace(1,2,"XX");// "hXXlo"

5. 查找

std::string s ="hello world";auto pos = s.find("lo");// 返回 3if(pos != std::string::npos){// 找到了// ...}auto pos2 = s.rfind('o');// 从右往左找,返回 7

6. 子串

std::string s ="abcdefgh"; std::string sub = s.substr(2,3);// 从下标 2 开始,取 3 个字符:"cde" std::string sub2 = s.substr(2);// 从 2 到末尾:"cdefgh"

7. 与 C 风格字符串互转

std::string s ="hello";constchar* p = s.c_str();// 或 s.data(),得到以 '\0' 结尾的指针,只读// 从 const char* 创建/复制constchar* c ="world"; std::string s2(c);// "world"

四、std::string 的数组、容器、范围 for

你看到的是 std::string二维数组 + 范围 for:

string groups[6][6]{};for(auto& s : allowed){ groups[s[0]-'A'][s[1]-'A']+= s[2];}

逐句拆一下:

  1. string groups[6][6]{};
    • 创建一个 6×6 的 string 数组
    • 每个元素都是空字符串 ""
  2. for (auto& s : allowed)
    • 遍历某个容器 allowed(应该是如 vector<string>array<string, N>
    • auto& s 表示:s 是每个元素的引用,并且类型由编译器根据 allowed 的元素类型推导,一般就是 std::string&
  3. groups[s[0]-'A'][s[1]-'A'] += s[2];
    • s 这个字符串的第 3 个字符 s[2] 追加到一个格子中
    • 行索引:s[0] - 'A'
    • 列索引:s[1] - 'A'

举个具体例子帮你完全理解:

std::vector<std::string> allowed ={"ABC","ABD","BAC","BAE"}; std::string groups[6][6]{};for(auto& s : allowed){ groups[s[0]-'A'][s[1]-'A']+= s[2];}

循环执行过程:

  1. s = "ABC"
    • s[0] = 'A' → 行索引 0
    • s[1] = 'B' → 列索引 1
    • s[2] = 'C'
    • groups[0][1] += 'C';
      原本 groups[0][1]"",现在变成 "C"
  2. s = "ABD"
    • 仍是位置 [0][1]
    • groups[0][1] += 'D';
      "C" + 'D'
      "CD"
  3. s = "BAC"
    • s[0] = 'B' → 行索引 1
    • s[1] = 'A' → 列索引 0
    • s[2] = 'C'
    • groups[1][0] += 'C';
      "" 变成 "C"
  4. s = "BAE"
    • 同样 [1][0]
    • groups[1][0] += 'E';
      "C" + 'E'
      "CE"

最后:

  • groups[0][1] == "CD"
  • groups[1][0] == "CE"
  • 其它格子还是 ""

这类写法在“预处理 + 构建查找表 / 分类表”时很常见。


五、C 风格字符串(简要对比)

你说“感觉不是很懂字符串了”,本质上是:C 风格字符串 和 std::string 在语法和行为上差异比较大,混起来看容易晕。

1. C 风格字符串的典型写法

char s1[]="hello";// 数组,大小为 6,包括末尾 '\0'char* s2 ="world";// 指向字符串常量(在现代 C++ 中不推荐修改)

它只是一个 char 数组,靠末尾的 '\0' 来表示结束。

2. 常见操作(需要 <cstring>

#include<cstring>char a[20]="hello";char b[20];// 拷贝 std::strcpy(b, a);// b = "hello"// 追加 std::strcat(a," world");// a = "hello world"// 比较int cmp = std::strcmp(a, b);// 按字典序比较

相比之下,std::string

  • 不需要关心 '\0'
  • 有自动内存管理
  • 有很多成员函数
  • 更安全、更方便

六、常见易混点与建议

  1. 优先使用 std::string,不要再自己手动分配 char* + new[] 来拼字符串。
  2. 如果一定要和 C 接口交互:
    • std::string::c_str()data() 拿到 const char*

不要混淆:

std::string s ="abc";// C++ 字符串char cstr[]="abc";// C 风格字符串

初始化数组时:

std::string arr[10];// 每个都是 "" std::string arr2[10]{};// 同样每个都是 ""

std::string 来说,两者一样;但 {} 的风格在现代 C++ 中更被推荐。


七、小总结(把你看到的写法串起来)

  • string groups[6][6]{}
    创建 6×6 的 std::string 的二维数组,每个元素初始化为空字符串。
  • for (auto& s : allowed)
    遍历一个字符串容器 alloweds 是每一个 std::string 的引用。
  • groups[s[0] - 'A'][s[1] - 'A'] += s[2];
    • s[0]s[1] 这两个大写字母映射成 0~5 的整数索引
    • 用这两个数字当成 groups 的行、列坐标
    • 在这个格子里,把 s[2] 这个字符追加到已有的字符串后面

这整段代码逻辑就是:
根据字符串 s 的前两个字符对第三个字符进行“二维分类”,并在每个分类格子里把同类的字符串在一起。

Read more

Flutter 组件 t_stats 的适配 鸿蒙Harmony 实战 - 驾驭高性能统计学运算、实现鸿蒙端海量数据实时态势感知与工业级描述性统计方案

Flutter 组件 t_stats 的适配 鸿蒙Harmony 实战 - 驾驭高性能统计学运算、实现鸿蒙端海量数据实时态势感知与工业级描述性统计方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 t_stats 的适配 鸿蒙Harmony 实战 - 驾驭高性能统计学运算、实现鸿蒙端海量数据实时态势感知与工业级描述性统计方案 前言 在鸿蒙(OpenHarmony)生态的工业实时监控看板、高频金融行情分析、以及需要对海量传感器数据(如:每秒 1000 次的压力采样)进行即时“数理诊断”的场景中,“统计学处理的效能”是决定应用能否实时响应的关键胜负手。面对一分钟产生的 60,000 个采样值。如果通过传统的逻辑循环逐个累加、手动排序求中位数。那么不仅会导致鸿蒙端 UI 产生严重的卡顿。更会因为频繁的浮点运算导致的精准度丢失。引发错误的业务预警。 我们需要一种“算法原生、计算极致”的数理治理艺术。 t_stats 是一套专注于极致性能、内存友好的统计学核心库。它通过对描述性统计(

By Ne0inhk

Flutter 三方库 dart_spawner 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明的跨项目 Isolate 动态脚本执行引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 dart_spawner 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明的跨项目 Isolate 动态脚本执行引擎 在鸿蒙(OpenHarmony)系统开发中,如何在一套 HAP 应用内动态加载并执行来自另一个 Dart 项目或特定 URI 的代码,且能保证彼此间的计算隔离?dart_spawner 为开发者提供了一套工业级的、基于 Dart VM Isolate 的动态脚本孵化方案。本文将带您深入实战其在鸿蒙高性能并发处理中的应用。 前言 什么是 Dart Spawner?它不仅是简单的 Isolate.spawn 封装,而是一个能够跨项目、跨文件路径(File/Uri)拉起独立 Dart VM

By Ne0inhk
【Linux系统编程】(三十五)揭秘 Linux 信号产生:从终端到内核全解析

【Linux系统编程】(三十五)揭秘 Linux 信号产生:从终端到内核全解析

前言         在 Linux 系统中,信号是进程间异步通信的 “信使”,而 “信号产生” 则是这个通信过程的起点。无论是我们熟悉的Ctrl+C终止进程,还是程序运行中出现的段错误、定时器超时,本质上都是信号被触发产生的过程。很多开发者只知道 “信号能终止进程”,却不清楚信号到底是怎么来的 —— 是用户操作触发的?还是系统自动产生的?不同场景下信号的产生机制有何不同?         本文将基于 Linux 内核原理,结合 5 种核心信号产生场景(终端按键、系统命令、函数调用、软件条件、硬件异常),用通俗的语言,带你全方位揭秘信号产生的底层逻辑,让你不仅 “知其然”,更 “知其所以然”。下面就让我们正式开始吧! 一、信号产生的核心本质:谁在 “发送” 信号?         在深入具体场景之前,我们先明确一个核心问题:信号是由谁产生并发送的?答案是操作系统(OS)。         无论信号的触发源头是用户按键、函数调用还是硬件异常,

By Ne0inhk