C程序设计.

Slides:



Advertisements
Similar presentations
C++语言程序设计教程 第5章 构造数据类型 第6章 C++程序的结构.
Advertisements

C语言程序设计 主讲教师 :张群燕 电话:
第一章 C语言概述 计算机公共教学部.
第4章 选择结构程序设计 在现实生活中,需要进行判断和选择的情况是很多的 如果你在家,我去拜访你 如果考试不及格,要补考
C语言程序设计 第八章 函数.
第一章 程序设计入门.
第5章 函数与模块化设计 学习目的与要求: 掌握函数的定义及调用方法 理解并掌握参数的传递方法 理解函数的嵌套与递归调用
C语言程序设计 第五章 选择结构程序设计.
C语言程序设计 课程 第5章 数组 主讲:李祥 博士、副教授 单位:软件学院软件工程系.
高级语言程序设计 主讲人:陈玉华.
第5章 函数与预处理 《 C语言程序设计》 (Visual C++ 6.0环境) 本章导读
第一章 C语言概述.
由C程序结构所知,一个完整的C语言程序是由一个且只能有一个main()函数(又称主函数)和若干个其他函数组合而成的。而前面各章仅学习main()函数的编程,本章将介绍其他函数的编程,包括其他函数的定义、调用、参数传递及变量的作用域等。
循环结构又称为重复结构:用来处理需要重复处理的问题,它是程序中一种很重要的结构。
第4章 函数与预处理 4.1 概述 4.2 定义函数的一般形式 4.3 函数参数和函数的值 4.4 函数的调用 *4.5 内置函数
Chap 10 函数与程序结构 10.1 函数的组织 10.2 递归函数 10.3 宏定义 10.4 编译预处理.
第4章 选择结构程序设计 4.1 选择结构和条件判断 4.2 用if语句实现选择结构 4.3关系运算符和关系表达式
C程序设计.
If … else 選擇結構 P27.
第七章 函数 目录 有参的加法函数的开发 函数定义的一般形式 函数参数和函数的值 函数的调用
QQ: 李祥 QQ: 欢迎多种方式的学习交流,祝大家学有所成.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Function.
Object-Oriented Programming in C++ 第一章 C++的初步知识
程序设计专题一 结构化程序设计与递归函数 主讲教师: 刘新国.
第八章 函数.
第5章 堆疊(Stacks) 5-1 堆疊的基礎 5-2 堆疊的表示法 5-3 堆疊的應用 - 運算式的計算與轉換
作弊是否很有诱惑性? 上堂课已经讲了 作业不一定在两个小时里都能完成 答疑没有一个人? 作弊是有记录的 心理系很多同学集体作弊,让人震惊
1. 說明一個一維整數陣列passwd,下標範圍0至49 2. 在屏幕顯示 "Enter password"
C语言 程序设计基础与试验 刘新国、2012年秋.
第5讲 结构化程序设计(Part II) 周水庚 2018年10月11日.
第七章 函数及变量存贮类型 7.1 函数基础与C程序结构 7.2 函数的定义和声明 7.3 函数的调用 7.4 函数的嵌套与递归
第4章 顺序程序设计.
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
C语言概述 第一章.
第1讲 C语言基础 要求: (1) C程序的组成 (2) C语言的标识符是如何定义的。 (3) C语言有哪些基本数据类型?各种基本数
C语言大学实用教程 第5章 函数与程序结构 西南财经大学经济信息工程学院 刘家芬
第 二 章 数据类型、运算符与表达式.
C语言复习2----函数.
第一章 程序设计和C语言 主讲人:高晓娟 计算机学院.
C语言程序示例: 1.输入10个数,按从小到大的顺序排序。 2.汉诺塔问题。.
1.2 C语言程序的结构与书写规则 一、 C语言程序的总体结构
C程序设计.
函数 概述 模块化程序设计 基本思想:将一个大的程序按功能分割成一些小模块, 特点: 开发方法: 自上向下,逐步分解,分而治之
浙江长征职业技术学院—计算机与信息技术系—相方莉制作
指標
Chap 5 函数 5.1 计算圆柱体积 5.2 使用函数编写程序 5.3 变量与函数.
Chap 5 函数 5.1 计算圆柱体积 5.2 数字金字塔 5.3 复数运算.
7.1 C程序的结构 7.2 作用域和作用域规则 7.3 存储属性和生存期 7.4 变量的初始化
第十四章 若干深入问题和C独有的特性 作业: 函数指针 函数作参数 函数副作用 运算 语句 位段 存储类别 编译预处理
第5章 函 数.
第一章 C语言概述 教师:周芸.
C语言程序设计 李祥 QQ:
資料結構與C++程式設計進階 遞迴(Recursion) 講師:林業峻 CSIE, NTU 6/ 17, 2010.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第二章 类型、对象、运算符和表达式.
第二章 基本数据类型 ——数据的表示.
#include <iostream.h>
第四章 函数 丘志杰 电子科技大学 计算机学院 软件学院.
第五章 逻辑运算和判断选取控制 §5.1 关系运算符和关系表达式
第七章  数 组.
第十二章 位运算.
Chap 7 数 组 7.1 排序问题 7.2 找出矩阵中最大值所在的位置 7.3 进制转换.
第二章 数据类型、运算符和表达式 §2.1 数据与数据类型 §2.2 常量、变量和标准函数 §2.3 基本运算符及其表达式 目 录 上一章
C/C++基礎程式設計班 C語言入門、變數、基本處理與輸入輸出 講師:林業峻 CSIE, NTU 3/7, 2015.
基本資料型態 變數與常數 運算子 基本的資料處理 授課:ANT 日期:2014/03/03.
Chap 10 函数与程序结构 10.1 圆形体积计算器 10.2 汉诺塔问题 10.3 长度单位转换 10.4 大程序构成.
第三章 流程控制 程序的运行流程 选择结构语句 循环结构语句 主讲:李祥 时间:2015年10月.
本节内容 指针类型 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
函式庫補充資料 1.
Presentation transcript:

