指针篇
数组指针,就是指向数组的指针
指针数组,就是一个数组,它的成员是指针
数组指针:
int arr[10]={0}
arr代表着首元素的地址,arr 等同于 &arr[0]
同时,arr还是数组名
而 &arr 表示的是 数组的地址
通常,在声明指针的时候,例如int *A=&B,就是说创建了一个整形指针A,它指向B的地址(B的地址被赋值给了A),那么A==&B,*A==B
同理,
int n[10]={0};
int (*arr)[10]=&n;
则说明arr==&n,*arr==n(也就是&n[0])
所以*(*arr)==n[0]
由此可以知道偏移量的规律
n+1,这里的n是首元素地址,因此偏移量是一个元素,也就是一个整形的大小
n+1==&n[1]
arr+1,这里的arr是数组指针,是数组的地址,因此偏移量是一个数组的大小,一个数组的大小是多少,这取决的arr指向的数组地址对应的数组的特征,比如arr就是指向元素为10个整形的数组,那么偏移量就是40字节
;;;;;;;;;;;;;;;;;;;;;;;;
这里复习一下十六进制和地址
在C语言里,地址用十六进制表示,十六进制的特征是0x开头,后面跟八位数
在十六进制中,0-9用阿拉伯数字表示,10-15用A-F表示
将十进制转化为16进制,可以参考一下将十进制转化为2进制,用短除法
;;;;;;;;;;;;;;;;;;;;;
这里还有二进制转化成十六进制的比较简单的方法
;;;;;;;;;;;;;;;;;;;;;
如果上面的例子中
n的地址是 0x027CFA8C
那么 n+1 是 0x027CFA90(增加了一个偏移量,也就是四个字节,现在说的4个字节的“4”是十进制的,转化为十六进制也是4,因此直接和十六进制地址相加就可以了,0x027CFA8C+4)
arr的地址也是0x027CFA8C(数组指针是将指针指向的地址对应的数据圈定为了数组,而不再和以前一样是整形等单独的数据类型,但是无论是数组还是数组的元素,数据存储的位置并没有改变)
arr+1的地址就是arr向后偏移40字节,十进制中的40在十六进制中是28,因此是0x027CFA8C+28,即0x027CFAB4
数组指针的构成(或者说字符串指针的构成)
int (*数组指针名)[指针指向的数组含有的元素个数]
指针数组:
写法:int *p[10]
意味着一个名为p的数组,有十个元素,元素类型是整形指针
同样的,也有char *x[10]
关于二维数组
二维数组的元素是一维数组
因此二维数组的首元素是第一个数组,二维数组的数组名也是第一个数组的地址,因此二维数组数组名本身就是一个数组指针,二维数组的元素本身就是数组(一维)
Eg:
int n[3][10]={0};
int (*arr)[3][10];
n[1][3]==(*arr)[1][3]==*((*arr)+1)+3
函数指针
就是指向函数的指针
函数其实是有地址的,函数名(或者&函数名)就是函数的地址
int Add(int x,int y)
{
return x+y;
}
int main()
{
int (*pf)(int,int)=&Add;
return 0;
}
由此可以看出函数指针的书写格式:
函数指针指向的函数的数据类型 (*函数指针名)(函数指针指向的函数的形参数据类型,同上)
对于函数而言,在取用函数的地址时,取地址符号可以省略,对于函数指针而言,定义的时候,指针名之前的解引用符号可以省略
int (*p)(int ,int)=&Add;
int (*P)(int ,int)=Add;
int (p)(int ,int)=&Add;
int (p)(int ,int)=Add;
//以上四种写法都是可以的
以下为几个例子
#include<stdio.h>
int Add(int x,int y)
{
return x+y;
}
int main()
{
int (*pf)(int,int)=&Add;
int sum=(*pf)(2,3);//对于函数指针解引用,调用函数,并进行传参
printf("%d/n",sum);
return 0;
}
(*(void(*)())0)()
//这段代码是啥意思呢
//先看里面,void(*)()是一个函数指针
//(void(*)())0是利用强制类型转换,把0强制转换为函数指针类型,且是void(*)()类型的指针
//*(void(*)())0对于这个强转后的函数指针进行解引用并传参使用
//但是这段代码没法运行 (出自《C陷阱与缺陷》)
void(*signal(int ,void(*)(int)))(int)
//这里本质是对于函数signal的声明
//这段代码的主体是函数指针的声明,在指针函数的声明中,第一个括号的取地址符号后面跟的应该是函数指针名,其实就是函数地址
//为什么这么说呢,因为在声明之后都是将某个函数的地址赋值给函数指针名,因此其实第一个括号的取地址符号后面跟的就是函数地址,而在这里却是一个函数,这只能说明一个问题,那就是后面跟的个函数的返回值是指针
//既然signal是个函数,那我们看一下他的形参,第一个是一个整形,第二个是一个函数指针,这个函数指针对应的函数返回值类型是void,形参是int类型,不过同样,这个也是无法使用的,因为没有函数指针名
但是要注意的是,这只能声明一下signal,而并非定义signal,因为函数要实现的功能没有说明
函数指针数组
用来存储同类的函数指针的数组
写法
函数指针对应函数返回类型 (*函数指针数组名[成员个数])(函数指针对应函数形参类型,同上...)
举个例子
#include<stdio.h>
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
int main()
{
int (*pf[4])(int , int )={Add,&Sub,&Mul,Div};
//函数和函数指针的取地址和解引用符号都可以省略
for(int i=0;i<4;i++)
{
printf("%d\n",pf[i](8,4));
//这里pf前面也省略了*
}
return 0;
}
指向函数指针数组(的地址)的指针
int Add(int x, int y )
{
return x+y;
)
int main()
{
int (*p)(int,int)=Add;//函数指针
int (*pf[4])(int,int);//函数指针数组
int (*(*PF)[4])(int,int)=&pf;//函数指针数组指针
return 0;
}
用这种思想可以套娃
回调函数
就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数是一种极致,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
Eg1:
#include<stdio.h>
int a(int x,int y)
{
return x+y;
}
int b(int x,int y)
{
return x-y;
}
int c(int x,int y)
{
return x*y;
}
int d(int x,int y)
{
return x/y;
}
void calc(int (*pf)
int main()
{
int w,x,y;
int (*pp[5])(int ,int )={0,a,b,c,d};
do
{
printf("输入 1 进行加法运算\n输入 2 进行减法运算\n输入 3 进行乘法运算\n输入 4 进行除法运算\n输入 0 结束程序\n");
scanf("%d",&w);
if(w==1|w==2|w==3|w==4)
{
printf("请输入你的两个数据\n");
scanf("%d%d",&x,&y);
printf("%d\n",(*pp[w])(x,y));
}
else if(w==0)
{
printf("程序结束\n");
break;
}
else
{
printf("输入错误\n");
}
}while(1);
return 0;
}
这里的加减乘除这四个函数就是回调函数,