C语言函数讲解(二)

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

总结

这篇文章讲解了函数和数组,函数和指针的联系,读者要什么的明白什么是函数指针,什么是指针函数,以及要了解递归函数的一些知识,希望读者好好的学习和揣摩。

结尾

希望读者真诚的对待每一件事情,每天都能学到新的知识点,要记住,认识短暂,开心也是过一天,不开心也是一天,无所事事也是一天,小小收获也是一天,所以,你的路你自己选择。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,422评论 3 44
  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,585评论 1 19
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,504评论 1 51
  • 上一章-云嫣之计 小说目录 第三十七章-明南来救 上官云嫣已无脸再见尘夕了,她心肠再阴毒,但也并非是一个厚颜无耻之...
    冰寒三尺阅读 370评论 0 8
  • 提交用户的隐私数据 一定要使用POST请求提交用户的隐私数据 GET请求的所有参数都直接暴露在URL中 请求的UR...
    iOS开发攻城狮阅读 996评论 0 5