96SEO 2026-02-20 07:05 15
第9章已经介绍过函数了因此本章就来讨论一个程序包含多个函数时所产生的几个问题。

本章的前两节讨论局部变量10.1节和外部变量10.2节之间的差异10.3节考虑程序块含有声明的复合语句问题10.4节解决用于局部名、外部名和在程序块中声明的名字的作用域规则问题10.5节介绍用来组织函数原型、函数定义、变量声明和程序其他部分的方法。
我们把在函数体内声明的变量称为该函数的局部变量。
在下面的函数中sum是局部变量:
duration也称为延续是程序执行时能够确保变量的存储空间必定存在的那一部分时间。
通常来说局部变量的存储空间是在包含该变量的函数被调用时“自动”分配的函数返回时收回分配所以称这种变量具有自动存储期。
包含局部变量的函数返回时局部变量的值无法保留。
当再次调用该函数时无法保证变量仍拥有原先的值。
块作用域。
变量的作用域是可以引用该变量的那一部分程序文本。
局部变量拥有块作用域从变量声明的点开始一直到所在函数体的末尾。
因为局部变量的作用域不能延伸到其所属函数之外所以其他函数可以把同名变量用于别的用途。
C99不要求在函数一开始就进行变量声明所以局部变量的作用域可能非常小。
在局部变量声明中放置单词static可以使变量具有静态存储期而不再是自动存储期。
因为具有静态存储期的变量拥有永久的存储单元所以在整个程序执行期间都会保留变量的值。
思考下面的函数
}因为局部变量i已经声明为static所以在程序执行期间它所占据的内存单元是不变的。
在f返回时变量i不会丢失其值。
静态局部变量始终有块作用域所以它对其他函数是不可见的。
概括来说静态变量是对其他函数隐藏数据的地方但是它会为将来同一个函数的再调用保留这些数据。
形式参数拥有和局部变量一样的性质即自动存储期和块作用域。
事实上形式参数和局部变量唯一真正的区别是在每次函数调用时对形式参数自动进行初始化调用中通过赋值获得相应实际参数的值。
传递参数是给函数传送信息的一种方法。
函数还可以通过外部变量external
静态存储期。
就如同声明为static的局部变量一样外部变量拥有静态存储期。
存储在外部变量中的值将永久保留下来。
文件作用域。
外部变量拥有文件作用域从变量被声明的点开始一直到所在文件的末尾。
因此跟随在外部变量声明之后的所有函数都可以访问并修改它。
为了说明外部变量的使用方法一起来看看称为栈stack的数据结构。
栈是抽象的概念它不是C语言的特性。
大多数编程语言都可以实现栈。
像数组一样栈可以存储具有相同数据类型的多个数据项。
然而栈操作是受限制的只可以往栈中压入数据项把数据项加在一端——“栈顶”或者从栈中弹出数据项从同一端移走数据项。
禁止测试或修改不在栈顶的数据项。
C语言中实现栈的一种方法是把元素存储在数组中我们称这个数组为contents。
命名为top的一个整型变量用来标记栈顶的位置。
栈为空时top的值为0。
为了往栈中压入数据项可以把数据项简单存储在contents中由top指定的位置上然后自增top。
弹出数据项则要求自减top然后用它作为contents的索引取回弹出的数据项。
基于上述这些概要这里有一段代码不是完整的程序为栈声明了变量contents和top并且提供了一组函数来表示对栈的操作。
全部5个函数都需要访问变量top而且其中2个函数还都需要访问contents所以接下来把contents和top设为外部变量。
在多个函数必须共享一个变量时或者少数几个函数共享大量变量时外部变量是很有用的。
然而在大多数情况下对函数而言通过形式参数进行通信比通过共享变量的方法更好原因列举如下:
在程序维护期间如果改变外部变量比方说改变它的类型那么将需要检查同一文件中的每个函数以确认该变化如何对函数产生影响。
如果外部变量被赋了错误的值可能很难确定出错的函数就好像侦察大型聚会上的谋杀案时很难缩小嫌疑人范围一样。
很难在其他程序中复用依赖外部变量的函数。
依赖外部变量的函数不是“独立的”。
为了在另一个程序中使用该函数必须带上此函数需要的外部变量。
许多C程序员过于依赖外部变量。
一个普遍的陋习是在不同的函数中为不同的目的使用同一个外部变量。
假设几个函数都需要变量i来控制for语句。
一些程序员不是在使用变量i的每个函数中都声明它而是在程序的顶部声明它从而使得该变量对所有函数都是可见的。
这种方式除了前面提到的几个缺点外还会产生误导以后阅读程序的人可能认为变量的使用彼此关联而实际并非如此。
使用外部变量时要确保它们都拥有有意义的名字。
局部变量不是总需要有意义的名字的因为往往很难为for循环中的控制变量起一个比i更好的名字。
如果你发现为外部变量使用的名字就像i和temp一样这可能意味着这些变量其实应该是局部变量。
请注意把原本应该是局部变量的变量声明为外部变量可能导致一些令人厌烦的错误请看下面这个例子
print_all_rows函数不是显示10行星号而是只显示1行。
在第一次调用print_one_row函数后返回时i的值将为11。
然后print_all_rows函数中的for语句对变量i进行自增并判定它是否小于或等于10。
*/为了获得更多关于外部变量的经验现在编写一个简单的游戏程序。
这个程序产生一个1~100的随机数用户尝试用尽可能少的次数猜出这个数。
n这个程序需要完成几个任务初始化随机数生成器选择神秘数以及与用户交互直到选出正确数为止。
如果编写独立的函数来处理每个任务那么可能会得到下面的程序。
initialize_number_generator(void);
choose_new_secret_number(void);
/************************************************************
************************************************************/
initialize_number_generator(void)
/************************************************************
************************************************************/
}/************************************************************
************************************************************/
}对于随机数的生成这次将缩放rand函数的返回值使其落在1~MAX_NUMBER范围内。
虽然guess.c程序工作正常但是它依赖一个外部变量。
把变量secret_number外部化以便choose_new_secret_number函数和read_guesses函数都可以访问它。
如果对choose_new_secret_number函数和read_guesses函数稍做改动应该能把变量secret_number移入main函数中。
现在我们将修改choose_new_secret_number函数以便函数返回新值并将重写read_guesses函数以便变量secret_number可以作为参数传递给它具体请看下面这个程序。
initialize_number_generator(void);
/************************************************************
************************************************************/
initialize_number_generator(void)
/************************************************************
************************************************************/
/************************************************************
************************************************************/
5.2节遇到过复合语句一个复合语句也是一个块block但块并非只有复合语句这一种形式。
块也叫程序块。
下面是程序块的示例
}这里整个if语句是一个程序块if语句的每一个子句也是程序块。
默认情况下声明在程序块中的变量的存储期是自动的进入程序块时为变量分配存储单元退出程序块时收回分配的空间。
变量具有块作用域也就是说不能在程序块外引用。
函数体是程序块。
在需要临时使用变量时函数体内的程序块也是非常有用的。
在上面这个例子中我们需要一个临时变量以便可以交换i和j的值。
在程序块中放置临时变量有两个好处(1)避免函数体起始位置的声明与只是临时使用的变量相混淆(2)减少了名字冲突。
在此例中名字temp可以根据不同的目的用于同一函数中的其他地方在程序块中声明的变量temp严格属于局部程序块。
C99允许在程序块的任何地方声明变量就像允许在函数体内的任何地方声明变量一样。
在C程序中相同的标识符可以有不同的含义。
C语言的作用域规则使得程序员和编译器可以确定与程序中给定点相关的是哪种含义。
下面是最重要的作用域规则当程序块内的声明命名一个标识符时如果此标识符已经是可见的因为此标识符拥有文件作用域或者因为它已在某个程序块内声明新的声明临时“隐藏”了旧的声明标识符获得了新的含义。
在程序块的末尾标识符重新获得旧的含义。
在声明1中i是具有静态存储期和文件作用域的变量。
在声明2中i是具有块作用域的形式参数。
在声明3中i是具有块作用域的自动变量。
在声明4中i也是具有块作用域的自动变量。
}一共使用了5次i。
C语言的作用域规则允许确定每种情况中i的含义。
1引用了声明2中的形式参数而不是声明1中的变量。
因为声明3隐藏了声明1而且声明2超出了作用域所以判定i
4引用了声明3中的变量。
声明4超出了作用域所以不能引用。
赋值i
我们已经看过构成C程序的主要元素现在应该为编排这些元素开发一套方法了。
目前只考虑单个文件的程序第15章会说明如何组织多个文件的程序。
include和#define这样的预处理指令类型定义外部变量声明函数原型函数定义。
执行到预处理指令所在的代码行时预处理指令才会起作用类型名定义后才可以使用变量声明后才可以使用。
虽然C语言对函数没有什么要求但是这里强烈建议在第一次调用函数前要对每个函数进行定义或声明。
为了遵守这些规则这里有几个构建程序的方法。
下面是一种可能的编排顺序
#include指令#define指令类型定义外部变量的声明除main函数之外的函数的原型main函数的定义其他函数的定义。
因为#include指令带来的信息可能在程序中的好几个地方都需要所以先放置这条指令是合理的。
类型定义放置在外部变量声明的上面是合乎逻辑的因为这些外部变量的声明可能会引用刚刚定义的类型名。
接下来声明外部变量使得它们对于跟随在其后的所有函数都是可用的。
在编译器看见原型之前调用函数可能会产生问题而此时声明除了main函数以外的所有函数可以避免这些问题。
这种方法也使得无论用什么顺序编排函数定义都是可能的。
例如根据函数名的字母顺序编排或者把相关函数组合在一起进行编排。
在其他函数前定义main函数使得阅读程序的人容易定位程序的起始点。
最后的建议在每个函数定义前放盒型注释可以给出函数名、描述函数的目的、讨论每个式参数的含义、描述返回值如果有的话并罗列所有的副作用如修改了外部变量的值。
为了说明构建C程序的方法下面编写一个比前面的例子更复杂的程序——给一手牌分类。
这个程序会对一手牌进行读取和分类。
手中的每张牌都有花色方块、梅花、红桃和黑桃和点数2、3、4、5、6、7、8、9、10、J、Q、K和A。
不允许使用王牌并且假设A是最高的点数。
程序将读取一手5张牌然后把手中的牌分为下列某一类列出的顺序从最好到最坏。
分类如下
同花顺即顺序相连又都是同花色。
四张4张牌点数相同。
葫芦3张牌是同样的点数另外2张牌是同样的点数。
同花5张牌是同花色的。
顺子5张牌的点数顺序相连。
三张3张牌的点数相同。
两对。
对子2张牌的点数相同。
其他牌任何其他情况的牌。
为了便于输入把牌的点数和花色简化如下字母可以是大写也可以是小写。
如果用户输入非法牌或者输入同一张牌两次程序将忽略此牌产生出错消息然后要求输入另外一张牌。
如果输入为0而不是一张牌就会导致程序终止。
把程序分为3个函数分别完成上述3个任务即read_cards函数、analyze_hand函数和print_result函数。
main函数只负责在无限循环中调用这些函数。
这些函数需要共享大量的信息所以让它们通过外部变量来进行交流。
read_cards函数将与一手牌相关的信息存进几个外部变量中然后analyze_hand函数将检查这些外部变量把结果分类放在便于print_result函数显示的其他外部变量中。
/************************************************************
************************************************************/
}/************************************************************
************************************************************/
card_exists[NUM_RANKS][NUM_SUITS];
(card_exists[rank][suit])printf(Duplicate
/************************************************************
************************************************************/
/************************************************************
************************************************************/
}注意read_cards函数中exit函数的使用第一个switch语句的分支0。
因为exit函数具有在任何地方终止程序执行的能力所以它对于此程序是十分方便的。
答当函数是递归函数时每次调用它都会产生其自动变量的新副本。
静态变量就不会发生这样的情况相反所有的函数调用都共享同一个静态变量。
问2在下面的例子中j初始化为和i一样的值但是有两个命名为i的变量
答代码是合法的。
局部变量的作用域是从声明处开始的。
因此j的声明引用了名为i的外部变量。
j的初始值是1。
本文是博主阅读《C语言程序设计现代方法第2版·修订版》时所作笔记日后会持续更新后续章节笔记。
欢迎各位大佬阅读学习如有疑问请及时联系指正希望对各位有所帮助Thank
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback