资讯动态

C语言经典练习:双指针实现字符串两端汇聚动画

发布时间:2026/9/15 6:44:19 来源:尧图企业网站定制
在C语言的入门练习里有一个特别经典的小题目多个字符从两端移动向中间汇聚。很多初学者第一次看到题目时会有点懵不知道从哪里下手。其实这个练习一旦想通等于把字符串、数组下标、循环控制、清屏延时这些基础知识点全串起来了。它做出来的效果也很直观屏幕上先是一整排占位符然后最左边的字符和最右边的字符同时亮出来接着往中间靠拢直到整句话完整显示出来。对于刚学完循环和数组的人来说这是一个非常合适的小项目既不会太难又能带来很强的成就感。我第一次做这个题是在学完翁恺老师那套C语言课程习题之后题目看起来像是“动画效果”实际上考的是双指针的雏形。后来我才发现字符串逆序、回文判断、甚至一些文件按行逆序输出的题底层都是同一个套路。所以今天把这个练习从头到尾拆一遍从环境准备到完整代码再到各种坑一次性讲清楚。1. 项目概述与核心思路拆解1.1 这个练习到底在做什么在C语言学习阶段很多练习都是零散的今天写数组明天写循环后天接触字符串。而“多个字符从两端移动向中间汇聚”正好把这些内容揉在一起。它要求程序运行后屏幕上先显示一串占位符通常是星号然后第一轮把目标字符串的第一个字符和最后一个字符替换到对应位置第二轮替换第二个和倒数第二个直到所有占位符被替换成目标字符串。整个过程看起来就像两端的字符在向中间奔跑汇聚。这个场景能练习的不只是“写出一个效果”更重要的是理解“如何用下标同时控制左右两边”。很多同学一上来会想用两个循环分别处理左边和右边结果发现顺序对不上。正确思路是让两个指针或下标在同一个循环里一左一右同步移动。等这个题目写顺了你再看其它双指针题目会觉得特别亲切。1.2 双指针思想与循环控制程序的核心非常简单一个整数变量 left 初始为0另一个整数变量 right 初始为 strlen(str)-1。每一轮循环中把目标字符串的第 left 个字符和第 right 个字符写进显示数组然后 left 加1、right 减1直到 left right 结束。这其实就是双指针思想在C语言里非常常见后面学快速排序、字符串逆序、回文判断还会碰到。这里的关键是循环次数。假设字符串长度是 n因为每轮替换两个字符所以大约要执行 n/2 轮。如果 n 是偶数比如 n22执行11轮如果 n 是奇数比如 n9执行5轮最后一轮 left 和 right 指向同一个位置赋值两次同一个字符也没问题。理解这一点就不会把循环边界写错。1.3 为什么用星号占位而非空格刚开始我尝试用空格占位结果运行起来什么都看不清。因为控制台背景通常是黑色或白色空格和背景色混在一起你根本不知道字符串有没有被替换。用星号就直观多了一眼就能看出还有哪些位置没有“点亮”。当然用下划线、井号、甚至随机字符都可以这是你的自由。占位符的选择还影响视觉效果。星号是半角字符宽度固定在等宽字体下排列整齐。中文用户如果拿全角符号做占位会出现字符宽度错位的问题所以初学者练这个题目时尽量用半角字符。2. 环境准备与基础必备知识2.1 开发环境搭建与编译流程写C语言并不需要多复杂的IDE。我试过最省事的是 Dev-C下载安装就能用。现在更多人用 VS Code配置C语言环境也很方便先装 C/C 插件再配一个编译器Windows 上推荐 MinGW-w64Linux 下直接用系统自带 gcc。配置好之后写一个 hello.c然后执行gcc hello.c -o hello就能编译。很多新手在跑这个汇聚动画时会遇到一个问题程序一闪而过看不清效果。这不是代码逻辑错了而是控制台窗口在 main 函数执行完就自动关闭了。解决方式是在 main 末尾加getchar()或者system(pause)。我习惯用getchar()因为它是标准库函数跨平台不依赖Windows命令。2.2 核心语法数组、字符串和strlen这个练习离不开字符数组和字符串结束符。你要明白C语言里没有真正的字符串类型所谓字符串就是 char 数组结尾必须有一个\0结束符。strlen返回的是字符串长度不含结束符。比如ABC在内存里占4个字节但strlen(ABC)返回3。最容易出错的地方是右端下标的计算。strlen(ABC) 的结果是3但最后一个字符 C 的下标是2所以 right 必须等于 strlen(str)-1。如果不小心写成了 strlen(str)第一次循环就会把显示数组结束符的位置替换成字符轻则字符串被截断重则越界访问内存出现乱码或崩溃。这个问题在实际作业里出现的频率非常高值得反复提醒。2.3 控制台输出、清屏和延时原理控制台动画涉及三个基本操作输出、清屏、延时。最直观的清屏方式是调用system(cls)Windows 下有效。但频繁清屏会让屏幕闪因为系统需要重新绘制整个控制台。另一种更平滑的方式是使用回车符\r它的作用是把光标移到本行行首后续输出会覆盖当前行的内容实现原地刷新。延时函数方面Windows 下常用Sleep(毫秒)需要#include windows.hLinux 下用usleep(微秒)需要#include unistd.h。为了让代码跨平台可以用条件编译封装一个宏后面我会给出具体写法。初学者可以先用 Windows 版本跑通再思考跨平台问题。3. 完整实现与代码讲解3.1 基础版system(cls)清屏先看一个最容易理解的版本。它使用system(cls)清屏配合Sleep延时代码逻辑非常直白适合课堂演示。#include stdio.h #include string.h #include windows.h int main() { char target[] Welcome to C Language!; int len strlen(target); char display[100]; int left 0; int right len - 1; for (int i 0; i len; i) { display[i] *; } display[len] \0; printf(%s\n, display); while (left right) { display[left] target[left]; display[right] target[right]; system(cls); printf(%s\n, display); Sleep(200); left; right--; } return 0; }每一轮循环里程序先把显示数组对应位置替换成目标字符然后清屏再打印。因为 left 和 right 同时更新效果就是首尾两个字符一起出现。代码里 display 数组大小是100只要目标字符串不超过99个字符就够用。这里直接用 strlen(target) 做边界比写死数字更不容易出错。不过这个版本有个明显问题system(cls)是 Windows 专用在 Linux/macOS 上会提示找不到命令。而且每轮清屏会有闪烁原因是控制台清空再重绘需要时间。更关键的是如果你把system(cls)写进循环里一旦清屏函数出错屏幕可能会残留大量重复内容干扰判断。3.2 进阶版\r原地刷新想要动画更顺滑可以把system(cls)换成\r和fflush(stdout)。\r是回车符它的作用是把光标移到本行行首而不是换行。这样每次打印结果都会覆盖上一次的输出像进度条一样在原地刷新。#include stdio.h #include string.h #include windows.h int main() { char target[] Welcome to C Language!; int len strlen(target); char display[100]; int left 0; int right len - 1; for (int i 0; i len; i) { display[i] *; } display[len] \0; while (left right) { display[left] target[left]; display[right] target[right]; printf(%s\r, display); fflush(stdout); Sleep(150); left; right--; } printf(\n); return 0; }关键区别在于printf(%s\r, display)末尾用的是\r而非\n。如果只在 Windows 下运行Sleep(150)表示0.15秒刷新一次。如果忘了fflush(stdout)屏幕可能不会及时更新因为标准输出默认是行缓冲只有遇到换行符才会刷新缓冲区。在行尾加\r后必须手动刷新这算是用这个技巧最容易踩的坑。3.3 指针版本用指针代替数组下标再进一步用指针来写能顺便熟悉C语言指针。指针版本的思路一样只是把 left/right 两个下标换成两个指针。#include stdio.h #include string.h #include windows.h int main() { char target[] Welcome to C Language!; int len strlen(target); char display[100]; char *left display; char *right display len - 1; for (int i 0; i len; i) { display[i] *; } display[len] \0; while (left right) { *left target[left - display]; *right target[right - display]; printf(%s\r, display); fflush(stdout); Sleep(150); left; right--; } printf(\n); return 0; }这里target[left - display]看起来有点绕因为 left 是指向 display 的指针left - display得到的是当前偏移量也就是目标字符串中的下标。当然更清晰的方式是定义两个指向 target 的指针不过上面的写法能帮你理解指针和下标的关系。实际写代码时如果你觉得别扭完全可以直接用下标版本没必要为了用指针而用指针。3.4 封装成可复用函数学会了核心逻辑后建议把动画过程封装成一个函数比如void revealFromBothEnds(const char *s)。这样以后想做加载动画、开机欢迎语直接调用函数就行。#include stdio.h #include string.h #include windows.h void revealFromBothEnds(const char *s) { int len strlen(s); char display[128]; int left 0, right len - 1; for (int i 0; i len; i) { display[i] *; } display[len] \0; while (left right) { display[left] s[left]; display[right] s[right]; printf(%s\r, display); fflush(stdout); Sleep(150); left; right--; } printf(\n); } int main() { revealFromBothEnds(Hello, C World!); revealFromBothEnds(This is a C demo); return 0; }封装的好处是主函数变得非常简洁逻辑被隔离在一个函数里。尤其当你稍后想增加“从两端一次替换多个字符”或者“支持不同占位符”时直接改函数内部即可。我在实际写代码时还会把延时时间作为参数传进来这样调用者能控制动画速度灵活性更高。3.5 跨平台兼容处理如果你在Linux或macOS上编译上面的代码会遇到Sleep未声明的问题。为了跨平台可以用条件编译封装一个宏#ifdef _WIN32 #include windows.h #define SLEEP_MS(x) Sleep(x) #else #include unistd.h #define SLEEP_MS(x) usleep((x) * 1000) #endif然后在代码里统一使用SLEEP_MS(150);。这样在 Windows 和 Linux 都能编译运行。usleep参数单位是微秒所以150毫秒要乘1000。这个方法是我在把课堂代码搬到Linux服务器上测试时试出来的属于老程序员常用的跨平台技巧。4. 运行效果与关键机制解析4.1 动画过程文字还原我用一个短字符串ABCD来演示动画过程这样最容易看清逻辑。初始状态是四个星号****第一轮结束后最左边变成 A最右边变成 DA**D第二轮 left1right2替换 B 和 C最终显示ABCD如果换成Welcome to C Language!长度是22程序会循环11轮。前几轮看起来就是 W 和 ! 先出现然后是 We 和 e!接着 Wel 和 ge!逐渐往中间合拢。使用\r原地刷新时屏幕不会出现多行而是在同一行不断覆盖看起来像文字从两端生长出来。4.2 为什么left和right必须同步更新这个程序最核心的一步是“替换-打印-延时-收缩”四个动作必须在同一个循环里完成。如果先处理左边全部替换完了再处理右边效果就变成从左往右逐字显示题目要求的“两端汇聚”就没了。很多初学者会写出两个for循环一个从左到右一个从右到左结果左半部分正常右半部分又覆盖了一遍逻辑完全乱掉。同步更新还有一个好处循环次数只有目标字符串长度的一半而不是全部长度。以22个字符为例leftright循环会执行11次因为每次替换两个字符。如果字符串长度为奇数中间的字符会在最后一次循环中被 left 和 right 同时指向同一个位置这时循环里给 display[left] 和 display[right] 赋同一个字符两次也没有问题最终显示正确。4.3 数组越界与\0的坑这个练习最容易出现的错误之一是初始化 display 时忘记补\0。display 是局部数组如果不初始化里面存的是随机值。如果你用 for 循环只给前 len 个元素赋了 *然后直接printf(%s, display)它会继续往后读 display[len] 及以后的内容直到遇到一个0才停下输出会出现一串乱码严重时甚至会段错误。解决办法有两种一种是在初始化时使用memset(display, *, len); display[len] \0;另一种是直接把 display 定义成char display[100] {0};然后循环赋值。我个人更推荐后者简单省事还能避免漏写\0。另一个常见问题是定义数组太小如果目标字符串超过数组大小越界写入会破坏栈空间直接导致程序崩溃。5. 常见问题与排查技巧实录5.1 程序运行后一闪而过看不到动画这是新手最常见的问题。代码逻辑没毛病但是在控制台程序里main 函数执行完窗口就关了你只看到黑框一闪。解决方法是程序末尾加getchar();或者system(pause);。我习惯在调试阶段用getchar()因为它是标准库函数跨平台。不过注意动态循环结束后输入缓冲区里可能残留换行符最好用两个getchar()或者在循环后清空缓冲区。另一个导致看不到动画的原因是有些编译器对 printf 默认行缓冲如果用\r而没有fflush(stdout)程序可能在循环结束后才一次性把所有内容打印到屏幕甚至因为\r的覆盖你只看到最后一行。所以记住用\r刷新必须配合fflush(stdout)。5.2 Sleep未声明和跨平台编译Sleep是 Windows 特有的函数需要#include windows.h。在 Linux 下应该用usleep(微秒)或者nanosleep。很多同学把代码从 Windows 课设搬到 Linux 服务器时第一遍编译就卡在这里。这时可以直接使用我前面写的条件编译宏把Sleep和usleep统一成SLEEP_MS。还有一种情况是在某些在线编译环境里windows.h 根本不存在编译器直接报错。这种环境通常也不支持控制台动画无法真实看到动态效果建议在本地编译器运行或者把延时调得足够长观察输出。即使看不到动画只要逻辑正确最终完整字符串也能打印出来。5.3 中文乱码问题Windows 控制台默认使用GBK编码而很多编辑器默认保存为UTF-8两者不一致时 printf 中文就会乱码。这个题目用英文或数字演示更方便。如果一定要中文你可以先输出一个中文字符串测试乱码就说明编码不一致。解决方式有两种一是把源文件另存为GBK/ANSI编码二是在程序开头调用SetConsoleOutputCP(CP_UTF8);并把源文件保持UTF-8。刚学C语言没必要纠结这个建议先掌握逻辑。5.4 常见症状速查表症状可能原因解决办法程序一闪而过main 执行完窗口关闭末尾加 getchar() 或 system(pause)全是星号不动Sleep 后没有刷新输出缓冲用 \r 时加 fflush(stdout)最后一两个字符不显示right 初始值写错少了 -1改成 strlen(str)-1输出乱码局部数组没有补 \0用 memset 或定义时初始化为0中文乱码编辑器编码与控制台编码不一致统一编码或改用英文字符串Linux下编译报错Sleep 是Windows函数用条件编译或 usleep屏幕闪得厉害频繁 system(cls)改用 \r 原地刷新这张表是我在实际教学和帮同学改代码时整理出来的基本覆盖了这个练习九成以上的报错点。如果遇到新问题先截图看现象再回来看代码逻辑往往很快就能定位。6. 扩展玩法与下一步学习建议6.1 一次汇聚多个字符题目里的“多个字符”如果理解为“每轮移动多个字符”也很容易扩展。比如每轮左右各替换两个字符只需要在内层加一个 for 循环控制 left 和 right 同时往中间走几个格子。关键是要处理越界如果剩余未替换的字符不足两个就只替换剩余部分。while (left right) { for (int i 0; i 2 left i right; i) { display[left i] s[left i]; display[right - i] s[right - i]; } printf(%s\r, display); fflush(stdout); Sleep(150); left 2; right - 2; }这个版本看起来更像“多个字符”从两端拉着手往中间跑。你可以把2改成3、4速度会明显变快。需要注意内层循环条件left i right保证了在字符串长度奇数时不会越界。我试过把步长改成5效果就很像一段文字被快速压缩到中心挺有意思。6.2 字符串逆序与回文判断学完这个练习建议顺手做两个经典题目字符串逆序、回文判断。这两个题都用双指针和今天的汇聚思路很像。字符串逆序是交换两端字符然后 left、right--回文判断是从两端扫描如果发现对应位置不同就不是回文。它们与“汇聚”共享同一个框架只是把“赋值”换成了“交换”或“比较”。在C语言里这种“一题通题题通”的感觉很值得体会。比如很多学生搜索“字符串逆序c语言pta”时看到答案代码半天看不懂其实只要理解了今天这个left和right的移动过程逆序题的代码瞬间就清晰了。建议自己手写一遍字符串逆序再回来对比一下这个汇聚程序你会发现两者简直是亲兄弟。6.3 用动画效果做控制台小工具我实际写代码时发现这个效果很适合做成一个简单的欢迎界面。比如程序启动时逐字显示“System is loading...”然后清屏进入主菜单。如果再配合system(color)修改控制台颜色或者使用 ANSI 转义序列控制光标位置效果会更好。不过别过度追求花哨先把核心逻辑玩明白以后学控制台编程时这些技巧都是基础。还可以把 target 字符串改成文件内容逐行从两端汇聚显示做一个极简的文件浏览器启动动画。这需要用到fopen、fgets等文件操作函数正好能顺便练习文件读写。C语言的文件读写操作代码并不难难的是把字符串处理和控制台操作组合在一起这个题目就是一个很好的跳板。6.4 调试小技巧放慢速度观察指针移动最后分享一个我调试这类动画代码时最常用的办法把Sleep(150)临时改大比如Sleep(1000)每次刷新间隔1秒这样能清楚地观察 left 和 right 的变化确认逻辑后再把速度调回去。如果配合打印 left 和 right 的值比如printf(left%d right%d\n, left, right);你甚至不需要猜测循环执行了几轮直接看输出就能判断边界对不对。这个方法特别适合双指针类题目。很多同学初次写这个汇聚程序时边界总差一个数字肉眼又看不出问题。把速度放慢再加上调试输出两分钟就能定位。等你多调几次以后写快速排序、二分查找这类双指针算法也会习惯性用这种方式验证边界。这是我在实际编程中收获最大的一点点经验分享给你。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价