前言
指针作为c语言学习过程中必不可少的一个重要部分,对我们后期数据结构等学习具有重要意义。很多初学者面对指针复杂琐碎的知识点不知从何下手,我将会分为初阶和进阶两个部分将指针的知识点进行梳理,本篇为指针初阶的知识点讲解,保证大家看完之后会对指针会拥有更系统和深刻的认识,接下来的博客中也会提供指针与数组相关的一些经典笔试题解析,帮助大家巩固指针知识,保证大家时间从入门到精通!!!
下面,请大家坐稳扶好,正文开始啦~~
1.指针是什么?
在写c语言程序时,无论是创建变量,还是数组等,都要在内存中开辟空间。而每个内存的最小单元(最小单元为一个字节)的编号,就是指针,也就是数据在存储时的地址。
接下来我们在vs中进行一次代码实现~
这里的000000E662CFFBA4就是在内存中为存放a开辟的空间的编号,也就是地址。而p是专门用来存放a的地址的,p就被成为指针变量。p前的标志的int*表示p是指针变量,其指向的对象类型是整型变量。
注意:有一些初学者可能会有疑问,a作为整型变量,在内存中应该占用四个字节的空间,那是如何将a的地址存放到p变量中的呢?
注意这里是将整型a的4个字节的第一个字节的地址存放在p变量中。如图所示:
这里我们可以在进行一个编址的知识点的补充:
在计算机中,如果是32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是1或者0;那么32根地址线产生的地址就会是:
那么,这里就有2^32个地址。
每个地址标识着一个字节,那我们就可以给2^32Byte==2^32/1024kb==2^32/1024/1024mb==4GB的空间进行编址。
同样的方法,如果是64位的机器,就是64根地址线,那么就会有2^64个地址。
总结:
- 在32位的机器上,地址就是32个0或者1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
- 同理,在64位的机器上,一个指针的大小就应该是8个字节。
2.指针类型
我们接下来要讨论一下:指针类型。
我们都知道,变量有着不同的类型,比如整型,浮点型等等。那么指针有没有类型呢?答案是肯定的。接下来我们先给出一个指针的定义方式:
type+ *
其中:
- type是p指向的对象类型;也就是p解引用的时候访问的对象大小是sizeof(type)
- *说明p是指针变量
这里进行几个举例:char*类型的指针是为了存放char类型变量的地址。
short*类型的指针是为了存放short类型变量的地址。
int*类型的指针是为了存放int类型变量的地址。
那么,指针类型的意义究竟是什么呢?
指针类型可以决定指针解引用的时候访问多少个字节,也就是决定了指针访问的权限。比如int* p,*p访问的字节长度就是4个字节。下面我们再看一看指针类型在指针的运算中的作用。
2.1指针+/-整数
我们刚才知道了,指针的类型决定了指针访问的权限的大小,也就是说,指针的类型决定了指针向前或者向后走一步有多大(距离)。
比如:整型指针+1-->说明指针要向后跳过4个字节
字符指针+1-->说明指针要向后跳过1个字节
下面我们来举例说明:
分析代码:
int n=10;//向内存申请四个字节的空间,存放变量n
char*pc=(char*)&n;//取n的地址,强制转换成char*类型的指针,存放到pc中
int* pi = &n;//取n的地址,存放到pi中
printf("%p\n", &n);//打印n的地址
printf("%p\n", pc);//打印pc的地址,pc中存放的是n的地址
printf("%p\n", pc + 1);//打印pc+1的地址,但是由于pc被强制转换成了char*类型的指针,所以内存中加1个字节
printf("%p\n", pi);//打印n的地址
printf("%p\n", pi + 1);//打印pi+1的地址,由于pi是int*类型的指针,所以内存中加上4个字节
总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)
2.2指针的解引用
我们接下来讲解指针的解引用与指针类型之间的关系,这里我们还是以代码讲解为例,观察在调试过程中内存的变化。
( 这里补充一个小知识,内存中的数据是倒着放的,我们在后续的博客中会补充相关的只是,在这里就不在细讲了。)
分析代码:int n=0x11223344;//数据倒放在内存中
这里的数据存储在内存中是倒放的。
char* pc = (char*)&n;//将n的地址取出,并且强制转换成char*类型的指针,再在内存中开辟一个空间,放入n的地址编号(这里我们仔细观察也会发现,地址的序列号也是倒放进内存中的)
int* pi=&n;// 将n的地址取出,再在内存中开辟一个空间,放入n的地址编号
注意:指针pc和pi之间类型是存在差别的,pc已经被强制转换成了char*类型,所以在解引用之后,*pc只能指向一个字节。
*pc=0;//pc指针类型是char*,所以只能操作一个字节,这里我们发现 *pc=0只改变了一个字节。
*pi=0;//pi指针类型是int*,所以可以操作四个字节,这里我们发现*pi=0改变了四个字节。
总结:1.指针变量是用来存放地址的,地址是唯一标识一个内存的最小单元。
2.无论是什么类型的指针,大小在32位平台是4个字节,在64位平台是8个字节。
3.野指针
概念:野指针就是指针指向的位置是不可知的。(随机的、不正确的、没有明确限制的)
3.1野指针的成因
1.指针未初始化
这里的指针p没有初始化,在vs中会出现报错。
2.指针的越界访问
这里我们还是用代码进行讲解:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
*(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针
printf("%d ", arr[i]);
}
return 0;
}
3.指针指向的空间释放
这里我们放在后续的动态内存开辟的时候进行讲解,这里可以简单用一个例子提示一下。
#include<stdio.h>
int* test()
{
int a=110;//a是局部变量
return &a;//
}
int main()
{
int* p=test();
printf("%d\n",*p);
return 0;
}
这里p可以获得a的地址吗?显然是不可以的,进入函数时,会创建一个局部变量a,而走出函数后,创建的局部变量会在内存中释放,所以p无法调用到a的地址。如图所示:
3.2如何规避野指针
1.指针初始化
2.小心指针越界
3.指针指向的空间释放,及时置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
4.指针运算
4.1指针+/-整数
数组名是首元素的地址,数组名和指针之间有着密切的联系。即p=&arr[0],我们用图像来具象地解释一下:
即*(P+i)==arr[i]
这里我们也可以提供一个新的理解下标引用操作符的新思路:[ ]和*一样,有着解引用的作用。那么*(arr+i)=arr[i]
4.2指针-指针
指针-指针的绝对值就是指针与指针之间的元素的个数。下面我们还是举个例子进行说明~~
我们知道arr1是指数组的首元素,而arr1+4就是数组的第四个元素 ,在这里指针p-q的绝对值得到的就是这两个指针之间的个数,也就是四个。
注意,这里有一个前提:两个指针指向的是用一个空间~~
4.3指针的关系运算
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
这个标准规定是什么意思呢?我们在这里还是用图像详细解释一下~~
举个例子:
for(vp=&values[N_VALUES];vp>&values[0];)
{
*--vp=0;
}
分析代码:vp初始化为数组的最后一个元素的地址,进入循环,先将vp减1,接着解引用,赋值为0,这样便把values[N_VALUES-1]到values[0]的所有元素全部赋值为0。
那么有些初学者可能会考虑到将代码这样去写:
for(vp=&values[N_VALUES-1];vp>=&values[0];vp--)
{
*vp=0;
}
分析代码:这样写的代码就违反了我们的标准:当vp=&values[0]时,vp会继续减1,那么就会导致首元素之前的内存位置的指针与首元素的指针进行比较。
实际上在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它是可以运行的。
初阶指针知识点大大放送(上)到这里就结束啦,接下来我们还会继续更新初阶指针知识点大放送(下),敬请期待哟~~
欢迎大佬们批评指正,创作者不易,别忘了三连一波吖~~小粉象这厢有礼啦~~