一:高级声明
int f;
int *f;
*f 声明为一个整数,因此f是一个整形数组
那么:
int *f(); //返回值为int *的函数f
int (*f)(); //f是一个函数指针,它所指向的函数返回一个整形值
int *(*f)(); //和前面相同,只是函数的返回值是一个整形指针
再把数组考虑进去:
int f[];
int *f[];
分别声明了一个数组和一个指针数组
再看两个非法的例子:
int f()[];
int f[]();
合法的应该如下:
int (*f[])(); //对括号内首先求值,*f[],f是一个数组,数组元素类型是函数指针,函数返回值是整形
int *(*f[])(); //和上面一样,不同点在于函数的返回值,是int指针
二:函数指针
函数指针在两个方面最有用途:回调函数、转移表
声明一个函数指针之后,在对它进行间接访问之前,还需要初始化,使它指向某个函数:
int f(int);
int (* pf)(int) = &f; //第二个声明,创建函数指针pf,初始化指向f函数。函数指针初始化之前,具有f的原型很重要!否则编译器无法检查f的类型是否和pf所指向的类型一致
操作符 & 是可选的,函数名在使用时,总是由编译器转换为函数指针,& 显示说明。
在函数指针被声明并且初始化之后,我们就可以用 三种方式调用函数:
int ans;
ans = f( 25 );
ans = ( *pf )( 25 );
ans = pf( 25 );
1:回调函数
将参数类型声明为 void * ,再强制转换为正确的类型。
比如单链表的查找,我们自己做一个比较函数:
#include <stdio.h>
Node *search_list(Node *node, void const *value, int (*compare)(void const *, void const *))
{
while (node != NULL) {
if (compare(&node->value, value) == 0)
break;
node = node->link;
}
return node;
}
比较函数需要 采用类似strcmp的返回值标准,大于返回正,等于返回0,小于返回负数
int compare_data(void const *a, void const *b)
{
if (*(int *)a > *(int *)b)
return 1;
else if (*(int *)a < *(int *)b)
return -1;
else
return 0;
}
调用的时候:
desired_node = search_list(root, &desired_value, compare_data);
注意强制转换,比较函数的参数必须声明为 void * 用来匹配查找函数的原型。
如果想要再一个字符串链表中进行查找,下面的代码可以完成任务:
desired_node = search_list(root, "desired_value", strcmp);
刚好,strcmp所执行的比较和我们所需要的一样。
2:转移表
给个例子,下面的代码实现一个袖珍计算器:
switch () {
case ADD:
result = add(op1, op2);
case SUB:
result = sub(op1, op2);
case MUL:
result = mul(op1, op2);
case DIV:
result = div(op1, op2);
...
}
如果是一个比较新的计算器,那么 switch语句会非常地长。
因此,可以考虑 调用函数,把具体操作和操作的代码分割开。
转换表: 就是一个函数指针数组 创建传唤表:声明并且初始化一个函数指针数组;确保函数原型出现再这个数组之前!
double add(double, double);
double sub(double, double);
...
double ( *oper_func[] )(double, double) = {
add, sub, mul, div, ...
}
add是0, sub是1, mul是2... 然后用它来替换前面的语句:
result = oper_func[ oper ](op1, op2);
oper 从数组中选择正确的函数指针,而函数调用操作符将执行这个函数
警告!!!
转换表中,数组下标越界不合法! 一旦出现这样的错误,调试起来非常不方便! 首先,下标标示的位置可能再分配给该程序的内存之外;如果程序没有终止,非法下标被提取,处理器访问该位置,调试非常困难!(联想:溢出攻击就是利用一定格式的长字符串修改ret返回地址从而改变程序流向!)
三:命令行参数
可以利用 argc来判断输入的参数,而 argv[0] 一定是 .c 文件的文件名!
Unix 中的 ls 程序就是如此
Unix的 echo 命令也是如此,比如我们做一个简单的打印所有命令行参数的程序:
#include <stdio.h>
#include <stdlib.h>
'
int main(int argc, char *argv[])
{
while (*++argv != NULL) {
printf("%s\n", *argv);
}
return EXIT_UCCESS;
}
再举个例子:
比如我们需要处理这样的命令行: prog -a -b -c name1 name2 name3
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
void process_standard_input( void );
void process_file( char *file_name );
int option_a, option_b;
int main(int argc, char *argv[])
{
// handle options: check whether it is started with '-'
while (*++argv != NULL && **argv == '-') {
// check the letter after the '-'
switch ( *++*argv ) {
case 'a':
option_a = TRUE;
break;
case 'b':
option_b = TRUE;
break;
...
}
// handle the filename parameter
if (*argv == NULL)
process_standard_input();
else {
do {
process_file( *argv );
} while (*++argv != NULL);
}
}
return EXIT_SUCCESS;
}
四:字符串常量
当一个字符串常量出现在表达式之中,它的值是一个指针常量! 比如:
"xyz" + 1 是什么意思呢?
没错,指针常量,因此,它的结果是一个指针,指针指向第二个元素: y
那么这个呢: *"xyz" ???
对指向字符的指针进行间接访问,结果应该是它指向的字符: x ,就是字符x!
再看个例子: "xyz"[2] 没错,下标访问,得到 z !
最后看个例子: *( "xyz" + 4 )
最后这个例子包含一个错误,偏移量 4 超出了字符串的范围,所以结果是一个不可以预测的字符! ( +3 是 '\0' )
什么时候可以使用到这个知识呢?
比如我们做十进制转换为十六进制
要求: 0 ~ 9 用数字, 10 ~ 15 用A ~ F 表示
先看典型:
remain = value % 16;
if (remain < 10)
putchar(remain + '0');
else
putchar(remain + 'A' - 10); //偏移量
再看:
putchar( "0123456789ABCDEF" [value % 16] ); //可读性下降,增加一条注释即可