函数定义
函数的使用是为了避免代码冗长,重复。预先定义的函数也叫做API,为了模块化设计,在函数体中事先写好相应功能性的代码,需要使用时,直接调用这些API。
函数先定义再使用,定义方式三要素:返回值类型 函数名 参数列表。函数名要做到"见名知意",驼峰命名法。
函数调用
调用条件
只有被定义的函数才能被调用,使用库函数的话,需要引入头文件,如果函数定义的位置在main函数的后边,需要在main函数前,做函数声明,写出函数的三要素。
函数嵌套
因为函数具有返回值,所以函数调用时候可以作为其他函数的参数。
不同函数之间可以互相调用,函数体中调用了其他函数,就是函数的嵌套,程序运行时会一层一层进入函数,然后进入到最后一层得到返回值后,又将返回值一层一层返回,然后回到第一层函数。
函数递归
函数递归相当于特殊的函数嵌套,嵌套的函数是他自己本身。注意:函数递归一定要写退出条件,不然会有可能会死循环。
参数
形式参数
形式参数是定义函数时,括号里面的参数。
实际参数
实际参数是调用函数时,括号里面的参数。
调用时的关系
函数类似一个有功能的黑匣子,在调用时,需要我们给他数据,函数便对数据进行操作,操作完之后,给我们一个结果。所以函数体和形式参数是一个功能框架,调用时给的数据便是实际参数,函数把实际参数的值,赋值给形式参数,整个过程中操作的也是形式参数,而不是实际参数。因为调用时操作的是形式参数那么在函数调用期间,实际参数不会发生改变。最后调用结束会将得到的相应结果,返回到主函数。
形式参数和实际参数是两个不同的存储单元,而且形式参数只有在调用时,才会分配空间,调用结束会释放空间,所以形式参数的生命周期只存在于开始调用和结束调用。
传参类型
数值型:将变量的值传递给形式参数,实际参数的值不变。
数组型:将数组的首地址传递给形式参数,且形式参数不用规定数组大小。如果用下标法改变数组元素,主函数中的数组的值也会发生改变,但是数组的地址不会发生改变。注意:二维数组传参时候,需要指定列的大小,因为二维数组是特殊的一维数组,不划分列数,操作系统不知道该数组中有几个一维数组。
指针型:将一个地址或者指针型变量的值传递给形式参数,实际参数的值不变,即实参形参同时指向一个地址,调用过程中主函数中该指针指向的地址不变。如果函数对形式参数操作时,形参指针发生变化,那么形参指向的地址便会和实参指向的地址不同。如果函数中有对形式参数进行取内容操作,则会修改该地址的里面的内容,主函数中该地址的内容也会发生变化。所以如果需要通过函数来修改主函数里面的变量的值的时候,可以在函数的参数列表里面多设置一个该类型变量的指针的形参,传参将变量的地址传入到形参,函数中对该地址取内容后的任何操作,都会影响主函数里面变量的值。这就是通过在a函数(main函数)里面调用b函数,b函数里面传参传a函数局部变量的地址,来改变a函数局部变量的值,比如main函数里面调用scanf,需要&。
数组型传参是一种特殊的指针型传参,实际上形参不存在数组概念,传数组相当于传地址。eg:
int *p=&arr[0]=arr; *(p+1)=*&arr[1]=arr[1]。(p为形参,arr为实参)。
当传递的参数是数组名时,形参是指针,该指针便可以当做数组使用,还是可以用下标法访问数组元素。
当传递的参数是数组名向后偏移了的地址的时候,指针也可以当做数组使用,只是访问的时候,指针指向的位置就不是原数组名指向的地址了,而是偏移后的地址,还是可以用下标法访问数组元素。
注意如果想操作数组的部分元素:
可以在传参的时候传数组名偏移后的地址,数组长度传参时也可以相应变化,在函数里面仍然可以通过下标法或者指针偏移取内容访问。
也可以在传参的时候传数组名和数组长度且不做修改,通过改函数里面的内容,将形参偏移,再将形参的数组长度变化,也可以通用下标法访问或指针偏移取内容访问。
如果讲究泛用性,一般采取第一种方式。
注意普通变量与指针变量的区别
变量名是数值的别名,在使用的时候,他就是这个变量地址里面存储的数据即变量值,他的地址是&x而不是x,若要把这个地址存储起来用另外一个变量表示,需要使用指针变量
指针变量是某个地址的别名,对指针变量取内容修改里面的内容,这个操作并没有改变指针变量本身的值,即指针变量存储的地址还是原来的地址,但是这个地址里面的存储的内容已经变化
指针变量 对变量名操作 就是修改该指针指向的地址,但是修改前后两个地址的内容没有变化。
普通变量 对变量名操作 就是对这个地址里面数据操作,这个变量的值已经变化
注意外部变量和局部变量的区别
局部变量定义在函数体中,作用域是从定义开始到函数结束。
外部变量定义在函数体外面,作用域是从定义开始到所有代码结束。
全局变量是定义在头文件下的特殊外部变量,作用域是整个代码。
建议:因为函数是强调功能性的封装,所以函数体中主要写必要功能的业务逻辑,还要把需要的数据要return到主函数中,不建议直接在函数里面完成所有的业务,而不使用返回值。
若调用时需要函数获得多项结果 但return只能返回一项 可以使用全局变量或者直接指针改数据。
作业案例:函数封装实现冒泡排序和选择排序
#include<stdio.h>
#include<string.h>
void swap(int *p1, int *p2){
int tem;
tem=*p1;
*p1=*p2;
*p2=tem;
}
void pr(int a[],int len){
for(int i=0;i<len;i++){
printf("%d ",a[i]);
}
}
int init(int a[],int len){
int cnt=0;
for(int i=0;i<len;i++){
scanf("%d",&a[i]);
if(a[i]==-1)break;
cnt++;
}
return cnt;
}
void bob(int a[],int len){
for(int i=0;i<len-1;i++){
for(int j=0;j<len-i-1;j++){
if(a[j]>a[j+1])swap(&a[j],&a[j+1]);
}
}
}
void choose(int a[],int len){
for(int i=0;i<len-1;i++){
for(int j=i+1;j<len;j++){
if(a[i]>a[j])swap(&a[i],&a[j]);
}
}
}
int main(){
printf("请输入数据以-1结束\n");
int a[100];
int cnt=init(a,100);
int b[100];
memcpy(b,a,sizeof(a));
printf("排序前a: ");
pr(a,cnt);
printf("\n排序前b: ");
pr(b,cnt);
printf("\n对a选择排序: ");
choose(a,cnt);
pr(a,cnt);
printf("\n对b冒泡排序: ");
bob(b,cnt);
pr(b,cnt);
return 0;
}