ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C语言第11天进阶:指针、字符串与输入输出的实战指南

C语言第11天进阶:指针、字符串与输入输出的实战指南 1. 第11天的学习版图这个阶段你在跟自己较什么劲1.1 为什么偏偏是“第11天”如果你刚好自学C语言十一天大概率已经扛过了最劝退的“前十天”环境配了半天、程序总在报错、指针还没正经碰过、字符串也只会笨拙地一个个字符折腾。第11天这个节点很有意思它既不是“Hello World”的新鲜期也不是已经熟练到能写小项目的舒适期而是从“抄代码”到“理解代码”的分水岭。前十天你大概已经见过了变量、数据类型、运算符、分支和循环可能还写了几个整型数组用来做冒泡排序。但到了第11天你会发现教材开始把一个叫“指针”的东西推到面前字符串函数开始成堆出现scanf的脾气也突然变得不可捉摸。这些知识不是学完就能用的它背后都在逼你完成同一个思维转变从“用变量存值”变成“用地址理解内存”。我在社群和论坛里看过不少“第X天学习C语言”的记录帖绝大多数人在第5到第7天左右最有热情每天能贴一堆代码到第10天左右反而陷入迷茫问的问题开始变成“为什么我输入一串字符后再回车程序就崩了”“为什么数组传进函数里就只剩8个字节了”。如果你刚好卡在这个位置别慌。第11天本来就不是用来突飞猛进的它更像一个缓冲让你把前面积累的语法碎片通过指针和字符串这两个“黏合剂”给串起来。1.2 从“值的世界”跳到“地址的世界”C语言入门阶段最核心的门槛不是语法规则背没背熟而是能不能真正理解“内存”。你定义一个int变量x编译器在内存里划出4个字节的空间x是那块空间的名字x才是那块空间的门牌号。第11天的指针学习本质上就是让你学会拿着门牌号去访问房间。我记得自己当年在第11天做的一个练习是“交换两个数的值”。就是写一个swap函数传两个整数进去交换。用值传递怎么都交换不了函数里明明换了回到main函数里两个变量纹丝不动。那时候老师只说了一句“要用指针”但完全没解释为什么。后来自己画内存框图才明白值传递是把房间里的物品拍了张照片给函数看函数再怎么改照片也动不了原来的房间指针传递则是把钥匙递进去函数拿着钥匙直接打开门改里面的家具。所以第11天的学习主线非常清晰指针、数组、字符串函数、scanf缓冲区这些知识点全部围绕“内存地址”展开。你不需要一次吃到撑但至少要把以下几个关键概念反复嚼透取地址和*解引用的关系别只背公式要能画图说明。数组名本质上是指向首元素的指针常量但又有细微差别。字符串用char[]存储操作字符串的函数几乎全部通过指针扫描内存。如果这一天你能理解“传参到底是传值还是传址”那你后面学结构体、链表、文件操作都会顺很多。这个阶段的坑我在后面几个部分会结合具体的环境和练习题展开细说因为我当时踩的每一个坑几乎都在热搜词里出现过环境配置、字符串逆序、鞍点、冒泡排序、scanf输入异常。一个一个来。2. 先别急着写代码开发环境这一关必须过2.1 Ubuntu虚拟机配C语言环境的实操细节很多初学者用的是Windows本子学习C语言时选择在虚拟机里装Ubuntu。这个方案本身没问题毕竟Linux下的gcc和终端调试更接近工业环境。但第11天我强烈建议你回头检查一下环境配置是否还留有隐患因为之前几天的练习代码量少还感觉不出来等开始认真做字符串和指针的题目之前“能编译就不管”的问题会集中爆发。Ubuntu下配C语言环境其实很简单无非是安装编译器和编辑器。我记得当初用的命令大概是下面这几条sudo apt update sudo apt install build-essential gdb -y sudo apt install vim -ybuild-essential这个包会顺带装好gcc、make、libc等基础工具链是一整套编译环境gdb是调式器第11天开始必须装上因为指针问题靠printf打日志排查会非常难受不如断点看内存地址来得直观。编辑器用vim也行用gedit也行甚至直接用VS Code连远程虚拟机也可以工具不挑顺手就行。装完以后我建议你干一件事写一个最简单的hello.c分别用gcc hello.c -o hello编译然后./hello运行确认没问题之后再顺手试一次gdb ./hello进去执行break main和run。为什么第11天要特意强调这一步因为后面的字符串函数、指针变量调试你会频繁用到watch命令看变量的地址值。如果你到现在还没用过gdb务必花两个小时把它基本用法过一遍“b打断点”“p打印变量”“x按地址查看内存内容”这三招足够应付整个C语言课程设计了。2.2 用VS Code跑C语言的正确姿势如果你实在不想用vimVS Code是目前很多学生的首选。它配置C语言环境有几个关键文件需要手动处理tasks.json负责告诉VS Code怎么编译当前文件launch.json负责告诉调试器怎么启动程序。很多新手在第11天被环境折磨往往不是缺编译器而是把这两个文件搞混。最简单的方案是装好C/C扩展后让VS Code自动生成。操作方法打开你的.c文件按F5选择“C (GDB/LLDB)”它会提示你选编译器然后自动在.vscode目录下生成配置文件。你只需要检查一下tasks.json里的args是不是-g和${file}这样的组合launch.json里的miDebuggerPath指向的是不是gdb路径。这一段我踩过一次坑因为没装gdblaunch调试一直报“miDebuggerPath无效”后来补装后立刻就好了。关于编译器选型第11天这个阶段乖乖用gcc就行不要折腾MinGW和Cygwin的差异化。网上一搜“vscode怎么运行c语言代码”会跳出一堆版本不一的教学帖有的让你装Code Runner有的让你配环境变量。我的建议是只装Microsoft官方那套C/C扩展只配置通过任务方式编译运行不要图省事装太多个插件。指不定哪天插件更新把配置搞崩了排查起来远比配环境本身麻烦。酒要一口一口喝路要一步一步走环境只要能用就别反复推倒重来。3. 第11天的重头戏指针、字符串与输入输出3.1 指针入门从数组传参到修改实参第11天最应该吃透的除了指针的基础语法还有指针最实用的两个场景数组和函数。数组名当参数传递进函数时它其实退化成指针。这带来一个很多初学者看不懂的结果在函数内部用sizeof(arr)求长度得到的不是数组元素个数而是864位系统指针大小。这个知识点我在第11天亲测翻过车。当时写一个求和函数想让函数知道自己要遍历多少元素传了一个数组进去之后在里面用sizeof(arr) / sizeof(arr[0])算长度结果算出来永远是2循环只跑两遍就结束。后来查资料才明白数组作为函数参数时编译器看到的只是一个指针它丢失了“数组长度”这个元信息。所以正确做法是传数组的同时再传一个长度变量。int sum_array(int arr[], int n) { int sum 0; for (int i 0; i n; i) { sum arr[i]; } return sum; }至于“通过指针修改实参”的场景最简单的练习就是swap交换函数。我建议你写完后在gdb里分别打印a和p的值亲眼看到它们的值一致才会真正理解指针传参的本质。这个阶段要建立的思维方式是int *p定义了一个指针变量它存的是某个整型变量的地址*p用在赋值号的左边代表“往这个地址里写入”*p用在赋值号的右边代表“把这个地址里存的数值取出来”。每个符号因位置不同含义不同这就是C语言灵活又危险的体现。别嫌啰嗦这个判断贯穿整个C语言学习生涯。3.2 字符串操作全家桶从逆序到按空格分割热搜词里的“字符串逆序c语言pta”“c语言将一个字符串按照里面的空格分开”是第11天非常典型的练习题。字符串和字符数组的界限很模糊但操作字符串的库函数基本都是指针扫描的套路。比如strlen的实现逻辑就是从传进来的指针开始一个一个往下读直到碰到字符串末尾的\0才停下。练习字符串逆序时我第一次提交PTA判题系统总是报“段错误”。排查半天原因出在我拿char *s hello这种方式定义字符串然后试图修改这个字符串的内容。这个定义方式在很多旧教材里都出现过但它在标准C里指向的是字符串字面量所在的内存区域禁止修改。正确的做法是定义成字符数组char s[] hello;至于按空格分割字符串C语言没有Python里那种现成的split()但strtok()函数能做到类似效果。它的原理很有意思第一次调用时传入整个字符串之后每次传入NULL函数内部会记住上次扫描到的位置用指针在字符串里不断查找分隔符并替换成\0。第11天用strtok做分割练习比用手写两层循环舒服得多也能帮你理解“库函数内部其实还是在玩指针”这个概念。#include stdio.h #include string.h int main() { char str[] Hello C Language; char *token strtok(str, ); while (token ! NULL) { printf(%s\n, token); token strtok(NULL, ); } return 0; }不过要提醒一点strtok会修改原字符串它会把你传入的分隔符在字符串里原地替换成\0。如果你后续还要拿原始字符串做别的操作记得先复制一份再用。3.3 scanf不是读不进值是缓冲区里还留着残渣第11天你大概率会遇到一个很怪的现象写完scanf(%d, n)再写scanf(%c, ch)第二个scanf总是读不到你想要的值直接跳过了。这不是编译器坏了而是第一次按回车时留在输入缓冲区里的换行符\n被第二次的%c读走了。缓冲区是C语言标准输入输出库里一个特别“像现实生活”的设计。你从键盘上敲的内容不会一个字符一个字符地被程序提取而是先进入一块缓冲区等按下回车后一次性交给程序。scanf实际上是从缓冲区里按格式串要求抓取数据的。%d会跳过空白字符、读取数字但%c不会跳过任何字符哪怕它是个空格或换行符。解决方案有好几种我列出实际用下来最稳的三个在第二次scanf前用while (getchar() ! \n);把残留的换行符清掉。用scanf( %c, ch)在%c前加一个空格让scanf自己跳过空白字符。干脆统一用fgets读整行进来再用sscanf解析完全绕开缓冲区残渣问题。第11天一定要花一个小时专门梳理这几个输入输出函数的行为差异因为后面做文件读写、字符串处理你对“该读取的内容到底在哪”的理解越深排查bug越快。此外scanf的返回值也是高频考点。它返回的是成功读取的数据个数如果输入不匹配会返回0甚至返回EOF。我在做PTA的题目时经常用它判断用户是否输入结束效果非常干净只是新手容易忽略这一点。4. 从九九乘法表到鞍点代码练手的科学顺序4.1 九九乘法表不只是循环嵌套很多人的第一道正经题目就是“九九乘法表”。这道题看起来简单其实考的是循环嵌套的边界控制。外层循环管行数从1到9内层循环管列数列数的上限是当前的行号。如果内层循环也写成固定到9输出来的就不是三角形而是方形表。网上能找到的参考代码五花八门我建议你自己动脑写一遍不要直接抄。核心思路是这样的逻辑for (int i 1; i 9; i) { for (int j 1; j i; j) { printf(%d*%d%-2d , j, i, i * j); } printf(\n); }这里的%-2d表示左对齐占2位宽度。如果不加对齐乘法表会排得歪歪扭扭看起来很丑。这道题别看简单它锻炼的目标是“确定循环边界”这个能力在第11天后面做鞍点、做排序题目时会反复用到。顺带一提实际上还有基础版本的printf(%d*%d%d , j, i, i*j);初学阶段跑通就行但我还是建议你练一练格式化输出的对齐技巧因为后面写课程设计时控制台输出是否整齐会影响最终的程序体验。4.2 冒泡排序从“记住代码”到“画过一次图”冒泡排序C语言版本是学习数组后绕不开的作业题。网上一搜一大把答案但真正理解它的人至少得亲手模拟过一轮数据交换过程。它的排序逻辑很简单从头开始两两比较相邻元素如果顺序反了就交换。一次从头到尾的扫描叫“一趟”每一趟会把当前未排序区间里的最大值“冒泡”到末尾。关键代码长这样for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (a[j] a[j 1]) { int temp a[j]; a[j] a[j 1]; a[j 1] temp; } } }那个n - 1 - i是多少新手理解不了的地方。原因其实很简单每排完一趟数组末尾就会多一个已经就位的最大值下一趟就没必要再把它拖进比较范围内了。i代表已经排好的趟数所以内层比较的次数是n - 1 - i。第11天做冒泡时我建议你顺手做一个小优化增加一个标志位如果这一趟扫描中一次交换都没发生过说明整个序列已经有序可以提前退出外层循环。这个写法教科书中不一定有但在实际工作中非常实用也是面试里考察“是否真正理解算法”的一个点。学会了以后你会发现后面的选择排序、插入排序都是同一个模式把“交换策略”拆出来思考即可。4.3 字符串逆序与分割双练PTA题库里的“字符串逆序”看起来比九九乘法表难一截因为你需要用双指针思路把字符位置反转。一个很漂亮的写法是从头尾同时往中间走交换两端的字符int left 0, right strlen(s) - 1; while (left right) { char temp s[left]; s[left] s[right]; s[right] temp; left; right--; }这段代码实践价值很高因为它用的是“指针/下标双向移动”的思想。学到这里你会发现C语言很多字符串操作都是两个指针在内存里追逐、交换和查找。练完这个后再回到“按空格分开字符串”你会觉得strtok那种内置状态的设计非常顺理成章。顺带说一句很多同学分不清“反转字符串”和“逆序输出”。逆序输出只需要从末尾往开头打印每个字符即可不需要修改原数组反转字符串则真正动了数组里的数据。PTA题如果明确要求“将字符串逆序”指的是后者提交前最好用printf验证一下原字符串是否真的变了。这个小差别我在群里见过不少人丢分第11天提前留意能省不少事。4.4 5×5鞍点问题多维数组的逻辑综合题热搜词里的“使用stdio.h和limits.h用C语言解决计算5*5鞍点问题”其实是一道很经典的二维数组题。题目要求是在一个矩阵里找出“既是某行的最大值又是某列的最小值”的元素如果存在就输出它的位置和值不存在则输出提示信息。我第一次做这道题时吃了大亏因为只找了每行的最大值就断言它是鞍点完全忘了还要验证它同时是所在列的最小值。正确思路分两步先找出每一行的最大值记录列号然后遍历该列检查这个值是否真的是整列的最小值。另外还要考虑矩阵里可能存在多个鞍点的情况以及完全没有鞍点的输出格式。这道题真正训练的不是语法而是“多个条件同时成立”的边界校验。这种题在课程设计、期末考试里非常容易变形比如鞍点问题改为“9×9矩阵”“多个鞍点同时输出”逻辑骨架不变。第11天做这道题时别急着抄网上的代码把你自己的思路用伪代码列出来再翻译成C语言效果会好很多。伪代码的练习我觉得是战略价值极高的因为C语言最常见的困境不是不会写而是“思路乱成一团”。5. 第11天高频翻车问题速查亲手填过的坑5.1 scanf“读不进数字”的几种解法前面已经提过缓冲区残留的问题这里再补充两个实战案例。一个是我之前做“输入日期算这是一年中的第几天”这类日期题时程序执行到第二次输入时直接跳过去最后发现是因为上一次用了scanf后又用了getchar两者的组合方式不对。另一个典型问题是用户输入字母时scanf返回0且不读取内容。如果你没有判断返回值程序可能会陷入死循环因为缓冲区里那个非数字字符一直没被消费掉下次scanf还是失败。解决方案是检测到输入失败后用while (getchar() ! \n);将所有残留字符清空并提示用户重新输入。int n; while (scanf(%d, n) ! 1) { while (getchar() ! \n); // 清空无效输入 printf(输入无效请重新输入); }这个套路在写“网吧计费管理小项目”或“课程设计”时经常派上用场。要做出一个用户体验稍好的控制台程序输入校验这一步是少不了的。5.2 while和do-while什么时候必须用后者热搜词里出现“c语言while和do-while区别”一点不奇怪这是第11天的经典困惑之一。while和do-while的本质区别只有一个执行时机。while先判断后执行如果一开始条件为假那循环体一次都不会执行do-while先执行一次再判断也就是说循环体至少会跑一次。做菜单循环时我强烈推荐do-while因为你至少要让菜单显示一次根据用户输入再决定是否继续。但做类似“输入密码直到正确”这种场景时用while更合适因为用户可能一次都不需要输入。说白了你要回答的不是“哪个更高级”而是“你的业务逻辑里循环体有没有必要至少执行一次”。另外还有一个容易踩的细节do-while在写完后一定要带分号。很多新手写代码时总会漏掉while(条件);末尾的分号导致编译报错。这类错误在gcc的报错信息里往往只显示下一行附近的问题初看一脸懵但检查到具体行号就明白了。5.3 其他常见问题速查表第11天学习过程中我把碰到的高频问题整理成了下面这张速查表方便对照排查问题现象常见原因解决办法编译报错“undefined reference to xxx”通常是没有链接数学库等链接时加上-lm之类的参数程序运行时闪退但编译正常数组越界或使用了未初始化的指针用gdb定位崩溃行字符串变量在函数里被修改后主函数没变化值传递字符串内容没改到传入字符数组时以指针方式传递或调整函数签名用strlen求出的长度和数组大小不一致字符串以\0结尾数组还包含未赋值部分确认初始化方式和字符串结束标记数组作为参数传进函数后长度“变小”数组退化成指针传长度变量不要在函数里用sizeof求长度无限循环刷屏输入缓冲区的无效字符没被清空用getchar循环清理或检查返回值这一阶段容易犯的另一类错误是“用比较字符串内容”。C语言里比较两个字符串比较的是地址不是内容。哪怕两个字符数组里存的字符完全一样它们的首地址不同if (s1 s2)也不会成立。要比较内容必须用strcmp函数。这个坑我在很多初学者代码里见过也是面试题常客。第11天你的C语言学习中场战其实才刚刚开始。指针和字符串是C语言的半壁江山这一天经历的迷茫和踩坑后面回头看都是必经之路。现在这些函数看得眼花缭乱但用不了半个月你就会发现它们不过是围绕着一块内存和几个指针在转来转去。环境方面重点其实是gdb和编译参数的熟练度工具链顺了后面无论做课程设计还是写控制台小项目都能把精力聚焦在逻辑本身。我自己当初学到这个阶段时最大的感受是“C语言不是教出来的是查错查出来的”。多写、多断点、多画内存图等到第21天再回头看第11天的那些懵圈问题你已经能轻松给室友讲明白了。
返回列表