内容正文:
C语言程序设计
电子工业出版社
8.4节:字符串与指针
字符串在内存中的存储方式
使用字符数组和字符指针变量两种方式引用字符串
熟练使用字符指针变量和字符数组编写简单的字符串处理程序
阅读和分析涉及字符串指针的 C 语言代码
培养严谨的逻辑思维和程序调试能力
提升对计算机内存模型的理解和抽象思维能力
知识目标
能力目标
素养目标
01
01
01
学习目标
目录
Part 01
教学导入
Part 02
知识讲授
Part 03
师生互动
Part 04
课堂练习
Part 05
课堂小结
教学导入:一个古老的传说
从前,有一张神秘的藏宝图,上面写着一行奇怪的字符:
"X Marks The Spot"
这行字符,就是我们在编程世界里经常打交道的 ——“字符串”
地图的起点,是一个叫做start的地方。你,作为寻宝者,站在起点,手里拿着一个神奇的罗盘。
而这个罗盘,就是我们今天的主角 ——指针
教学导入:寻宝的两种方式
方式一:步步为营
你把地图上的每一个字符都抄在自己的笔记本上,从第一个字符'X'开始,依次查看每一个字符,直到找到最后一个字符't'。
• 你的笔记本就是一个字符数组。
• 你按顺序访问每个字符,就像通过数组下标访问元素。
方式二:直指核心
你使用罗盘,直接定位到地图上第一个字符'X'的位置。然后,罗盘会告诉你下一个字符在哪里,依此类推,直到找到终点。
• 你的罗盘就是一个字符指针。
• 它不存储字符,只存储字符的地址。
今天,我们就来学习这两种寻宝方式:如何通过指针引用字符串!
知识讲授
字符串与指针
字符串的表示形式
字符数组与字符串指针变量比较
第一部分
字符串的表示形式
知识讲授
知识讲授:字符串的本质
核心定义
在C语言中,字符串是由双引号括起来的字符序列,且在内存中会自动以特殊字符'\0'(ASCII码0)作为结束标志。
结束符 '\0' 的关键作用
它是字符串的“终止标记”,决定了程序从哪里停止读取。像strlen、printf等标准库函数都依赖它来判断字符串的实际有效长度。
存储空间计算
字符串在内存中占用的字节数 = 字符个数 + 1。额外的 1 个字节专门用来存放结束符'\0',这一点常被初学者忽略。
知识讲授:使用字符数组
▍ 定义与初始化
字符数组是存储字符串最基础的方式。它本质上是一个char类型的数组,系统会自动在字符串末尾隐式添加结束符'\0'来标志字符串的终止。
// 方式 1:逐个字符手动赋值 (需自行管理长度)
char str1[6];// 需预留空间给结束符
str1[0] = 'H'; str1[1] = 'e'; str1[2] = 'l'; str1[3] = 'l'; str1[4] = 'o';
str1[5] = '\0';// 必须手动添加结束符
// 方式 2:直接用字符串常量初始化 (更推荐)
char str2[] = "Hello";// 自动添加'\0',数组长度自动为 6
// 等价于:char str2[6] = {'H','e','l','l','o','\0'};
知识讲授:如何访问字符数组中的字符串?
01. 使用数组下标访问
#include <stdio.h>
main() {
char str[] = "Hello";
while (str[i] != '\0')
printf("%c", str[i++]); }
💡 核心逻辑:
通过索引循环,逐一打印字符,直到遇到结束符'\0'。
02. 直接输出整个数组
#include <stdio.h>
main() {
char str[] = "Hello";
// 使用 %s 格式符直接输出字符串
printf("%s
", str); }
⚡ 更优方案:
利用printf("%s", str)自动遍历并输出,代码更简洁。
重要概念:数组名 str 代表了数组首元素的地址。当使用 printf("%s", str) 时,程序会自动从该地址开始逐个输出字符,直到在内存中检测到字符串结束符 '\0' 才停止。
知识讲授:使用字符指针变量
定义与初始化
我们可以定义一个字符指针变量,让它直接指向一个字符串常量的首地址,从而实现对字符串的访问。
/* 将字符串常量 "Hello" 的首字符 'H' 的地址赋给指针变量 p */
char *p ="Hello";
关键点:变量p本身存储的是一个内存地址,并不存储字符串内容。而 "Hello" 是字符串常量,通常存储在程序的只读数据区。
知识讲授:字符指针访问字符串的方法
方法 01 · 使用指针移动
#include <stdio.h>
main()
{ char *p = "Hello";//循环直到指针指向的内容是'\0'
while (*p != '\0')
{ printf("%c", *p); //输出当前指向的字符
p++; //指针自增,指向下一个字符
}
printf("
");}
输出结果:Hello
核心逻辑:通过 `*p` 取值,通过 `p++` 移动指针
方法 02 · 直接输出字符串
#include <stdio.h>
main()
{char *p = "Hello";
/*使用%s格式符直接输出,
它会自动从指针指向的地址开始,
逐个输出直到遇到 '\0' 结束 */
printf("%s
", p); }
输出结果:Hello
核心逻辑:利用 %s 格式符自动处理字符串输出
知识讲授:输出字符串的三种方法
程序文本:输出字符串的三种方法
#include<stdio.h>
main() {
char string[] ="I love China!";// 初始化字符串
char *p1=NULL,*p2=NULL;int i;
// 方法1:下标法 | 方法2:数组名法 | 方法3:指针变量法
for(i=0; string[i]!='\0'; i++)
printf("%c", string[i]); /* 逐一访问数组元素 */
printf("
");
for(p1=string; p1<string+13; p1++)
printf("%c", *p1); /* 数组名隐式转换为指针 */
printf("
");
for(p2="I love China!"; *p2; p2++)
printf("%c", *p2);} /* 指针直接指向常量字符串 */
知识讲授:输出字符串的三种方法
背诵记忆
字符串引用的两种方式
▍ 字符数组:
`char str[] = "Hello";`
• 内容直接存储在数组分配的内存中。
• 数组名 `str` 是地址常量,不可改变指向。
▍ 字符指针:
`char *p = "Hello";`
• 指针变量 `p` 存储字符串常量的首地址。
• `p` 是变量,可以灵活指向不同的字符。
`'\0'` 的关键作用
它是 C 语言中字符串结束的唯一标志。在内存中它对应 ASCII 码值为 0 的空字符。
直接决定程序行为:
1.`strlen()`:返回从首地址到 `'\0'` 之前的字符个数,不包含它本身。
2.`printf("%s")`:从起始指针开始输出字符,直到遇到 `'\0'` 停止。
`*` 与 `++` 操作符
`*p` : 解引用
访问指针变量 `p` 中存储的地址所指向的内存单元内容,即“透过地址找值”。
`p++` : 指针移动
将指针指向的地址向后移动一个单位。对于 `char*` 类型,即向后移动一个字节,指向内存中的下一个字符。
例题分析:这段代码输出什么?
#include <stdio.h>
main() {
char str[] = "Programming";
char *p = str; p += 3;
printf("%s
", p);
}
🔍 一步步拆解逻辑
1. 定义字符数组 `str`,存储字符串 "Programming"。
2. 指针变量`p`被赋值为数组首地址,即指向第一个字符`'P'`。
3. `p += 3`:指针向后移动3个`char`长度,指向第4个字符 `'g'`。
4. `printf` 遇到`%s`会从指针当前指向的地址开始输出,直到遇到`\0`。
💡 核心考点回顾
在 C 语言中,字符串本质上是以 `\0` 结尾的字符数组。
当指针指向字符串时,移动指针即改变了字符串输出的起始位置。这是指针操作中非常经典的基础应用场景,需熟练掌握指针的加减运算含义。
正确输出结果
gramming
例题分析
课堂问答
Q1:字符串 "C Language" 在内存中占用多少个字节?
A. 10 B. 11 C. 12 D. 不确定
Q2:执行 `char *p = "Hello"; p++` 后,`*p` 的值是什么?
A. 'H' B. 'e' C. 地址值 D. 不确定
答案:C. 12
解析:"C Language" 包含11个可见字符,在C语言中字符串是以 `'\0'` 作为结束标志的,这个结束符也占用一个字节,所以总共占用12个字节。
答案:B. 'e'
解析:指针变量 `p` 初始指向字符串常量 `"Hello"` 的第一个字符 `'H'` 的地址。执行 `p++` 后,指针向后移动一个 `char` 类型的长度(即1个字节),指向了第二个字符 `'e'` 的地址。`*p` 是取指针指向的内容,因此结果是 `'e'`。
课堂练习(一):字符串逆序输出
题目要求
请编写一个 C 语言程序,核心要求是:
• 必须使用字符指针 (char*)实现
• 处理输入字符串:"Hello World"
• 将其完全逆序并打印输出
预期输出
程序运行后,控制台应打印:
dlroW olleH
解题思路提示
Step 1. 定位终点
利用指针遍历,直到找到字符串结束标志'\0'。
Step 2. 逆向遍历
回退指针指向最后一个有效字符,然后通过递减指针,从后向前逐个输出字符。
课堂练习(一):字符串逆序输出
程序文本:字符串逆序输出
#include<stdio.h>
main() {
char str[]="Hello World";
char *p = str;
// 步骤1:让指针p指向字符串末尾的'\0'
while(*p!='\0') { p++; }
// 步骤2:回退一步指向最后一个有效字符
p- -;
// 步骤3:从后向前输出直到开头
while(p >= str)
{ printf("%c", *p);
p- -; }
}
课堂练习(一):字符串逆序输出
课堂练习(二):统计字符个数
题目要求
不使用strlen函数,使用字符指针编写一个程序,统计以下字符串中包含的字符个数(注意:不包含字符串结束符 '\0'):
"Pointer is powerful"
预期输出
The length is:19
解题思路
01. 初始化:定义并初始化一个整型变量 count 为 0,作为字符计数器。
02. 循环遍历:利用 while 循环,让指针不断后移,判断指针指向的内容是否为 '\0'。每访问一个有效字符,计数器加 1。
课堂练习(二):统计字符个数
程序文本:统计字符个数
#include<stdio.h>
main() {
char str[ ]="Pointer is powerful";
char *p = str;
int count = 0;
// 遍历字符串,直到遇到结束符
while(*p != '\0') {
count++;// 计数器加1
p++;// 指针指向下一个字符
}
printf("The length is: %d
", count);
}
课堂练习(二):统计字符个数
第二部分
知识讲授
字符数组与字符串指针变量比较
知识讲授
知识讲授:字符指针 vs 字符数组
?
我们已经学习了两种引用字符串的方式。它们可以互相替代吗?在什么情况下该用哪一种?
char str[] = "Hello";
字符数组
char *p = "Hello";
字符指针
这节课,我们将深入剖析它们之间的区别。
区别一:存储内容与位置不同
char str[] = "Hello";
📦 存储内容:数组 `str` 在栈区分配连续内存,直接存储字符内容(含结束符 `'\0'`)。
💡 核心本质:是一个字符集合,本身就是内容的载体。
char *p = "Hello";
📍 存储内容:指针变量 `p` 存储的是一个地址值,指向存储在只读数据区的字符串常量。
💡 核心本质:是一个地址,它只是指向内容的“路标”。
区别二:赋值操作的灵活性不同
字符数组
初始化可以,但整体赋值不行!
// 正确:初始化时赋值
char str[10] = "Hello";
// ❌ 错误:不能用赋值语句给数组整体赋值
str = "Hi"; // 数组名str是地址常量,无法被赋值
字符指针
非常灵活,可以随时改变指向!
// 正确:初始化指向字符串常量
char *p = "Hello";
// ✅ 正确:指向另一个字符串常量
p = "Hi"; // 改变指针变量的指向
核心结论:指针本质是一个变量,存储内存地址,所以可以随时重新赋值改变指向;而数组名是一个地址常量,其值不可修改。
区别三:内容的可修改性不同
字符数组:内容通常可修改
char str[] = "Hello";
str[0] = 'h';
// 正确!此时 str 内容变为 "hello"
// 数组空间分配在栈上,可读可写
✅ 原因:字符数组在栈上分配了独立且连续的内存空间,属于程序运行时的“可写”区域,所以可以通过下标自由修改其中的字符。
字符指针:指向常量通常不可修改!
char *p = "Hello";
*p = 'h'; // ❌ 严重错误!程序可能崩溃
// "Hello" 是只读内存中的字符串常量
🚫 原因:指针指向的字符串常量存储在程序的“只读数据区”(RODATA)。尝试写入只读内存会触发操作系统的内存保护机制,导致程序异常终止。
重要提醒:在C语言开发中,请务必养成习惯,切勿尝试修改字符指针指向的字符串常量内容!
区别四:初始化细节
字符数组
// 显式指定大小,但字符串过长
char str[5] = "Hello";
⚠️警告:字符串长于数组大小,导致截断。
str = {'H','e','l','l','o'},缺少 '\0' 结束符!
这会导致后续的字符串操作行为未定义。
字符指针
// 指针指向只读数据区的字符串常量
char *p = "Hello";
✅ 安全:指针 p 仅存储字符串首字符的地址,不关心字符串的实际长度。系统自动维护了结束符。
核心总结:
使用字符数组时,必须手动保证数组的物理空间足够容纳所有字符和结束符 `\0`;而字符指针无需操心内存分配,直接指向字符串存储位置即可。
知识讲授:字符指针 vs 字符数组
程序文本
#include<stdio.h>
main()
{
char str[] ="I am happy!";
char *p="I am student!";
printf("str = %s
", str);
printf("p = %s
", p);
str[7] = 'H';// 正确,修改数组内容
//*p = 'H'; // 错误!试图修改字符串常量
printf("str = %s
", str);
printf("p = %s
", p);
}
思考时刻
如果我们在代码中取消对这一行的注释:
*p = 'H';
再次运行程序,你认为会发生什么现象?为什么?
知识讲授:字符指针 vs 字符数组
背诵记忆
字符数组 `char str[]`
存储内容:存储字符串的内容
存储位置:程序运行时的临时内存区,拥有独立的可写空间
可赋值性:数组名是地址常量,不能被整体赋值或修改指向
灵活性:内容可直接修改,但需注意数组长度限制,防止越界。
字符指针 `char *p`
存储内容:存储字符串的首元素地址
存储位置:指针本身在临时内存区,但它指向的字符串常量通常存放在程序的只读区域
可赋值性:指针变量本身是变量,可随时指向不同的字符串或地址
灵活性:无需担心数组大小限制,指向灵活,但直接修改指向的字符串内容可能引发程序崩溃。
课堂问答
01以下哪种写法可以正确修改字符串的第一个字符为 'x'?
A. char str[] = "test"; str[0] = 'x';
B. char *p = "test"; *p = 'x';
C. char *p; *p = 'x';
D. char str[3] = "test"; str[0] = 'x';
✅ 正确答案:A
• B错误,试图修改只读的字符串常量,会导致程序崩溃。
• C错误,指针 p 未初始化,属于“野指针”,直接赋值非法。
• D错误,数组长度为3,无法容纳"test"及末尾的结束符'\0'。
02`char str[] = "abc";` 和 `char *p = "abc";`
中,`str` 和 `p` 的本质区别是什么?
A. 没区别,都是字符串 B. str 是数组,p 是指针
C. str 是常量,p是变量 D. B 和 C 都对
✅ 正确答案:D
• 数组名 `str` 是地址常量,它的值不能改变,且指向一块可写的内存。
• 指针变量 `p` 是变量,可以指向任何地方,但它指向的是只读的字符串常量区。
• 两者在使用上有很多相似性,但本质完全不同。
例题分析:这段代码有问题吗?
#include <stdio.h>
main() {
char *p;
p[0] = 'H'; p[1] = 'i'; p[2] = '\0';
printf("%s
", p);
}
🔍 错误分析
1.野指针风险:定义了指针 `p` 但未初始化,它指向的是随机的、不确定的内存地址。
2.非法写入:直接通过 `p[0]` 向未知地址写入数据,这是非法的内存访问。
3.后果严重:会触发**未定义行为**,最常见的是程序崩溃。
✅ 正确做法
使用指针前,必须让它指向一块有效的、可写的内存空间。
💡 例如:分配堆内存(malloc) 或 指向内存中已存在的数组/变量。
课堂练习(一):代码纠错
📝 题目要求
下面的代码试图将字符串 "Hello" 改为 "Jello",但运行时会出现错误。请找出逻辑错误并修正它。
#include <stdio.h>
main() {
char *str ="Hello";
str[0] ='J';
printf("%s
", str);
}
🔍 错误分析
指针指向了“只读”的字符串常量
`char *str = "Hello";` 这段代码使得指针 `str` 指向了存储在只读内存段中的字符串常量。
试图修改只读内存导致未定义行为
`str[0] = 'J';` 试图直接修改字符串常量的值,这会导致程序崩溃或出现未定义行为。
课堂练习(一):修正方案
方法一:使用字符数组
#include <stdio.h>
main() {
// 使用字符数组,内容存储在可写内存区
char str[] = "Hello";
str[0] = 'J';// 正确修改
printf("%s
", str);// 输出 Jello
}
课堂练习(一):修正方案
方法二:让指针指向字符数组
#include <stdio.h>
main() {
char buffer[] = "Hello";
// 字符串存储在数组中
char *str = buffer;// 指针指向数组
*str = 'J';// 通过指针修改数组内容
printf("%s
", str);// 输出 Jello
}
课堂练习:概念辨析
题目要求:判断下列说法的正误,并说明理由。
01
`char str[]="abc";`和 `char *p="abc";`完全等价
思考:内存分配方式一样吗?
`str` 是数组名,`p` 是指针变量,二者在内存中的存储性质有何不同?
02
字符指针变量只能指向字符串常量
思考:指针的本质是地址。
字符指针是否也可以指向一个字符变量,或者字符数组中的某个元素?
03
数组名可以看作是一个指向数组第一个元素的常量指针。
思考:为什么说它是“常量”指针?
既然是指针,它具备哪些指针的特性?又有哪些不同之处?
课堂练习(二):参考答案
1. 错误
理由:它们在存储方式、可赋值性、内容可变性上都有本质区别。`str`是存储字符的数组,`p`是存储地址的指针。
2. 错误
理由:字符指针非常灵活,它可以指向任何`char`类型的内存地址,包括字符数组的元素、动态分配的内存等。
3. 正确
理由:数组名在大多数表达式中会隐式转换为指向其首元素的指针,且这个指针的值不能被改变(即常量指针)。例如,`str`和`&str[0]`的值是相等的。
课堂小结
字符串的本质
以'\0'结尾的字符序列,是C语言处理文本数据的基础。
两种引用方式
•字符数组:栈区存储,可修改
•字符指针:存地址,灵活,常指向只读区
记住:数组是家,里面住着人(字符);指针是地图,告诉你家在哪里。
指针核心操作
•*p:
解引用,访问指针指向的字符
•p++:
指针移动,遍历整个字符串
课堂小结
课后任务
基础巩固 · 熟练操作
01.字符串大写转小写:编写程序,使用字符指针将字符串 "HELLO WORLD" 中的所有大写字母转换为小写字母并输出。
02.字符串内容复制:编写程序,使用字符数组存储一个字符串,然后使用字符指针将其内容完整复制到另一个字符数组中。
进阶提升 · 深入思考
03.统计特定字符个数:编写程序,使用字符指针统计字符串 "Pointer is powerful" 中字母 'o' 出现的次数。
★ 04. (挑战)判断回文字符串:编写程序,使用字符指针判断字符串 "abba" 是否为回文字符串(即正读和反读都一样)。如果是,输出 "Yes",否则输出 "No"。
谢谢观看
THANKS
$