C语言函数讲解(二)
谨记
都说人生如戏,戏如人生,其实就是这样的,我们每个人在社会环境扮演的不同的角色,有点扮演军人,有的扮演农民,有的扮演白领,有的扮演老师等等,每一个职业都是他们扮演的角色,这些形形色色的角色却创造了这样一个美妙的世界,在人生扮演的旅途上,希望每个角色都能坚持到最后,因为那是一份职业操守,更是一份责任。是的,责任,从你落地的那一刻起,你就得肩负起扮演角色的责任,在扮演角色的同时你还需要对你自己的角色负责,或许你听着感觉很奇怪,其实,就是这样的。一份责任不是对自己的负责,更是对其他扮演的角色负责。
前言
本篇文章将为读者展现函数和指针的用法,比如指针函数和函数指针,为什么会讲这个呢?因为在我们实际开发中我们会经常看到,在进行实际开发的时候,有的时候我们需要传参,而参数就是指针,有的时候我们需要用一个指针还指向一个函数,这些都是我们需要的技术,当你去看别人写的代码的时候,或许你也会经常看到,所以,希望读者认真学习和掌握。
指针函数
什么叫指针函数
通常一个函数都有返回值。如果一个函数没有返回值,则该函数是一个无值型函数。若一个函数的返回值是指针,则称函数为指针函数。
指针函数的定义的一般形式如下:
<数据类型> *<函数名称>(<参数说明>)
{
语句序列;
}
其中,<数据类型> 、<函数名称>、<形式参数说明> 等与一般函数定义相同。在<函数名称>之前的*符号,说明该函数返回一个地址量。
我们可以通过一个示例来看:
#include <stdio.h>
int *sum(int *a, int *b){
int j;
int *p = &j;
j = *a +*b;
return p;
}
int main(int argc, const char * argv[]) {
int i = 10;
int k = 20;
int *s;
s = sum(&i, &k);
printf("%d\n",*s);
return 0;
}
输出结果:
30
Program ended with exit code: 0
改程序中声明和实现了一个返回值为int *,函数名为sum的一个函数,通过这个返回值为int ,因此,像这类的我们称为指针函数,
在实现一个指针函数时,读者应该特别注意,指针函数返回的地址,在主调函数中,必须是有效的,是可以访问的内存。在主函数中我们调用了这个函数,虽然打印的是s,其实就是变相的用了函数返回的p。
如果我们函数是一个指针函数,但是返回的是一个局部变量,又会出现什么情况呢?我们通过例子来讲解:
#include <stdio.h>
char *mystring(void){
char str[20] = {0};
strcpy(str, "Welcome");
return str;
}
int main(int argc, const char * argv[]) {
printf("%s\n", mystring());
return 0;
}
输出结果:
(第一次运行):
\223\316\346\271@\364
Program ended with exit code: 0
(第二次运行):
a\312\334\310"P
Program ended with exit code: 0
注意
通过上面的示例,我们可以看出,mystring函数的返回值是char *,因此,这是一个指针函数,函数返回了字符数组名str,确实是一个char *。但是,编译程序时,有一个警告,提示返回了一个局部变量的地址,程序的执行结果,打印的是乱码。
在实现一个指针函数时,读者应该特别注意,指针函数返回的地址,在主调函数中,必须是有效的,是可以访问的内存。在上面程序中,str是函数内部的局部数组,局部变量分配在堆栈中,当函数执行完后,局部变量自动释放,在主调函数中,不能再访问,因此会有警告。访问一段释放的内存,是非法操作,显示的是乱码,若修改非法内存中的值,程序的后果可能更严重,是不可预料的。
了解了上面的注意点后,那么,针对于上面的这个程序我们可以进行一个修改;
#include <stdio.h>
char *mystring(void){
//把数组声明为一个static,一个静态的
static char str[20] = {0};
strcpy(str, "Welcome");
return str;
}
int main(int argc, const char * argv[]) {
printf("%s\n", mystring());
return 0;
}
输出结果:
Welcome
Program ended with exit code: 0
在该程序中,把局部数组改成了静态数组。静态变量,当程序结束时才回收内存。因此,在main函数中,依然可以访问数组。
针对于上面的示例,我们还可以改成另外一种写法:
#include <stdio.h>
char *mystring(void){
char *str = “Welcome”;
return str;
}
int main(int argc, const char * argv[]) {
printf("%s\n", mystring());
return 0;
}
输出结果:
Welcome
Program ended with exit code: 0
上面两个程序会有相同的执行结果,在程序中,str指向一个字符串常量,字符串常量和静态变量类似,都是程序结束时,才释放内存,因此指针函数可以返回一个字符串常量的地址。当然指针函数还可以返回堆上的地址,这里就先不介绍了,后面讲解内存管理的时候在详细说明。
提示
指针函数不可以返回局部变量的地址,可以返回的地址有3种情况:一、静态变量的地址;二 、字符串常量的地址;三、堆上的地址。
对于指针函数,我们就介绍这么多,当然以前学的字符串的拷贝和拼接函数,其实都是指针函数,读者可以回去看看,关于数组讲解二里面的字符串相关知识。
函数指针
函数指针是专门用来存放函数地址的指针。函数地址是一个函数的入口地址,函数名代表了函数的入口地址。
当一个函数指针指向了一个函数,就可以通过这个指针来调用该函数,可以将函数作为参数传递给函数指针。
函数指针变量说明的一般形式如下:
<数据类型> (*<函数指针名称>)(<参数说明列表>);
<数据类型>是函数指针所指向的函数的返回值类型;
<函数指针名称>符合标识符命名规则;
<参数说明列表>应该与函数指针所指向的函数的形参说明保持一致;
(*<函数指针名称>)中,*说明为指针,()不可缺省,表明为指向函数的指针。
定义函数指针类型
函数指针类型说明的一般形式如下:
typedef <数据类型> (*<函数指针类型名称>)(<参数说明列表>);
在函数指针变量说明前面,加上typedef,就变成了函数指针类型。
可以通过一个示例看看
#include <stdio.h>
typedef int (*MFunc)(int, int);
int test(int a, int b, MFunc pFunc);
int plus(int a, int b); //函数声明
int minus(int, int); //函数声明,缺省形参名称
int main(int argc, const char * argv[]) {
int x = 5, y = 8;
MFunc pFunc;
pFunc = plus;
printf("%d\n", (*pFunc)(x, y));
pFunc = minus;
printf("%d\n", (*pFunc)(x, y));
printf("%d\n", test(15, 5, plus));
printf("%d\n", test(15, 5, minus));
return 0;
}
int plus(int a, int b){
return (a+b);
}
int minus(int a, int b){
return (a-b);
}
int test(int a, int b, MFunc pFunc){
return ((*pFunc)(a, b));
}
输出结果:
13
-3
20
10
Program ended with exit code: 0
函数指针数组
函数指针数组是一个包含若干个函数指针变量的数组。
定义形式如下:
<数据类型> ( * <函数指针数组名称> [<大小>] ) ( <参数说明列表> );
其中,<大小>是指函数指针数组元素的个数。
通过示例代码来看:
#include <stdio.h>
int plus(int, int);
int minus(int, int);
int main(int argc, const char * argv[]) {
int (*pFunc[2])(int, int);
int i;
pFunc[0] = plus;
pFunc[1] = minus;
for (i = 0; i < 2; i++)
printf ("%d\n", (* pFunc[i])(15, 85));
return 0;
}
int plus(int a, int b){
return (a+b);
}
int minus(int a, int b){
return (a-b);
}
输出结果:
100
-70
Program ended with exit code: 0
在该程序中,pFunc是一个含有2个元素的一维数组,则pFunc[0]是第一个函数指针,pFunc[1]是第二个函数指针。
那么,函数指针就介绍到这里,当然,例子大家也可以去在网上搜一搜,这类例子我就不多举了。
递归函数
所谓递归函数是指一个函数的函数体中直接调用或间接调用了该函数自身的函数。
递归函数调用的执行过程分为两个阶段。
递推阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件。
回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解。
#include <stdio.h>
double factorial(int n);
int main(int argc, const char * argv[]) {
double r;
r = factorial(5);
printf("5!=%lf\n", r);
return 0;
}
double factorial(int n){
if (n <= 1)
return 1;
return (n × factorial(n-1));
}
输出结果:
5!= 120.000000
Program ended with exit code: 0
该程序实现了n!,已知0!或1!是1。递归规律是:n! = n × (n-1)!。
函数调用机制说明
任何函数之间不能嵌套定义, 调用函数与被调用函数之间相互独立(彼此可以调用)。 发生函数调用时,被调函数中保护了调用函数的运行环境和返回地址,使得调用函数的状态可以在被调函数运行返回后完全恢复,而且该状态与被调函数无关。
被调函数运行的代码虽是同一个函数的代码体,但由于调用点,调用时状态, 返回点的不同,可以看作是函数的一个副本,与调用函数的代码无关,所以函数的代码是独立的。被调函数运行的栈空间独立于调用函数的栈空间,所以与调用函数之间的数据也是无关的。函数之间靠参数传递和返回值来联系,函数看作为黑盒。
递归函数的调用形式
递归调用有直接递归调用和间接递归调用两种形式。
直接递归即在函数中出现调用函数本身。
例如,求斐波那契数列第n项。 斐波那契数列的第一和第二项是1,后面每一项是前二项之和,即1,1,2,3,5,8,13,…
#include <stdio.h>
long fib(int n){
if (n == 0 || n == 1)
return 1;
else
return (fib(n-1)+fib(n-2));
}
int main(int argc, const char * argv[]) {
int i;
for (i = 0; i < 8; i++)
printf("%ld ", fib(i));
printf("\n");
return 0;
}
输出结果:
1 1 2 3 5 8 13 21
Program ended with exit code: 0
间接递归调用是指函数中调用了其他函数,而该其他函数却又调用了本函数。例如,下面的代码定义两个函数,它们构成了间接递归调用:
int fnl(int a){
int b;
b=fn2(a+1); //间接递归
}
int fn2(int s){
int c;
c=fnl(s-1); //间接递归
}
上例中,fn1()函数调用了fn2()函数,而fn2()函数又调用了fn1()函数。
递归的条件
一个问题能否用递归实现,看其是否具有以下特点。
① 须有完成函数任务的语句。
#include <stdio.h>
void count(int val) //递归函数可以没有返回值
{
if(val>1)
count(val-1);
printf(“ok:%d\n”, val);
}
② —个确定是否能避免递归调用的测试。
例如,上例的代码中,语句"if(val>1)"便是—个测试, 如果不满足条件,就不进行递归调用。
③ 一个递归调用语句。
该递归调用语句的参数应该逐渐逼近不满足条件,以至最后断绝递归。
例如,上面的代码中,语句“if(val>1)”便是一个递归调用,参数在渐渐变小,这种发展趋势能使测试“if(val>1)”最终不满足。
④ 先测试,后递归调用。
在递归函数定义中,必须先测试,后递归调用。也就是说,递归调用是有条件的,满足了条件后,才可以递归。
完整的示例代码:
#include <stdio.h>
void count(int val){
if (val > 1)
count(val - 1);
printf("OK:%d\n", val);
}
int main(int argc, const char * argv[]) {
int n = 10;
count(n);
return 0;
}
输出结果:
OK:1
OK:2
OK:3
OK:4
OK:5
OK:6
OK:7
OK:8
OK:9
OK:10
Program ended with exit code: 0
总结
这篇文章讲解了函数和数组,函数和指针的联系,读者要什么的明白什么是函数指针,什么是指针函数,以及要了解递归函数的一些知识,希望读者好好的学习和揣摩。
结尾
希望读者真诚的对待每一件事情,每天都能学到新的知识点,要记住,认识短暂,开心也是过一天,不开心也是一天,无所事事也是一天,小小收获也是一天,所以,你的路你自己选择。