目录
2.函数的定义与调用
2.1无参函数定义
2.2有参函数定义
4.形参和实参及函数返回值
4.1形参和实参
4.2函数的返回值
5.函数的嵌套和递归调用
5.1函数的嵌套
5.2函数的递归调用
6.变量的作用域和生命周期
7.变量存储类型
1.函数简述
前面学习过程中,输入函数scanf和输出printf都是系统内部提供的函数,像这种系统自带的函数称为库函数。自己封装的函数称为自定义函数。
为什么要自定义函数?有两点:
1.避免代码冗长;在项目开发中,程序中有时需要多次实现某一功能,就需要重复地编写实现此功能的代码。这样会使程序冗长,重复代码多。可以将这些重复代码封装成为一个函数,使用时调用就行。
2.模块化设计;在项目开发中,我们要实现很多功能,将不同功能代码封装称为一个函数。有利于后期调试和维护,使主函数变得简单,方便程序阅读。
2.函数的定义
函数和变量一样都必须“先定义后使用”。
2.1无参函数的定义
函数名后面的括号是空的,定义无参函数的一般形式为:
函数类型名 函数名()
{
函数体;
}
或者
函数类型名 函数名(void)
{
函数体;
}
说明:
1.函数的类型名:对函数类型的说明,应与return语句中返回值表达式的类型一致,也就是说函数的类型是函数返回值的类型,它可以是我们已经学习过的int、char、floar、double中的任意类型,也可以是我们要在后面学习的构造数据类型和指针类型。
2.函数名:由自己定义的标识符,同样要遵循标识符的命名规则。函数名要见名知意,要体现该函数功能。
3.函数体:执行什么样的功能,涉及的处理代码叫做函数体。
4.void:作为函数类型时,是无类型函数,return语句可以不用写。在函数名括号里面,表示该函数没有参数,可以不写(在keil里面,函数没有参数要写void,否则会报错)。
示例:
#include <stdio.h>
void printfWelcome()
{
printf("=================\n");
printf(" 你好,靓仔!\n");
printf("=================\n");
}
这里,只把 main 改为printfWelcome 作为函数名,函数类型为viod。printfWelcome 函数是一个无参函数,当被其它函数调用时,输出 Hello world 字符串。
2.2有参函数的定义
有参函数比无参函数多了一个内容,即形式参数表列。定义无参函数的一般形式为:
函数类型名 函数名(形式参数列表)
{
函数体;
}
说明:
形式参数列表:在形参表中给出的参数称为形式参数,它们可以是各种类型的变量,各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。形参既然是变量,必须在形参列表中给出形参的类型说明。
int add(int a, int b)
{
int c;
c = a + b;
return c;
}
第一行说明 add函数是一个整型函数,其返回的函数值是一个整数。形参为 a,b,均为整型量。a,b 的具体值是由主调函数在调用时传送过来的。函数返回值c,c同样为整型量。
3.函数的调用
函数调用的一般形式为:
函数名(实参列表);
如果是调用无参函数,则“实参表列”可以没有,但括号不能省略。
按照被调用函数在主调函数中出现的位置来分类,可以有3种函数的调用方式:
1.函数作为一个语句,例如:
#include <stdio.h>
void printfWelcome()
{
printf("=================\n");
printf(" 你好,靓仔!\n");
printf("=================\n");
}
int main()
{
printfWelcome();
return 0;
}
运行结果:
2.函数调用作为一个函数的实参,例如:
#include <stdio.h>
int add(int a, int b)
{
int c;
c = a + b;
return c;
}
int main()
{
int i = 3, j = 5;
//函数调用作为另一个函数实参
int sum = add(i,add(i,j));
printf("最终结果:%d\n",sum);
return 0;
}
运行结果:
函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作
为 实 参 进 行 传 送 , 因 此 要 求 该 函 数 必 须 是 有 返 回 值 的 。
3.函数调用参与表达式的运算,例如:
#include <stdio.h>
int add(int x, int y)
{
int z;
z = x + y;
return z;
}
int main()
{
int x = 3, y = 5;
//参与表达式
int sum = 5+add(x,y);
printf("最终结果:%d\n",sum);
return 0;
}
该情况和上面情况一样,都是利用函数的返回值,该情况以函数返回值参与表达式的运算。两种方式要求函数是有返回值的。
请问上面代码中,主调函数调用函数add的参数列表里x,y和add函数形参列表中x,y是同一变量吗?
直接说答案:不是!为什么?后面讲。
新手调用函数注意事项:
1.调用函数时,带了函数类型:
比如:
#include <stdio.h>
void printfWelcome()
{
printf("=================\n");
printf(" 你好,靓仔!\n");
printf("=================\n");
}
int main()
{
void printfWelcome();
return 0;
}
你会发现最后并没有输出“printfWelcome”函数里面的内容,记住调用函数一不要带函数类型。好比你买了一台笔记本电脑,你得撕掉包装(函数类型)。
2.调用函数时,里面参数(实参)带了类型:
#include <stdio.h>
int add(int a, int b)
{
int c;
c = a + b;
return c;
}
int main()
{
int a = 3, b = 5;
int sum = add(int a,int b);
printf("最终结果:%d\n",sum);
return 0;
}
运行结果:
我们发现最后运行结果,两处报错,一处提醒。就像你买了笔记本电脑,撕掉了外包装,但是内包装又没撕。
3.被调用函数的位置,在主调用函数的位置后面(没有对函数原型说明)
比如:
#include <stdio.h>
int main()
{
int a = 3, b = 5;
int sum = add( a, b);
printf("最终结果:%d\n",sum);
return 0;
}
int add(int a, int b)
{
int c;
c = a + b;
return c;
}
4.形参和实参及函数返回值
4.1形参和实参
在调用有参函数时,主调函数和被调用函数之间有数据传递关系。定义函数时,函数名后面的变量名称为 “形式参数”(简称“形参”) ;主调函数中调用一个函数时,函数名后面参数称为“ 实际参数”(简称“实参”) 。
实际参数除了为变量,还可以为常量,表达式。
例如:
#include <stdio.h>
//形参
int add(int a, int b)
{
int c;
c = a + b;
return c;
}
int main()
{
int a = 3, b = 5;
//实参是常量 //实参是表达式
printf("最终结果分别为:%d %d\n",add(4,5),add(a+b,a-b));
return 0;
}
运行结果:
在上面例子中,定义函数add后面的变量名为 “形参”,主调函数main调用函数add后面的参数为“实参”。
回到函数调用时的留下的一个坑,主调函数调用函数add的x,y和add函数形参列表中x,y是同一变量吗?如下:
#include <stdio.h>
int add(int x, int y)
{
int z;
z = x + y;
return z;
}
int main()
{
int x = 3, y = 5;
int sum = 5+add(x,y);
printf("最终结果:%d\n",sum);
return 0;
}
我们知道变量有四要素:变量名,类型,内存地址,值。变量名和地址都能一目了然,来看一下内存地址和值。
示例:
#include <stdio.h>
int add(int a, int b)
{
int c;
printf("add函数里:a = %d,b = %d\n",a,b);
c = a + b;
return c;
}
int main()
{
int a = 3, b = 5;
int sum = 5+add(a,b);
printf("最终结果:%d\n",sum);
return 0;
}
运行结果:
不难看出,形式参数的值和实际参数的值一样,这是因为系统在调用函数过程中 ,系统会把实参的值传递给被调用函数的形参或者说形参从实参得到一个值。但是,发现没有它们的地址不一样,也就是它们存储位置不一样所以”它俩“不是同一个变量。(好比,有人去模仿明星。声音,外表再像,你还是你,别人还是别人)
4.2函数的返回值
C语言主函数的框架一般为:
#include <stdio.h>
int main()
{
return 0;
}
我们可以看到主函数返回值,windows系统操作系统为例。
按住win+r,输入cmd,打开命令提示符对话框。找到该程序存储位置。
盘符号+“:”:切换盘符
cd:打开该目录
先去编译器,编译并运行该程序。再在命令提示符对话框输入echo %errorlevel%就可以看到主函数返回值
可以修改一下主函数返回值,看一下这个命令是不是主函数返回值
#include <stdio.h>
int main()
{
return 1;
}
函数的返回值都是通过关键字“return”进行返回。一般表达式为:
return 表达式;
该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个 return
语句,但每次调用只能有一个 return 语句被执行,因此只能返回一个函数值。(return后面是表达式,不一定是变量,常量)
返回为表达式时,示例:
输入两整数返回最大数:
#include <stdio.h>
int max(int a, int b)
{
//后面跟表达式
return a>b ? a:b;
}
int main()
{
int a,b;
puts("请输入两整数:");
scanf("%d%d",&a,&b);
printf("最大数为::%d",max(a,b));
return 0;
}
运行结果:
函数里有两个返回值示例:
#include <stdio.h>
int max(int a,int b)//获取最大值
{
if(a > b)
return a;
else
return b;
}
int main()
{
int a,b;
puts("请输入两个整数:");
scanf("%d%d",&a,&b);
int c = max(a,b);
printf("最大数为:%d",c);
return 0;
}
在max函数里有两个返回值,通过判断语句if最终只执行一个返回语句。
运行结果:
函数值的类型和函数定义中函数的类型应保持一致。如果两者不一致,则以函数类
型为准,自动进行类型转换。
5.函数的嵌套和递归调用
5.1函数的嵌套
C语言函数里面不能定义函数,但可以嵌套调用函数,即调用一个函数的过程中又可以调用另一个函数。
输入四个整数得到最大的数:
代码:
#include <stdio.h>
int getMaxNum1(int a,int b)//获取最大数
{
if(a > b)
return a;
else
return b;
}
int getMaxNum2(int a,int b,int c,int d)
{
int max;
max = getMaxNum1(a,b);//调用getMaxNum1函数获得两数最大值
max = getMaxNum1(max,c);
max = getMaxNum1(max,d);
return max;
}
int main()
{
int a,b,c,d;
puts("请输入四个整数:");
scanf("%d%d%d%d",&a,&b,&c,&d);
int max = getMaxNum2(a,b,c,d);
printf("最大数为:%d\n",max);
return 0;
}
5.2函数的递归调用
在调用一个函数的过程中调用该函数本身称为函数的递归调用。递归函数将无休止地调用其自身,无终止地进行。为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。
例如求出10的阶乘:
代码:
#include <stdio.h>
int Factorial(int num)
{
if(num == 1)
num = 1;
else
num = Factorial(num-1)*num;
}
int main()
{
int sum;
sum = Factorial(10);
printf("10的阶乘为:%d\n",sum);
}
6.变量的作用域和生命周期
在函数内定义的变量是局部变量(内部变量),而在函数之外定义的变量称为外部变量(全局变量)。
局部变量只在本函数或复合语句范围内有效,从定义点开始到函数或复合语句结束,将它们的有效范围称为作用域。生命周期也是从定义开始一直到“结束”。
示例:
#include <stdio.h>
int main()
{
int i = 0;
for(int j = 0;j < 10;j++)
{
printf("%d\n",i);
i++;
}
return 0;
}
运行结果:
上面例子中,变量i的作用域和生命周期从定义i开始一直到main函数结束,而变量j的作用域和生命周期只存在for循环中。
可以看一下超出它们的作用域会怎样?我们可以在其它范围打印出这俩个数:
示例:
#include <stdio.h>
void printfi()
{
prnitf("%d\n",i);
}
int main()
{
int i = 0;
for(int j = 0;j < 10;j++)
{
printf("i = %d\n",i);
i++;
}
printf("j = %d\n",j);
return 0;
}
运行结果:
编译器报错结果显示都是未声明(定义)。
全局变量的作用域是从定义变量的开始位置到本文件结束。生命周期从定义开始一直到程序结束。
如下:
#include <stdio.h>
int num1 = 2,num2 = 2;
void printf1()
{
puts("printf1:");
printf("num1 = %d,num2 = %d\n",num1,num2);
}
void printf2()
{
puts("printf2:");
printf("num1 = %d,num2 = %d\n",num1,num2);
}
int main()
{
printf1();
printf2();
puts("main:");
printf("num1 = %d,num2 = %d\n",num1,num2);
}
运行结果:
通过上面例子可以发现,变量num1和num2在任何函数都可以使用。并没有像局部变量那样,在编译时会弹出未声明的错误。
7.变量类型
局部变量,如果不专门声明存储类别,函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类。这类局部变量称为自动变量。自动变量用关键字 auto 作存储类别的声明,可以省略不写。
静态变量:有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值(上一次调用的值),这时就应该指定局部变量为“静态局部变量”,用关键字 static 进行声明。
例如:求5的阶乘
#include <stdio.h>
int fac(int n)
{
static int f=1;
f=f*n;
return(f);
}
int main()
{
int i;
for(i=1;i<=5;i++)
printf("%d!=%d\n",i,fac(i));
}
运行结果:
将fac函数里的关键字static去掉,看一下运行结果:
通过两次对照案例,可以发现静态局部变量在编译时赋初值,只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
注意: 如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值 0(对数值型变量)或’\0’(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值(现在编译器会自动变量赋初值跟静态变量情况一样)。
外部变量: 外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。
例如:
#include <stdio.h>
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
extern A,B;
printf("%d\n",max(A,B));
}
int A=13,B=-8;
运行结果:
全局变量A,B位置在函数main 之后,原来main 函数中不能引用外部变量 A,B。现在在main 函数中用extern 对 A 和 B 进行“外部变量声明”,即可应用。
8.函数原型说明
1.在调用库函数时需要在文件开头用 #include命令包含该库函数的头文件包含到该文本中。例如 #include < stdlib.h > ,#include < string.h>。
2.调用自定义函数时,该函数的定义位置应该出现在主调函数前面,如果被调函数的定义位置出现在主调函数的后面,这时必须对被调函数进行原型说明。
3.函数原型说明有两种:
①返回值类型 函数名(参数类型1,参数类型2,…,参数类型n)
②返回值类型 函数名(参数类型1 参数名1,参数类型2 参数名2,…,参数类型n 参数名n)
对函数原型说明有以下几种方式:
1.在函数内进行函数原型说明:
例如:输入一个整数n,求出该数的前n项和
代码:
#include <stdio.h>
int main()
{
int sum(int );//函数原型为第1种
int a;
puts("请输入一个数:");
scanf("%d",&a);
printf("该数前n项和:%d\n",sum(a));
return 0;
}
int sum(int a)
{
int ans = 0;
for(int i = 1;i <= a;i++)
{
ans += i;
}
return ans;
}
运行结果:
第一种形式为基本形式,有利于程序阅读,为函数声明时加上参数名时,编译系统不检查参数名,因此参数名是什么都无所谓。
将上面代码函数声明参数名与定义函数形参参数名修改成不一样:
#include <stdio.h>
int main()
{ //参数名为xyz
int sum(int xyz);
int a;
puts("请输入一个数:");
scanf("%d",&a);
printf("该数前n项和:%d\n",sum(a));
return 0;
}
int sum(int a)
{
int ans = 0;
for(int i = 1;i <= a;i++)
{
ans += i;
}
return ans;
}
运行结果:
2.在所有函数前面进行函数原型形说明:
在函数内部进行函数原型说明,但它的有效范围,是从它说明位置一直到函数结束。
将上面例子代码进行修改:
#include <stdio.h>
int sum(int xyz);
void printfSum()
{
int a;
puts("prnitfSum:");
puts("请输入一个数:");
scanf("%d",&a);
printf("该数前n项和:%d\n",sum(a));
}
int main()
{
int a;
puts("请输入一个数:");
scanf("%d",&a);
puts("main:");
printf("该数前n项和:%d\n",sum(a));
printf("\n");
printfSum();
return 0;
}
int sum(int a)
{
int ans = 0;
for(int i = 1;i <= a;i++)
{
ans += i;
}
return ans;
}
运行结果:
运行结果在printfSum里该函数没有定义。将函数原型说明位置放在所有函数之前。
例如:
#include <stdio.h>
int sum(int xyz);
void printfSum()
{
int a;
puts("prnitfSum:");
puts("请输入一个数:");
scanf("%d",&a);
printf("该数前n项和:%d\n",sum(a));
}
int main()
{
int a;
puts("请输入一个数:");
scanf("%d",&a);
puts("main:");
printf("该数前n项和:%d\n",sum(a));
printfSum();
return 0;
}
int sum(int a)
{
int ans = 0;
for(int i = 1;i <= a;i++)
{
ans += i;
}
return ans;
}
运行结果:
函数原型说明有效范围更变量的作用域一样。在函数内部的范围,是从’它’出现位置一直到函数结束,在函数外部的范围,是‘它’的出现位置一直到本文件结束。