函数
函数的分类
在C语言中可从不同的角度对函数分类
-
从函数定义的角度看,函数可分为库函数和用户定义函数两种
- 库函数: 由C语言系统提供,用户无须定义
- 用户定义函数:由用户按需编写的函数。
-
从函数执行结果的角度来看, 函数可分为有返回值函数和无返回值函数两种
- 有返回值函数: 此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。(必须指定返回值类型和使用return关键字返回对应数据)
- 无返回值函数: 此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。(返回值类型为void, 不用使用return关键字返回对应数据)
-
从主调函数和被调函数之间数据传送的角度看,又可分为无参函数和有参函数两种
- 无参函数: 在函数定义及函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。
- 有参函数: 在函数定义及函数说明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)
-
为什么要定义函数?
- 首先,如果不定义函数:
- 重复代码冗余代码过多;
- 需求发生改变时;需要修改很多代码
- 如果定义函数
- 将某些代码封装起来,方便以后的使用;
- 提高代码的复用性;
- 使代码更加简洁;
- 便于维护;
- 首先,如果不定义函数:
函数的定义
- 格式:
// 标注 有参数有返回值函数的定义
返回值类型 函数名称(形参列表){
被封装的函数;
返回值;
}
例子:
int main(){
printf("hello world");
return 0;
}
// 获取最大值
int getMax(int num1, int num2){
int max = num1 > num2 ? num1 : num2;
return max;
}
// 无参数无返回值的函数定义:
void 函数名(){
函数体;
}
// 无参有返回值函数定义
返回值类型 函数名(){
函数体;
return 值;
}
// 有参无返回值函数定义
void 函数名(参数类型 形式参数1, 参数类型 形式参数2, ...){
函数体;
}
- 初学者如何定义函数
- 1.确定函数的名称(给函数起一个有意义的名称,让调用者见名知意)
- 注意点: 函数名称也是标识符的一种,所以也需要遵守标识符的命名规则: 字母,数字,下划线的驼峰命名;
- 2.确定形参列表(告诉调用者,调用时是否需要传递一些辅助的数据);
- 注意点: 形参列表的格式 (数据类型 变量名称, 数据类型 变量名称, ...)
- 数据类型 变量名称 可以有零个或多个;
- 3.确定函数的功能代码(也就是要封装的代码)
- 4.确定返回值和返回值的类型
- return 的作用之一就是将后面的数据返回给函数的调用者;
#include <stdio.h>
int getAverage(int num1, int num2){
int avg = (num1 + num2) / 2;
return avg;
}
int main()
{
int a = 10;
int b = 20;
// int res = (a + b) / 2;
int res = getAverage(a, b);
printf("average = %i\n", res);
return 0;
}
// 注意点: main函数的作用域和getAverage作用域不同
函数的参数和返回值
- 函数的参数:
- 函数可以有参数,也可以没有参数;
- 函数的参数可以是零个或多个;
- 函数可以有返回值;也可以没有返回值;
- 注意:如果函数没有返回值,那么返回值类型写void就行;
- 函数的返回值类型可以是所有C语言类型
- 形式参数:
- 在定义函数时,函数名后面小括号()中定义的变量成为形式参数,简称形参
- 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。
- 因此,形参只有在函数内部有效,函数调用结束返回主调函数后则不能再使用该形参变量
int getSum(int num1, int num2){ // 形式参数
return num1+num2;
}
- 实际参数:
- 在调用函数时,传入的值称为实际参数,简称实参;
- 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参
- 因此应预先用赋值,输入等办法使实参获得确定值
int main(){
int a = 99, b =100;
int sum = getSum(a, b); // 实际参数
}
- 返回值的注意点:
- 如果函数没有编写返回值类型,那么默认就是int类型!
- 如果函数的返回值类型和实际return返回的类型不同,那么会隐式(自动)转换为返回值类型;
test1(){
return 3.14;
}
// 返回值为3
//如果函数的返回值类型和实际return返回的类型不同,那么会隐式(自动)转换为返回值类型
int test2(){
return 3.14; // double
}
// 返回值3
int main()
{
double res = test1();
printf("res = %lf",res); // 3.000000
return 0;
}
- 函数的注意点:
-
1.函数的名称不能相同;
- 哪怕返回类型不同,函数名称也不能相同
- 哪怕形参列表不同,函数名臣也不能相同
- 2.函数不能嵌套定义,哪怕编译器允许的,也不建议这么干;
- 3.如果是基本类型的数据作为函数的参数 ,那么在函数内修改形参, 不会影响外面实参的值;
- 4.在函数的内部不能定义和形参同名的变量
void test(){ int num = 666; // 这么定义变量会报错 printf("num = %i\n", num); }
- 函数不要嵌套定义
-
1.函数的名称不能相同;
函数的声明
函数的定义可以写在函数调用之前,也可以写在函数调用之后,但是要进行函数的声明;
标准的写法:函数的声明只需要将函数定义{前面的代码拷贝即可;
函数声明,就是在函数调用之前告诉系统, 该函数叫什么名称, 该函数接收几个参数, 该函数的返回值类型是什么
函数的声明只需将函数定义{前面的代码拷贝即可
-
声明函数的注意点:
-
1.函数的声明只要写在调用之前
- 也就是说,函数的声明,可以写在函数的外面
- 也可以写在函数里面
2.函数声明的时候,形参可以不用在指定名称
3.如果函数的返回值类型是整型,那么可以不用编写函数声明(但是还是建议写上);
由于函数声明仅仅是为了告诉编译器,我们有一个什么函数,所以函数的声明是可以声明多次,但是函数的的实现只能一次;
-
总结:
- 1.以后但凡函数的定义写在函数调用之前,一定要在调用之前编写函数的声明;
- 2.函数的声明只会告诉系统函数叫什么,接收几个什么类型参数,返回值是什么类型即可;
- 3.一般情况下我们会将函数的声明写到函数的外面,而不会写到函数的里面;
-
main函数分析
-
main函数分析
- main是函数的名称, 和我们自定义的函数名称一样, 也是一个标识符
- 只不过main这个名称比较特殊, 程序已启动就会自动调用它
-
return 0;的含义:
- 告诉系统main函数是否正确的被执行了
- 如果main函数的执行正常, 那么就返回0
- 如果main函数执行不正常, 那么就返回一个非0的数
-
返回值类型:
- 一个函数return后面写的是什么类型, 函数的返回值类型就必须是什么类型, 所以写int
在企业开发中,我们是可以通过控制台(cmd)在运行程序的时候给main函数传递参数就可以了
int main(int argc, const char *argv[])
{
// 代表数组中有一个元素
// 代表数组中有两个元素
// 默认情况下, 系统在调用main函数的时候, 会给argv这个数组中存放一个元素
printf("argc = %i\n", argc);
printf("argv[0] = %s\n", argv[0]);
printf("argv[1] = %s\n", argv[1]);
printf("argv[2] = %s\n", argv[2]);
return 0;
}
递归函数
- 递归: 自己搞自己
- 递归可以实现循环的功能,但是递归的性能比循环差很多
- return 只能返回给调用者
// do - while 写法:
int num = -1;
do{
// 提醒用户如何输入数据
printf("请输入一个正整数,以回车结束;\n");
// 接收用户输入的数据;
scanf("%i", &num);
}while( num < 0);
// 输出
printf("%i",num);
// 递归写法
int test(){
// 定义用户保存的变量
int num = -1;
// 提示用户如何输入数据
printf("请输入一个正整数,以回车结束;\n");
// 接收用户输入的数据
scanf("%i", &num);
if(num < 0 ){
// 用户输入不合法
return test();
}
return num;
}
-
循环和递归的区别
- 能用循环实现的功能,用递归都可以实现;
- 递归常用于"回溯", "树的遍历","图的搜索"等问题
- 但代码理解难度大,内存消耗大(易导致栈溢出), 所以考虑到代码理解难度和内存消耗问题, 在企业开发中一般能用循环都不会使用递归
设计一个函数用来计算B的n次方
#include <stdio.h>
int pow(int b, int n);
int main()
{
/*
* 编写一个函数实现 B的n次方;
* B^n
*
* b^0 = 1;
* b^1 = b;
* b^2 = b*b;
* b^3 = b*b*b;
*
* b^0 = 1;
* b^1 = b^0 * b;
* b^2 = b^1 * b;
*
* 规律: B^n = b^(n-1) * b;
*
*/
// 定义用户输入的变量
int B = -1, n = -1;
// 提示用户输入
printf("请输入两个数,代表B^n;以逗号隔开\n");
// 接收用户输入
scanf("%i,%i", &B, &n);
// 接收结果
int res = pow(B, n);
printf("res = %i\n",res);
return 0;
}
// 传入格式: B^n
int pow(int b, int n){
if(n == 0){
return 1;
}else{
return pow(b, n-1) * b;
}
}
- 用递归法求N的阶乘
#include <stdio.h>
int jieCheng(int n);
int main()
{
/*
* 利用递归实现阶乘
*
* 3! = 3*2*1;
* 2! = 2*1;
* 1! = 1;
*
* 规律 n = (n-1)! * n;
*
*/
// 定义变量 n;
int n = -1;
// 提示输入n
printf("请输入N的阶乘:\n");
// 接收用户输入的n
scanf("%i", &n);
// 保存结果
int res = jieCheng(n);
// 输入结果
printf("%i! = %i", n, res);
return 0;
}
int jieCheng(int n){
if(n==1){
return 1;
}else {
return jieCheng(n-1) *n;
}
return n;
}
- 有5个人坐在一起,问第5个人多少岁?他说比第4个人大两岁。问 第4个人岁数,他说比第3个人大两岁。问第3个人,又说比第2个 人大两岁。问第2个人,说比第1个人大两岁。最后问第1个人, 他说是10岁。请问第5个人多大?
#include <stdio.h>
int getAge(int n);
int main()
{
/*
* 有5个人坐在一起,问第5个人多少岁?他说比第4个人大两岁。
* 问 第4个人岁数,他说比第3个人大两岁。
* 问第3个人,又说比第2个 人大两岁。
* 问第2个人,说比第1个人大两岁。
* 最后问第1个人, 他说是10岁。
* 请问第5个人多大?
*
* 第一个人: 10;
* 第二个人: 10 + 2;
* 第三个人: 10 + 2 + 2;
* 第四个人: 10 + 2 + 2 + 2;
* 第五个人: 10 + 2 + 2 + 2 + 2;
*
* 规律:(n -1) + 2;
* 其中第几个人是n, 10就是n的默认值;
*
*
*
*/
// 定义第n个用户输入的变量
int n = -1;
// 提示用户输入
printf("请输入一个整数:\n");
// 接收输入
scanf("%i", &n);
// 定义岁数
int age = getAge(n);
// 输出
printf("age = %i", age);
return 0;
}
// 递归实现年龄
int getAge(int n){
if(n == 1){
return 10;
}else{
return getAge(n-1) + 2;
}
}