C程序设计

第8章 函数 8.1 概述 一个较大的程序一般应分为若干个程序模块,每一个模块用来实现一个特定的功能。所有的高级语句中都有子程序这个概念,用子程序实现模块的功能。在C语言中,子程序的作用是由函数来完成的。一个C程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数也可以相互调用。同一个函数可以被一个或多个函数调用任意多次。 在程序设计中,常将一些常用的功能模块编写成函数,放在公共函数库中供大家选用。程序设计人员要善于利用函数,以减少重复编写程序段的工作量。 为什么需要函数 避免重复性操作 有利于程序模块化设计

[例8.1]函数调用的简单例子 #include <stdio.h> void main() { void printstar(); void print_message(); printstar(); print_message(); } void printstar() { printf(“**********\n”); } void print_message() printf(“How do you do!\n”); 什么叫函数 逻辑上:能完成特定功能的独立代码块; 物理上:能够接受数据(也可不接受) 能够对接受的数据进行处理 能够对数据处理的结果返回(也可以不返回任何值) 总结: 函数是个工具,它是为解决大量类似问题而设计的。函数可以当做一个黑匣子。

说明: (1)一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对较大的程序,一般不希望把所有内容全放在一个文件中,而是将它们放在若干个源文件中,再由若干个源程序文件组成一个C程序。一个源程序文件可以为多个C程序共用。 (2)一个源程序文件由一个或多个函数以及其他有关内容(如命令行、数据定义等)组成。一个源程序是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。 (3)C程序的执行是从main函数开始的,如是在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。 (4)所有函数都是平行的,即在定义函数时是分别进行的,是相互独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以相互调用,但不能调用main函数。main函数是系统调用的。

(5)从用户使用的角度看,函数有两种。 ①标准函数。标准函数即库函数,它是由系统提供的,用户不必自己定义而直接使用它们。 ②用户自己定义的函数。它是用以解决用户专门需要的函数。 (6)从函数的形式看,函数分两类。 ①无参函数。在调用无参函数时,主调函数不向被调用函数传递数据。无参函数一般用来执行指定的一组操作。无参函数可以带回或不带会函数值,但一般以不带回函数值的居多。 ②有参函数。在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。

8.2 函数定义的一般形式 8.2.1 无参函数定义的一般形式 定义无参函数的一般形式为: 类型标识符 函数名() { 声明部分 语句部分 } 在定义函数时要用“类型标识符”指定函数值的类型,即函数带回来的值的类型。 int f( ) { int i=1; return I; }

