在openssl里出现了大量的指向指针的指针和函数指针,一直不太理解,今天看书上有一章介绍指针的高级话题,赶紧把它记录下来。
1,指针以及初始化
指针是一个地址,它指向内存中某个指定类型的值,而操作符*就是访问指向这个地址的数据。讨论一下指针的初始化。
考虑:
int *a;
*a=100;
也许会觉得这样没有错误,因为(*a)就是int型的,所以把100赋予一个int型的没错。但是我们在这里创建了一个指针变量a,但是没有给a初始化,然后用*操作符去访问它,当然是会出错的。
2,指向指针的指针,下面分别看几个例子
char string[3]={'c','d','e'}.定义了三个char型的变量string[0]='c',string[1]='d',string[2]='e'.
char *pstring={"cde"},定义了一个字符串,其中string[0]='c',string[1]='d',string[2]='e'.
现在明显可以看出上面两者的联系了。其实string=pstring.
char string[3][3]={'M','o','n';'T','u','e';'W','e','d'};这样string[0][0]='M',,,,,
char *pstring[3]={"Mon","Tue","Wed"},定义了三个char *string型的变量。string[0]="Mon",string[0][0]='M'
char **ppstring.那这个是什么呢,他就是个指向指针的指针,可以直接初始化ppstring=pstring,或者可以ppstring=&(pstring[0]),使用的时候可以完全和*pstring[3]差不多使用了。
所以指向指针的指针也就是多了一个间接访问而已,只要知道了指针就是地址,*就是去访问地址的数据就可以了。
3,函数指针。
int (*f)(),前面一个括号迫使间接访问在函数调用之前,那么就是说访问内存中的某个位置的函数。这就是函数指针的概念。
初始化:int f(int); int (*pf)(inf)=&f;
调用方式:int ans=pf(25);
下面讨论两个具体应用的问题:
1),回调函数
我们先看看下面这段程序:
int comp(int a,int b)
{
if(a==b)return 0;
else return 1;
}
完成的功能实在是简单,就是比较一下两个数是否一致。但是你是否发现这个只能比较int型的数据,要是比较char的呢,那必须得重新写一个函数,比较float也得重新写一个,这样是不是有点麻烦,并且易用性也不好,换一个类型就要换一个函数。那么我们考虑是否在comp的时候不传具体的类型呢,因为我们根本也不知道要传什么类型给comp。解决难题的办法就是把参数类型声明为void *,表示"一个指向未知类型的指针"。然后加一个回调函数作为参数,这样在按照不同的类型编写不同的回调函数,在上层就保证了comp函数的易用性。下面请看改写的函数
int comp(void *a,void *b, int (*compare)(void const *,void const *))
{
if(compare(a,b))return 1;
else return 0;
}
int compare_int(void const *a,void const *b)
{
if(*(int*)a==*(int*)b) return 0;
else return 1;
}
int compare_char(void const *a,void const *b)
{
if(*(char*)a==*(char*)b) return 0;
else return 1;
}
在主函数中可以这样调用
int a=2;int b=2;
comp(&a,&b,compare_int);
char c='d';char d='d';
comp(&d,&c,compare_char);
现在在C++中已经有重载和模板的技术了,上面的任务根本不用这么麻烦实现的。但是openssl是用C语言写的,里面大量的void类型,并且大量的指向函数的指针,希望这点讲解对大家看openssl能有点帮助。
2),转移表
我们下面看个例子,在一个计算器的例子中,有如下一些语句:
switch(oper)
case ADD:
result=add(op1,op2);
break;
case SUB:
result=sub(op1,op2);
break;
case MUL:
result=mul(op1,op2);
break;
case DIV:
result=div(op1,op2);
break;
...
对于一个功能复杂的计算器,那么switch语句将非常长。为了使用switch语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来完成这个任务。转换表就是一个函数指针数组。
创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原形出现在这个数组的声明之前。
double add(double,double);
double sub(double,double);
double mul(double,double);
...
double (*oper_func[])(double,double)={add,sub,mul,div,...};
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子假定ADD是0,SUB是1,MUL是3,,,
第2个步骤是用下面语句替换前面整条switch语句:
result=oper_func[oper](op1,op2);
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。