一、指针数组与数组指针
1.1 概念与区别
- 指针数组的实质是一个数组,这个数组中存储的内容全部是指针变量。
- 数组指针的实质是一个指针,这个指针指向的是一个数组。
1.2 一般规律
- 找核心: 在定义一个符号的时候,关键在于搞清楚定义的符号是什么
- 找结合: 其次看谁和核心最近、谁和核心结合(结合的实质是运算)
- 如果
核心
和*
结合,表示核心
是指针
- 如果
核心
和[]
结合,表示核心
是数组
- 如果
核心
和()
结合,表示核心
是函数
1.3 指针与数组的区分
1.3.1 int *p;
核心是p
,p
和*
结合,所以p
是一个指针
1.3.2 int p[5];
核心是p
,p
和[]
结合,所以p
是一个数组
1.4 指针数组与数组指针的区分
优先级:[]
、()
、.
、->
的优先级是所有运算符中最高的,均比*
高
1.4.1 指针数组 int *p[5];
1.4.1.1 分析:
- 核心是
p
p
先和[]
运算成一个数组,数组有5
个元素- 数组中的元素与
*
结合,数组中的元素都是指针,指向的元素类型是int
类型
1.4.1.2 结论:
int *p[5];
是一个指针数组
1.4.1.3 测试代码:
#include <stdio.h>
int main(void)
{
int a[5];
int *p[5];
for (int i = 0; i < 5; i ++)
{
*(p + i) = &(*(a + i));
}
for (int i = 0; i < 5; i ++)
{
printf("&(*(a + %d)) = %p\n", i, &(*(a + i)));
printf(" *(p + %d) = %p\n", i, *(p + i));
}
return 0;
}
1.4.1.4 测试结果
我们可以发现a[i]
的地址和p[i]
的值是一样的。
1.4.2 数组指针 int (*p)[5];
1.4.2.1 分析:
- 核心是
p
- 由于
()
的存在,p
先和*
运算成一个指针 - 指针指向一个数组,数组有
5
个元素,数组中存的元素是int
类型
1.4.2.2 结论:
int (*p)[5];
是一个数组指针
1.4.2.3 测试代码:
#include <stdio.h>
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
int (*p)[5];//定义数组指针
p = &a;//p指向数组a
for(int i = 0; i < 5; i++)
{
printf(i == 4 ? "%d\n" : "%d ", *(*(p) + i));
}
return 0;
}
1.4.2.4 测试结果
我们可以发现(*p)[i]
和a[i]
的值是一样的。
1.4.3 指针数组 int *(p[5]);
和int *p[5];
效果是一样的,()
并没有起作用
二、函数指针
2.1 函数指针的实质
- 函数指针的实质还是指针,也就是指针变量,在32位系统中,也是占4个字节。
- 所有的指针变量类型其实本质都是一样的,函数指针、数组指针、普通指针之间并没有本质区别,只是指针指向的变量不同。
- 函数的实质是一段代码,这一段代码在内存中是连续分布的,对于函数来说最关键的就是函数中的第一句代码的地址,这个地址就是函数地址,在C语言中函数名这个符号来表示。
- 结合函数的实质,函数指针其实就是一个普通变量,这个普通变量的类型是函数指针变量类型,它的值就是某个函数的地址(也就是它的函数名这个符号在编译器中对应的值)
2.2 函数指针的书写方法
为什么要研究函数指针的书写?
C语言本身是强类型语言(每一个变量都有自己的变量类型),编译器可以帮我们做严格的类型检查。
类型检查测试代码:
#include <stdio.h>
int main(void)
{
int* p; //普通指针变量
int (*a)[5];//数组指针变量
p = a;
return 0;
}
编译结果:
我们可以看出编译器报了assigment from incompatible pointer type
**(不兼容指针类型的分配)**的警告
2.2.1 函数指针的书写
- 假设一个函数结构为:
函数返回值类型 函数名 (函数参数列表)
- 那对应的函数指针为:
函数返回值类型 (*指针变量名) (函数参数列表)
例如:
函数为:
void func(void)
{
printf("Test func_pointer!\n");
}
其对应的函数指针是void (*pFunc)(void);
类型是:void (*)(void)
2.2.2 测试代码
#include <stdio.h>
void func1(void);
void func2(void);
int main(void)
{
void (*pFunc1)(void);//定义函数指针
void (*pFunc2)(void);//定义函数指针
pFunc1 = &func1;
pFunc2 = func2; //这个在下面的结果分析中说明
pFunc1();
pFunc2(); //调用函数
printf("%p\n%p\n",func1,&func1);
return 0;
}
void func1(void)
{
printf("Test func1_pointer!\n");
}
void func2(void)
{
printf("Test func2_pointer!\n");
}
2.2.3 测试结果分析
我们发现程序通过了正常的编译,运行结果也有点出乎意料。我们在这里可以看出,函数名和数组名其实是不同的。我们需要记住以下两点:
- 函数名做右值时,
func
和&func
是一样的,都表示函数的首地址,这个我们需要特别注意一下。 - 调用的时候不需要再对函数指针进行解引用操作!