8.2.2 有参函数定义的一般形式 例如: 定义有参函数的一般形式为: int max(int x, int y) { int z; z = x>y ? x : y; return z; } 定义有参函数的一般形式为: 类型标识符 函数名(形式参数表列) { 声明部分 语句部分 } Return 表达式的含义 1、终止被调用函数,想主调函数返回表达式的值; 2、如果表达式为空,则只终止函数,不向主调函数返回任何值; 3、break是用来终止循环和switch的,return是用来终止函数的。 例题1 void f( ) { return; //return只用来终止函数,不向主调函数返回任何值; } int f( ) return 10;//第一:终止函数,第二:向主调函数返回10

8.2.3 空函数 在程序设计中有时会用到空函数,它的形式为: 类型说明符 函数名() { }

8.3 函数参数和函数的值 8.3.1 形式参数和实际参数 在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系。在定义函数时函数名后面括号中的变量名称为“形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括号中的参数(可以是一个表达式)称为“实际参数”(简称“实参”)。

[例8.2]调用函数时的数据传递 #include <stdio.h> void main() { int max(int x, int y); int a, b, c; scanf(“%d,%d”, &a, &b); c=max(a, b); printf(“Max is %d”, c); } int max(int x, int y) { int z; z = x > y ? x : y; return z; } 在这个程序中需要了解函数的形参在内存中是如何建立的,它只有在发生函数调用时,形参才会被分配单元,用完内存单元被释放。

关于形参与实参的说明: (1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数max中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。 (2)实参可以是常量、变量或表达式。但要求它们有确定的值。在调用时将实参的值赋给形参。 (3)在被定义的函数中,必须指定形参的类型。 (4)实参与形参的类型应相同或赋值兼容。 (5)实参向形参的数据传递是“值传递”,单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的单元。在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。 用单步执行法演示刚才的程序说明参数的传递和生存周期

8.3.2 函数的返回值 通过函数调用使主调函数能得到一个确定的值,这就是函数的返回值。 (1)函数的返回值是通过函数中的return语句获得的。return语句将被调用函数中的一个确定值带回主调函数中去。 (2)函数值的类型。既然函数有返回值,这个值当然应属于某一个确定的类型,应当在定义函数时指定函数值的类型。 (3)在定义函数时指定的函数类型一般应该和return语句中的表达式类型一致。如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。

[例8.3]返回值类型与函数类型不同 #include <stdio.h> void main() { int max(flaot x, float y); float a, b; int c; scanf(“%f,%f”, &a, &b); c=max(a, b); printf(“Max is %d\n”, c); } int max(float x, float y) { float z; z = x > y ? x : y; return z; } 函数返回值的类型也称为函数的类型,因为如果 函数名前的返回值类型和函数执行体中的return表达式中的类型不同的话,则最终函数返回值的类型 以函数名前的返回值类型为准 例题: int f( ) { return 10.5 //因为函数的返回值类型是int,所以最终返回的结果是10 }

(4)对于不带回值的函数,应当用“void”定义函数为“无类型”(或称“空类型”)。这样,系统就保证不使函数带回任何值,即禁止调用函数中使用被调用函数的返回值。此时在函数体中不得出现return语句。

8.4 函数的调用 8.4.1 函数调用的一般形式 函数调用的一般形式为 函数名(实参表列); 如果是调用无参函数,则“实参表列”可以没有,但括号不能省略。如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配。实参与形参按顺序对应,一一传递数据。但应说明,如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。(VC++是自右向左) 一定要注意函数调用和函数定义的顺序 形参和实参 个数相同,位置一一对应 数据类型必须相互兼容

[例8.4]实参求值的顺序 #include <stdio.h> void main() { int f(int a, int b); int i=2, p; p=f(i, ++i); printf(“%d\n”, p); } int f(int a, int b) { int c; if(a>b) c=1; else if(a==b) c=0; else c=-1; return c; } 该程序可以验证系统调用参数是从左向右或从右向左

8.4.2 函数调用的方式 按函数在程序中出现的位置来分,可以有以下3种函数调用方式。 1. 函数语句 把函数调用作为一个语句。这时不要求函数带回值,只要求函数完成一定的操作。 2. 函数表达式 函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。 3. 函数参数 函数调用作为一个函数的实参。 1 printstar( ); 2 c=2 * max (a,b) 3 m=max(a,max(b,c))

8.4.3 对被调用函数的声明和函数原型 在一个函数中调用另一个函数需要具备的条件如下 。 (1)首先被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。但光有这一条件还不够。 (2)如果使用库函数,还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中去。 (3)如果使用用户自己定义的函数,而该函数的位置在调用它的函数(即主调函数)的后面(在同一个文件中),应该在主调函数中对被调用的函数作声明。 补充程序: 1、函数的声明_1.cpp 2、函数的声明_2.cpp 如果函数调用写在了函数定义的前面,则必须加函数前置声明 函数前置声明: 1、告诉编译器即将可能出现的若干个字母代表的是一个函数; 2、高所编译器即将可能出现的若干字母所代表的函数的形参和返回值的具体情况; 3、函数声明是一个语句,末尾必须加分号; 4、对库函数的声明是通过#include <库函数所在文件的名字.h>来实现的

[例8.5]对被调用的函数作声明 #include <stdio.h> void main() { float add(float x, float y); float a, b, c; scanf(“%f,%f”, &a, &b); c=add(a, b); printf(“sum is %f\n”, c); } float add(float x, float y) { float z; z=x+y; return z; }

在函数调用之前用函数原型做了函数声明。因此编译系统记下了所需调用的函数的有关信息。编译系统根据函数的原型对函数的调用的合法性进行全面的检查。与函数原型不匹配的函数调用会导致编译出错,它属于语法错误。用户根据屏幕显示的出错信息很容易发现和纠正错误。

函数原型的一般形式有两种,分别为 (1)函数类型 函数名(参数类型1, 参数类型2, …, 参数类型n); (2)函数类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, …, 参数类型n 参数名n);

说明: (1)如果被调用函数的定义出现在主调函数之前,可以不必加以声明。 (2)如果已在文件的开头(在所有函数之前),已对本文件中所调用的函数进行了声明,则在各函数中不必对其所调用的函数再作声明。 补充程序 如何合理设计函数_1.cpp——如何合理设计函数_5.cpp

8.5 函数的嵌套调用 C语言的函数定义是相互平行、独立的。在定义函数时,一个函数内不能包含另一个函数。

[例8.6]函数嵌套调用的应用 在此将例题8.6改了一下用来说明函数的调用

8.6 函数的递归调用 在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。 1、递归调用首先是一个嵌套调用,只不过每次调用的是函数自己; 2、递归调用需要一个递归出口,不能无穷尽执行下去。

[例8.7]有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第3个人,又说比第2个人大2岁。问第2个人,说比第1个人大2岁。最后问第1个人,他说是10岁。请问第5个人多大

[例8.8]用递归方法求n!

[8.9]Hanoi塔问题。古代有一个梵塔,塔内有3个座A、B、C,开始时A座上有64个盘子,盘子大小不等,大的在下,小的在上。有一个老和尚想把这64个盘子从A座移到C座,但每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求编程序输出移动的步骤 用递归实现的问题,满足两个条件: 1、问题可以逐步简化成自身比较简单的形式(递归式) 2、递归最终能结束(递归出口) 二者条件缺一不可

8.7 数组作为函数参数(此部分不要求) 前面已经介绍了可以用变量作函数参数,显然,数组元素也可以作函数参数,其用法与变量相同。此外,数组名也可以作实参和形参,传递的是数组首元素的地址。

8.7.1 数组元素作函数实参 由于实参可以是表达式,而数组元素可以是表达式的组成部分,因此数组元素当然可以作为函数的实参,与用变量作实参一样,是单向传递,即“值传送”方式。 [例8.10]有两个数组a和b,各有10个元素,将它们对应地逐个比较(即a[0]与b[0]比,a[1]与b[1]比……)。如果a数组中的元素大于b数组中的相应元素的数目多于b数组中元素大于a数组中相应元素的数目(例如,a[i]>b[i]6次,b[i]>a[i]3次,其中i每次为不同的值),则认为a数组大于b数组,并分别统计出两个数组相应元素大于、等于、小于的次数。

8.7.2 数组名作函数参数 可以用数组名作函数参数,此时形参应当用数组名或用指针变量。 [例8.11]有一个一维数组score,内放10个学生成绩,求平均成绩 在这的程序用指针更容易理解

说明: (1)用数组名作函数参数,应该在主调函数和被调函数分别定义数组。 (2)实参数组与形参数组类型应一致,如不一致,结果将出错。 (3)在被调用函数中声明了形参数组的大小为10,但在实际上,指定其大小是不起任何作用的,因为C语言编译对形参数组大小不做检查,只是将实参数组的首元素的地址传给形参数组。因此,形参数组名获得了实参数组的首元素的地址。 (4)形参数组可以不指定大小,在定义数组时在数组名后面跟一个空的方括号。有时为了在被调用函数中处理数组元素的需要,可以另设一个形参,传递需要处理的数组元素的个数。 [例8.12]形参数组不定义长度

(5)用数组名作函数实参时,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占同一段内存单元。形参数组中各元素的值如发生变化会使实参数组元素的值同时发生变化。

[例8.13]用选择法对数组中10个整数按由小到大排序。所谓选择法就是先将10个数中最小的数与a[0]对换;再将a[1]到a[9]中最小的数与a[1]对换……每比较一轮,找出一个未经排序的数中最小的一个。共比较9轮

8.7.3 多维数组名作函数参数 多维数组元素可以作函数参数。 用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。但是不能把第二维以及其他高维的大小说明省略。

[例8.14]有一个3×4矩阵,求所有元素最大值 先使变量max的初值为矩阵中第一个元素的值,然后将矩阵中各个元素的值与max相比,每次比较后都把“大者”存放在max中,全部元素比较完后,max的值就是所有元素的最大值。

8.8 局部变量和全局变量 8.8.1 局部变量 在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。这称为“局部变量”。例如: float f1(int a) { int b, c; …… } char f2(int x, int y) { int i, j; void main() { int m, n; 按作用域分: 局部变量 :在一个函数内部定义的变量或者函数的形参,都统称为局部变量 void f(int i) { int j=20; } i和j都是局部变量 局部变量只能在本函数内部使用 全局变量: 在所有函数外部定义的变量叫全局变量; 使用范围: 从定义位置开始到整个程序结束。 注意的问题: 全局变量和局部变量命名冲突的问题 在一个函数内部如果定义的局部变量的名字和全局变量名一样时,局部变量会屏蔽掉全局变量

说明: (1)主函数中定义的变量也只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。 (2)不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。 (3)形式参数也是局部变量。 (4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为“分程序”或“程序块”。

8.8.2 全局变量 程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。

例如: int p=1, q=5; float f1(int a) { int b, c; …… } char c1, c2; char f2(int x, int y) { int i, j; void main() { int m, n;

说明: (1)设置全局变量的作用是增加了函数间数据联系的渠道。 [例8.15]有一个一维数组,内放10个学生成绩,写一个函数,求出平均分、最高分和最低分 (2)建议不在必要时不要使用全局变量,原因如下: ①全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。 ②它使函数的通用性降低了,因为函数在执行时要依赖于其所在的外部变量。 ③使用全局变量过多,会降低程序的清晰性,难以清除地判断出每个瞬时各个外部变量的值。 (3)如果在同一个源文件中,外部变量与局部变量同名,则在局部变量的作用范围内,外部变量被“屏蔽”,即它不起作用。 [例8.16]外部变量与局部变量同名 补充程序: 1、全局变量和局部变量.cpp 2、全局变量和局部变量命名冲突的问题.cpp 3、变量作用域例题.cpp

8.9 变量的存储类别 8.9.1 动态存储方式与静态存储方式 从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量。 从变量值存在的时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。 所谓静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。

程序的存储空间可以分为三部分: 程序区 静态存储区 动态存储区 数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。

在动态存储区中存放以下数据: ①函数形式参数。在调用函数时给形参分配存储空间。 ②自动变量(未加static声明的局部变量) ③函数调用时的现场保护和返回地址等。 对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,分配给此函数中局部变量的存储空间地址可能是不同的。如果一个程序包含若干个函数,每个函数中的局部变量的生存期并不等于整个程序的执行周期,它只是程序执行周期的一部分。根据函数调用的需要,动态地分配和释放存储空间。

在C语言中,每一个变量和函数有两个属性:数据类型和数据的存储类别。存储类别指的是数据在内存中存储的方式。存储方式分为两大类:静态存储类和动态存储类。具体包含4种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。

8.9.2 auto变量 函数中的局部变量,如果不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。 1、函数调用时,才定义变量,分配存储空间; 2、函数调用结束时,回收存储空间; 3、对同一函数调用,分配的存储单元不一定相同;

例如: int f(int a) { auto int b, c=3; //等价于int b,c=3; …… }

8.9.3 用static声明局部变量 有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。 1、静态变量在静态存储区分配存储单元; 2、执行程序时定义变量,分配单元,运行结束时回收。

[例8.17]考察静态局部变量的值 #include <stdio.h> void main() { int f(int); int a=2, i; for(i=0; i<3; i++) printf(“%d “, f(a)); } int f(int a) { auto int b=0; static int c=3; b=b+1; c=c+1; return (a+b+c);

对静态局部变量的说明 (1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,函数调用结束后即释放。 (2)对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。 (3)如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量而说,如果不赋初值则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。 (4)虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。

需要用静态局部变量的情况如下。 (1)需要保留函数上一次调用结束时的值。 [例8.18]输出1到5的阶乘值 (2)如果初始化后,变量只被引用而不改变其值,则这时用静态局部变量比较方便,以免每次调用时重新赋值。 但是应该看到,用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可供多个变量使用,节约内存),而且降低了程序的可读性,当调用次数多时往往弄不清静态局部变量的当前值是什么。因此,若非必要,不要多用静态局部变量。

8.9.4 register变量 一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。 如果有一些变量使用频繁(例如,在一个函数中执行10000次循环,每次循环中都要引用某局部变量),则为存取变量的值要花费不少时间。为提高执行效率,C语言允许将局部变量的值放在CPU中的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做寄存器变量,用关键字register作声明。

[例8.19]使用寄存器变量 #include <stdio.h> void main() { long fac(long n) long i, n; scanf(“%ld”, &n); for(i=1; i<=n; i++) printf(“%ld!=%ld\n”, i, fac(i)); } long fac(long n) { register long i, f=1; for(i=1; i<=n; i++) f=f*i; return f; }

说明: (1)只有局部自动变量和形式参数可以作为寄存器变量,其他(如全局变量)不行,在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束释放寄存器。此后,在调用另一个函数时又可以利用它来存放该函数的寄存器变量。 (2)一个计算机系统中的寄存器数目是有限的,不能定义任意多个寄存器变量。不同的系统允许使用的寄存器数目是有限的,而且对register变量的处理方法也是不同的,有的系统对register变量当作自动变量处理,分配内存单元,并不真正把它们存放在寄存器中,有的系统只允许将int、char和指针型变量定义为寄存器变量。 (3)局部静态变量不能定义为寄存器变量。

8.9.5 用extern声明外部变量 外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。编译时将外部变量分配在静态存储区。 有时需要用extern来声明外部变量,以扩展外部变量的作用域。

1、在一个文件内声明外部变量 如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。

[例8.20]用extern声明外部变量,扩展它在程序文件中的作用域 #include <stdio.h> void main() { int max(int, int); extern A, B; //声明A、B是已经定义的外部变量 printf(“%d\n”, max(A, B)); } int A=13, B=-8; //由于前面已经声明A、B,所以可以在这个位置定义A、B的值,否则应把它们放到max(A,B)之前 int max(int x, int y) int z; z=x>y ? x : y; return z;

2、在多文件的程序中声明外部变量 如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件中各自定义一个外部变量Num,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任一个文件中定义外部变量Num,而在另一个文件中用extern对Num作“外部变量声明”。即“extern Num;”。在编译和连接时,系统会由此知道Num是一个已在别处定义的外部变量,并将在另一文件中定义的外部变量的作用域扩展到本文件,在本文件中可以合法地引用外部变量Num。

[例8.21]用extern将外部变量的作用域扩展到其他文件 #include <stdio.h> int A; void main() { int power(int); int b=3, c, d, m; printf(“enter the number a and its power m:\n”); scanf(“%d, %d”, &A, &m); c=A*b; printf(“%d**%d=%d\n”, A, m, d); } extern A; int power(int n) { int i, y=1; for(i=1; i<=n; i++) y*=A; return y; }

8.9.6 用static声明外部变量 有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义外部变量时加一个static声明。 例如: file1.c file2.c static int A; extern int A; void main() void fun(int n) { { …… …… } A=A*n; …… }

8.9.7 关于变量的声明和定义 对变量而言,声明与定义的关系稍微复杂一些。在声明部分出现的变量有两种情况:一种是需要建立存储空间的(如:int a;),另一种是不需要建立存储空间(如:extern a;)。前者称为“定义性声明”,或简称定义。后者称为“引用性声明”。广义地说,声明包括定义,但并非所有的声明都是定义。对“int a;”而言,它既是声明,又是定义。而对“extern a;”而言,它是声明而不是定义。一般为了叙述方便,把建立存储空间的声明称定义,而把不需要建立存储空间的声明称为声明。显然这里指的声明是狭义的,即非定义性声明。例如: void main() { extern A; …… } int A;

8.9.8 存储类别小结 从上可知,对一个数据的定义,需要指定两种属性:数据类型和存储类别,分别使用两个关键字。例如: static int a; auto char c; register int d; 此外,可以用extern声明变量为已定义的外部变量,例如: extern b;

下面从不同角度做些归纳: (1)从作用域角度分,有局部变量和全局变量。它们采用的存储类别如下: 局部变量: 自动变量,即动态局部变量(离开函数,值就消失) 静态局部变量(离开函数,值仍保留) 寄存器变量(离开函数,值就消失) (形式参数可以定义为自动变量和寄存器变量) 全局变量: 静态外部变量(只限本文件引用) 外部变量(即非静态的外部变量,允许其他文件引用)

(2)从变量存在的时间(生存期)来区分,有动态存储和静态存储两种类型。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。 动态存储: 自动变量(本函数内有效) 寄存器变量(本函数内有效) 形式参数(本函数内有效) 静态存储: 静态局部变量(函数内有效) 静态外部变量(本文件内有效) 外部变量(其他文件可引用)

(3)从变量值存放的位置来区分,可分为: 内存中静态存储区: 静态局部变量 静态外部变量(函数外部静态变量) 外部变量(可为其他文件引用) 内存中动态存储区: 自动变量和形式参数 CPU中的寄存器: 寄存器变量

(4)对一个变量的性质可以从两个方面分析,一是变量的作用域,一是变量值存在时间的长短,即生存期。前者是从空间的角度,后者是从时间的角度。二者有联系但不是同一回事。 (5)static对局部变量和全局变量的作用不同。对局部变量来说,它使变量由动态存储方式改变为静态存储方式。而对全局变量来说,它使变量局部化(局部于本文件),但仍为静态存储方式。从作用域角度看,凡有static声明的,其作用域都是局限的,或者是局限于本函数内(静态局部变量),或者局限于本文件内(静态外部变量)。

8.10 内部函数和外部函数 函数本质上是全局的,因为一个函数要被另外的函数调用,但是,也可以指定函数不能被其他文件调用。根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。

8.10.1 内部函数 如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即: static 类型标识符 函数名(形参表); 内部函数又称静态函数,因为它是用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件,在不同的文件中有相同的内部函数,互补干扰。这样不同的人可以分别编写不同的函数,而不必担心所用函数是否会与其他文件中函数同名。

8.10.2 外部函数 (1)在定义函数时,如果在函数首部的最左端加关键字extern,则表示此函数是外部函数,可供其他文件调用。 [例8.22]有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。用外部函数实现。

习题 8.3 写一个判素数的函数,在主函数输入一个整数,输出是否素数的信息。 8.4 写一个函数,使给定的一个3×3的二维整型数组转置,即行列互换。 8.5 写一个函数,使输入的一个字符串按反序存放,在主函数中输入和输出字符串。 8.6 写一个函数,将两个字符串连接。

8.13 用递归方法求n阶勒让德多项式的值,递归公式为: