函数
- 目前我们在开发中遇到的问题
随着程序规模的变大
——main函数变得相当冗杂
——程序复杂度不断提高
——代码前后关联度高,修改代码往往牵一发而动全身
——变量的命名都成了问题
——为了在程序中多次实现某功能,不得不重复多次写相同的代码
——...
函数的出现就解决了上述问题
先动手,再解释
#include<stdio.h>void printf_C();//函数的声明;注意有分号void printf_C() //函数的定义
{printf(" ###### \n");printf("## ##\n");printf("## \n");printf("## \n");printf("## ##\n");printf(" ###### \n");
}
int main()
{printf_C();//函数的调用;调用几次就打印几次return 0;}
编译结果:######
## ##
##
##
## ########
- 什么是函数?
一段封装的代码,实现了某一个功能
- 函数的定义
类型名 函数名(参数类型 参数)
{
函数体
return 返回值;
}
注:类型名是函数的返回值的类型;如果不返回任何值,那么就写void;如果没写,默认返回整型。
函数名尽量以实现的功能来命名,一目了然。
参数列表:
- 函数的声明
在函数定义前调用函数的话,要加声明。
所谓声明,就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把它定义上。
但是最好按照声明,定义,调用的顺序编写,一目了然。
- 返回值
返回值:函数定义的时候声明函数返回值类型,函数结束前返回一个值
注意:
1、函数声明中可以只写 参数类型 ,不写形参名字,但是定义不行
2、函数参数名如果有,要和定义保持一致(不一致可能会出现问题)
3、void类型函数可以没有返回值
- 形参和实参
形参:定义或者声明时的参数为形式参数,简称形参
实参:函数被调用时的参数为实际参数,简称实参
说白了形参和实参的功能就是用于数据传输,当函数发生调用的时候,实参的值会传递给形参,
函数传参:形参就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有两种向函数传递参数的方法:
(1)传值调用
该方法把参数的实际值复制给函数的形参。在这种情况下,修改函数内的形参不会影响实参。
例:互换两个变量的值:
#include<stdio.h>void swap(int,int);void swap(int x,int y)
{int temp;printf("In void,互换前:x = %d,y = %d\n",x,y);temp = y;y = x;x = temp;printf("In void,互换后:x = %d,y = %d\n",x,y);
}
int main()
{int x = 3,y = 5;printf("In main,互换前:x = %d,y = %d\n",x,y);swap(x,y);printf("In main,互换后:x = %d,y = %d\n",x,y);return 0;
}
编译结果:
In main,互换前:x = 3,y = 5
In void,互换前:x = 3,y = 5
In void,互换后:x = 5,y = 3
In main,互换后:x = 3,y = 5
这里由于没有返回值,所以在主函数中尽管调用了swap函数,x和y的值在主函数中也没有改变。
(2)传址调用
通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
例:还是互换两个变量的值,用传址
#include<stdio.h>void swap(int *x,int *y);void swap(int *x,int *y)
{int temp;printf("In void,互换前:x = %d,y = %d\n",*x,*y);temp = *y;*y = *x;*x = temp;printf("In void,互换后:x = %d,y = %d\n",*x,*y);
}
int main()
{int x = 3,y = 5;printf("In main,互换前:x = %d,y = %d\n",x,y);swap(&x,&y);printf("In main,互换后:x = %d,y = %d\n",x,y);return 0;
}
编译结果:
In main,互换前:x = 3,y = 5
In void,互换前:x = 3,y = 5
In void,互换后:x = 5,y = 3
In main,互换后:x = 5,y = 3
这里是把实参即变量x和y的地址传给形参两个指针变量——传址
与传值不同的是main函数中x和y的值也随着互换,因为传的是地址,而地址是唯一的,故形参地址对应的值变了,实参也跟着变。
----------
例1:编写一个函数sum,由用户输入参数n,计算1+2+3+...+n的结果并返回
#include<stdio.h>int sum(int n);int sum(int n)//形参
{int result = 0;do{result += n;}while(n-- >0);return result;
}
int main()
{int n,result;printf("请输入n的值:\n");scanf("%d",&n);result = sum(n);//实参 printf("1+2+3+...+n = %d\n",result);return 0;
}
编译结果:
请输入n的值:
100
1+2+3+...+n = 5050
例2:编写一个函数max,接收两个整形参数,并返回他们中较大的值。
#include<stdio.h>int max(int ,int);//声明时参数可以不写 int max(int x,int y)
{if(x > y)return x;elsereturn y;
}int main()
{int a,b,c;printf("请输入两个整数:\n");scanf("%d%d",&a,&b);c = max(a,b);printf("他们中较大的是:%d\n",c);return 0;
}
编译结果:
请输入两个整数:
8 23
他们中较大的是:23
注:形参和实参符号可以相同也可以不同,互不影响的,因为各个函数内部是独立的。
---------
传数组
例:
#include<stdio.h>int get_array(int a[10]);
int get_array(int a[10])
{int i;for(i = 0;i < 10;i++){printf("a[%d] = %d\n",i,a[i]);}
}
int main()
{int a[10] = {1,2,3,4,5,6,7,8,9,0}; get_array(a);return 0;
}
编译结果:
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 6
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 0
由实参传向形参的是整个数组吗?
答案是否定的,传递的仅仅是数组的第一个元素的地址(数组名就是数组第一个元素的地址)
证明:
#include<stdio.h>int get_array(int a[10]);int get_array(int a[10])
{int i;a[5] = 520;for(i = 0;i < 10;i++){printf("a[%d] = %d\n",i,a[i]);}
}
int main()
{int a[10] = {1,2,3,4,5,6,7,8,9,0}; int i;get_array(a);for(i = 0;i < 10;i++){printf("a[%d] = %d\n",i,a[i]);}return 0;
}
编译结果:
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 520
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 0
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 520
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 0
可以看到当修改get_array中数组元素a[5]的值时,main函数中a[5]的值也跟着一起改变,也就验证了传递的仅仅是地址。
也可以通过这个程序验证:
#include<stdio.h>void get_array(int b[10]);void get_array(int b[10])
{printf("sizeof(b) = %d\n",sizeof(b));
}
int main()
{int a[10] = {1,2,3,4,5,6,7,8,9,0};printf("sizeof(a) = %d\n",sizeof(a));get_array(a);return 0;}
编译结果:
sizeof(a) = 40
sizeof(b) = 8
指针函数和函数指针
- 指针函数
格式:函数返回值类型 *指针变量名(参数类型 参数 );
例:
#include<stdio.h>char *geteword(char);char *getword(char input)//函数名表明返回值是一个地址
{switch(input){case 'A':return "Apple";case 'B':return "Banana";case 'C':return "Cat";case 'D':return "Dog";default:return"None";}
}
int main()
{char input;printf("请输入一个字母:\n");scanf("%c",&input);printf("%s\n",getword(input));//返回的是字符串首字符的地址,用%s就可以把整个字符串打印出来return 0;}
编译结果:
请输入一个字母:
A
Apple
main函数中要从getword函数中获得的是一个字符串,也就是要求getword函数返回值是一个字符串;
但是是没有一个类型来定义字符串的,通常来用char 类型的指针来定义字符串,即用一个char类型的指针变量来指向字符串的第一个元素,这时返回的就是这个字符串的第一个字符的地址。
像这样用指针变量作为函数的返回值,就是指针函数。
注意:不要返回局部变量的指针!
#include<stdio.h>char *geteword(char);char *getword(char input)
{char str1[] = "Apple";//这里定义str1是局部变量,用完即毁char str2[] = "Banana";char str3[] = "Cat";char str4[] = "Dog";char str5[] = "None";switch(input){case 'A':return str1;//自然无法返回到main函数case 'B':return str2;case 'C':return str3;case 'D':return str4;default:return str5;}
}
int main()
{char input;printf("请输入一个字母:\n");scanf("%c",&input);printf("%s\n",getword(input));return 0;}
编译结果:
有警告——[Warning] function returns address of local variable(函数返回了局部变量)
但还是编译了
请输入一个字母:
A
Apple
对比之前的程序知道,直接在函数中返回字符串的首地址是可行的,因为这个字符串没有定义在这个函数中,也就是不是局部变量。
- 函数指针
(1)什么是函数指针
如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。
(2)格式:函数返回值类型 (*指针变量名)( 函数参数列表);
”函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。
(3)怎么用?
例:
#include<stdio.h>int square(int);int square(int num)
{return num*num;
}
int main()
{int num;int (*fp)(int); //定义函数指针 printf("请输入一个整数:");scanf("%d",&num);fp = square; //将square函数的首地址(即函数名)赋给指针变量fpprintf("%d * %d = %d\n",num,num,(*fp)(num));//即square(num)return 0;
}
请输入一个整数:8
8 * 8 = 64
注:printf("%d * %d = %d\n",num,num,(*fp)(num));中,(*fp)(num)也可以写成fp(num)或square(num),但是(*fp)(num)可以直观看出这是一个函数指针,推荐。
- 函数指针作为参数
例:
#include<stdio.h>//函数声明
int add(int,int);
int sub(int,int);
int cacl(int (*fp)(int,int),int,int);//函数定义
int add(int num1,int num2)
{return num1 + num2;
}int sub(int num1,int num2)
{return num1 - num2;
}int cacl(int (*fp)(int,int),int num1,int num2)
{return (*fp)(num1,num2); //返回函数指针指向的函数地址
}//函数调用
int main()
{printf("3 + 5 = %d\n",cacl(add,3,5));printf("3 - 5 = %d\n",cacl(sub,3,5));return 0;
}
编译结果:
3 + 5 = 8
3 - 5 = -2
事实上就是一层函数的嵌套;
int cacl(int (*fp)(int,int),int,int);
//函数指针作为函数cacl的一个参数,这个参数可以传入一个地址,这个地址具有函数指针的属性(返回一个整型值,有两个参数,且两个参数的类型都是整型),故这个指针指向add或sub