函数的返回值
若不需要返回值,函数中可以没有return语句。
一个函数中可以有多个return语句,但任一时刻只有一个return语句被执行。
被调函数返回给主调函数的结果数据(可以是变量、常量、表达式,只要是有确定值即可。)
返回值类型一般情况下需要和函数中return语句返回的数据类型保持一致,如果不一致,以函数定 义时指定的返回类型为标准。也就是返回值类型和实际返回值可以存在自动类型转换或者强制类型 转换的关系。
函数的调用
调用方式
1. 函数语句: test(); int result = max(2,4);
2. 函数表达式: 4 + max(2,4);
3. 函数参数: printf("%d",max(2,4));
在一个函数中调用另一个函数须具备以下条件
① 被调用的函数必须是已经定义的函数;
② 若使用库函数,应在本文件开头用#include包含;
③ 若使用用户定义的函数,而用户函数又在主调函数的后面,则应在主调函数中对被调用的函数进 行声明。声明的作用是把函数名、函数参数的个数和类型等信息通知编译系统,以便在遇到函数时, 编译系统能正确识别函数,并检查函数调用的合法性。
函数声明
函数调用时,往往要遵循 先定义后调用 ,但如果我们对函数的调用操作出现在函数的定义之前,则需 要对函数进行声明。
函数声明的作用:
是把函数名、函数参数的个数和返回类型等信息通知给编译系统,以便于在遇到函数时,编译系统能 正确识别函数,并检查函数调用的合法性。
// 函数调用错误演示
int main()
{
int c = add(12,13); // 此时会报编译错误,因为函数没有经过声明,所以编译系统无法正确识别函数
printf("%d\n",c);
}
int add(int x,int y)
{
return x + y;
}
// 函数调用正确写法
// 函数声明和实现放在一起
int add(int x,int y)
{
return x + y;
}
int main()
{
int c = add(12,13); // 此时会报编译错误,因为函数没有经过声明,所以编译系统无法正确识别函数
printf("%d\n",c);
}
// 函数调用正确写法
// 在函数调用之前,声明函数
int add(int x,int y);
int main()
{
int c = add(12,13);
printf("%d\n",c);
}
int add(int x,int y)
{
return x + y;
}
声明的方式
函数首部后加上分号:
void fun(int a);
函数首部后加上分号,可省略形参名,但不能省略参数类型。
void fun(int);
函数的嵌套调用
函数不允许嵌套定义,但允许嵌套调用。
嵌套调用:在被调函数内有去主动去调用其他函数,这样的函数调用方式,称之为嵌套调用;
/**
* 需求:编写一个函数,判断给定的3~100正整数是否是素数,若是返回1,否则返回0
*/
#include <stdio.h>
// 定义一个函数,求素数
int sushu(int n)
{
int k,i,flag = 1;
// 素数:只能被1和自身整除的数,需要校验的是2~n-1
for(i = 2; i < n-1;i++)
{
if(n % i == 0)
{
flag = 0;
}
}
return flag;
}
// 主函数
int main()
{
for(int i = 3; i <= 100;i++)
{
if(sushu(i)==1)
{
printf("%d是素数\n",i);
}
}
printf("\n");
return 0;
}
函数的递归调用
递归调用的含义:在一个函数中,直接或者间接调用了函数本身称之为函数的递归调用。
// 直接调用
a()->a();
// 间接调用
a()->b()->a();
a()->b()->..->a();
递归调用的本质:
是一种循环结构,它不同于之前所学的while,do-while,for这样的循环结构,这些循环结构是借 助循环变量,而递归是利用函数自身实现循环结构,如果不加以控制,很容易产生死循环。
递归调用的注意事项:
1. 递归调用必须要有出口,一定要终止递归(否则会产生死循环)。
2. 对终止条件的判断一定要放在函数递归之前。
3. 进行函数的递归调用。
4. 函数递归的同时一定要将函数调用向出口逼近。
/**
* 需求:递归案例-有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。
问第4个人岁数,他说比第3个人大2岁。
问第3个人,又说比第2个人大2岁。
问第2个人,说比第1个人大2岁。
最后问第1个人,他说是10岁。请问第5个人多大。
*/
#include <stdio.h>
/* 求年龄的递归函数 */
int age(int n)
{
// 存放函数的返回值,也就是年龄
int c;
if(n == 1)
{
c = 10;// 第一个人的年龄是10岁
}
else if(n > 1)
{
c = age(n-1)+2; // 当前这个人的年龄 = 上一个人的年龄+2
}
return c;
}
int main()
{
printf("%d\n",age(5));
return 0;
}
数组做函数参数
注意:
当用数组做函数的实际参数时,则形参应该也要用数组/指针变量来接收,但请注意,此次并不 代表传递了数组中所有的元素数据,而是传递了第一个元素的内存地址(数组首地址),形参接收这 个地址后,则形参和实参就代表了同一块内存空间,则形参的数据修改会改变实参的。这种数据传递 方式我们可以称之为“引用传递”。
如果用数组做函数形式参数,那么我们提供另一个形参表示数组的元素个数。原因是数组形参代 表的仅仅是实际数组的首地址。也就是说形参只获取到了实际数组元素的开始,并未获取元素的结 束。所以提供另一个形参表示数组的元素个数,可以防止在被调函数对实际数组元素访问的越界。
但有一个例外,如果是用字符数组做形参,且实际数组中存放的是字符串数据(形参是字符数 组,实参是字符串)。则不用表示数组元素的个数的形参,原因是字符串本身会自动结束符\0。
变量的作用域
概念:变量的作用范围,也就是说变量在什么范围是有效的。
变量的分类
根据变量的作用域不同,变量可分为全局变量和局部变量
建议在全局变量定义时初始化。如果不初始化,系统会将全局变量初始化为0(0 | \0 |0.0)。
使用全局变量的优缺点:
优点: 1. 利用全局变量可以实现一个函数对外输出的多个结果数据。
2. 利用全局变量可以减少函数形参个数,从而降低内存消耗,以及因形参传递带来的时间消耗。
缺点: 1. 全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。
2. 过多的全局变量会引起程序的混乱,造成程序结果错误。
3. 降低程序通用性,特别是当我们进行函数移植时,不仅仅要移植函数,还要考虑全局变量。
4. 违反了“高内聚,低耦合”的程序设计原则。
总结:我们发现弊大于利,建议尽量减少对全局变量的使用,函数之间要产生联系,仅通过实参形参的方式产生联系。如果全局变量(外部变量)和局部变量同名,程序执行的时候, 就近原则
变量的生命周期
概念:变量在程序运行中的存在时间。
根据变量存在的时间不同,变量可分为静态存储方式和动态存储方式。
变量的存储类型
变量的完整定义格式:[存储类型] 数据类型 变量列表
存储类型
auto
auto存储类型只能修饰局部变量,被auto修饰的局部变量是存储在动态存储区的。auto也是局部变 量默认的存储类型。
static
修饰局部变量:局部变量会被存储在静态存储区。局部变量的生命周期被延长,但是作用域不发 生改变。 修改全局变量:全局变量的生命周期不变,但作用域被衰减。一般限制全局变量只能在本文件 内。
extern
外部存储类型:只能修饰全局变量,次全局变量可以被其他文件访问。相当于扩展了全局变量的 作用域。extern修饰外部变量,往往是外部变量进行声明,声明该变量是在外部文件中定义的;不是变量定 义
register
寄存器存储类型:只能修饰局部变量,用register修饰的局部变量会直接存储到CPU的寄存器中, 往往将循环变量设 置为寄存器存储类型。
static关键字的作用
1. static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。
2. static修饰全局变量,不影响全局变量的生命周期,会限制全局变量的作用域仅限本文件内使 用;
3. static修饰函数:此函数就称为内部函数,仅限本文件内调用。 static int funa(){..}
内部函数和外部函数
内部函数:使用static修饰的函数,称作内部函数,内部函数只能在当前文件中调用。
外部函数:使用extern修饰的函数,称作外部函数,extern是默认的,可以不写,也就是说本质上 我们所写的函数都是外部函数,建议外部函数在被其他文件调用的时候,在其他文件中声明的时 候,加上extern关键字。