第九章函数的再探

9.1 函数指针

函数的内存地址存储了函数的开始执行的位置,存储在函数指针中的内容就是这个地址。

9.1.1 声明函数指针

函数指针变量的声明

int (*pfunction) (int);   //函数指针变量的声明

指针只能赋予有这个返回类型的函数
函数指针名
指针只能赋予有这些参数类型的函数

9.1.2通过函数指针调用函数

函数原型

int sum(int a , int b);

函数指针

int (*pfun) (int,int) = sum;

这条语句声明了一个函数指针pfun,它存储函数的地址。该语句还用sum()函数的地址初始化pfun。要提供初始值,只需要使用有所需原型的函数名。
注意:像使用函数名那样使用函数指针名调用该指针指向的函数,不需要取消引用运算符。

9.1.3函数指针的数组

如果需要的是数组的地址,只要使用数组名即可,同样,如果需要的是函数的地址,只要使用函数名即可。

  • 如何通过指针调用函数

指针变量的声明
指针数组变量的声明

数组的所有元素的类型都是相同的
函数指针数组的的所有元素都是相同的类型

数据数组
可以在声明中初始化指针数组的所有的元素
大括号中的初始值的个数确定了数组中的元素的数目
函数指针数组的初始化列表
数组的初始化列表的
函数调用的方式一样

9.1.4作为变元的函数指针

要声明函数指针数组,只需要将数组的大小放在函数指针数组名之后:

int (*pfunctions[10]) (int);

9.2 函数中的变量

  • 简单 、简化开发程序的过程,

9.2.1 静态变量:函数内部的追踪

  • 自动变量:
  • 静态变量:只要程序开始执行,静态变量就一直存在,但是他只能在声明它的范围内可见。不能在该作用域的外部引用
  • 所有的静态变量都会初始化为0,除非将它们初始化为其他值。
    静态变量的用法。

声明一个静态变量

static int count = 0;
  • 注意可以在函数内创建任意类型的静态变量。

静态变量和自动变量的不同
(一)静态变量在函数的作用域内定义,但当执行退出静态变量后,这个静态变量不会销毁。
(二)自动变量每次进入作用域时,都会初始化一次,但是声明为static的变量只能在程序开始时初始化一次。
静态变量只能在包含其声明的函数中可见

9.2.2在函数之间共享变量

  • 全局变量是由位置决定的。只要可以在任意位置访问,就是全局变量

注意:
在c语言中,最好不要给本地变量和全局变量使用相同的名称。虽然合法,但是缺容易出错。

9.3 调用自己的函数:递归(recursion)

(factorial)

函数调用自己称为递归
递归在程序设计中不常见,但是他是一个效率很高的技巧
问题:如何停止递归过程

void Looper(void)
{
   printf("Looper function called.\n");
   Looper();
}

代码中没有停止该过程的机制。
一个调用自己的函数必须包含停止处理的方式

9.4 变元个数可变的函数

  • 编写参数个数可变的函数时,第一个明显的问题是如何指定它的原型。
double average(double v1, double v2, ... );

在前两个固定的变元后面,可以有数量可变的变元

  • 在编写函数时如何引用变元?
    唯一的方法是通过指针间接地指定变元
    <stdarg.h>头文件为此提供了通常实现为宏的例程,

要实现变元个数可变的函数,必须同时使用3个宏:
va_start() 、 va_arg() 、 va_end()

第一个宏的形式如下:

void  va_start(va_list  parg , list_fixed_arg);

这个宏的名称variable argument start
这个函数接受两个变元:va_list类型的指针和为函数指定的最后一个固定参数的名称,
va_list类型:用于存储支持可变参数列表的例程所需信息

double average(double v1, double v2, ...)
{
    va_list parg;
    //More code to go here....
    va_start (parg,  v2);
     //More code to go here...
}

调用va_start()的结果是将变量parg设定为指向传送函数的第一个可变变元。

如何访问每个可变变元值

//function  to calculate the average of two or more arguments
double average (double v1,double v2,...)
{
   va_list  parg;
   double sum = v1+v2;
   double value = 0.0;
   int count = 2;

    va_start  (parg ,v2)
    while((value = va_arg(parg,double)) != 0.0)
    {
         sum += value;
         ++count;
    }
    va_list (parg);
     return sum/count;
}
while((value = va_arg(parg,  double)) != 0.0)

循环条件调用了<stdarg.h>头文件中的另一个函数va_arg(),
va_arg()函数的第一个变元是通过调用va_start()初始化的变量parg,
第二个变元是期望确定的变元类型的说明。
va_arg()函数会返回parg指定的当前变元值,并将它存储到value中,同时会更新parg指针,使之根据调用中指定的类型,指向列表中的下一个变元。
必须有某种方式来确定可变变元的类型。

9.4.1 复制va_list

va_list parg_copy;
va_copy(parg_copy, parg);

9.4.2 长度可变的变元列表的基本规则

变元数目可变的函数的基本规则

  • 在变元数目可变的函数中,至少要有一个固定变元
  • 必须调用va_start()初始化函数中可变变元列表指针的值。变元指针的类型必须声明为va_list类型
  • 必须有确定每个变元的类型的机制
  • 必须有确定何时终止变元列表的方法。
  • va_arg() 的第二个变元指定了变元值得类型。这个指针类型可以在类型名的后面加上*来指定
  • 在退出变元数目可变的函数前,必须调用va_end();

9.5 main()函数

main()函数可以有两个参数,也可以没有参数

//Program 9.8 A program to list the command line arguments
#include <stdio.h>
int main(int argc, char *argv[0])
{
    printf("Program name: %s\n",argv[0]);
    for(int i =1 ; i < argc ; ++i)
         printf("Argument %d: %s\n", i , argv[i]);
    return 0;
}

argc的值至少是1,因为执行程序时,必须输入程序的名称。argv[0]是程序名称。

int arg_value = 0;
if(argc > 1)
    arg_value = atoi(argv[1]);
else
{
   printf("Command line argument missing.");
   return 0;
}

9.6 结束程序

stdlib.h头文件提供的几个函数可以用于终止程序的执行。标识出在程序正常结束时要调用的一个或多个自定义函数。

9.6.1 abort()函数

abort();

9.6.2 exit()和atexit()函数

exit(EXIT_SUCCESS);

如果变元是EXIT_FAILURE,就把表示终止不成功的消息返回给主机环境。无论如何,exit()都会清空所有的输出缓冲区,把它们包含的数据写入目的地,再关闭所有打开的流,之后把控制权返回给主机环境。

调用atexit()会标识应用程序终止时要执行的函数。

void CleanUp(void)
···
if(atexit(CleanUp))
   printf("Registration of  function failed!\n");

把要调用的函数名作为变元传递给atexit(),如果注册成功,就返回0,否则返回非0 值。调用几次atexit(),就可以注册几个函数,且注册函数最多为32个。把几个函数注册为调用exit()时执行,它们就在程序终止时,以注册顺序的倒序调用。即调用atexit()注册的最后一个函数最先执行。

9.6.3 _Exit()函数

_Exit(1);

会正常的终止程序,并把变元值返回给主机环境,区别是它无法影响程序终止时调用_Exit()函数的结果,因为它不调用任何已经注册的函数

9.6.4 quick_exit()和at_quick_exit()函数

9.7 提高性能

有3个工具可以使编译器生成性能更加的代码。

9.7.1内联声明函数

短函数的每次调用可以用实现该函数的内联代码替代,以提高执行性能。

inline double bmi(double kg_wt, double m_height)
{
   return kg_wt/(m_height*m_height);
}

9.7.2使用resteict关键字

errno_t strcpy_s(char * restrict s1, rsize_t  slmax, const char * restrict s2)
{
      //     
}

9.7.3_Noretrun函数限定符

永远不返回函数

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

推荐阅读更多精彩内容