1.指针函数
(1)本质是一个函数,不过它的返回值是一个指针。其声明的形式:类型名 *函数名(函数参数列表);
int * pfun(int, int);
由于“*”的优先级低于“()”的优先级,因而pfun首先和后面的“()”结合,也就意味着,pfun是一个函数;
接着再和前面的“*”结合,说明这个函数的返回值是一个指针。由于前面还有一个int,也就是说,pfun是一个返回值为整型指针的函数。
(2)返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。
(3)void型指针
void指针是一种不明确类型的指针,任何指针都可转换为void指针。
指针有两个非常重要的信息:
- 指针的值(指针目标对象的内存首地址)
- 指针指向对象的类型
注意点:void指针只保存了 指针的值 并没有记录 指针指向对象的类型。因此在用到对void指针解引时,需要先把void指针转换成原本的数据类型。
int n = 500; //定义一个int变量
int * p = &n; //定义int类型指针
void * pv = p; //定义void指针,只保存了p的值(即n的内存首地址)
//错误的写法
printf("%d\n", *pv); //这里会报错,因pv指针没有明确数据类型,因此也不知道需要取多少字节的数据
//正确写法
printf("%d\n", *( (int*)pv ) ); //先把pv指针转为int类型指针,再对其解引
(4)举例:
//打印第m个学生的成绩
#include <stdio.h>
float *find(float(*pionter)[4],int n);//函数声明
int main(void)
{
static float score[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};//3个学生的成绩
float *p; //定义float类型的指针
int i,m;
printf("Enter the number to be found:");
scanf("%d",&m);
printf("the score of NO.%d are:\n",m);
p=find(score,m-1); //将find函数返回的地址给p
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i)); //打印(score+n)行i列数据
return 0;
}
float *find(float(*pionter)[4],int n)/*定义指针函数*/
{
float *pt; //定义float类型的指针
pt=*(pionter+n); //将(pionter+n)行的首地址给pt
return(pt); //返回float类型的地址
}
共有三个学生的成绩,函数find()被定义为指针函数,其形参pointer是指针指向包含4个元素的一维数组的指针变量(即简称数组指针)。
补充:为什么*(point+n)是表示地址?而不是取值。
*(point+1)单独使用时表示的是第 1行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址。因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。
main()函数中调用find()函数,将score数组的首地址传给pointer。
2.函数指针
(1)函数指针是指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。
C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。
(2)函数指针的声明方法为:
返回值类型 ( * 指针变量名) ([形参列表]);
注1:“返回值类型”说明函数的返回类型,“(指针变量名 )”中的括号不能省,括号改变了运算符的优先级。若省略整体则成为一个函数说明,说明了一个返回的数据类型是指针的函数,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例如:
int func(int x); /* 声明一个函数 */
int (*f) (int x); /* 声明一个函数指针,是一个指向返回值为int的函数的指针 */
f=func; /* 将func函数的首地址赋给指针f */
或者使用下面的方法将函数地址赋给函数指针:
f = &func;
赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。
(3)举例:
#include<stdio.h>
int max(int x,int y){return (x>y? x:y);} //返回两个数的最大值
int main()
{
int (*ptr)(int, int); //定义指向函数的指针变量
int a, b, c;
ptr = max; //将max函数的地址给ptr
scanf("%d%d", &a, &b);
c = (*ptr)(a,b); //ptr和max函数地址相同,相当于调用max函数
printf("a=%d, b=%d, max=%d", a, b, c);
return 0;
}
ptr是指向函数的指针变量,所以可把函数max赋给ptr,作为ptr的值,即把max函数的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址。不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你想怎么做了。在程序中把哪个函数的地址赋给它,它就指向哪个函数。而后用指针变量调用它,因此可以先后指向不同的函数。不过注意,指向函数的指针变量没有++和--运算,用时要小心。
(4)补充
- 函数指针可作为参数
- 函数指针可作为返回值
#include <stdio.h>
int add(int num1,int num2)
{
return num1+num2;
}
int sub(int num1,int num2)
{
return num1-num2;
}
int fun(int (*fp)(int,int),int num1,int num2) //函数指针做参数
{
return (*fp)(num1,num2);
}
int (*select(char c))(int,int) //函数指针作为返回值
{
switch(c)
{
case '+': return add;
case '-': return sub;
}
}
int main()
{
int num1,op,num2;
int (*fp)(int,int);
printf("请输入一个表达式,比如(1+3):\n");
scanf("%d%c%d",&num1,&op,&num2);
fp=select(op); //返回的函数指针赋予fp
printf("%d%c%d=%d\n",num1,op,num2,fun(fp,num1,num2));
/*
printf("3+5=%d\n",fun(add,3,5)); 函数名就是函数的首地址
printf("3-5=%d\n",fun(sub,3,5));
*/
return 0;
}
int (*add)(int, int);
定义了一个函数指针add,用于指向返回值为int,并且有两个int参数的函数
int (*select(char c))(int,int) 分解如下:
- select与后面的(char c)结合,说明是一个函数,即select(char c)
- 在和*结合,说明select函数的返回值是一个指针,即*(select(char c))
- 在和后面的(int,int)结合,说明select函数返回的指针指向函数,不是指向int类型,即int ((*select(char c)))(int,int)
- 总结:返回值为select函数指针,该返回值指向一个 返回值为int,两个参数为int类型的函数
(5)使用typedef关键字
int (*PF)(int *, int); PF是一个函数指针变量,用于指向返回值为int,一个int类型参数的函数。
当使用typedef声明后,则PF就成为了一个函数指针类型,即 typedef int (*PF)(int *, int); 这样就定义了返回值的类型。
再用PF作为返回值来声明函数:PF func(int); // func(int)就是一个返回值为函数指针,一个int类型参数的函数
再用PF来声明:PF phead; //phead就是一个函数指针
(6)地址直接操作函数和指针
if((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)
其中:
*(vu32*)(0X20001000+4))== (*(__IO uint32_t*)(0X20001000+4))==(*(volatile unsigned int*)(0X20001000+4))
(*(vu32*)(0X20001000+4)) 通过内存寻址访问地址为(0x20001000 + 4)中的值
(0X20001000+4)只是一个常量;
(volatile unsigned int*)(0X20001000+4) 将0x20001000 + 4这个常量强制转化成volatile unsigned int类型的指针;
(*(volatile unsigned int*)(0X20001000+4)) 相对于取0x20001000 + 4地址处的值。
为什么要将0x20001000 + 4这个常量强制转化成volatile unsigned int类型的指针呢?
假设定义*P , 取地址符 &P 得到P地址,这个地址是随机的系统分配空闲地址。我们不能直接给P赋地址值,因为那样是不合法的,我们只能给指针赋值为NULL。
但是现在必须让指针指向一个已知地址(0X20001000+4),必须转换类型,在地址前面加上指针转换的类型我们这里用的volatile unsigned int*,如果不转换,不成功。
假如使用一个32位处理器,要对一个32位的内存地址进行访问,可以这样定义:
#define RAM_ADDR (*(volatile unsigned long *)0x01234567)
然后就可以用C语言对这个内存地址进行读写操作了。
读:tmp = RAM_ADDR;
写:RAM_ADDR = 0x55;
这里使用 volatile关键字的好处就是:
1.volatile是一个类型修饰符(typespecifier);
2.volatile关键字声明的变量,编译器对访问该变量的代码就不再进行优化;
3.volatile 关键字声明的变量,对变量的存取不能缓存到寄存器,每次使用时需要在内存中重新存取。
(7)使用typedef定义函数指针
typedef的意义
typedef int a[10]; // a 类型是 int[10];(存放int型数据的数组)
a arr; // 定义一个数组:int arr[3];
typedef void (*p)(void); //p 类型是void ( * )void
p A; //是指void(*A)(void);
语法上typedef属于存储类声明说明符。
a[10]不是int的别名,(*p)(void)不是void的别名。
上面的语句把a声明为具有10个int元素的数组的类型别名,p是一种函数指针的类型别名。
函数指针对象赋值用法
两种用法
typedef void (*IapFun)(void); //定义函数指针
void func(void); //定义函数
IapFun fun = func; //为函数指针对象赋值
fun(); //这里的fun()其实就相当于跳转到了func()里
typedef void (* IapFun)(void); //定义函数指针
IapFun jump2app; //定义函数指针对象
jump2app=(IapFun) * (vu32*)(appxaddr+4); //为函数指针对象赋值 appxaddr为函数指针地址,例如0x08000000
jump2app(); //调用函数