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, ... );
在前两个固定的变元后面,可以有数量可变的变元
- 在编写函数时如何引用变元?
唯一的方法是通过指针间接地指定变元
头文件为此提供了通常实现为宏的例程,
要实现变元个数可变的函数,必须同时使用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)
循环条件调用了
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
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);
}