C++ 中string的初始化和使用方法
一、先弄清楚:C++ 里有哪些“字符串”?
在 C++ 中,和“字符串”相关的常见类型主要有:
- C 风格字符串(C-style string)
- 本质:以
'\0'结尾的char数组 - 例:
char s[] = "hello";
- 本质:以
std::string(C++ 标准库字符串)- 本质:封装好的动态字符串类,自动管理内存
- 例:
std::string s = "hello";
- 宽字符/Unicode 相关的:
wchar_t、char16_t、char32_t、std::wstring、std::u16string、std::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++ 中,string 是 std::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');// 从右往左找,返回 76. 子串
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];}逐句拆一下:
string groups[6][6]{};- 创建一个 6×6 的
string数组 - 每个元素都是空字符串
""
- 创建一个 6×6 的
for (auto& s : allowed)- 遍历某个容器
allowed(应该是如vector<string>或array<string, N>) auto& s表示:s是每个元素的引用,并且类型由编译器根据allowed的元素类型推导,一般就是std::string&
- 遍历某个容器
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];}循环执行过程:
s = "ABC"s[0] = 'A'→ 行索引0s[1] = 'B'→ 列索引1s[2] = 'C'groups[0][1] += 'C';
原本groups[0][1]是"",现在变成"C"
s = "ABD"- 仍是位置
[0][1] groups[0][1] += 'D';→
"C" + 'D'"CD"
- 仍是位置
s = "BAC"s[0] = 'B'→ 行索引1s[1] = 'A'→ 列索引0s[2] = 'C'groups[1][0] += 'C';
从""变成"C"
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' - 有自动内存管理
- 有很多成员函数
- 更安全、更方便
六、常见易混点与建议
- 优先使用
std::string,不要再自己手动分配char*+new[]来拼字符串。 - 如果一定要和 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)
遍历一个字符串容器allowed,s是每一个std::string的引用。groups[s[0] - 'A'][s[1] - 'A'] += s[2];- 把
s[0]、s[1]这两个大写字母映射成 0~5 的整数索引 - 用这两个数字当成
groups的行、列坐标 - 在这个格子里,把
s[2]这个字符追加到已有的字符串后面
- 把
这整段代码逻辑就是:
根据字符串 s 的前两个字符对第三个字符进行“二维分类”,并在每个分类格子里把同类的字符串在一起。