一、指针是什么?
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向 (points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以 说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址 的内存单元。
我们的内存是类型下图这样的空间: 我们在使用的过程中,把内存划分成一个个小的字节,每个字节称为一个内存单元。我们给每个内存单元进行编号,每个编号唯一对应一个内存单元。类似身份证,每人一个身份证,我有这个身份证就可以唯一确定你这个人。这里也是类似的,地址指向了一个确定的内存空间,所以地址也被形象的被称为指针。
举个例子:
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
int* pa=&a,也就是pa里面放的a的地址(指针),pa是(整形)指针变量,它是用来存放指针的
二、指针和指针类型
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
我们按f10进行调试,监视一下,可以看到我们pa里面存放的就是a的地址 监视一下内存,可以看到,我们内存中经过代码pa = 0; 32位是全部变成了0 ps:下图中我们是16进制一共8位,对应2进制32位 经过pa = 0;赋值后
那么问题来了,我们之前说过,32位下指针大小都是4字节,64位下指针大小都是8字节。 假如我们都在32位下,我能否用char*来接收这个int型的地址(指针)呢?
我们仅把上面代码中的int * 改成 char * 来测试一下
可以看到放进去是没有问题的,但是如果要对内容修改呢? 经过*pa = 0;赋值后 为什么我这里只改动了一个字节呢?因为我是char * 的指针,我只负责我指向地址开始往后的1个字节。同理,如果我是int * 的指针,我只负责我指向地址开始往后的4个字节。
指针类型的意义1:指针类型决定了指针解引用操作的时候,一次访问几个字节(访问内存的大小)
由于这个性质,我们来看看下面这个题目
int main()
{
int a = 10;
int* pa = &a;
char* pa2 = &a;
printf("%p\n", pa);
printf("%p\n", pa2);
printf("%p\n", pa+1);
printf("%p\n", pa2+1);
return 0;
}
可以看到,我们的pa虽然和pa2都是一个地址,但是当他们加1的时候,就受到了指针类型的管控。char * 指针+1只会跳1个字节,而int * 的指针+1则会跳4个字节。
指针类型的意义2:指针类型决定了指针±整数时的步长(跳过几个字节)
三、野指针
野指针就是指针指向的位置是不可知的 (随机的、不正确的、没有明确限制的)
3.1野指针成因
1. 指针未初始化
int main()
{
int* p;
*p = 20;
return 0;
}
如果你没有初始化p,那么指针p里面放的是随机的地址,你*p=20是想把随机地址里面的值改成20。但是我们这个随机地址是不属于我们当前程序的,就会造成非法访问,p就是野指针。
2. 指针越界访问
int main()
{
int arr[10] = 0;
int i = 0;
int*p = arr;
for (int i = 0;i <= 10;i++)
{
*p = i;
p++;
}
return 0;
}
3. 指针指向的空间释放 这个知识点笔者会在后面的动态内存释放进行详细讲解,这里简单提一下
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("%d",*p);
return 0;
}
我们在main函数里调用test函数,test函数返回a的地址。 需要注意的是,一旦返回了a的地址,test函数结束,a的生命周期也结束了 也就是说,a所在的空间返还给了操作系统。那么你在main函数中通过p再访问a之前的地址,这就是非法访问了。
打个比方:你和你好朋友出去旅游,然后你们开了一间房。在使用期内,你们想在房间里干啥都行。但是房间到期后,你再访问,旅店要告你的。
3.2规避野指针的方法
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 指针使用之前检查有效性
示例如下:
#include <stdio.h>
int main()
{
int *p = NULL;
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
四、指针运算
4.1指针±整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
上面这段代码啥意思,请看下图的解释 我们在for循环中判断vp指向的地址是否小于&values[N_VALUES] ps:地址本质是一个16进制数,可以用来比较
发现vp指向的地址小于&values[N_VALUES],我们就进行 *vp++, 需要注意的是,++的优先级是高于 * 的,所以我们本应先进行++再 *, 但我们这里是后置++也就是说vp在本轮还是指向数组0下标元素,然后 * 解引用赋值为0, vp因为++了,所以指向了数组1下标元素
然后重复for循环,后面的都一样了,就是把数组元素赋为0,然后vp++直到vp指向3停止 (vp < &values[4])
4.2指针-指针
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[2]);
printf("%d\n", &arr[2] - &arr[9]);
return 0;
}
指针-指针,得到的数字的绝对值,是指针之间元素的个数
注:肯定有一些杠精会问:“啊,你这个是两个相同类型的指针啊,如一个char类型指针-int类型呢?”
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
char brr[10] = { 0 };
printf("%d\n", &arr[9] - &brr[2]);
return 0;
}
像上面这种代码,你自己想想有多亏贼? 首先你两个地址都是随机的,你怎么知道两个随机地址之间隔了多远? 还有一个问题就是,你两个不同类型指针,到时候划分元素个数是按1字节来化还是4字节来化?
指针-指针的前提:两个指针指向同一空间(比如指向同一数组)
具体应用实例:求字符串长度 法一:使用库函数strlen
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
int len = strlen(arr);
printf("%d\n", len);
return 0;
}
法二:使用指针
int my_strlen(char* p)
{
int count = 0;
while (*p != '\0')
{
count++;
p++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
法三:使用指针-指针
int my_strlen(char* p)
{
char* tmp = p;
while (*p != '\0')
{
p++;
}
int count = p - tmp;
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
五、指针和数组
区别: 数组是一块连续的空间,里面放的是相同类型的元素。 数组大小和元素类型,元素个数有关系,比如int arr[10],该数组大小=4*10
指针是一个变量,里面存放地址 指针变量的大小是4/8byte,这个取决于是32位系统还是64位
联系: 数组每个内存单元都有自己的内存地址,而我们指针可以存放这些地址
重点!!!:数组名是什么?
#include <stdio.h>
int 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;
}
可见数组名和数组首元素的地址是一样的。 这里的一样,不仅仅是数值上,是各种意义上! ps:数组名确实是首元素地址,但是有两个例外 1.sizeof(数组名),这里的数组名表示的是整个数组 2.&数组名,这里的数组名也是整个数组,&数组名表示整个数组的地址
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n", &arr);
printf("---------我是分界线----------\n");
printf("%p\n", arr+1);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr+1);
return 0;
}
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组元素就成为可能。
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0;i < sz;i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
return 0;
}
六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
示意图如下: 对于二级指针,我们也是可以解引用的 *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
int b = 20;
*ppa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a
**ppa = 30;
当然了,你也可以无限套娃,就会有三级指针、四级指针。。。
七、指针数组
指针数组是指针还是数组? 这个问题你就想,好男孩,好男孩的本质是男孩啊 指针数组本质还是数组
举个例子:
int main()
{
int arr1[5];
char arr2[3];
int* parr[5];
char* pbrr[4];
return 0;
}
示意图如下 应用实例:
int main()
{
int a = 123;
int b = 213;
int c = 312;
int* arr[3] = { &a,&b,&c };
for (int i = 0;i < 3;i++)
{
printf("%d\n", *arr[i]);
}
return 0;
}
总结
本文介绍了指针和指针类型(着重掌握)、野指针及其成因、指针运算、指针数组等相关知识。作为C语言的大头,指针这块知识必须要拿下,最后祝读者学业有成,奥利给!
|