# 思维导图
# 一、指针
一、指针
1.1,指针是什么?
指针理解的要点:
a,指针是内存中一个最小单元的编号,也就是地址。
b,平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
c,把内存划分为一个个的内存单元,这个内存单元大小是1字节,从概念上讲:编号 == 地址 == 指针
总结:指针就是地址,口语中说的指针通常指的是指针变量。
1.2,指针变量
a,我们可以通过& (取地址操作符) 取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。
例子:
#include <stdio.h>
int main(){
int a = 10; //在内存中开辟一块空间
int *p = &a; //这里我们取出变量a的地址,也就是&a
//a变量占用4个字节的空间
//这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。
return 0;
}
总结:指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
1,一个小的单元到底是多大?(一个字节)
2,计算机是如何编址的?(具体详细解释可参考本人的 指针初阶文章)
二、指针类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确来说:有的
char *pc = NULL; //这里指针的类型是char 所以它指向 1个字节而不是4个字节
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
其实这里就可以看到,指针的定义方式是:类型(type) + *
char *类型的指针是为了存放char 类型变量的地址
short*类型的指针是为了存放short类型变量的地址
int*类型的指针是为了存放int类型变量的地址
三、指针运算
3.1指针加减整数
int a = 10;
int *pi = &a;
char *pc = &a;
printf("%p ",pi+1);
printf("%p ",pc+1);
代码解析:
这里指针变量pi,pc都是存放变量a的地址,但是由于它们定义的类型不一样,所以出来的结果也不同。
例如 char 类型,定义一个变量时,它所占空间的大小为1个字节。虽然指针变量在内存中的大小都是4个字节(32位机器上)但是char类型的指针变量它只能操作一个字节。
所以 pi+1 其实就是a变量地址 往后跳过了4个字节的地址
pc + 1 则是a变量地址 往后跳过了1个字节的地址
总结:指针类型决定了,对指针解引用的时候有多大权限(能操作几个字节)。
指针类型决定了指针向前或者向后走一步有多大(距离)。
3.2指针减指针
两个指针指向同一个数组中的(可能不同的)元素的时候,它们的差就是两个元素之间的元素个数+1
例如:
int a[5];
int* p;
int* q;
p = &a[1]; // 第二个元素
q = &a[3]; // 第四个元素
这里 q - p 等于 2 (从 p 指向的元素往后数两个,就是 q 指向的元素)
如果两个指针不是指向同一个数组中的元素,结果是未定义的
# 四、野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不准确的、没有明确限制的)
通俗来讲:就相当于路上的野狗,你不知道它的主人是谁,而且野狗也会随机的乱咬人,是非常危险的,所以说野指针对程序来说也非常危险
4.1野指针的成因
1,指针未初始化
2,指针越界访问
3,指针指向的空间释放
指向的空间释放:
例如: 1,局部变量的定义,在释放之后指向其变量的指针未销毁或未置空
4.2如何规避野指针?
1.指针初始化
2,小心指针越界
3,指针指向空间释放
4,避免返回局部变量的地址
5,指针使用之前检查有效性
# 五、指针和数组
首先,我们看一个例子:
# include <stdio.h>
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
return 0;
}
运行之后,打印的都是数组的首元素地址。
结论:数组名表示的是数组首元素的地址
但是有两种例外:
1,sizeof(数组名)2,&数组名 表示的是取出整个数组的地址
数组名是地址的话那么我们就可以通过地址访问数组
如下:
int main(){
int arr[10] = { 1,2,3,4,5,6,7,8,9,0};
int *p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for(i = 0;i<sz;i++){
printf("%d ",*(p+1));
}
return 0;
}
因为数组是int类型,所以定义一个int 类型的指针每次+1来操作数组的元素。
# 六、二级指针
指针变量也是变量,是变量就有地址,那么指针变量的地址存放在哪里?
这就不得不说到二级指针了。
a的地址存放在pa中,pa的地址存放在ppa中。pa是一级指针,ppa是二级指针。
对二级指针的运算有:
*ppa通过对ppa中的地址解引用,这样找到pa,*ppa其实就是访问pa
**ppa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,那找到的就是a。
# 七、指针数组
1,指针数组是数组,不是指针,是存放指针的数组。
2,指针数组是指由若干个具有相同存储类型和数据类型的指针变量构成的集合
3,声明一个指针数组------ double * p[2] (指针数组)
# 八、常见指针面试问题
⼀、堆和栈上的指针
1.指针所指向的这块内存是在哪⾥分配的,在堆上称为堆上的指针,在栈上为栈上的指针.
2.在堆上的指针,可以保存在全局数据结构中,供不同函数使⽤访问同⼀块内存.
3.在栈上的指针,在函数退出后,该内存即不可访问.
⼆、什么是指针的释放?
具体来说包括两个概念.
1,释放该指针指向的内存,只有堆上的内存才需要我们⼿⼯释放,栈上不需要.
2,将该指针重定向为NULL.
三、数据结构中的指针?
其实就是指向⼀块内存的地址,通过指针传递,可实现复杂的内存访问.
四、函数指针?
指向⼀块函数的⼊⼝地址.
五、指针作为函数的参数?
⽐如指向⼀个复杂数据结构的指针作为函数变量 这种⽅法避免整个复杂数据类型内存的压栈出栈操作(比如结构体),提⾼效率. 注意:指针本⾝不可变,但指针指向的数据结构可以改变.
六、指向指针的指针?
指针指向的变量是⼀个指针,即具体内容为⼀个指针的值,是⼀个地址. 此时指针指向的变量长度也是4位.
七、指针与地址的区别?
1,指针意味着已经有⼀个指针变量存在,他的值是⼀个地址,指针变量本⾝也存放在⼀个长度为四个字节的地址当中,⽽地址概念本⾝并不代表有任何 变量存在.
2,指针的值,如果没有限制,通常是可以变化的,也可以指向另外⼀个地址. 地址表⽰内存空间的⼀个位置点,他是⽤来赋给指针的,地址本⾝是没有⼤⼩概念,指针指向变量的⼤⼩,取决于地址后⾯存放的变量类型.
⼋、指针与数组名的关系?
其值都是⼀个地址,但前者是可以移动的,后者是不可变的.
九、怎样防⽌指针的越界使⽤问题?
必须让指针指向⼀个有效的内存地址,
1,防⽌数组越界
2,防⽌向⼀块内存中拷贝过多的内容
3,防⽌使⽤空指针
4,防⽌改变const修改的指针
5,防⽌改变指向静态存储区的内容
6,防⽌两次释放⼀个指针
7,防⽌使⽤野指针.
⼗、指针的类型转换?
指针转换通常是指针类型和void *类型之前进⾏强制转换,从⽽与期望或返回void指针的函数进⾏正确的交接