字符串教学讲义


1. 字符串简介

1.1 什么是字符串

字符串(String):由零个或多个字符组成的有限序列。在 C++ 中,字符串可以用字符数组(char[])或标准库 string 类来表示。

单个字符用 char 类型存储,例如 'A'、'9'、'#'。而字符串则是多个字符依次排列,并以结束符 \0(空字符)标记结尾。

图示:字符串在内存中的存储

char arr[] = "hello";

内存布局:
  地址   →  [0] [1] [2] [3] [4] [5]
  内容   →   h   e   l   l   o   \0
索引(下标) 0   1   2   3   4   5 (\0 不计入有效长度)

有效长度 = 5,存储空间 = 6(包含 \0)

1.2 为什么需要字符串

  • 文本处理:姓名、地址、题目描述、用户输入等都需要字符串。
  • 信息学竞赛:字符串处理(如回文、子串、匹配、加密)是高频考点。
  • 输入输出:文件读写、格式化输出常与字符串打交道。

2. C++ 中字符串的三种形式

2.1 字符 char

  • 单个字符,用单引号括起。
  • 本质是 ASCII 码(0~127 或 0~255 的整数),可参与算术运算。

常用函数(需包含 <cctype> 或 <bits/stdc++.h>):

函数 作用 示例
isupper(ch) 是否为大写字母 isupper('A') → true
islower(ch) 是否为小写字母 islower('a') → true
isalpha(ch) 是否为字母 isalpha('Z') → true
isdigit(ch) 是否为数字 isdigit('3') → true
isalnum(ch) 是否为字母或数字 isalnum('a') → true
toupper(ch) 转换为大写(返回整型,需强转) toupper('b') → 'B'
tolower(ch) 转换为小写 tolower('G') → 'g'

代码示例:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char ch = 'A';
    cout << isupper(ch) << endl;   // 1
    cout << islower(ch) << endl;   // 0
    cout << isalpha(ch) << endl;   // 1
    cout << isdigit(ch) << endl;   // 0
    cout << isalnum(ch) << endl;   // 1
    cout << char(toupper('b')) << endl; // 'B'
    cout << char(tolower('G')) << endl; // 'g'
    return 0;
}

2.2 字符数组 char[]

  • 固定大小的连续内存,以 \0 结尾。
  • 定义时必须指定长度(或自动推断)。

常用操作:

操作 函数/方式 说明
输入(不含空格) cin >> arr; 遇空白停止
输入整行(含空格) cin.getline(arr, sizeof(arr)); 读取一行,最多 size-1 个字符
输出 cout << arr; 输出直到 \0
获取定义长度 sizeof(arr) 编译时确定的字节数
获取有效长度 strlen(arr) 从开头到 \0 的字符数
复制 strcpy(dest, src); 将 src 复制到 dest
拼接 strcat(dest, src); 将 src 连接到 dest 末尾
比较 strcmp(s1, s2); 返回 0(相等)、正(s1>s2)、负

代码示例:

#include <bits/stdc++.h>
using namespace std;
int main() {
    char arr[100] = "hello world";
    cin >> arr;                      // 输入 "abc" -> arr="abc"
    cin.getline(arr, 100);           // 读取一行(会吸收之前的换行符,注意配合 cin.ignore())
    cout << arr << endl;
    
    cout << sizeof(arr) << endl;     // 100(字节数)
    cout << strlen(arr) << endl;     // 实际字符个数(不含 \0)
    
    char arr1[100] = "hello";
    char arr2[100] = "world";
    strcpy(arr1, arr2);              // arr1 变为 "world"
    strcat(arr1, arr2);              // arr1 变为 "worldworld"
    cout << strcmp(arr1, arr2) << endl; // 比较结果
    return 0;
}

注意:字符数组操作容易导致缓冲区溢出(目标空间不足),推荐使用 string。

2.3 string 类(标准库)

  • 可变长度,自动管理内存,提供丰富的成员函数。
  • 需要包含 <string>(<bits/stdc++.h> 已包含)。

