函数
- 目前我们在开发中遇到的问题
随着程序规模的变大
——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;
else
return 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函数的首地址(即函数名)赋给指针变量fp
printf("%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); //调用函数指针指向的函数add或sub,返回一个值
}
//函数调用
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
- 函数指针作为返回值
例:现在让用户输入一个表达式,然后程序根据用户输入的运算符来决定调用add还是sub函数进行运算
#include<stdio.h>
//函数声明
int add(int,int);
int sub(int,int);
int calc(int (*fp)(int,int),int,int);
int (*select(char op))(int,int);
//函数定义
int add(int num1,int num2)
{
return num1 + num2;
}
int sub(int num1,int num2)
{
return num1 - num2;
}
int calc(int (*fp)(int num1,int num2),int num1,int num2)
{
return (*fp)(num1,num2);
}
int (*select(char op))(int,int)//函数select (有一个char类型的参数,返回一个指针)返回的函数指针有两个int型参数,返回值是int型
{
switch(op)
{
case'+':return add;
case'-':return sub;
}
}
//调用函数
int main()
{
int num1,num2;
char op;
int (*fp)(int,int);//定义函数指针
printf("请输入一个式子(如1 + 3):");
scanf("%d%c%d",&num1,&op,&num2);
fp = select(op);
printf("%d %c %d = %d",num1,op,num2,calc(fp,num1,num2));
return 0;
}