前言
“指针”,顾名思义就是位置的标记,在历史上我们通过在航海地域上,设置大头针来标记船只的位置,让我们在往后在找寻我们之前的足迹的时候不至于迷路,而当我们来到了现代,我们在数据流的海洋中遨游,为了不让我们迷路,C语言的创造者丹尼斯老先生就创造了指针,那么指针到底是什么呢? 就让我们来看看吧~~
1.内存长什么样?
在计算机执行命令的时候,CPU(中央处理器)在处理数据,而数据又是从内存中获取的(输入的),在CPU中完成了计算以后,又把运算的结果返回内存。
而内存作为存储数据的空间,一般的电脑内存大小至少都有8GB(这是一块很大的空间),我们想先其中存入各种各样不同的数据,就会需要一块块不同的空间,而内存其实也是这样划分的,内存在使用的时候会被划分为一块块内存单元,而每块内存单元的大小取1个字节。
补充:内存的单元转化关系:
1byte(字节) =8bit 1KB= 1024byte 1MB = 1024KB 1GB = 1024MB 1TB = 1024G
1PB = 1024TB ,1bit(比特位)即一个而二进制位,用于存放一个二进制数字(0/1)
2.什么是指针?我们为什么需要指针?
在我们的日常生活中,我们在点外卖、寄快递等等情况下都会用到一个东西---地址,比如当你在点外卖的时候,你不填写地址,那么我们外卖员就只能一层层,一间间取遍历寻找你的位置,这样对于饥肠辘辘的你来说肯定是不能接受的,但如果你填写了地址,那么外卖员就能通过地址精准的找到你,大大缩短了时间。
计算机在执行指令的时候,输入器从外部设备输入数据,如果不提供存储这个数据的容器,那么这个数据也就只能在能存空间的多个内存单元,一个个遍历才能放入正确的存储空间,但如果能有那块储存空间的地址呢?那么我们就可以直接把数据传到正确的位置,相当于外卖员知道收货地址一样的。而这里的“地址”的另外一个名字就是大名鼎鼎的指针。
那么指针到底是什么呢?其实C语言为其内存空间中的每块内存空间都编了号,而这个编号就相当于你家的邮件编号,CPU能通过这个编号快速找到其所对应的内存空间。
具体的对应关系如下:内存单元的编号==地址==(C语言)指针
3.地址是如何生成的?
地址的生成我们其实还有另外一个名字----编制
CPU与内存之间依靠地址总线、数据总线、控制总线进行联系。
<!>数据输入的过程:数据在输入进内存以后,控制总线接到要进行计算的指令以后,32根(对于x86)地址线,通过改变电线的电频来区分0/1,32根地址线,就是32个二进制数字组成的二进制序列,而这个序列就是我们输入的数据被内存空间所分配的地址,由此访问到储存在此位置上的数据,在由数据总线传回CPU
<2>数据输出的过程
CPU中完成计算以后,再次由地址线的32个二进制数字所表示结果存储的位置,把运算的结果放在其在内存中被分配的空间。
注:地址就是内存空间的各个单元的编号,地址在内存当中不会被储存起来,只是与内存单元存在映射的关系。
3.指针变量,地址以及相关的操作符
3.1取地址操作符(&)
取地址操作符:就是一个能取出变量地址的运算符。
变量的创建,其在本质上就是在内存中文该数据申请了一块空间,变量的空间是由其内存为其随机分配的,即两次创建的变量的地址先后关系是随机的。
#include<stdio.h>
int main()
{
int a=10;
return 0;
}
变量的创建==>在内存在内存中申请了4个字节的空间,用于存放整形数据10
而内存为每个内存单元(1字节)都编了号,4个字节就有4个不同的地址,而这4个字节用于存放这个整型变量的数据。
1 0x010FFAE4
2 0x010FFAE5
3 0x010FFAE6
4 0x010FFAE7
而取地址操作符取出的其实为第一个字节的地址作为变量a的地址。4个字节共同构成10这个数字,其在内存空间中自然是连续存放的,我们找到了第一个字节的地址就能顺藤摸瓜地找到后几个字节的地址。后面几个字节的地址=元素(首)地址+相距的字节数
注:虽然在计算机内部,数据均是以2进制存储数据的,但在输出时,32太长,多以16进制数来展示。
3.3指针变量与解引用操作符(*)
指针变量顾名思义就是存储指针的操作符。
int *pa=&a;
注:其实指针变量还有其他两者写法,其都是合法的。
int* pa=a;
int * pa=a;
我们通过取地址操作符把a变量的地址取出来,存放在指针变量pa当中。
注:我们其实可以把取地址&和解引用*看为两个可逆的操作。
由此我们也不能推出这里用取地址符号取出的其实为a变量的首地址,即我们向指针变量中放入的是变量a的首地址。
注意:我们要区分清楚这几个概念:
指针变量,也是一种变量,其内部存储的为其所指向对象的地址,而其本身作为变量也会想内存空间申请自己的空间。
指针:地址,为数据在内存单元的编号。
变量(某指针变量指向的)内存放的则是一个数据。(其实地址本身也是一种数据)
int *pa:int 与其后*为两个独立的部分,只有两者同时存在时在能用于存放一个地址。
这里的“*”是在说pa为一个指针变量,而前面的int则是指的这个指针变量所指向的变量的数据类型。
指向:我们向指针变量中放入了变量的首地址,我们由此顺藤摸瓜就能找到剩下3个字节的地址,从而找到放在对应位置上的变量a的数值,因此我们说指针变量pa指向变量a
变量pa中存放有a的地址,我们通过pa中存放的地址就能访问(加解引用操作符*)到在对应位置上的整型变量a,即*pa==a.
*pa其实可以看为变量a的一个代理,把指令作用在pa上,由pa中存储的数组找到在对应位置上的变量a,在对数据进行操作以后再覆盖掉原来的数据(有时无法直接改变a的数据就用*pa的方式来访问)
对*pa进行操作,虽然a的位置没发生改变,但在对应位置上的数据已经不一样了。
3.3指针变量的大小
前面我们说了指针变量根据其指向的数据的类型,也可以分为不同类型的指针。那么他们的大小也是不一样的吗?
其实这个我们在前面已经提示过了,用于表示的地址的地址线一共有32根,每条都能表示一个0/1的数字,而这一串的二进制序列就组成指针。1个字节为8个比特位,那么32个比特就是4个字节(64位系统就是8字节)。即地址的大小其实是由所使用的硬件决定的。
注:指针变量就是用于存放指针的,不论把什么存进去,都会被判定为指针。就如:在锤子眼里什么都是钉子一样~~
4.指针变量的一点简单运用
既然指针变量的大小和类型无关 ,只要是指针变量 ,在同一个平台下 ,大小都是一样的 ,为什么还要有各 种各样的指针类型呢?
1.不同类型的指针的访问权限不一样
//代码1
#include<stdio.h>
int main()
{
*pi = 0;
但存储的内容相同
return 0;
}
//代码2
#include<stdio.h>
int main()
{
int n=0x11223344;
char*pc=(char*)&n;
*pc=0;
return 0;
}
虽然再变量pi,pc中储存的都是n被分配的4个字节空间中较小的那个的地址,但不同类型的指针在解引用的权限是不一样的。
int*的指针一个访问4个字节的内容
char*一次这只能访问1个字节的内容
注:2个16进制数字占1个字节(1个16进制数字可用4个二进制数字来表示)
注:如果一个数据未存入对应类型的指针变量当中,非对应应类型的指针变量只要存的下,虽然这个变量的地址不会损坏,但是会报警告
此时我们可用强制类型转换,把int*的指针变量转化为char*,此时就没有警告,但是一次只能访问1个字节的内容。
根据我们一次想要访问的内容,选择不同类型的指针来储存地址:
3.8字节--double*
4.4字节--int*
5.2字节--short*
6.1字节--char*
2.指针+-整数(浮点数不可)
我们从一个实例来入手,把一个长度为10的数组内全部的元素置为1后,再打印出来:
之前我们通过打印下标来实现的各个数组元素的访问:
#include<stdio.h>
int main()
{
int arr[10]={0};
int i=0;
for(i=0;i<10;i++)
{
arr[i]=1;
}
for(i=0;i<10;i++)
{
printf("%d",i);
}
return 0;
}
那么当我们学习了指针以后,这段代码又将如何实现呢?
int *p=arr;
p中储存的为数组arr的首地址,且为int*类型的指针,即一次访问4个字节的内容,先找到一个元素的首地址在解引用操作,以此可对数组每个元素进行操作
注:<区分>*(p+1)!=*p+1
*(p+1):p中存储为数组arr的首地址,(p+1)即储存的为第二个元素的首地址,*(p+1)即为第二个元素,以此类推,(p+i),即为下表为i的元素的首地址。
*(p+i)/p++即能实现指向数组中每一个元素的效果。
而*p+1,即为首个元素加上1的结果
注:*p=1;无法实现,把数组中所有元素置为1的效果,p当中储存的arr仅为数组的首地址,对*p只能影响首个元素的值。
最终的代码:
#include<stdio.h>
int main()
{
int arr[10]={0};
int i=0;
int* p=&arr[0];
for(i=0;i<10;i++)
{
*(p+i)=1;//p未变,只是相对于数组的首地址在增加,依次指向不同的元素
}
for(i=0;i<10;i++)
{
printf("%d",*(p+i));
}
return 0;
}
那么如果我们此时的需求变了,希望把数组中元素的每一个字节均改为1,那么此时用char*来进行访问就更为合适。
这里就又体现了指针类型的差异:向前向后一步的差异为多少(int一次跳过4个字节,char一次跳过1个字节)
3.void*指针
其实除了我们常见的int,char,float...类型的指针外,还有一种类型的指针,即void类型的指针。
void的指针其实
可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。
但是也有局限性:
由于①运算符两端的数据类型必须相同,②未知加一个整数的时候,一次跳过多少个字节,所以void类型的指针不能直接进行指针的+-整数和解引用的运算。
如:整型变量取出来的地址属性就是确定的,即int*,不用int*来接受这个地址是就会报警告
注:使用void*类型,能接受任何数据类型的地址,当我们不清楚要接受的指针为何种类型的时就能用void*来接收,用于临时的储存数据,相当于一个”垃圾桶“。
特别用法:放于形参列表处,用于接受地址,实现泛型编程,使得一个函数来处理多种类型的数据
注:开始用时,应取出放入对应类型的指针当中去
创作不易,求点赞,收藏,关注❤~~