c语言:高级指针话题

一:高级声明

	
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返回地址从而改变程序流向!)



三:命令行参数


int main(int argc, char *argv[])


可以利用 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] ); 	//可读性下降,增加一条注释即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值