
本文共 4510 字,大约阅读时间需要 15 分钟。
指针
一、指针三连问
1.1 什么是指针?
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
1.2 为什么使用指针?
提高查找效率
总结:指针是地址,指针变量是一个变量,用来存放内存单元的地址,通过指针能找到以它为地址的内存单元。
1.3 指针怎么用?
举个栗子:
#includeint main(){ int a = 10;//在内存中开辟一块空间 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。 //将a的地址存放在p变量中,p就是一个之指针变量。 return 0;}
这里需要知道计算机当中是如何编址的?
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或
者0) 那么32根地址线产生的地址就会是: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 … 11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以得出: 2 32 B y t e = 2 32 / 1024 K B = 2 32 / 1024 / 1024 M B = 2 32 / 1024 / 1024 / 1024 G B = 4 G B \begin{aligned} 2^{32}Byte \\ &=2^{32}/1024KB \\ &=2^{32}/1024/1024MB \\ &=2^{32}/1024/1024/1024GB\\ &=4GB \end{aligned} 232Byte=232/1024KB=232/1024/1024MB=232/1024/1024/1024GB=4GB 即4G的空闲进行编址。 同样的方法,64位机器,如果给64根地址线,就可以得出: 2 64 B y t e = 2 64 / 1024 K B = 2 64 / 1024 / 1024 M B = 2 64 / 1024 / 1024 / 1024 G B \begin{aligned} 2^{64}Byte \\ &=2^{64}/1024KB \\ &=2^{64}/1024/1024MB \\ &=2^{64}/1024/1024/1024GB\\ \end{aligned} 264Byte=264/1024KB=264/1024/1024MB=264/1024/1024/1024GB 对于一般计算机来说不会将64位地址总线全部用到,通常只用8G的空闲进行编址。 这里我们就明白:在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的。 指针的大小在32位平台是4个字节,在64位平台是8个字节。二、指针和指针的类型
2.1 指针类型
char *pc = NULL;int *pi = NULL;short *ps = NULL;long *pl = NULL;float *pf = NULL;double *pd = NULL;
指针的定义方式:type + *
char* 类型的指针是为了存放 char 类型变量的地址。 short* 类型的指针是为了存放 short 类型变量的地址。 int* 类型的指针是为了存放int 类型变量的地址。 举个栗子来理解指针类型的定义#includeint main() { int n = 10; char *pc = (char *)&n; int *pi = &n; printf("%p\n", &n); printf("%p\n", pc); printf("%p\n", pc+1); printf("%p\n", pc-1); printf("%p\n", pi); printf("%p\n", pi+1); printf("%p\n", pi-1); return 0;}
结果

可以看到在32位操作系统下指针变量是4个字节,64位操作系统下指针变量是8个字节。
总结:对指针加1其实是加上指针所指向类型的大小,指针的类型决定了指针向前或者向后走一步有多大(距离)。
2.2 指针的解引用
#includeint main(){ //2.2指针解引用 int n = 0x11223344; char *pc = (char *)&n; int *pi = &n; *pc = 0; *pi = 0; return 0;}


从调试的结果图中可以看出,当指针变量类型为char类型时,对指针解引用只能操作一个字节,而当指针变量类型为int类型时,对指针解引用可以一次操作四个字节。
总结:
指针的类型决定了对指针解引用的时候有多大的权限,即能操作几个字节。
三、野指针
3.1 概念
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.2 野指针成因
3.2.1 指针未初始化
#includeint main(){ int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0;}
3.2.2 指针越界访问
#includeint main(){ int arr[10] = { 0}; int *p = arr; int i = 0; for(i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0;}
3.2.3 指针指向的空间释放
可以看以下博客的第三部分
3.3 如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即置NULL
- 指针使用之前检查有效性
四、指针运算
4.1 指针加减整数
#define N_VALUES 5float values[N_VALUES];float *vp;//指针+-整数;指针的关系运算for (vp = &values[0]; vp < &values[N_VALUES];){ *vp++ = 0;}
对指针加1其实是加上指针所指向类型的大小,指针的类型决定了指针向前或者向后走一步有多大(距离)。
标准规定: 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
4.2 指针减指针
int my_strlen(char *s){ char *p = s; while(*p != '\0' ) p++; return p-s;}
两个指针相减,代表两个指针经历的元素个数。
- 元素类型由指针表明
- 两个指针必须指向同一块内存才有意义(通常是同一数组、字符串)
4.3 指针的关系运算
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--){ *vp = 0;}
指针可以进行大小比较,通常要教教的指针需要指向同一块内存(同一数组,字符串)。
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证
它可行。五、指针和数组
首先明确指针和数组没有任何关系,下面举个栗子
#includeint main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,0}; printf("%p\n", arr); printf("%p\n", &arr[0]); return 0;}
数组名表示的是数组首元素的地址
我们可以使用指针访问数组,举个栗子
#includeint main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; printf("%p\n", arr); printf("%p\n", &arr[0]); int *p = arr; //指针变量p存放的数组首元素的地址 int arrSize = sizeof(arr) / sizeof(arr[0]); for (int i = 0; i < arrSize; i++) { printf("%d ", p[i]); printf("%d ", *(p+i)); printf("%d ", arr[i]); printf("%d \n", *(arr+i)); } return 0;}
六、二级指针
指针变量是变量,那么是变量就会有地址,二级指针就是用来存放指针变量地址的。
通过一个图可以理解

a的地址存放在pa中,pa的地址存放在ppa中
pa是一级指针,ppa是二级指针
对于二级指针运算
int a = 10; int *pa = &a; printf("%d\n", *pa); int **ppa = &pa; int b = 20; *ppa = &b; //等价于pa=&b printf("%d\n", *pa); **ppa = 30; // 等价于*pa=30 等价于b=30 printf("%d\n", *pa); printf("%d\n", b);
结果

这里对**ppa的操作有两步
- 第一个解引用*ppa,找到的是pa的地址,因此将b的地址赋值给pa
- 第二个解引用**ppa,首先通过*ppa找到pa,然后对pa进行解引用操作:*pa,找到的就是b
发表评论
最新留言
关于作者
