1、什么是函数?
function --> 功能、函数
c语言中函数是指对能对完成某个特定功能的代码块的封装形式
怎么封装?
把这些特定功能的代码块放在一个{}内
函数有什么用?为什么要用?
结构化程序设计者 ,主张把很大的任务分成多个小任务(函数)来完成。
把每一个小任务封装成函数,有利于简化代码,提高代码复用率,因为有些小任务可能会在程序中多次使用,
如果把这些功能我们封装成函数,每次要用到那个功能时候,不用重复的编写这个功能的的代码,
只需要“调用”该函数即可。
封装成函数有利于维护代码(找bug 方便升级)
如:
求两数的和
int sum(int a,int b)
{
int s;
s = a + b;
return s;
}
2、怎么设计函数/怎么写函数
定义函数的语法:
返回值类型 函数名(形参类型1 形参名1,形参类型2 形参名2,....) ------> 函数头
{
//完成特定功能的代码块
...
...// {}内 称为 函数体
...
return 值;// 有时候可以省略
}
如上 求和的函数
(1)明确干什么,完成什么任务 取个名 ----> 函数名function
符合 C语言标识符定义,不能取关键字相同 最好能够顾名思义 sum
(2)明确完成这个任务,需要什么条件 这些条件放在函数名后面的()中
----> 通过"参数列表" 告诉使用者
参数列表:
形参类型1 形参名1,形参类型1 形参名1...
求和需要的条件 两个整型数据 (只有知道这两个数才能去和)
形参类型 C语言合法类型
形参名 符合 C语言标识符定义,不能取关键字
有些情况 可以不用 参数, ()中为空或者写 void
(3)明确完成任务 后的 结果 ----返回值
返回值是执行完该函数/完成该功能 之后 的结果
大部分情况都有一个结果,有对应的类型称为返回值类型
怎么返回:return 结果;
return s;
有些特殊情况可以没有返回值的,返回值类型就写 void
(4) 函数体
具体的代码实现
完成该功能的代码块
int s;
s = a + b;
练习:
写一个判断是否为闰年的函数 如果是闰年就返回1,否则返回0
3、怎么使用函数(调用函数)
函数写好后,不会自动执行,需要被调用的时候才会执行。
怎么调用:
函数名(实参列表);---> 函数调用表达式
实参列表,要和被调用的函数的形参列表对应(参数个数要相同,类型要相同或兼容)
但是实参列表中 不需要写参数类型
int sum(int a,int b)
{
int s;
s = a + b;
return s;
}
int a = 10,b = 20;
sum(a,b);
sum(b,a);
sum(5,7);
sum(2*2,1/1);
.....
sum(int x,int y);//error 不需要写参数类型
总结:
实参列表中的实参可以是常量、变量、表达式 或它们的组成形式,都不需要写参数类型
如果函数有返回值的话(有 return 语句),则需要接收该返回值
变量名 = 函数名(参数列表);
变量名类型 要和 返回值类型 相同 / 兼容
如:
int z = sum(3,5);// int z; z = sum(3,5);
z = sum(2*2,1/1);
....
主调函数和被调函数
主调函数:调用函数的函数,可以是任意函数
被调函数:被调用的函数,可以是除了main函数之外任意函数
练习:
调用刚刚写的求闰年的函数
写一个判断一个数是否为质数的函数并在主函数内调用
4、函数的声明
声明:告诉编译器这是一个函数
声明一个函数的语法:
返回值类型 函数名(形参列表);
就把函数头复制过来,加个分号即可,写在头文件之后
注意:形参列表中可以省略形参名字,但是不能省略类型
函数声明不是必须的,一般来讲被调函数写在主调函数之后,就需要申明,否则编译器可能会不认识这是一个函数
5、函数调用详细过程
大概分为三步:
(1)传参
把实参的值一一对应的传递给形参
这个过程中会为形参分配空间并且初始化(初始值就是 对应实参)
在被调函数中改变形参的值,不会影响实参的值。因为它们是独立的变量
(2)进入被调函数的函数体中执行代码
(3)执行 return 语句,遇到 } 也会结束
无论是在函数的那个位置遇到 return 直接结束函数
return 后面的值会赋值给 函数调用表达式 ,如果没有 return 语句,那么函数调用表达式的值不确定
分析:
long long int jiecheng(int n)
{
if(n == 1)
return 1;
else
{
long long int s = jiecheng(n -1) * n;//自己调用自己递归
return s;
}
}
int main()
{
int a = 20;
long long int j = jiecheng(a);
printf("j = %lld\n",j);
return 0;
}
作业:
理解函数相关知识
分析上面代码
写一个求阶乘的函数 和上面的不一样
写一个函数 把10进制转换成n进制 (2<= n <= 16)
f(1) = 1;
f(m) = f(m-1) * f(m-2) * .... * f(2) * f(1);
f(m) = m*....4*3*2*1
6、递归
递归是指 函数体中调用函数本身的方式(主调函数和被调函数 都是本身)
在数学上经常用到
如:
求阶乘
f(n) = 1 (n = 1)
= f(n-1) * n(n > 1)
求斐波拉契数列
f(n) = 1(n =1 或 n = 2)
= f(n - 2) + f(n - 1)(n > 2)
特点:
在解决问题的某个步骤需要用到这个问题本身
并且 问题的规模 逐渐缩小,当缩减到一定程度的时候结果显而易见。
7、数组作为函数参数
比如
写一个求一维数组最大值函数
一般需要两个参数
第一个参数是这个数组名
第二个参数是数组的元素个数
例:
int arraymax(int a[],int n)
{
int max = a[0];
int i;
for(i = 0;i < n;i++)
{
if(a[i] > max)
{
max = a[i];
}
}
return max;
}
void test()
{
int b[10] = {1,3,5,7,8,10,12,2,4,6};
int max = arraymax(b,10);//调用的时候不用 []
printf("该数组的最大值是%d\n",max);
}
8、练习:
写一个求一维数组所有元素和的函数
写一个函数 判断一维数组是否为升序(是的话返回0,不是返回1)
递归:
当 n = 1 肯定升序1个元素
当 n > 1 ,除了最后一个元素之外的子数组是升序 并且 最后一个元素大于 倒数第二个元素 -->升序
作业:
求100以内的“完数”
完数: 所有的因数(除去它本身)之和等于它本身
6 = 1 + 2 + 3
提示:
先得到 该数的因数
再累加完 判断一下 是否 相等 不用做了 输出就好
如果 大于 了 不用做了 换个数
附加题:
汉诺塔问题,打印出最优次数和最优解的步骤 A 、B、C代表那三根柱子
把A 上面的 所有 放到 B 上面
1、 A上面的 n-1 先放到 C 上面
2、 把 A上面剩的最大的 盘 放到B
3、C 上面 的 n-1 个放到 B上面
1、3 还是一个汉诺塔问题 但是 规模减小了