跟着小甲鱼学C——Lesson6函数

函数

  • 目前我们在开发中遇到的问题

随着程序规模的变大

——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;
 } 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值