小菜的C语言之路第二课:作用域、常量与字符串,几个容易误会的地方

发布时间:2026/10/4 8:53:33
小菜的C语言之路第二课:作用域、常量与字符串,几个容易误会的地方 这一课仍然沿着我最初学习 C 的顺序变量与常量、字符串和转义字符最后接到选择和循环。回头检查时我发现有几处不是“讲得不够细”而是当时理解错了。这次把错误和正确对照放在一起避免读者跟着踩同一个坑。1. 作用域不是运行到哪里就能使用哪里的变量我最初把作用域简单理解为“变量起作用的范围”。这个入口没有问题但要注意调用关系不等于作用域包含关系。下面故意是一段不能编译的程序#includestdio.hvoidtest(void){printf(%d\n,a);/* 错误此处没有可见的 a 声明 */}intmain(void){inta100;test();return0;}main调用了test并不意味着test进入了main的大括号。编译器检查名字时看的不是“谁调用我”而是这个位置能看到什么声明。本机 GCC 的诊断是a undeclared。正确做法是把需要的数据传进去而不是让函数猜调用者的局部变量#includestdio.hstaticvoidtest(intvalue){printf(test:%d\n,value);}intmain(void){inta100;{intb20;printf(inner:%d,%d\n,a,b);}/* 这里能使用 a不能使用上面那个 b。 */test(a);return0;}输出inner:100,20 test:100普通内部代码块可以使用外层可见的变量。独立函数不因此获得访问权形参value是另一个对象。一个分号让代码含义完全不同原文还曾把下面这种写法当作“在 main 内定义 test”#includestdio.hintmain(void){inta100;voidtest(void);{printf(%d\n,a);}return0;}实际上void test(void);是函数声明后面的{ ... }是普通代码块。它既没有定义test也没有调用test。所以打印成功不能证明独立函数能访问a。标准 C 不允许在另一个函数体内定义函数。某些编译器扩展不能当作所有 C 环境的共同规则。作用域和存储期不是同一个问题作用域回答“名字在哪里可见”存储期回答“对象存在多久”。普通自动局部对象通常在进入对应块时开始存在在退出时结束但不能将这一点推广到所有局部变量#includestdio.hstaticintnext_count(void){staticintcount;returncount;}intmain(void){intfirstnext_count();intsecondnext_count();printf(%d,%d\n,first,second);return0;}输出1,2。count的名字只在函数块内可见对象却具有静态存储期不会每次调用都重新变成零。文件作用域变量也要在使用位置有可见声明跨文件使用还涉及声明与链接不能理解成“全工程随处都能直接使用”。2. 常量const、宏、枚举不能混为一谈100、D、3.14是常见字面量。字符串字面量如abc会提供包括终止符在内的字符序列不能修改字符串字面量的内容。#includestdio.h#defineMAX10enumState{FIRST5,SECOND,THIRD};intmain(void){constintlimit10;intarr[MAX]{0};printf(%d,%d,%d\n,FIRST,SECOND,THIRD);printf(limit%d, elements%zu\n,limit,sizeofarr/sizeofarr[0]);return0;}输出枚举值为5,6,7不是0,1,2。未指定值时才从零开始后续未指定项在前一项基础上递增。用枚举项表达状态更清晰但 C 的枚举并不是保证运行时取值永远属于列出的几项的自动校验器。const int limit 10;定义的是不可通过这个名字修改的对象不是 C11 的整数常量表达式。#define MAX 10是预处理替换不要在宏值后随手加分号。我当时遇到的 VLA 问题原来的 VS 与 GCC 对照图保留如下编辑器是 vim不代表决定语法的也是 vim实际判断由编译器、语言模式和选项完成。C99 引入 VLAC11 实现不一定支持原来的 VS 环境不支持这段写法。在支持 VLA 的 C11 实现里块内int n 10; int arr[n];可以成立但int arr[n] {0};不能把它当普通定长数组初始化。运行时长度还必须有效、合理不能让外部输入无限消耗栈空间。入门练习先用固定长度数组有需要时再学习动态分配。3. 字符数组不一定是字符串#includestdio.h#includestring.hintmain(void){chararr1[]abc;chararr2[]{a,b,c};chararr3[]{a,b,c,\0};printf(arr1: bytes%zu, length%zu\n,sizeofarr1,strlen(arr1));printf(arr2: bytes%zu\n,sizeofarr2);printf(arr3: length%zu\n,strlen(arr3));printf(abcdef: length%zu\n,strlen(abcdef));return0;}输出arr1: bytes4, length3 arr2: bytes3 arr3: length3 abcdef: length6sizeof arr1包含终止符strlen只统计终止符前的字节。strlen返回size_t这里用%zu。不能调用strlen(arr2)或用%s打印 arr2。它没有终止符库函数可能越过数组边界行为未定义。原图里出现的乱码只是那次错误运行的现象不是“这个函数会返回随机数”的规则对高级一点的读者这里值得追问接口收到的是指针时谁保证终止符存在已知长度的字节缓冲区不应无条件套用字符串接口。4. 转义字符区分源代码表示与终端效果#includestdio.hintmain(void){printf(abc\ndef\n);printf(%s\n,abc\0def);printf(%s\n,abc\\ndef);printf(%s\n,c:\\net\\nest.c);printf(%c\n,\x63);return0;}在本机 ASCII 兼容环境输出abc def abc abc\ndef c:\net\nest.c c\n是换行\\表示一个反斜杠\0是零值字符\x63是十六进制转义。\b表示退格控制字符不保证删除已输出的字节\a也不保证每个终端都会响。原文的三字母词属于历史兼容内容本课不依赖它。不应把编译器某一次未转换的结果当作所有 C 版本的规则。5. 注释、选择和循环放进同一个小练习#includestdio.hintmain(void){intcount;/* 输入不完整时不使用未得到的值。 */if(scanf(%d,count)!1||count0||count5)return1;for(inti0;icount;i){// C99 及之后支持这种单行注释。printf(practice:%d\n,i1);}return0;}输入3打印三行练习编号。/* ... */是块注释且不能嵌套//注释到当前行结束。if检查条件for重复执行while、do while和switch后续再展开。6. 本次怎样验证正常示例用 GCC 的 C11 模式、-Wall -Wextra -pedantic编译并核对输出故意错误的作用域例子单独编译确认失败原因。缺终止符的错误示例不再执行来“猜输出”。这次最重要的修正不是多记一个语法点而是程序碰巧打印出某个结果不等于我对它的解释正确。先辨别语法、对象范围和前提再看运行输出。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询