8 string字符串及C风格应用(二)

正在加载视频播放器…

GESP C++ 三级:string 字符串及 C 风格应用梳理

核心目标:在掌握 string 类的基础上,掌握 C 风格字符串(char 数组)的定义、输入输出、核心函数及与 string 的互转,理解 '\0' 结束符的作用,能根据题目要求选择合适的方式处理字符串。
string 安全便捷,自动管理内存;C 风格字符串固定长度,以 '\0' 结尾,函数操作需自行保证空间充足。strlen 算有效字符数,sizeof 算数组总字节数,两者千万不能混。

一、string 与 C 风格字符串的核心区别

这是三级考试的高频选择题考点。C++ 中同时存在两种字符串表示方式,各有适用场景。

对比项 string(C++ 风格) char 数组(C 风格)
头文件 <string> <cstring>
定义方式 string s = "Hello"; char s[] = "Hello";
内存管理 自动管理,动态扩展 手动管理,固定大小
长度获取 s.size() / s.length() strlen(s)
末尾标志 不需要手动添加 必须有 '\0'
赋值 s = "world"; 需用 strcpy()
拼接 s += "world"; 需用 strcat()
比较 s1 == s2 需用 strcmp()
越界检查 s.at(i) 会抛异常 无检查,越界行为未定义
推荐度 GESP 考试推荐使用 需掌握,使用需谨慎

最关键的差异:C 风格字符串以 '\0' 作为结束标志。字符串 "GESP" 在内存中实际占用 5 个字节:'G'、'E'、'S'、'P'、'\0',有效下标为 0~3,'\0' 位于下标 4。

二、C 风格字符串的定义与初始化

1. 必须包含头文件

#include <cstring>   // 使用 strcpy、strlen 等 C 风格函数
//万能头包含

2. 三种初始化方式

// 方式一:字符串字面量初始化,编译器自动添加 '\0'
char s1[] = "GESP";        // 数组长度自动为 5(4个字符 + '\0')

// 方式二:显式指定长度,剩余位置补 '\0'
char s2[10] = "GESP";      // s2 = "GESP\0\0\0\0\0\0"

// 方式三:逐字符初始化(必须手动加 '\0',否则不是字符串!)
char s3[5] = {'G', 'E', 'S', 'P', '\0'};   // 正确
char s4[4] = {'G', 'E', 'S', 'P'};          // 危险!缺少 '\0'

致命陷阱:char str[5] = {'H','e','l','l','o'}; 缺少 '\0' 终止符,使用 cout << str 或 strlen(str) 会越界访问内存,输出结果不可预测。

三、C 风格字符串的输入输出

1. 输入

char s[100];

cin >> s;                     // 遇到空格/换行停止,不读空格
cin.getline(s, 100);          // 读入整行(含空格),最多 99 个字符 + '\0'

cin.getline(数组名, 长度):从输入流读取一行,直到遇到换行符或读满 长度-1 个字符,自动在末尾添加 '\0'。换行符被读取并丢弃。

混合使用 cin >> 和 cin.getline 时的陷阱:

int n;
char s[100];
cin >> n;              // 输入数字后按回车
cin.getline(s, 100);   // ❌ 会立即读到空行(缓冲区残留的换行符)

解决方法:在两者之间加 cin.ignore() 清空缓冲区。

2. 输出

char s[] = "GESP";
cout << s << endl;          // 输出 GESP(自动在 '\0' 处停止)

四、C 风格字符串核心函数

1. 长度计算:strlen

char s[] = "GESP";
cout << strlen(s) << endl;   // 4(不含 '\0')
cout << sizeof(s) << endl;   // 5(含 '\0',数组总字节数)

strlen 与 sizeof 的本质区别:

函数 计算内容 示例(char s[] = "GESP")
strlen(s) 实际字符数,不含 '\0' 4
sizeof(s) 数组总字节数,含 '\0' 5

必记:strlen 是函数,运行时计算;sizeof 是运算符,编译时确定。考试中经常考两者的输出差异。

2. 复制:strcpy / strncpy

char dest[20];
char src[] = "GESP";

strcpy(dest, src);          // 将 src 复制到 dest(dest 空间必须足够大)
strncpy(dest, src, 2);      // 只复制前 2 个字符,结果 "GE"(不自动加 '\0')

安全守则:dest 的空间必须 ≥ strlen(src) + 1,否则会缓冲区溢出。

3. 拼接:strcat

char s1[20] = "GESP";
char s2[] = " C++";

strcat(s1, s2);             // 将 s2 拼接到 s1 末尾,结果 "GESP C++"

安全守则:s1 的剩余空间必须 ≥ strlen(s2) + 1。

4. 比较:strcmp / strncmp

strcmp("Apple", "Banana");  // 返回负值('A' < 'B')
strcmp("apple", "Apple");   // 返回正值('a' > 'A')
strcmp("GESP", "GESP");     // 返回 0(相等)
strncmp("GESP", "GET", 2);  // 只比较前 2 个字符,返回 0("GE" == "GE")

返回值规则:按字典序逐字符比较 ASCII 码,首对不同字符的 ASCII 码差值即为返回值。完全相同时返回 0。

注意:strcmp 返回的是差值,不是固定的 -1/0/1。strcmp("a", "A") 返回 32(97-65),不是 1。

5. 查找字符:strchr

char s[] = "GESP";
char *p = strchr(s, 'E');   // 返回指向 'E' 的指针,未找到返回 NULL
if (p != NULL) {
    cout << *p << endl;     // E
}

6. 初始化:memset

char s[100];
memset(s, 0, sizeof(s));    // 将数组所有字节置为 0(即全部为 '\0')

常用于清空字符数组,等价于 char s[100] = {0};。

五、string 与 C 风格字符串的互转

1. string → C 风格字符串:c_str()

当 C 风格函数需要 char* 参数时,可以用 c_str() 将 string 转换:

string s = "GESP";
int len = strlen(s.c_str());   // 将 string 转为 const char* 后调用 strlen
cout << len << endl;           // 4

注意:c_str() 返回 const char*,不能修改内容。data() 与 c_str() 类似,但 c_str() 保证末尾有 '\0',推荐使用 c_str()。

2. C 风格字符串 → string

char s[] = "GESP";
string str = s;                // 直接赋值即可,自动转换
string str2(s);                // 或调用构造函数

六、真题精讲

真题1:分割字符串

题目描述:从左到右扫描字符串,第一次遇到 1 个空格时分割,对右侧继续扫描,第一次遇到连续 2 个空格时分割,下一次需要连续 3 个空格,以此类推。输出每次分割左侧的字符串,以及最后一次分割的右侧字符串。

解题思路:使用 string 的 find 和 substr 实现。

#include<bits/stdc++.h>
using namespace std;

int main() {
    string s;
    getline(cin, s);

    int need = 1;
    int start = 0;
    bool split = false;

    while (true) {
        string target(need, ' ');
        size_t pos = s.find(target, start);

        if (pos == string::npos) {
            if (!split) cout << s << endl;
            else cout << s.substr(start) << endl;
            break;
        }

        cout << s.substr(start, pos - start) << endl;
        split = true;
        start = pos + need;
        need++;
    }
    return 0;
}

关键细节:

  • getline(cin, s) 读入含空格的整行,不能用 cin >> s。
  • s.find(target, start) 从 start 开始查找目标子串。
  • 空字符串分割时必须输出空行,不能省略(样例 2 特别强调)。
  • split 标志区分“从未分割”和“已分割”的输出逻辑。

真题2:字符数组长度辨析(选择题)

定义字符数组 char str[20] = {'G', 'E', 'S', 'P'};,则 str 的字符串长度为( )。
答案:4。str 初始化时只显式赋了 4 个字符,其余 16 个位置自动补 '\0',因此 strlen(str) 返回 4。

真题3:sizeof 与 strlen 辨析(选择题)

char msg[] = "GESP"; cout << strlen(msg) << " " << sizeof(msg); 输出什么?
答案:4 5。strlen 返回有效字符数 4,sizeof 返回数组总字节数 5(含 '\0')。

七、易错点汇总

1. '\0' 终止符缺失(最高频陷阱)