构造与赋值:

string s1;                     // 空字符串
string s2 = "hello";           // 直接初始化
string s3(10, 'x');            // 10 个 'x': "xxxxxxxxxx"
string s4(s2);                 // 拷贝构造
s1 = s2;                       // 赋值

输入输出:

string s;
cin >> s;                      // 遇空白停止
getline(cin, s);               // 读取整行(包含空格)
cout << s << endl;

常用成员函数:

函数/操作 示例 说明
size() / length() s.size() 返回字符串长度
empty() s.empty() 是否为空
clear() s.clear() 清空字符串
push_back(ch) s.push_back('A') 末尾添加字符
append(str) / += s += "def" 字符串拼接
insert(pos, str) s.insert(2, "abc") 在位置 2 插入子串
erase(pos, len) s.erase(2, 3) 从位置 2 删除 3 个字符
substr(pos, len) s.substr(2, 3) 提取子串(从 2 开始,长度 3)
find(str) s.find("llo") 查找子串,返回首下标(失败返回 npos)
replace(pos, len, str) s.replace(2, 3, "xyz") 替换子串
c_str() s.c_str() 返回 const char*(用于 C 函数)

代码示例:

#include <bits/stdc++.h>
using namespace std;
int main() {
    string s = "hello world";
    cout << s.size() << " " << s.length() << endl; // 11 11
    s.push_back('!');
    s += "!!!";
    cout << s << endl;               // hello world!!!!
    
    s.insert(5, " dear");
    cout << s << endl;               // hello dear world!!!!
    
    cout << s.substr(6, 4) << endl;  // dear
    s.replace(6, 4, "beautiful");
    cout << s << endl;               // hello beautiful world!!!!
    
    auto pos = s.find("world");
    if (pos != string::npos)
        cout << "found at " << pos << endl;
    
    s.erase(5, 10);                  // 删除 " beautiful "
    cout << s << endl;               // hello world!!!!
    
    s.clear();
    cout << (s.empty() ? "empty" : "not empty") << endl;
    return 0;
}

迭代器与范围 for:

string s = "hello";
for (char c : s) {
    cout << c << " ";   // h e l l o
}
// 使用迭代器
for (auto it = s.begin(); it != s.end(); ++it) {
    *it = toupper(*it);
}
cout << s;              // HELLO

数值与字符串互转:

// 数值 → 字符串
int n = 123;
string s1 = to_string(n);          // "123"
double d = 3.14;
string s2 = to_string(d);          // "3.140000"

// 字符串 → 数值
string s = "123456";
int x = stoi(s);                   // 123456
long long y = stoll(s);
double z = stod("123.456");        // 123.456

字符数组与 string 互转:

// char[] → string
char arr[100] = "hello";
string s = arr;                     // 直接赋值

// string → char[]
string s = "world";
char arr[100];
strcpy(arr, s.c_str());             // 注意 c_str() 返回 const char*

3. 字符串的常见操作图示

查找子串:

s = "hello world"
     012345678910
s.find("world") → 6
s.find("abc")   → string::npos (通常为 -1)

提取子串:

s = "hello world"
s.substr(2, 3) → "llo"  (从下标2开始,取3个字符)

插入与删除:

原串: "hello world"
s.insert(5, " dear") → "hello dear world"
s.erase(5, 5)        → "hello world"(删除 " dear")

4. 课堂例题

例题1:统计字母、数字、空格个数

题目描述

输入一行字符串(可能包含空格),统计其中大写字母、小写字母、数字、空格以及其他字符的个数。

输入:一行字符串(长度 ≤ 1000)。 输出:四个整数,依次为大写字母数、小写字母数、数字数、空格数、其他字符数。

思路解析

使用 getline 读取整行字符串。遍历每个字符,利用 <cctype> 函数判断类型,分别累加。注意:isalpha 不区分大小写,但我们需要分别统计大写和小写,可用 isupper 和 islower。

代码实现

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

int main() {
    string s;
    getline(cin, s);           // 读取整行
    int upper = 0, lower = 0, digit = 0, space = 0, other = 0;
    for (char ch : s) {
        if (isupper(ch)) upper++;
        else if (islower(ch)) lower++;
        else if (isdigit(ch)) digit++;
        else if (ch == ' ') space++;
        else other++;
    }
    cout << upper << " " << lower << " " << digit << " " << space << " " << other << endl;
    return 0;
}

例题2:字符串反转与回文判断

题目描述

输入一个字符串(不含空格),判断它是否是回文(正读反读相同)。若是,输出 "yes",否则输出 "no"。

输入:一行字符串(长度 ≤ 1000)。 输出:"yes" 或 "no"。

思路解析

方法一:直接比较第 i 个字符与倒数第 i 个字符是否相等,只需遍历前一半。 方法二:将字符串反转后与原串比较。

注意:空字符串和单个字符视为回文。

代码实现

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

int main() {
    string s;
    cin >> s;
    bool ok = true;
    int len = s.size();
    for (int i = 0; i < len / 2; i++) {
        if (s[i] != s[len - 1 - i]) {
            ok = false;
            break;
        }
    }
    cout << (ok ? "yes" : "no") << endl;
    return 0;
}

例题3:字符串加密(凯撒密码)

题目描述

输入一个由大写字母组成的字符串,将其每个字母向后移动 3 位(即 A→D, B→E, …, Z→C),输出加密后的字符串。

输入:一行大写字母字符串(长度 ≤ 1000)。 输出:加密后的字符串。

思路解析

遍历每个字符,将其 ASCII 码加 3。若超过 'Z',则回绕到 'A'。公式:newChar = (ch - 'A' + 3) % 26 + 'A'。

代码实现

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

int main() {
    string s;
    cin >> s;
    for (char& ch : s) {
        if (isupper(ch)) {
            ch = (ch - 'A' + 3) % 26 + 'A';
        }
    }
    cout << s << endl;
    return 0;
}

5. 课后练习

以下题目在洛谷(luogu.com.cn)上搜索题号即可。

序号 题目名称 题号 知识点
1 【深基5.例2】输出字符三角形 P5738 字符串输出、循环
2 【深基5.例3】数字反转 P5739 字符串反转
3 【深基5.例4】统计单词数 P1308 字符串查找、大小写转换
4 【深基5.例5】自动修正 P5735 字符替换
5 书名号 P1957 字符串拼接、数值转换
6 手机号 P1765 字符映射(模拟)
7 拼数 P1012 字符串排序(自定义比较)
8 删除单词后缀 P1415 子串判断、删除
9 子串查找 P3375 KMP(提高)或简单查找
10 单词反转 P1553 字符串分割、反转

建议:先独立完成基础题(1~6),再挑战排序和模式匹配。

6. 总结

形式 优点 缺点 推荐场景
char 简单,直接处理 ASCII 单个字符,无法表示多个字符 字符判断、转换
字符数组 char[] 兼容 C 语言,适合底层操作 长度固定,易溢出,函数繁琐 极少数对性能要求极高的场合
string 安全、方便、功能丰富 略慢于字符数组(可忽略) 日常竞赛首选

核心技巧:

  • 输入带空格的字符串:用 getline(cin, s)。
  • 判断字符类型:优先使用 <cctype> 函数,而不是手写范围。
  • 字符串查找:find() 返回 string::npos 表示未找到。
  • 字符串转数字:stoi() / stoll() / stod();数字转字符串:to_string()。
  • 频繁修改字符串(插入、删除)时,string 效率依然很高。

教练寄语:字符串处理是信息学竞赛的基础技能。请务必熟练使用 string 的各种成员函数,并养成用 cctype 函数判断字符类型的习惯。课后的 10 道练习题涵盖了常见考点,认真完成它们,你将能应对大部分字符串问题。

参考资料

  • 【书籍】《信息学奥赛一本通》第4章:字符串
  • 【书籍】《C++ Primer Plus》第16章:string 类和标准模板库
  • 【在线资源】cppreference - std::string