函数的基本用法
定义和三要素
函数的定义
函数是一个可以完成特定功能的代码模板,其程序代码是独立的,并且可以有返回值,如果没有返回值可以为空。
函数三要素
功能
函数要实现的功能。
参数
函数在声明和调用时定义的变量,用于传递信息给函数。
参数分为形参和实参:
形参只是声明的形式,可以理解为一个符号名字。
实参是在函数调用中传递给函数的实际数值。
返回值
函数调用结束后留下的唯一右值。
函数的声明和定义
函数声明
存储类型 数据类型 函数名(数据类型 形参1,数据类型 形参2,……);
函数定义格式
存储类型 数据类型 函数名(数据类型 形参1,数据类型 形参2,……)
{
函数体;
}
其中:
函数名:是一个标识符,要符合标识符命名规则。
数据类型:是整个函数的返回值类型,如果无返回值应该写void。
形式参数说明:是逗号分隔开的多个变量的说明形式,通常简称为形参。形参就是函数声明和定义时函数名后面括号中的变量,因为形式参数只有在函数调用时才有实际的数值(即分配内存空间)。
大括号对{语句序列}:称为函数体,语句序列是大于等于零个语句组成的。
函数数据的总结:
- 没有参数时:参数列表可以省略,也可以用void。
- 没有返回值时:函数数据类型为void,函数体内没有return语句。
- 有返回值时:要根据返回值的数据类型定义函数的数据类型,函数体内可以用return来返回函数值。
- 定义子函数时:可以直接定义在主函数上面,如果要定义在主函数下面那么要事先声明函数。
函数的调用
没有返回值:直接调用:函数名(实参);
有返回值:如果需要接收返回值,就再定义一个与返回值类型相同的变量取接收函数调用之后的返回值,如果不需要接收返回值的话,直接调用函数就行。
实参:在调用有参数的函数时,函数名后面括号中的参数叫做 “实参” ,是传递给函数的真实数值,可以是常量、变量、表达式或函数等。
例如:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void function()//无返回值无参数的函数
{
printf("in the function\n");
}
void add(int a,int b)//无返回值有参数 的函数
{
printf("in add:a+b=%d\n",a+b);
}
int add2(int a,int b)//有返回值有参数的函数
{
return a+b;
}
int sub(int a,int b);//定义在main函数下面的函数需要事先声明
int main(int argc,char const *argv[])
{
int a=2,b=3;
int sum=0,diff=0;
function();
add(a,b);
sum=add2(5,6);
printf("in main:the sum of add2 is %d\n",sum);
diff=sub(a,b);
printf("in the main:diff=%d\n",diff);
return 0;
}
int sun(int a,int b)
{
if(a>b)
return a-b;
else
return b-a;
}
练习:
求x的n次方值的函数(x是实数,n为正整数)
#include <stdio.h> #include <stdlib.h> #include <string> float power(float x,int n) { float result=1; if(n<=0) { printf("error\n"); return -1; } for(int i=1;i<=n;i++) r*=x; return r; } int main(int argc,char const *argv[]) { float result,x; int n; scanf("%f %d",&x,&n); result=power(x,n); printf("%f\n",result); return 0; }
编写一个函数,函数有两个参数,其中第一个参数是一个字符,第二个参数是一个char *类型的数据,返回字符串中中该字符的个数。
#include <stdio.h> #include <stdlib.h> #include <string.h> int fun(char c,char *p) { int num=0; while(*p!='\0') { if(c==*p) num++; p++; } return num; } int main(int argc,char const *argv[]) { int n; char c; char s[15]; scanf("%c %s",&c,s); n=fun(c,s); printf("the number of %c is %d\n",c,n); return 0; }
函数传参
值传递
单向传递,将实参传递给形参使用,改变形参实参不会被影响。
#include <stdio.h> #include <stdlib.h> #include <string.h> void fun(int a,int b) { printf("in the fun:a=%d,b=%d\n",a,b); a=10; b=20; printf("in the fun:a=%d,b=%d\n",a,b); } int main(int argv,char const *argv[]) { int a=1,b=2; printf("in the main:a=%d b=%d\n",a,b); fun(a,b); printf("in the main:a=%d b=%d\n",a,b); return 0; }
打印结果:
in the main:
a=1 b=2
in the fun:
a=1 b=2
in the fun:
a=10 b=20
in the main:
a=1 b=2
地址传递
双向传递,在函数中改变形参,实参也会改变。
#include <stdio.h> #include <stdlib.h> #include <string.h> void fun(int *a,int *b) { printf("in the fun:a=%d,b=%d\n",*a,*b); *a=10; *b=20; printf("in the fun:a=%d,b=%d\n",*a,*b); } int main(int argv,char const *argv[]) { int a=1,b=2; printf("in the main:a=%d b=%d\n",a,b); fun(&a,&b); printf("in the main:a=%d b=%d\n",a,b); return 0; }
打印结果:
in the main:
a=1 b=2
in the fun:
a=1 b=2
in the fun:
a=10 b=20
in the main:
a=10 b=20
数组传递
和地址传递一样,参数中存在数组的地址,也认为是指针。
#include <stdio.h> #include <stdlib.h> #include <string.h> void fun(int *arr,int n) { printf("in fun:\n"); for(int i=0;i<n;i++) printf("%d ",arr[i]); printf("\n"); *(arr+2)+=100; printf("in fun:\n"); for(int i=0;i<n;i++) printf("%d ",arr[i]); printf("\n"); } int main(int argc,char const *argv[]) { int a[5]; for(int i=0;i<5;i++) scanf("%d",&a[i]); printf("in main:\n"); for(int i=0;i<5;i++) printf("%d ",a[i]); printf("\n"); fun(a,5); printf("in main:\n"); for(int i=0;i<5;i++) printf("%d ",a[i]); printf("\n"); return 0; }
打印结果:
in main:
1 2 3 4 5
in fun:
1 2 3 4 5
in fun:
1 2 100 4 5
in main:
1 2 100 4 5
函数和栈区的简单理解
栈区
栈是用来存储函数内部的变量(包括main函数),是一个FILO(First In Last Out)先进后出的结构,是由CPU自动管理的,不需要手动申请和释放。
开辟堆空间
堆的概念
申请的空间分为五个区域:栈区、堆区、全局区、常量区和代码区。
堆区特点:可以随时申请随时释放。
malloc()函数申请堆空间
#include <stdlib.h> void *malloc(size_t size);
功 能:在堆区开辟大小为size的空间
参 数:size:开辟空间的大小(单位:字节)
返回值 :开辟空间的首地址,如果失败返回空指针NULL
用法
malloc()内的参数是需要动态分配的字节数,而不是元素的个数。
malloc()要和free()搭配使用。
格式
数据类型 * 指针变量名=(数据类型*)malloc(sizeof(数据类型)*个数)
free()函数的定义
#include <stdlib.h> void free(void *ptr);
功 能:释放之前调用的malloc()、calloc()和realloc()所分配的内存空间
参 数:ptr:申请堆空间的首地址
返回值:无
可以释放完堆区空间后,赋值为空指针:
free(p);
p=NULL;
例如:
#include <stdio.h> #include <stdlib.h> int main(int argc,char const *argv[]) { int *p=(int *)malloc(sizeof(int)*10); if(p==NULL) printf("lost\n"); else printf("success\n"); free(p); p=NULL; return 0; }
注意:
- 手动开辟堆空间,要注意内存泄漏。当指针指向开辟的堆空间后,又对指针重新赋值,则该指针没有指向堆区空间,可能会造成内存泄漏。
- 使用完堆区空间后要及时释放。
例如:
char *p=(char *)malloc(sizeof(char )*100); //p="hello"; //该语句改变了p的指向,不指向堆空间了 scanf("%s",p); printf("p:%s\n",p); free(p); p=NULL;
函数中开辟堆空间
#include <stdio.h> #include <stdlib.h> #include <string.h> void fun(char *p) { p=(char *)malloc(32); strcpy(p,"hello"); printf("in fun:%s\n",p); } int main(int argc,char const *argv[]) { char *m=NULL; fun(m); printf("%s\n",m); return 0; }
Segmentation fault (core dumped)代码出现段错误,原因是函数执行完会栈空间被销毁,不会保留函数内开辟堆空间的首地址,并不会改变m的指向。此时m还是指向空。
解决方法一:通过返回值
#include <stdio.h> #include <stdlib.h> #include <string.h> char *fun() { char *p=(char *)malloc(32); strcpy(p,"hello"); return p; } int main(int argc,char const *argv[]) { char *a=fun(); printf("a:%s\n",a); free(a); a=NULL; return 0; }
方法二:通过传递指针
#include <stdio.h> #include <stdlib.h> #include <string.h> void fun(char **p) { *p=(char *)malloc(32); strcpy(*p,"hello"); } int main(int argc,char const *argv[]) { char *a=NULL; fun(&a); printf("a:%s\n",a); free(a); a=NULL; return 0; }
练习:实现strlen函数的功能,strlen函数是计算字符串实际长度,不包含 '\0'
#include <stdio.h> #include <stdlib.h> #include <string.h> int fun(char *p) { int num=0; while(*p!='\0') num++; return num; } int main(int argc,char const *argv[]) { char s[32]; int len; scanf("%s",s); len=fun(s); printf("strlen:%d\n",len); return 0; }
string函数族
strcpy()
#include <string.h> char *strcpy(char *dest,const char *src);
功 能:复制整个字符串,包含 '\0'
参 数:dest:目标字符串首地址
src:源字符串首地址
返回值:目标字符串首地址
char s1[32]="hello world"; char s2[32]="ni hao"; strcpy(s1,s2); printf("s1:%s\ns2:%s\n",s1,s2);
char *strnpy(char *dest,const char *src,size_t n);
复制前n个字符。
strlen()
#include <string.h> char *strlen(const char *s);
功 能:计算字符串长度
参 数:s:字符串的首地址
返回值:返回字符串实际长度,不包括 '\0'在内
strcat()
#include <string.h> char *strcat(char *dest,const char *src);
功 能:用于字符串拼接
参 数:dest:目标字符串首地址
src:源字符串首地址
返回值:目标字符串首地址
例如:
char s1[32]="hello world"; char s2="ni hao"; strcat(s1,s2); printf("s1:%s\ns2:%s\n",s1,s2);
char *strncat(char *dest,const char *src,size_t n);
拼接前 n个字符。
strcmp()
#include <string.h> int strcmp(const char *s1,const char *s2);
功 能:用于比较两个字符串
参 数:s1和s2是用于比较的两个字符串的首地址
返回值:从字符串首个字符开始比较ASCII码值的大小,如果相等继续向后比较
1:s1>s2
0:s1==s2
-1:s1<s2
char s1[32]="hello world"; char s2[32]="hello world"; int a=strcmp(s1,s2); if(a==0) printf("相等\n"); else printf("不相等\n");
int strncmp(const char *s1,const char *s2,size_t n);
比较前n个字符。
递归函数
概念
所谓递归函数就是指一个函数体内直接或间接的调用了该函数本身,直接是指这个函数体内本身调用了他自己,间接是指这个函数体内部调用了一个其他函数,这个其他函数右调用了该函数本身。
- 从调用自身层面:函数递归就是自己调用自己。
- 从编程技巧层面:大事化小,化繁为简。
执行过程
递归函数调用执行分为两个阶段:
- 递推阶段: 从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件。就是从最里层开始算,然后一层一层算,直到终止。
- 回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解。
例如:求5的阶乘5!
#include <stdio.h> #include <stdlib.h> #include <string.h> int fun(int n) { if(n==1) return 1; else if(n>1) return n*fun(n-1); } int main(int argc,char const *argv[]) { int result=0; result=fun(5); printf("%d\n",result); return 0; }
输入一个正整数,求斐波那契数列的该位置的数值
斐波那契数列:1、1、2、3、5、8、13、21、34……
在数学上,斐波那契数列以如下被递归的方法定义:F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2)(n>2,n∈N*)
#include <stdio.h> #include <stdlib.h> #include <string.h> int fun(int n) { if(n<=2) return 1; else return fun(n-1)+fun(n-2); } int main(int argc,char const *argv[]) { int result=0,num; scanf("%d",&num); result=fun(num); printf("result=%d\n",result); return 0; }
封装函数,实现两个数交换的功能。
#include <stdio.h> #include <stdlib.h> #include <string.h> void fun(int *p1,int *p2) { int w; w=*p1; *p1=*p2; *p2=w; } int main(int argc,char const *argv[]) { int n1,n2; scanf("%d %d",&n1,&n2); printf("before swap:n1=%d n2=%d\n",n1,n2); fun(&n1,&n2); printf("after swap:n1=%d n2=%d\n",n1,n2); return 0; }