若程序性能较多,规模较大,将所有的程序写在一个 main
主函数中,会使得主函数庞杂,浏览和保护艰难大。于是提出 模块化程序设计:
在设计一个较大的程序时,往往将它分为若干个程序模块,每个模块包含一个或多个函数(function),每个函数实现一个特定的性能。
- 一个 C 程序可由一个
main
函数和若干个其余函数形成,程序的执行是从main
函数开始的。 - 函数间能够互相调用,但其余函数不能调用
mian
函数,一个函数能够被一个或多个函数调用屡次。 - 所有函数都是平行的、互相独立的,函数不能嵌套定义。
从用户应用的角度看,函数分为:库函数和用户自定义的函数。
从函数模式上看,函数分为:无参函数和有参函数。
一、定义函数
C 语言要求程序中所有用到的函数,必须先定义后应用。定义函数应包含以下几个内容:
- 函数的名字。
- 函数的类型,即函数返回值的类型。
- 函数的参数名和参数类型(无参数函数不须要)。
- 函数的性能。
对于 C 编译系统提供的库函数,是由编译系统当时定义好的,只需应用 #include
指令引入到程序文件中即可应用。
1. 定义无参函数
定义无参函数的个别模式有两种:
// 模式 1
类型名 函数名()
{函数体}
// 模式 2
类型名 函数名(void)
{函数体}
模式 2 中括号内 void
示意“空”,即函数没有参数。
定义函数时,如果函数无类型,即无函数值,能够应用 void
代替类型名。这时,执行函数后,不会把任何值带回 main
函数中。
2. 定义有参函数
定义有参函数的办法为:
类型名 函数名(形式参数表列)
{函数体}
例如,定义一个求两个数中最大值的函数:
int max(int x, int y)
{
int z;
z = x > y ? x : y;
return(z);
}
数组元素的作用与变量相当,因而,数组元素也能够作为函数实参,其用法与变量雷同。此外,数组名也能够作为实参和形参。形参数组能够不指定数组大小(高维数组只能疏忽第一维)。
float average(float array[], int n)
{float aver, sum = array[0];
int i;
for(i = 1; i < n; i++)
sum = sum + array[i];
aver = sum / n;
return aver;
}
须要留神,应用数组名作为函数实参时,不是吧数组元素的值传递给形参,传递的是数组的第一个元素的地址,这样两个数组就占用同一段内存单元,当形参数组中各元素值发生变化,实参数组元素值也会发生变化。这与变量作函数参数的状况不同。
3. 定义空函数
所谓空函数,即函数体是空的:
类型名 函数名()
{}
在程序设计中,往往依据须要定义若干模块,别离由一些函数来实现。而在第一阶段只设计最根本的模块,其余一些性能须要后续补上。因而,在程序开始的最后阶段,能够在未来筹备裁减的中央应用空函数。
二、调用函数
1. 函数调用的模式
函数调用的个别模式为:
函数名(理论参数表列)
如果调用的是无参函数,实参表列能够省略,但括号不能省略。函数能够独自调用,也能够呈现在一个表达式中,这时要求函数返回一个确定的值以加入表达式的运算。同时,函数调用也能够作为另一个函数的实参:
m = max(a, max(b, c);
2. 数据传递
函数定义时函数名前面括号内的变量名称为 形式参数 ,简称形参。函数调用时,函数名前面括号内的参数为 理论参数,简称实参。实参能够时常量、变量或表达式。
在函数调用过程中,零碎会把实参的值传递给形参。定义函数时指定的形参不会占据存储单元,只有函数被调用时,形参才会被调配长期内存单元。因为形参和实参在内存中占据不同的存储单元,调用完结后,形参单元被开释,实参单元保留原值。
3. 函数的返回值
函数的返回值通过 return
语句取得。return
语句前面的括号能够不要,return(x);
余 return x;
等价。return
语句中表达式的类型应该与函数定义时指定的类型统一。如果函数类型与 return
语句中表达式的类型不统一,以函数类型为准,数值型数据可自行转化。即 函数类型决定返回值类型。对于不带返回值的函数,该当定义函数为 void
类型。
4. 申明调用函数
在一个函数中调用另一个函数须要具备以下条件:
- 被调用的函数必须是曾经定义的。
- 如果应用库函数须要在文件结尾应用
#include
指令引入无关文件。 - 如果自定义的函数在调用它的函数 (主调函数) 之后,应该在主调函数中对被调用函数作 申明(declaration)。申明的作用是把函数名、函数参数等学习告诉编译系统,以便在调用函数时能正确辨认并查看调用的合法性。
函数的申明和函数的第一行基本一致,也把函数的首行称为 函数原型(function prototype)。
#include <stdio.h>
int main()
{float add(float x, float y); // 函数的申明
float a, b;
scanf("%f, %f", &a, &b);
printf("sum is %f", add(a, b));
return 0;
}
float add(float x, float y)
{return (x + y);
}
实际上,编译系统只关怀参数个数和参数类型,而不查看参数名。因而函数申明中能够省略形参名,只保留参数类型:
float add(float, float);
如果曾经在文件的结尾 (即所有函数之前) 对本文件中所调用的所有函数进行了申明,则在函数中不用对其所调用的函数再做申明。
5. 嵌套调用
C 语言函数定义时互相平行、相互独立的,不能嵌套定义,但能够嵌套调用。
#include <stdio.h>
int main()
{int max4(int, int, int, int);
int a, b, c, d;
scanf("%d %d %d %d", &a, &b, &c, &d);
printf("max is %d", max4(a, b, c, d));
return 0;
}
int max2(int a, int b)
{return(a>=b ? a:b);
}
int max4(int a, int b, int c, int d)
{int max2(int, int);
return max2(max2(max2(a, b), c), d);
}
6. 递归调用
在调用一个函数的过程中又呈现间接或间接地调用该函数自身,称为函数的递归调用。
#include <stdio.h>
int main()
{int fac(int);
int n;
scanf("%d", &n);
printf("%d", fac(n));
return 0;
}
int fac(int n)
{if (n == 1)
return 1;
else
return fac(n - 1) * n;
}
三、局部变量和全局变量
每个变量被定义之后都只能在肯定的范畴内应用才无效,这就是变量的 作用域 问题。定义变量有 3 种状况:
- 在函数结尾定义,只能在本函数内应用。
- 在函数内的复合语句中定义,只能在本复合语句中应用。
- 在函数内部定义,能够从定义之处起为本文件中所有函数调用。
前两种状况定义的是 局部变量 ,最初一种状况定义的是 全局变量 。为了辨别局部变量和全局变量,编写程序时通常将全局变量名的首字母大写(习惯,非规定)。
倡议非必要不应用全局变量,因为:
- 全局变量在程序执行的全程中占用存储单元。
- 使函数的通用性升高。因为如果在函数中援用了全局变量,函数的执行状况会受到无关内部变量的影响,也不便于函数移植到其余程序文件。程序设计划分模块时,要求模块的内聚性强、与其余模块的耦合性弱,即模块性能繁多、与其余模块的相互影响小,而应用全局变量不合乎这一准则。
- 应用全局变量过多,会升高程序的清晰度,难以分明判读程序执行过程中内部变量的值。
四、变量的存储形式和生存期
1. 动静存储与动态存储
从变量作用域 (空间) 的角度,能够把变量分为局部变量和全局变量。而从变量值 生存期 (工夫) 的角度,有的变量在程序运行的这个过程都存在,有点变了只有调用其所在函数时才会被长期调配存储单元。因而,变量的存储形式有两种:
- 动态存储:在程序运行期间由零碎调配固定的存储空间。
- 动静存储:在程序运行期间依据须要动态分配存储空间。
在内存中,供用户应用的存储空间分为 3 个局部:
- 程序区。
- 动态存储区:寄存全局变量。
- 动静存储区:寄存函数形式参数、函数中定义的没有应用关键字
static
申明的变量(即主动变量)、函数调用时的现场爱护和返回地址等。
2. 局部变量的存储类型
在 C 语言中,每一个变量和函数都有两个属性:数据类型和数据的存储类型。存储类型指的是数据在内存中的存储形式(如动态存储和动静存储)。在定义和申明变量与函数时,个别应同时指定其数据类型和存储类型,也能够采纳默认形式,即用户不指定,零碎主动指定。
C 语言的存储类型有 4 种:主动的(auto)、动态的(static)、寄存器的(register)、内部的(extern)。
主动变量 auto
函数中的局部变量和形参,如果不专门申明 static
存储类别,都是动态分配存储空间。这类局部变量称为 主动变量,应用关键字 auto
作为存储类型阐明:
int f(int a) // 定义 f 函数,a 为形参
{
auto int b, c = 1; // 定义 b 和 c 为主动变量
...
}
程序中大部分变量都是主动变量,定义时关键字 auto
能够省略。
动态局部变量 static
有时须要函数种局部变量的值在函数调用完结之后持续保留,即其占用的存储单元不开释。这时应应用关键字 static
指定该局部变量为 动态局部变量。
#include <stdio.h>
int main()
{int f(int);
int n, i, r;
scanf("%d", &n);
for (i = 2; i < n + 1; i++)
r = f(i);
printf("%d\n", r);
return 0;
}
int f(int n)
{
static int x = 1;
x = x * n;
return x;
}
动态局部变量在编译时赋初值,只赋值一次,当前每次调用函数都不再从新赋值,而保留上次函数调用完结后的值。如果不赋初值,零碎主动为数值型动态局部变量赋初值为 0,为字符型动态局部变量赋初值为 \0
。尽管动态局部变量在函数完结后仍旧保留在内存种,但毕竟时局部变量,只能由定义的函数应用,其余函数不能调用。
因为动态局部变量多占内存,且升高了程序的可读性,因而非必要不应用。
寄存器变量 register
个别状况下变量的值是放在内存中的,当程序运行须要时,由控制器收回指令将该变量的值传到运算器,运算后如果须要持续存储,再传回到内存。如果有一些变量频繁应用,为节俭存取变量的值破费的工夫,能够将局部变量的值放到 CPU 的寄存器中,须要时间接从寄存器中读取。因为寄存器的存取速度远高于内存,这样做能够进步程序执行效率。这种变量称为 寄存器变量,应用关键字 register
定义。
register int a;
因为当初计算机的性能越来越好,优化后的编译系统能辨认出应用频繁的变量,主动将这些变量寄存在寄存器中,不须要设计程序时独自指定。因而实际上用 register
申明变量的必要性不大。
3. 全局变量的存储类别
一般来说,内部变量是在函数内部定义的全局变量,它的作用域是从定义处开始,到本程序完结。在此作用域内,全局变量能够被各个函数应用。但有时会心愿扩大内部变量的作用域。
在一个文件内扩大内部变量的作用域
如果内部变量不在文件的结尾定义,其无效范畴只限定于定义处到文件完结。如果因为某种思考,须要定义点之前的程序能援用该内部变量,应应用关键字 extern
在援用前对该变量作 内部变量申明。
#include <stdio.h>
int main()
{int max();
extern A, B, C; // 扩大内部变量 A,B,C 的作用域到此处
scanf("%d %d %d", &A, &B, &C);
printf("max is %d", max());
return 0;
}
int A, B, C; // 定义内部变量 A,B,C
int max()
{
int m = A > B ? A : B;
if (C > m)
m = C;
return m;
}
应用 extern
申明内部变量时能够省略变量类型。通常提倡将内部变量的定义放在援用它的所有函数之前,这样能够防止在函数中多加一个 extern
申明。
将内部变量的作用域扩大到其余文件
如果程序是由多个源程序文件组成的,想在一个文件中援用了一个文件定义的内部变量,此时不能在两个文件中同时定义该变量,而应在一个文件中定义后,在另一个文件中应用 extern
对变量做内部申明。用法与在文件内扩大内部变量的作用域用法雷同。
实际上,编译系统遇到 extern
时,会当初本文件中寻找内部变量的定义,如果找到,就在本文件中扩大内部变量的作用域,找不到时再从其余文件中寻找。
将内部变量的作用域限度在本文件中
如果心愿某些内部变量只能被本文件援用,能够在定义内部变量时加一个 static
申明:
static int A;
这种只能用于本文件的内部变量称为 动态内部变量。
五、外部函数与内部函数
变量由作用域,用部分和全局之分,函数也有相似的问题。函数实质上时全局的,因为定义一个函数的目标就是心愿它能被其余函数调用。如果不加申明,一个函数能够被本文件和其余文件中的函数调用。也能够指定某些函数不能被其余函数调用。依据函数是否能被其余源文件调用,分为 外部函数 和内部函数。
1. 外部函数
如果一个函数只能被本文件中的其余函数调用,称为外部函数,又称为动态函数。定义时在函数类型后面加 static
关键字:
static int f(int x)
通常把只能本文件应用的动态内部变量和动态函数放在文件结尾,进步程序可读性。
2. 内部函数
定义函数时在函数类型前加 extern
关键字,指定该函数为内部函数,可被其余文件调用。extern
能够省略:
extern int f(intx)
调用内部函数也要先申明,如果该函数来自其余文件,申明时也要加关键字 extern
。
Reference:
谭浩强《C 程序设计(第五版)》