char str[5] = {'H', 'e', 'l', 'l', 'o'};   // ❌ 缺少 '\0'
cout << strlen(str);   // 结果不可预测

诊断公式:数组长度 ≥ 可见字符数 + 1。当字符数组用于存储字符串时,必须预留一个位置给 '\0'。

2. strlen vs sizeof 混淆

char s[] = "GESP";
cout << strlen(s) << endl;   // 4(有效字符数)
cout << sizeof(s) << endl;   // 5(数组总字节数)

3. strcmp 返回值理解错误

if (strcmp(s1, s2) == 1)    // ❌ 错误:返回值不一定是 1
if (strcmp(s1, s2) > 0)     // ✅ 正确:只判断正负
if (strcmp(s1, s2) == 0)    // ✅ 正确:相等判断

4. cin.getline 与 cin >> 混用

int n;
char s[100];
cin >> n;
cin.getline(s, 100);   // ❌ 读到空行
// 正确:cin.ignore(); cin.getline(s, 100);

5. strcpy 目标空间不足

char dest[3];
strcpy(dest, "GESP");   // ❌ 缓冲区溢出,dest 需要至少 5 字节

6. substr 起始位置越界

string s = "GESP";
s.substr(4, 1);    // ❌ 抛出 out_of_range 异常(起始位置必须 < s.length())
s.substr(2, 100);  // ✅ 不会报错,截取到末尾

7. 字符数组长度必须是常量

int n = 10;
char s[n];    // ❌ 标准 C++ 不允许变长数组
char s[10];   // ✅ 正确

八、速查表

string 常用函数

需求 写法
长度 s.size() / s.length()
判空 s.empty()
访问字符 s[i] / s.at(i)
查找 s.find(sub, pos),找不到返回 string::npos
截取 s.substr(pos, len)
插入 s.insert(pos, str)
删除 s.erase(pos, len)
替换 s.replace(pos, len, str)
追加 s.append(str) / s += str
末尾加字符 s.push_back(c)
转 C 风格 s.c_str()

C 风格字符串函数

需求 写法 关键注意
长度 strlen(s) 不含 '\0'
数组字节数 sizeof(s) 含 '\0'
复制 strcpy(dest, src) dest 空间要足够
限长复制 strncpy(dest, src, n) 不自动加 '\0'
拼接 strcat(dest, src) dest 剩余空间要足够
比较 strcmp(s1, s2) 返回差值,非固定 ±1
限长比较 strncmp(s1, s2, n) 只比较前 n 个字符
查找字符 strchr(s, c) 未找到返回 NULL
初始化 memset(s, 0, sizeof(s)) 全部置 '\0'
输入整行 cin.getline(s, len) 含空格
转 string string str = s; 直接赋值

九、总结

string 与 C 风格字符串是 GESP 三级字符串模块的两大核心。

  1. string 自动管理内存,C 风格字符串固定长度且必须以 '\0' 结尾。
  2. cin >> s 遇空格停止,读整行用 getline(cin, s)(string)或 cin.getline(s, len)(char 数组)。
  3. strlen 返回有效字符数,sizeof 返回数组总字节数,两者经常一起考。
  4. strcpy 复制、strcat 拼接、strcmp 比较,目标空间必须足够大。
  5. strcmp 返回的是 ASCII 差值,不要用 == 1 判断。
  6. cin >> 和 cin.getline 混用时,中间要加 cin.ignore()。
  7. C 风格字符串的三种函数操作(复制、拼接、比较)在 string 中分别对应 =、+=、==。
  8. string 转 C 风格用 s.c_str(),C 风格转 string 直接赋值。
  9. 字符数组长度必须是编译期常量,不能用变量。
  10. 所有 C 风格字符串操作前,先确认 '\0' 存在且目标空间充足。

复习建议:重点练习 strlen 与 sizeof 的辨析、strcmp 返回值判断、cin.getline 与 cin >> 混用、string 与 char 数组的互转四类选择题,以及“分割字符串”类编程题。考试中先判断题目要求使用哪种字符串类型,再选择对应的函数,最后检查 '\0' 和空间是否充足。