一. 引言

要说学习C语言的过程中什么最头疼,那必然是指针!咱能放弃吗,咱不能!看完这篇,助你拿下指针~
首先来快速回顾一下指针的基础知识:

  1. 指针也是一个变量,只不过这个变量是用来存放地址的。要知道的是,地址唯一指向一块内存空间 image.png

在这个例子中,指针变量p中存放的是a的地址

  • 示意图: image.png
  1. 指针是有类型的,指针的类型决定指针 + - 运算时候的步长及解引用操作时的权限大小

  2. 指针变量的大小无关指针变量的类型,固定为4/8个字节(32位平台/64位平台)

image.png

(此测试是在32位平台下进行)

  1. 对于操作符 (&) 和(*) 可以理解为一对相反作用的符号:(pa = &a)(*pa = a)

& (取出变量的地址): &a --> 取出变量a的地址 * (取出地址对应的数据): *pa --> 得到指针变量pa(a的存储地址)所指向的值

二.正文

1.字符指针

字符指针,就是一个指针变量存储地址所指向的内容是char类型的数据。

  • 举个栗子
char ch = 'w';
char* pch = &ch;

但要注意的是:字符指针在存储字符地址的时候只存储首字符地址

  • 举个栗子
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	return 0;
}
  • 输出结果
str1 and str2 are not same
str3 and str4 are same

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2. 指针 & 数组

首先掌握以下三个必须烂熟于心的数组基础知识

  1. 一般来讲,数组名指的是数组首元素的地址
  2. 当数组名arr 与 sizeof() 结合时候 (如 sizeof ( arr ) ),数组名代表的是整个数组,而非首元素地址
  3. 当数组名arr 与 & 结合时候*(如 & arr )*,数组名代表的是整个数组,而非首元素地址

2.1 数组指针 vs 指针数组

首先要分清楚 什么是数组指针, 什么是指针数组,二者并不一样,不要混淆!

名称 解释 类型
指针数组 是一个存放指针的数组 数组
数组指针 一个指向数组的指针 指针
  • 举个栗子
int *p1[10];  // 指针数组, 符号[]的优先级高于*

int (*p2)[10];  // 数组指针,通过()使得 * 和 p 先进行结合,然后指向一个大小为10个整形的数组

2.2 & 数组名 vs 数组名

思考以下,对于下面的数组, arr 和 &arr 分别是什么意思?

int arr[10];

对于arr,很容易理解,但是&arr可能会有一些困惑。但可以明确的是,==1.数组名也是数组首元素的地址==,==2.&的含义是取出变量的地址==。看下面这段代码:

int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
  • 输出结果 image.png

可以看到,二者的输出结果是一致的,但能说明二者完全相等吗,答案是 不能

  • 验证
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
  • 输出结果

image.png

含义 +1后的结果
&arr 表示数组的地址 跳过一个元素
arr 表示数组首元素的地址 跳过整个数组
  • 指针数组的示意图 image.png

  • 尝试辨析下面代码的意思

int arr[5];  // 数组
int *parr1[10];  // 指针数组 
int (*parr2)[10];  // 数组指针
int (*parr3[10])[5];  //数组指针(所接收的数组为指针数组)

是否对于最后一项 int (*parr3[10])[5] 仍然存疑?其实可以看作int (*p)[5] ,很明显这是一个数组指针,因为存在()使得 * p 先进行了结合,然后 * p 代表的是 原式中的指针数组,该指针 指向的数组的示意图如下:

image.png

2.3 补充 -- 数组传参

  1. 正常数组传参时候,如果传递的是数组名,其实是在传递首元素的地址

2.二维数组传递参数时, 函数的形参设计只能省略第一个 [] 的数字,如下:

void test1(int arr[][5])
{}  // 正确
void test2(int arr[3][])
{}  // 错误 省略了列

3 函数指针

函数指针,就是接收函数地址的指针

  • 举个栗子
int (*pf)(int, int);
// 有时候可以简化为
int (*)();

有趣的是,函数和指针的结合规律同数组有些相似,如下:

void test()
{
printf("hehe\n");
}
// 下列二式中哪个有能力存放test函数的地址?
void (*pfun1)(); 
void *pfun2(); 

答案是 punf1 可以存放,是因为 * 和pfun1 先结合,所以是函数指针, 可以存放函数的地址

4. 大烩菜系列

4.1 函数 & 指针 & 数组 的结合体 —— 函数指针数组

看到三者,莫不是有点头晕?别怕!将函数指针数组拆解为两部分:函数指针和数组

  • 首先看数组:数组 是一个存放相同类型数据的存储空间。

image.png

  • 而函数指针是一个指针,一个用来接收函数的指针。

它的用途是: 转移表

比如编写一个计算器,内有加减乘除四种运算函数,将这四种运算函数的地址存放在一个数组中,该数组即为函数指针数组。(具体代码可在各大博客平台找到,此处不贴)

4.2 套娃更深一层 ——指向函数指针数组的指针

首先可以明确,它是一个指针,该指针指向一个数组,且数组的元素都是函数指针

img

  • 举个栗子
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}

回调函数就是该通过这种指针来实现的。

\

本节完,练习题下节见~