目录
- 0.铺垫
- 1.指针是什么?
- 2.指针变量
- 3.指针和指针类型
- 4.指针类型的意义
- 5.野指针
- 1.野指针成因
- 2.如何规避野指针
- 6.指针运算
- 6.指针和数组
- 7.二级指针(n级指针)
- 8.指针数组
- 9.数组指针
- 10.&数组名VS数组名
- 11.函数指针
- 12.函数指针数组
- 13.回调函数
0.铺垫
- 在C中,任何变量&都是从最低地址开始
1.指针是什么?
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
- 指针就是地址,口语中说的指针通常指的是指针变量
2.指针变量
- 可以通过&(取地址操作符)取出变量在内存中的起始地址,把地址可以存放到一个变量中,这个变量就是指针变量
int a = 10; // 在内存中开辟一块空间 int *p = &a; // 取出a的地址,将a的4个字节的第一个字节的地址存放在p变量中,p就是要给指针变量
- 总结:
- 指针变量,用来存放地址的变量,存放在指针中的值都被当成地址处理
- 指针是用来存放地址的,地址是唯一标示一块地址空间的
- 指针的大小在32位平台是4个字节,在64位平台是8个字节
3.指针和指针类型
- 指针的定义方式:
type + *
char*
类型的指针是为了存放char
类型变量的地址short*
类型的指针是为了存放short
类型变量的地址int*
类型的指针是为了存放int
类型变量的地址- …
4.指针类型的意义
- 指针类型决定了:指针解引用的权限有多大 -> 能操作几个字节
- 比如:
char*
解引用就只能访问一个字节,而int*
解引用就能访问四个字节
- 比如:
- 指针类型决定了:指针走一步,能走多远 -> 步长
5.野指针
- 概念: 野指针就是指针指向的位置是不可知的 -> 随机的、不正确的、没有明确限制的
1.野指针成因
- 指针未初始化
int *p; // 局部变量指针未初始化,默认为随机值 *p = 20; // error
- 指针越界访问
int arr[10] = {0}; int *p = arr;for(int i = 0; i <= 11; i++) {*(p++) = i; // 当指针指向的范围超出数组arr的范围时,p就是野指针 }
- 指针指向的空间释放
2.如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使其置为
NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
6.指针运算
- 指针±整数
#define N 5float values[N]; float *vp = &values[0];// 指针+-整数:指针的关系运算 while(vp < &values[N]) {*vp++ = 0; }
- 指针-指针
- 指针和指针相减的前提:两个指针指向同一块空间
- 指针相减,代表指针之间所经历的元素的个数
int MyStrlen(char* s) {char* p = s;while(*p != '\0'){p++;}return p - s; }
- 指针的关系运算
for(vp = &values[N]; vp > &values[0];) {*--vp = 0; }// 获取你想将代码简化成这样 for(vp = &values[N - 1]; vp >= &values[0]; vp--) {*vp = 0; }
- 上述第二种代码实际中在绝大部分的编译器上是可以顺利完成任务的,然而还是应该避免这样写,因为标准并不保证它可行
- 标注规定
- 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较
- 但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
6.指针和数组
- 数组名表示的是数组首元素的地址
int arr[10] = {1, 2, 3, 4, 5, 6}; int *p = arr; // p存放的是数组首元素的地址
- 既然可以把数组名当成地址存放到一个指针中,使用指针来访问一个数组就成为可能
- 例如
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; int *p = arr; // 指针存放数组首元素的地址 int sz = sizeof(arr) / sizeof(arr[0]);for(int i = 0; i < sz; i++) {printf("&arr[%d] = %p <==> p+%d = %p\n", i, &arr[i], i, p + i); }
(p+i)
其实计算的是数组arr
下标为i
的地址int arr[10] = {1, 2, 3, 4, 5, 6};int* p = arr; // 数组名 printf("%d\n", arr[2]); printf("%d\n", p[2]); // p[2] -> *(p + 2)// []是一个操作符,2和arr是两个操作数 // 类似于 a + b,b + a printf("%d\n", 2[arr]); printf("%d\n", arr[2]);// arr[2] -> *(arr + 2) -> *(2 + arr) -> 2[arr] // arr[2] <-> *(arr + 2) <-> *(p + 2) <-> *(2 + p) <-> *(2 + arr) <-> 2[arr] // 2[arr] <-> *(2 + arr)
7.二级指针(n级指针)
- 指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
- 二级指针存放一级指针的地址,解引用出来是一级指针里的内容
*ppa == pa
*pa == a
- 二级指针存放一级指针的地址,解引用出来是一级指针里的内容
- n级指针同理
int a = 10; int* pa = &a; // pa是指针变量,一级指针// ppa是一个二级指针变量 int** pa = &pa; // pa也是个变量,&pa取出pa在内存中起始地址
8.指针数组
- **指针数组是指针还是数组? **
- 数组,是存放指针的数组
int* arr[5];
- 数组,是存放指针的数组
9.数组指针
- 例如:
int (*p)[10]
p
先和*
结合,说明p
是一个指针变量,然后指针指向的是一个大小为10个整形的数组- 所以
p
是一个指针,指向一个数组,叫数组指针 - 注意:
[]
的优先级高于*
,所以必须加上()
来保证p
先和*
结合
10.&数组名VS数组名
&arr
和arr
,虽然值一样,但是意义不一样&arr
表示的是数组的地址,而不是数组首元素的地址,例如:`int arr[10]&arr
的类型是:int(*)[10]
,是一种数组指针类型- 数组的地址 + 1,跳过整个数组的大小,所以
&arr + 1
相遇&arr
的差值是40
11.函数指针
- &函数名 = 函数名
12.函数指针数组
- 用途:转移表
- 样例:计算器(Test.c中)
13.回调函数
- 回调函数:通过函数指针调用的函数
- 如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说这是回调函数
- 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
- 样例:模仿
qsort()