函数是C语言的基本组成方式,如之前所遇到的main(),printf()等都是函数。
实例1 计算圆柱体积
输入圆柱体的高和半径,求圆柱体积,volume=πr2h。
要求定义和调用函数cylinder (r, h )计算圆柱体的体积。
代码如下:
# include <stdio.h>
double cylinder (double r, double h); /* 函数声明*/
int main( void )
{
double height, radius, volume;
printf ("Enter radius and height: ");
scanf ("%lf%lf", &radius, &height);
/* 调用函数,返回值赋给volume */
volume = cylinder (radius, height );
printf ("Volume = %.3f\n", volume);
return 0;
}
/* 定义求圆柱体积的函数,用main()调用 */
double cylinder (double r, double h) /*函数头*/
{
double result;
result =3.1415926 * r * r * h; /* 计算体积 */
return result; /* 返回结果 */
}
运行结果:
♾️ c 代码:Enter radius and height: 3.0 10
Volume = 282.743
函数定义
函数是指完成一个特定工作的独立程序模块。
- 函数分类:
库函数:由C语言系统提供定义,如scanf ()
、printf ()
等函数。
自定义函数:需要用户自己定义,如计算圆柱体体积函数cylinder ()
。 - main ()也是一个函数,C程序由一个main ()或多个函数构成。
程序中一旦调用了某个函数,该函数就会完成特定的计算,然后返回到调用它的地方。
在自定义函数中,一般结构:
♾️ c 代码:函数类型 函数名(形参表) /* 函数首部,形参表后没有分号!!!*/
{ /* 函数体 */
函数实现过程(代码)
return 表达式;
}
在这里,函数返回值的类型与函数类型一致, return 表达式;
把函数运算的结果回送给主函数。
return只能返回一个值。
形参:
形参表:(类型1 参数1 ,类型2 参数2 ,……,类型n 参数n)
- 参数之间用逗号分隔,每个参数前面的类型都必须分别写明。
如 (double r, double h)
不能写成 (double r,h)
。
函数的调用
- 定义一个函数后,就可以通过程序来调用这个函数。
- 调用标准库函数时,在程序的最前面用
#include
命令包含相应的头文件。 调用自定义函数时,程序中必须有与调用函数相对应的函数定义。
调用过程:
以实例1为例:
1.main()函数一直运行到:
♾️ c 代码:# include <stdio.h> double cylinder (double r, double h); /* 函数声明*/ int main( void ) { double height, radius, volume; printf ("Enter radius and height: "); scanf ("%lf%lf", &radius, &height); /* 调用函数,返回值赋给volume */ volume = cylinder (radius, height );
然后调用cylinder()函数,暂停main()函数,将radius和height的值传递给形参r和h;
2.计算机转到cylinder()函数,形参r和h接受radius和height的值:
♾️ c 代码:double cylinder (double r, double h) /*函数头*/ { double result; result =3.1415926 * r * r * h; /* 计算体积 */
3.函数cylinder()执行
♾️ c 代码:return result
,结束函数运行,带着函数的结果result,返回到main()中调用它的地方;return result; /* 返回结果 */ } volume = cylinder (radius, height );//返回到这一步
4.从先前暂停的位置继续执行,将返回值赋给变量volume,输出体积:
♾️ c 代码:volume = cylinder (radius, height ); printf ("Volume = %.3f\n", volume); return 0; }
把调用其他函数的函数称为主调函数,如main();被调用的函数称为被调函数。
函数调用的形式
一般形式:
函数名(实际参数表)
实际参数(实参)可以是常量、变量和表达式。
♾️ text 代码:
对于实现计算功能的函数,函数调用通常出现在两种情况:
赋值语句:volume = cylinder (radius, height );
输出函数的实参:
♾️ text 代码:printf ("%f", cylinder (radius, height ) );
参数传递
- 两种参数:
形参:函数定义时的参数被称为形式参数(简称形参),如:r和h,
double cylinder (double r, double h);
实参:函数调用时的参数被称为实际参数(简称实参),如:radius和height,
volume = cylinder (radius, height);
- 形参:用于接受实参传递过来的值,还可当做普通变量,是自定义函数里的;
- 实参:一般是常量、变量或表达式,把值复制给形参,是主调函数里的;
形参和实参必须一一对应:数量一致,类型一致,顺序一致
程序运行遇到函数调用时,实参的值一次传给形参,即参数传递。
如果实参是变量,它与所对应的形参是两个不同的变量。
只与允许实参把值复制给形参
函数结果返回
return 表达式;
应先求解表达式的值,再返回其值。
return语句的作用:
1.结束函数的运行;
2.带着运算结果(表达式的值)返回主调函数。return语句只能返回一个值
函数原型声明
- 函数必须先定义后调用,将主调函数放在被调函数的后面,就像变量先定义后使用一样。
- 如果自定义函数在主调函数的后面,就需要在函数调用前,加上函数原型声明。
函数原型声明:只写函数定义中的第1行(函数首部),并以分号结束。一般形式:
函数类型 函数名(参数表);
如:
double cylinder (double r, double h);
void pyramid (int n);
函数声明是一条C语句,而函数定义时的函数首部不是,后面不需要分号。
函数程序设计:
实例2:计算五边形的面积
将一个五边形分割成三个三角形,输入这些三角形的7条边长,计算该五边形的面积。
要求定义和调用函数area(x,y,z)计算边长为x、y、z的三角形面积。
代码如下:
# include <stdio.h>
double area(double x, double y, double z);/* 函数声明*/
int main( void )
{
double a1, a2, a3, a4, a5, a6, a7,s;//定义五边形的7条边
double area(double x, double y, double z);//函数调用
printf("Please input 7 side lengths in the order a1 to a7:\n");
scanf("%lf%lf%lf%lf%lf%lf%lf", &a1, &a2, &a3, &a4, &a5, &a6, &a7);
s = area(a1, a5, a6) + area(a4, a6, a7) + area(a2, a3, a7); /* 调用3次area */
printf("The area of the Pentagon is %.2f\n", s) ;
return 0;
}
/* 使用海伦-秦九韶公式计算三角形面积的函数 */
double area(double x, double y, double z) /* 函数首部 */
{
double p = (x + y + z) / 2;
return sqrt(p * ( p - x ) * ( p - y ) * ( p - z )) ;
}
运行结果:
♾️ c 代码:Please input 7 side lengths in the order a1 to a7:
2.5 2.5 2.5 2.5 2.5 3.6 3.6
The area of the Pentagon is 10.47
实例3:使用函数判断完全平方数
定义函数IsSquare(n):当n为完全平方数时返回1,否则返回0。
1+3+5+7+......+(2×m−1)=m^2=n
代码如下:
/* 判断完全平方数的函数 */
int IsSquare (int n) /* 函数首部 */
{
int i;
for (i = 1; n > 0; i = i + 2) {
n = n - i;
}
if (n == 0){
return 1; /* 是完全平方数返回1 */
}else{
return 0; /* 不是完全平方数返回0 */
}
}
实例4:使用函数求最大公约数
定义函数gcd(int m, int n):计算m和n的最大公约数
辗转相除法(欧几里得算法):
(1)r = m %n;
(2)若r为0,则返回n的值作为结果并结束;否则转第(3)步;
(3)m = n, n = r,返回第(1)步。
代码如下:
♾️ c 代码:int gcd(int m, int n)
{
int r, temp;
if(m < n){
temp = m;
m = n;
n = temp;
}
r = m % n;
while(r != 0){
m = n;
n = r;
r = m % n;
}
return n;
}
实例5:使用函数判断素数
求100以内的全部素数,每行输出10个。素数就是只能被1和自身整除的正整数,1不是素数,2是素数。
要求定义和调用函数prime (m)判断m是否为素数,当m为素数时返回1,否则返回0。
代码如下:
♾️ c 代码:#include <stdio.h>
#include <math.h>
int prime (int m);
int main(void)
{
int count, m;
count = 0;
for(m = 2; m <= 100; m++){
if ( prime(m) != 0 ){
printf("%6d", m );
count++;
if (count %10 == 0)
printf ("\n");
}
}
printf ("\n");
return 0;
}
int prime (int m)
{
int i, n;
if ( m <= 1 ) return 0;
else if ( m == 2 ) return 1;
else{
limit = sqrt (m) + 1;
for( i = 2; i <= limit; i++){
if (m % i == 0){
return 0;
}
}
return 1;
}
}
实例6:数字金字塔
代码如下:
♾️ c 代码:/* 输出数字金字塔 */
#include <stdio.h>
void pyramid (int n); /* 函数声明 */
int main (void)
{
pyramid(5); /* 调用函数,输出数字金字塔 */
return 0;
}
void pyramid (int n) /* 函数定义 */
{
int i, j;
for (i = 1; i <= n; i++){ /* 需要输出的行数 */
for (j = 1; j <= n-i; j++) /* 输出每行左边的空格 */
printf(" ");
for (j = 1; j <= i; j++) /* 输出每行的数字 */
printf("%d ", i); /* 每个数字的后面有一个空格 */
putchar ('\n');
}
}
不返回运算结果的函数定义
类似上面的数字金字塔,不返回运算结果即表示不需要返回值,只需要输出结果即可。
不返回结果的函数,在定义、调用、参数传递、函数声明上,思路完全与以前相同,只是函数类型变为void,以独立的调用语句方式:pyramid(n);
函数类型为void,表示不返回结果,不可以省略;否则函数类型被默认定义为int。
不返回运算结果的函数定义:
♾️ c 代码:void 函数名(参数表) /* 函数首部 */ { /* 函数体 */ 函数实现过程 return; /* 可以省略return */ }
- 省略了return语句,不意味着函数不能返回,如pyramid()遇到最后一个大括号即自动返回主调函数。
结构化程序设计思想
强调程序设计的风格和程序结构的规范化,提倡清晰的结构。
- 基本思路是将一个复杂问题的求解过程划分为若干阶段,每个阶段要处理的问题都容易被理解和处理。
按自顶向下的方法对问题进行分析、模块化设计和结构化编码等3个步骤。
自顶向下的分析方法
把大的复杂的问题分解成小问题后再解决。
- 面对一个复杂的问题,首先进行上层(整体)的分析,按组织或功能将问题分解成子问题。
- 如果子问题仍然十分复杂,再做进一步分解,直到处理对象相对简单,容易处理为止。
- 当所有的子问题都得到了解决,整个问题也就解决了。
每一次分解都是对上一层的问题进行细化和逐步求精,最终形成一种类似树形的层次结构,来描述分析的结果。
模块化设计
- 将模块组织成良好的层次系统
- 模块用函数实现。
- 一个模块只完成一个指定的功能。
模块之间只通过带参数的函数进行调用。
实例7:复数运算
分别输入2个复数的实部与虚部,用函数实现计算2个复数之和与之积。
分析:
若2个复数分别为:
c1=x1+y1i , c2=x2+y2i,
则:
c1+c2 = (x1+x2) + (y1+y2)i
c1*c2 = (x1*x2-y1*y2) + (x1*y2+x2*y1)i
代码如下:
♾️ c 代码:# include<stdio.h>
double result_real, result_imag; /* 全局变量,用于存放函数结果 */
/* 函数声明 */
void complex_prod(double real1, double imag1, double real2, double imag2);
void complex_add(double real1, double imag1, double real2, double imag2);
int main (void)
{
double imag1, imag2, real1, real2; /* 两个复数的实、虚部变量 */
printf ("Enter 1st complex number(real and imaginary): ");
scanf ("%lf%lf", &real1, &imag1); /* 输入第一个复数 */
printf ("Enter 2nd complex number(real and imaginary): ");
scanf ("%lf%lf", &real2, &imag2); /* 输入第两个复数 */
complex_add (real1, imag1, real2, imag2); /* 求复数之和 */
printf ("addition of complex is %f+%fi\n", result_real, result_imag);
complex_prod (real1, imag1, real2, imag2); /* 求复数之积 */
printf ("product of complex is %f+%fi\n", result_real, result_imag);
return 0;
}
运行结果:
♾️ c 代码:Enter 1st complex number(real and imaginary):1 1
Enter 2nd complex number(real and imaginary):-2 3
addition of complex is -1.000000+4.000000i
product of complex is -5.000000+1.000000i
局部变量和全局变量
局部变量:
- 定义:在函数内定义的变量(包括形参);定义在复合语句内的变量。
- 作用范围:本函数内部;复合语句内部。
- 优点:最大程度确保了各函数之间的独立性,避免函数之间相互干扰。
全局变量:
- 定义:在函数以外定义的变量,不从属于任一函数。
- 作用范围:从定义处到源文件结束(包括各函数)。
- 优点:对作用范围内所有的函数都起作用。
局部变量一般定义在函数或复合语句的开始处,标准C规定其不能定义在中间位置。
- 同一范围(比如一个{}里)不可以重名,不同范围可以。
不同范围里一旦重名,以最近的范围为主。
一般情况下把全局变量定义在程序的最前面,即第一个函数的前面。
实例8:用函数实现财务现金记账。
先输入操作类型(1收入,2支出,0结束),再输入操作金额,计算现金剩余额,经多次操作直到输入操作为0结束。要求定义并调用函数,其中现金收入与现金支出分别用不同函数实现。
代码如下:
# include<stdio.h>
double cash; /* 定义全局变量,保存现金余额 */
void income (double number), expend(double number); /* 函数声明 */
int main (void)
{
int choice;
double value;
cash = 0; /* 初始金额=0 */
printf ("Enter operate choice(0--end, 1--income, 2--expend):");
scanf ("%d", &choice); /* 输入操作类型 */
while (choice != 0){ /* 若输入类型为0,循环结束 */
if (choice == 1 || choice == 2) {
printf ("Enter cash value:"); /* 输入操作现金额 */
scanf ("%f", &value);
if (choice == 1) income(value); /* 函数调用,计算现金收入 */
else expend(value); /* 函数调用,计算现金支出 */
printf ("current cash:%.2f\n", cash);
}
printf ("Enter operate choice(0--end, 1--income, 2--expend):");
scanf ("%d", &choice); /* 继续输入操作类型 */
}
return 0;
}
/* 定义计算现金收入函数 */
void income(double number)
{
cash = cash + number; /* 改变全局变量cash */
}
/* 定义计算现金支出函数 */
void expend(double number)
{
cash = cash - number; /* 改变全局变量cash */
}
运行结果:
♾️ c 代码:Enter operate choice(0--end, 1--income, 2--expend):1
Enter cash value:1000
current cash:1000.000000
Enter operate choice(0--end, 1--income, 2--expend):2
Enter cash value:456
current cash:544.000000
Enter operate choice(0--end, 1--income, 2--expend):0
- 过多使用全局变量会带来副作用,导致各函数间出现相互干扰。
- 因此在变量使用中,应尽量使用局部变量和函数参数。
变量生命周期和静态局部变量
变量生命周期:
定义:变量从定义开始分配存储单元,到运行结束存储单元被回收的整个过程。
自动变量(auto): 普通的局部变量
自动变量定义形式:
auto 类型名 变量表;
,如:auto int x, y;
,auto char c1;
。- 函数调用时,定义变量,分配存储单元。
- 函数调用结束,收回存储单元。
自动变量如果没有赋初值,其存储单元中将是随机值。
全局变量:
- 定义:从程序执行开始,到程序的结束,存储单元始终保持。
静态变量
- 静态变量定义格式:
static 类型名 变量表
,如static int x;
。 - 作用范围:局部变量
- 生命周期:全局变量
- 赋初值只在函数第一次调用时起作用,以后调用都按前一次调用保留的值使用。
- 静态局部变量受变量作用范围限制,不能作用于其他函数(包括主函数)。
静态变量赋初值只在函数第一次调用时起作用,如果定义时没有赋初值,系统将自动赋0。
静态变量与全局变量均位于静态存储区
- 共同点是生命周期贯穿整个程序执行过程。
- 区别在于作用范围不同,
全局变量可作用于所有函数,
静态变量只能用于所定义函数,而不能用于其他函数。