C++ 字符、字符数组与字符串高阶精讲课件(竞赛向)
📚 课程基础信息
| 项目 | 内容 |
|---|---|
| 前置知识 | C++基础变量、输入输出、循环、条件判断,具备基础代码读写能力 |
| 学习难度 | ⭐⭐⭐(基础+进阶,竞赛必考,上手简单、深挖易错) |
| 核心目标 | 吃透char、字符数组、string三者底层逻辑,规避刷题高频易错点,熟练掌握高阶简便写法,适配竞赛做题节奏 |
| 适用场景 | 洛谷日常刷题、算法竞赛文本处理、字符串模拟类真题、日常程序开发 |
🔍 课程引入:为什么要学习字符体系?
1. 知识背景(完整知识链)
在 C++ 编程语言中,文字处理分为三级体系 :单个字符char、字符数组char\[\]、字符串string。在之前的基础学习中,我们使用int、double 这类数值类型,只能单纯存储数字数据。但在竞赛、日常开发中,绝大多数数据都是文字。
计算机底层只能识别二进制,所有字符都依靠编码存储。从最基础的单个字符,到老式固定长度字符数组,再到现代动态string,三者构成完整文字处理体系。其中字符数组是衔接char与string的中间关键,也是很多老式洛谷真题、C语言代码的通用写法,必须掌握。
2. 三者通俗定位
- char:单格盒子,只能放1个字符;
- char\[\] 字符数组:固定格子硬纸箱,出厂定死大小,不能扩容;
- string:伸缩收纳袋,自动扩容、灵活不限长度。
3. 适用场景划分
- char:单个字符判定、ASCII运算;
- 字符数组:老式竞赛题、定长字符、内存极低消耗场景;
- string:不定长字符串、频繁修改、现代刷题通用写法。
🎯 模块一:char 字符类型(基础底层)
1. 概念通俗讲解
定义 :char 是C++专属用于存储单个字符的基础数据类型,在内存中固定占用1字节存储空间,是所有文字类型的最小存储单元。
生活类比 :char 就像日常生活中的单格独立收纳盒,盒子内部空间有限,有且只能放置1件物品,可以是一支笔、一块橡皮、一枚贴纸,绝对不能同时存放多件物品,对应代码中单个字母、数字、符号的存储逻辑。
2. 基础语法规范
cpp
// 标准定义格式
char 变量名 = '单个字符';
// 示例代码
char ch1 = 'M'; // 大写英文字母
char ch2 = '6'; // 字符形态数字(区别于整型数字6)
char ch3 = '#'; // 特殊标点符号
3. 关键硬性规则(必考)
- 字符内容必须用**单引号 ''**包裹,误用双引号会直接编译报错,是新手最基础的语法错误
- 单引号内部有且仅有1个字符,空白、多个字母、多个符号均属于非法写法
- 肉眼可见的空格、换行符、制表缩进符,全部属于合法特殊字符,可直接用char存储
4. 核心重难点:ASCII编码
4.1 原理
计算机底层硬件仅能识别0和1组成的二进制数字,无法直接识别人类通用的文字、符号。为了统一规范,行业制定了ASCII编码标准,每一个通用字符都绑定唯一的十进制编号,这个专属编号就是ASCII码。本质上,字符就是被包装过的特殊数字,支持直接进行加减算术运算。
生活类比 :ASCII码相当于每一个字符专属的身份证号,每一个字符编号唯一,没有重复,系统依靠编号精准区分不同字符。
4.2 刷题高频ASCII码表(必背)
| 字符范围 | ASCII码区间 | 固定差值 | 刷题用途 |
|---|---|---|---|
| 数字字符 '0'~'9' | 48 ~ 57 | - | 字符转数字运算、数字字符判定 |
| 大写字母 'A'~'Z' | 65 ~ 90 | 32 | 大小写字母互相转换、字母分类判定 |
| 小写字母 'a'~'z' | 97 ~ 122 | 32 | 大小写字母互相转换、字母分类判定 |
4.3 进阶运算公式(洛谷通用)
cpp
char c = 'a';
c -= 32; // 小写转大写:a → A(利用大小写固定差值32)
c += 32; // 大写转小写:A → a(利用大小写固定差值32)
int num = '7' - '0'; // 字符数字转整型:'7' → 7(刷题最常用字符转数字技巧)
5. 高阶知识点:转义字符
部分特殊功能字符无法直接在控制台打印展示,也无法直接书写,需要借助反斜杠 \ 进行转义处理,这类字符统称为转义字符,属于竞赛冷门但极易扣分的易错知识点。
| 转义字符 | 作用 | 刷题场景 |
|---|---|---|
| \n | 实现换行操作 | 多行格式化输出、排版规整 |
| \t | 水平制表符(生成固定空格缩进) | 数据对齐排版、表格格式化输出 |
| \ | 原样输出反斜杠符号 | 文件路径字符串、特殊符号打印 |
| ' | 原样输出单引号符号 | 特殊格式文本、符号拼接打印 |
🎯 模块二:char\[\] 字符数组(过渡必学)
1. 概念通俗讲解
定义 :字符数组是连续存放多个char字符的定长数组,保留C语言原生写法,内存固定不可改变,是早期C++存储字符串的唯一方式。
生活类比:硬质纸箱,出厂尺寸固定,纸箱格子数量写死,装多了溢出、装少了浪费,不能自由伸缩。
2. 基础定义语法
cpp
#include <iostream>
#include <cstring> // 字符数组专用头文件
using namespace std;
char s1[105]; // 定义长度为105的空字符数组
char s2[10] = "abc"; // 存入字符串,实际有效字符为abc
char s3[] = {'a','b','c'}; // 逐个字符赋值
3. 核心重难点:结束符 \0
这是字符数组最大难点、90%初学者踩坑点。
字符数组不会自动判定字符串末尾,必须依靠隐藏结束符:\0(ASCII=0)。
编译器读到 \0 才判定字符串结束,后面的内容全部忽略。
示例:char s[10] = "abc";
内存分布:a b c \0 空 空 空 空 空 空
刷题铁律 :字符数组定义长度必须大于最大输入长度+1,预留\0位置,否则无法终止、出现乱码。
4. 字符数组四大专用函数(cstring头文件)
| 函数写法 | 功能作用 | 刷题注意事项 |
|---|---|---|
| strlen(s) | 获取有效字符长度,不计\0 | 速度慢,遍历统计 |
| strcpy(a,b) | 把b字符串复制给a | a数组必须更大 |
| strcat(a,b) | 将b拼接在a末尾 | 极易数组越界 |
| strcmp(a,b) | 字典序比较字符串大小 | 不能直接用==比较 |
5. 字符数组致命缺点(为什么淘汰)
- 长度固定:定义多大只能用多大,无法动态扩容;
- 操作繁琐:复制、拼接、清空全部需要库函数;
- 安全性极低:极易越界、产生乱码、程序玄学报错;
- 不能直接运算:不支持+拼接、不支持> <比较。
6. 数组刷题易错点
- ❌ 忘记预留 \0 结束符,输出乱码;
- ❌ 使用strcat拼接超出数组长度,内存溢出;
- ❌ 使用 == 判断两个字符数组是否相等;
- ✅ 正确:竞赛现代代码优先放弃char数组,使用string。
🎯 模块三:String 字符串高阶精讲(竞赛向)
1. 课前引入:为什么要深挖 String?
1.1 知识背景(痛点铺垫)
在 C++ 初期学习中,很多人会使用老旧的 char 字符数组 存储文字,例如:char s[105];。但是字符数组存在三大致命硬伤,也是竞赛刷题最大痛点:
- 长度固定:定义多大只能用多大,容易造成空间浪费或者数组空间不足;
- 操作繁琐:拼接、复制、比较字符串必须手写循环,代码冗余冗长;
- 安全性差:极易出现数组越界、内存溢出,刷题时出现莫名报错、玄学bug。
为了解决以上问题,C++ 内置了 string 字符串类,属于 STL 标准容器,是竞赛、刷题、编程实战中处理文本的核心工具。
1.2 生活化类比
- char 字符数组:硬质固定大小纸箱,多大尺寸只能装多少东西,不能变大变小,死板僵硬;
- string 字符串:可伸缩收纳袋,自动扩容、自动收缩,存放文字随心所欲,灵活便捷。
1.3 适用场景(什么时候必须用 string)
- 处理不定长文字、数字字符串(如大数存储、密码、字符序列);
- 需要频繁进行拼接、截取、删除、比较字符串;
- 含有空格、特殊字符的一整行文本输入;
- 洛谷字符串模拟题、字符处理题、字典序排序题目。
2. 基础认知:String 核心底层特性
2.1 核心定义
string 本质是动态字符数组,无需手动开辟空间,程序自动智能管理内存,不需要人为考虑越界、扩容、释放内存等底层问题。
2.2 char数组 VS string 对比表(重难点必背)
| 对比维度 | char 字符数组 | string 字符串 |
|---|---|---|
| 空间大小 | 固定不可变,容易浪费空间 | 动态扩容,自动适配长度 |
| 拼接方式 | 依赖 strcat 函数,容易越界 | 直接使用 + 拼接,极简高效 |
| 比较方式 | 依赖 strcmp,不能直接用 == | 支持 > < == 直接比较 |
| 获取长度 | strlen(),遍历统计、速度慢 | .size() / .length(),O(1)读取 |
| 适用场景 | 定长简单字符、老旧代码 | 刷题、竞赛、复杂字符处理 |
3. String 初始化方式(全覆盖写法)
3.1 四种初始化方式(表格汇总)
| 初始化代码 | 代码解释 | 适用场景 |
|---|---|---|
string s; |
定义空字符串,默认长度为0 | 后期动态赋值、多组数据 |
string s = "abc"; |
直接赋值常量字符串 | 固定初始内容、静态文本 |
string s(5,'a'); |
生成5个a:aaaaa | 批量生成相同字符、填充占位 |
string s2 = s1; |
拷贝s1字符串给s2 | 字符串复制、数据备份 |
3.2 下标访问规则(重点易错)
string 和数组规则完全一致,下标强制从 0 开始,不存在下标为1的第一个字符。
示例:string s = "hello";
s0 = 'h' 、s1 = 'e'、s2 = 'l'、s3 = 'l'、s4 = 'o'
最后一位下标固定写法 :s.size()-1
刷题铁律:绝对不能访问 ss.size(),该位置为内存盲区,直接造成越界报错、程序崩溃!
4. 核心语法:常用函数大全(竞赛必背)
4.1 基础操作函数(表格整理)
| 函数写法 | 功能说明 | 生活举例 |
|---|---|---|
s.size() |
获取字符串有效长度 | 数收纳袋里物品个数 |
s.empty() |
判断是否为空,空返回true | 判断袋子是否空空如也 |
s.clear() |
清空所有字符,长度置0 | 倒空收纳袋全部物品 |
s.push_back(x) |
末尾追加单个字符 | 袋子末尾新增一件物品 |
s.pop_back() |
删除末尾单个字符 | 拿走袋子最后一件物品 |
4.2 高阶操作(彰显水平,刷题必考)
4.2.1 字符串截取 substr
语法 :s.substr(起始下标, 截取长度);
示例 :s = "abcdef",s.substr(1,3) → 从下标1开始,截取3位 → "bcd"
生活化比喻:切面包,从第1刀位置,连续切下3块面包。
4.2.2 查找字符 find
语法 :s.find("ab"),找到字符串则返回起始下标,找不到固定返回 string::npos
竞赛注意 :禁止直接判断-1,必须使用 string::npos 做判定条件。
4.2.3 插入与删除
s.insert(pos,str):在pos下标位置,插入一段字符串;s.erase(pos,len):从pos下标开始,删除len个字符。
4.3 字符串拼接与比较
4.3.1 拼接(极简写法)
string 支持直接 + 拼接,无需任何函数、无需循环,是string最大优势:
cpp
string a = "123";
string b = "456";
string c = a + b; // c = "123456"
4.3.2 比较规则(字典序,重难点)
string 比较不看字符串长度,逐位对比字符ASCII码,遵循字典排序规则:
- 示例1:"abc" < "abd"(前两位相同,第三位c<d)
- 示例2:"123" < "45"(第一位1<4,直接判定大小)
核心口诀:逐位比对,高位优先,长短无关
5. 输入大坑:cin 与 getline 区别(洛谷高频易错)
5.1 两种输入方式对比
| 输入写法 | 终止条件 | 优缺点 |
|---|---|---|
cin >> s |
空格、回车自动截断 | 不能读空格,读取速度快 |
getline(cin,s) |
仅回车截断 | 可读空格,存在缓存残留报错 |
5.2 致命易错点(90%学生踩坑)
如果前面使用 cin 输入数据,输入缓冲区会残留回车换行符,直接使用 getline 会强制读入空串,数据直接缺失。
✅ 竞赛通用简便解决写法:
cpp
cin.ignore(); // 一键清空缓冲区残留回车、空格
6. 洛谷刷题实战经验总结
6.1 适用 string 的高频题库
- 基础入门:B2045 字符菱形、B2126 字符串反转;
- 进阶模拟:B3615 字符统计、大数处理类题目;
- 高阶排序:字典序排序、字符串拼接排序。
6.2 竞赛简便写法(提速技巧)
- 空串判断优先用
empty(),比size()==0执行速度更快、代码更专业; - 字符大小写转换:牢记ASCII码,'A'=65、'a'=97,差值固定32;
- 字符串反转:直接使用
reverse(s.begin(),s.end()),无需手写循环; - 大数存储:超过long long范围的超大数字,全部用string存储处理。
6.3 高频易错点汇总
- ❌ 错误:混淆下标,把字符串最后一位写成 s.size();
- ❌ 错误:cin 和 getline 混用,不清理缓冲区;
- ❌ 错误:find 函数判定不用
string::npos,直接判断-1; - ❌ 错误:误以为长字符串一定大于短字符串(忽略字典序规则);
- ✅ 正确:所有字符串遍历,循环条件统一写成
for(int i=0;i<s.size();i++)。
7. 综合真题代码示例(洛谷通用模板)
题目:字符串反转(B2126)
要求:输入一串字符,倒序输出字符串。
cpp
#include <iostream>
#include <string>
#include <algorithm> // reverse反转函数专属头文件
using namespace std;
int main()
{
string s;
cin >> s;
reverse(s.begin(),s.end()); // 一键反转,竞赛最简写法
cout << s << endl;
return 0;
}
⚖️ 模块四:三者终极整合(char / char\[\] / string)
4.1 终极对比总表(考前必背)
| 对比维度 | char 单个字符 | char\[\] 字符数组 | string 字符串 |
|---|---|---|---|
| 存储数量 | 只能存1个字符 | 固定个数字符 | 动态无限存储 |
| 引号规范 | 单引号 '' | 双引号 "" | 双引号 "" |
| 内存特性 | 固定1字节 | 静态不可变 | 动态自动扩容 |
| 拼接运算 | 不支持拼接 | 只能strcat函数 | 直接+拼接 |
| 比较判断 | 可用==判断 | 只能strcmp | 支持><== |
| 竞赛推荐 | 少量字符判定 | 不推荐使用 | ✅ 全场通用 |
4.2 三者互相转换(竞赛拔高)
cpp
// 1. char数组 转 string
char arr[100] = "abc";
string s = arr;
// 2. string 转 char数组
string s2 = "123";
char brr[100];
strcpy(brr,s2.c_str());
// 3. 单个char塞入string
char ch = 'x';
string str;
str.push_back(ch);
📝 课后分层习题(适配洛谷难度)
基础题(必做,巩固语法)
- 输入一个字符,判断该字符为大写字母、小写字母还是数字,清晰输出判定结果。
- 定义字符数组存储一串字符,手动遍历输出每一位字符。
- 输入一串普通string字符串,自动输出字符串总长度以及末尾最后一个字符。
提高题(进阶,熟练函数)
- 编写完整代码,读取带空格的英文句子,将句子中所有字母全部转为大写并输出。
- 使用字符数组完成字符串反转,不允许使用string。
- 统计任意一串字符串中,大写字母、小写字母、数字三类字符的具体个数。
竞赛题(拔高,贴合真题)
- 输入一串纯数字字符串,将字符串整体反转后,转换为整型数字输出(禁止直接int输入读取)。
- 判断任意输入字符串是否为回文串(正反读取内容完全一致),输出判断结果。
📌 全文知识总结
- char:单字符存储、强制单引号、固定占用1字节内存,依托ASCII码实现字符与数字双向转换,专一用于单个符号运算、字符判定。
- char\[\]字符数组:定长静态数组,依靠\0结束符判定末尾,操作繁琐、容易越界,仅作兼容学习,竞赛不推荐。
- string:动态字符容器,完美替代char数组,支持自动扩容、直接拼接、字典序比较,刷题竞赛必备。
- 核心函数:熟记substr、find、insert、erase四大高阶函数,掌握截取、查找、插入、删除操作。
- 刷题避坑:牢记下标从0开始、cin/getline缓存问题、字典序比较、数组结束符\0四大易错点。
- 竞赛技巧:优先使用reverse反转、+拼接、empty判断,简化代码、压缩写题时间、降低出错率。