对于初学者来说,指针是比较头疼的东西,但是,如果你想深入了解底层的一些东西,指针你又是避不开的。既然逃避不了,干嘛不加入呢?一起继续探索指针中更高级更好玩的技巧吧!
指针也指内存地址,指针变量是用来存放内存地址的变量,在32位的操作系统中,它的大小为4个字节,在64位的操作系统中则是8个字节,依次类推…
c语言中常见的指针变量定义
/**********************part one**********************/
//声明一个存储整型变量地址的指针
int *p;
//使用方式
int c;
p = &c;
/**********************part two**********************/
//声明一个存储 指向整型变量的指针 的地址的指针 即指向指针的指针
int **pp;
//使用方式
int *p_1;
pp = &p_1;
/**********************part three**********************/
int *p[]; //声明一个数组,该数组存储的内容为整型变量地址即整型指针
//使用方式
int d;
p[0] = &d;
/**********************part four**********************/
int (*p)[4]; //声明一个指针变量,指向的是数组
//使用方式
int a[4];
p = &a;
从回顾中,我们简单回顾了一些常见的指针用法,下面继续看看一些有意思的声明以及理解这个声明到底是什么意思。
注意: 用于声明变量的表达式和普通的表达式在求值时所使用的规则相同。
示例一
//一个整型变量
int i;
//一个指向整型的指针
int *i_p;
针对这个*i_P
,可以理解成被声明为了一个整数,但它用*
表示了这个表达式,所以可以理解成这是一个指针变量,它指向的地址存储的是整数
示例二
//声明一个函数原型
int f();
//声明一个返回整型指针的函数原型
int *f();
对于表达式*f()
,优先级最高的是()
,所以声明时最先与f
结合的是()
,所以这是一个函数,既然知道了它是一个函数,那么剩下的部分就应该的是它的返回值类型了,所以可以理解成这是一个回整型指针的函数原型
示例三
//1.
int (*f)();
//2.
int *(*f)();
如果能够理解示例一与二的推断方式,那么针对这个示例应该是很容易理解。
表达式(*f)()
中由于括号优先级,可以看到f
先与*
结合,所以f
是一个指针,接下来的是与第二个括号结合,说明这个指针指向的是一个函数,这个函数的返回值为整型。所以整体理解就是这是一个指向/返回整型的函数的/首地址的指针,即函数指针
表达式*(*f)()
这个与(*f)()
声明多了左边的*
,也就是相当于函数的返回类型是整型指针,故可以理解为这是一个指向/返回整型指针的函数的/首地址的指针
示例四
//以下先省略数组长度
//1.
int * f[];
//错误示例
//2.
int f[]();
//3.
int f()[];
下面序号1、2、3分别代码中的三个声明:
*f[]
中优先级最高的是[]
,所以这个是一个数组,数组元素存储的是整型指针。f[]()
中的f
先与[]
结合,所以它是一个数组,然后它的是元素类型是函数,这是错的!,对于一个函数来说,不同的函数长度是不一样的,然而对于数组来说,要求是一样的,所以这个声明无法成立。f()[]
中的f
先与()
结合,所以它是一个函数,那么它的返回值就是数组,然而函数中不能够返回数组,所以这个声明也是错的。示例五
int *(*f[])();
这个声明表达式阅读起来确实有点困难!但是依然可以用上面的拆解方式进行解读。*(*f[])()
是由*
,(*f[])
,()
组成,显然(*f[])
优先级更高,所以根据这个括号的内容知道这个f
一定是一个数组,然后它应该存储的是xx类型的指针,要看是什么类型的指针,还得看外面,噢是()
啊,那就是函数,所以它存储的内容是函数的指针。那么这个函数的返回值是什么呢?噢,左边是一个*
,所以它是返回了一个整型的指针。
分析这个表达式实在是头大的话,建议反复阅读这几个示例!
注意,如果函数有参数的话,声明涉及到函数原型时最好注明参数类型,如:
int (*f)(int,float);
int *(*g[])(int,float);
函数指针上面举例子的时候,已经被举例过了,它就是指向函数的指针变量。在c中,函数也会有存储空间的,也有对应的入口地址,函数名一般就是指向这个入口的地址。
函数指针的初始化与使用
int f(int);
int (*pf)(int) = &f; //这个&是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。
int ans;
ans = f(25); //函数名调用
ans = (*pf)(25); //间接访问得到一个函数名然后调用
ans = pf(25); //与第一个一样,间接访问非必须
说起回调函数,其实在很多地方能够见到这个东西,比如点击按钮时,执行什么;发起一个http
异步请求,处理响应结果时等等。
回调函数往往时这样: 把一个函数作为参数传递给其他函数,后者将会”回调“该函数。
示例代码
#include
#include
#include
#define DataType char
struct Node{
DataType value;
struct Node * next;
};
typedef struct Node* LinkNode;
typedef struct Node* LinkList;
LinkList creat_linklist();
void add_element(LinkList list,DataType value);
/************************************key point*****************************************************/
int search_value(LinkList list,void const* value,int (*compare)(void const * ,void const *));
int compare_self_impl(void const * op1,void const * op2);
int compare_self_impl_char(void const * op1,void const * op2);
int main()
{
//创建一个空链表
LinkList list=creat_linklist();
add_element(list,'a');
add_element(list,'b');
add_element(list,'d');
char c='a';
int result = search_value(list,&c,compare_self_impl_char);
printf("%d", result);
return 0;
}
LinkList creat_linklist()
{
LinkList list = (LinkList) malloc(sizeof(struct Node));
list->next=NULL;
return list;
}
void add_element(LinkList list,DataType value)
{
if(!list)
exit(ERROR_INVALID_PARAMETER);
LinkNode new_node = (LinkNode) malloc(sizeof(struct Node));
new_node->next=list->next;
new_node->value=value;
list->next=new_node;
}
/************************************key point*****************************************************/
int search_value(LinkList list,void const * value,int (*compare)(void const * ,void const *))
{
int result = 0;
if(!list)
exit(ERROR_INVALID_PARAMETER);
LinkNode p = list->next;
while (p)
{
if(compare(&p->value,value) == 1)
{
result = 1;
break;
}
p=p->next;
}
return result;
}
//实现自定义的比较函数 数字
int compare_self_impl(void const * op1,void const * op2)
{
return *((int *)op1) == *((int *)op2) ? 1:0;
}
//实现自定义的比较函数 字符
int compare_self_impl_char(void const * op1,void const * op2)
{
return *((char *)op1) == *((char *)op2) ? 1:0;
}
search_value
函数用于查找某个值是否在链表中,它的内部必然涉及两个元素进行比较。如果我们的链表只针对某个特定的元素类型,那么它的实现就比较简单,直接内部进行取值比较就行了。但是如果想要更为通用,那么这个比较就不能在函数内部实现了(当然,你直接实现也可以,但是你得在内部进行判别类型)。这时候函数指针的用处就体现出来了,只需声明一个函数指针参数,其他元素类型只需重写这个比较函数,并提供函数地址,由search_value
函数在内部调用即可。
search_value
函数的原型如下:
int search_value(LinkList list,void const * value,int (*compare)(void const * ,void const *));
不知道你是否注意到其中的int (*compare)(void const * ,void const *)
参数类型?由于声明这个函数原型的时候,是不知道具体参与比较的元素的类型的,所以为了匹配所有元素,故将类型定为void *
。到时候声明一个符合该函数指针要求的函数时,则需在内部进行强制类型转换即可,这时肯定能知道所需要比较的参数类型了。
假设,我们想要实现一个简单的加减乘除计算器,常见的实现方式:
int select_no,op1,op2,result;
switch (select_no) {
case ADD:
{
result= op1 + op2;
break;
}
case SUB:
{
result= op1 - op2;
break;
}
case MUL:
{
result= op1 * op2;
break;
}
case DIV:
{
result= op1 / op2; //这里实现有点小瑕疵,但也很常见的
break;
}
}
如果我们想再增加更多的功能模块的话,这个switch
语句将会更长。如果想要达到良好的程序设计,建议把具体操作与选择操作分开,即加减乘除操作的实现与选择分开,所以推荐使用函数实现,以便于后期扩展。
对于switch
,需要知道的是: 表示操作符的分支必须是整数。所以我们可以想到,如果是从0开始的连续的整数,可以用数组来映射的,这就是转移表。所谓数组,就是存储函数指针的数组。
示例代码
#include
double self_add(double a,double b){ return a+b;}
double self_sub(double a,double b ){return a-b;}
double self_mul(double a,double b ){return a*b;}
double self_div(double a,double b ){return a/b;}//这里实现有点小瑕疵
//定义一个函数指针数组,然后初始化
double (*oper_func[])(double ,double )={
&self_add, &self_sub, &self_mul, &self_div
};
enum OP{ADD=0,SUB,MUL,DIV}; //定义 枚举
int main()
{
double a=oper_func[ADD](3,1);
printf("%lf",a);
return 0;
}
这里需要主要注意的是:
既然用到了数组,就有可能发生越界行为,就可能会造成不可预料的结果,所以建议在使用需要保证在合理的范围内。