C语言的指针
运行程序时计算机会把数据都存储到存储器中,在储存器中有很多很多的单元,
每个单元都可以存储数据,就像下面那样这么多小方块,
每一个小方块都是可以用来存储数据的,那么当计算机把数据存到存储器中,当需要用的时候怎么找到这个数据呢,
有一个最简单的办法,就是把这些小方块都编一下序号,像这样,现在每一个小方块都有一个唯一的数字标识,
称作 地址 。
现在程序打算存储一个数字8,计算机会在内存中找到一个空白区域,然后把8存进去,并保留存储单元的地址,也就是图中的 “12” ,
这样,当计算机存储数据的时候只要记得是存到了那个方块中(比如图中的12号),然后需要用那个数据的时候,只需要 到那个12号方块中找到那个数字就行了。
int n =8; 创建了一个变量,变量的值是8
现在这个n就是那个方块,
使用符号&可以得到这个方块的地址。
int *p =&n; 现在指针p保存了方块n的地址,就是上面图中的12;
所以现在p的值应该=12 因为指针的值就是地址,而地址就是一个数字,这个数字就标志了存储单元在内存中的位置,如上图中的 12 。
我们可以直接把地址当数字输出来,
**print("%d",p);**
这样也能运行,但是C语言有专门的符号用来输出指针,p
**print("%p",p);**
这两个结果应该是一样的,让我们试一试:
额,好像不一样,不对,让我好好想一想。
第二个数字好像是0x开头的,这说明,说明
哦,说明他是十六进制的,
好,现在让我们把上面那个也换成十六进制的试试,十六进制的修饰符你应该没忘吧,对就是 x
好像差不多了,但是怎么好像差了几位,
嗯,应该是溢出了,就是我们指定的 x 应该默认的是int型长度的,有些计算机int型数据的长度是16,所以printf()这个函数接受%x时,只取16位,而这个指针的值应该超过int型长度,让我们加大一点长度试试,怎么加长呢, 当然是用 l 啦。
现在好了,一样了吧!
现在让我们再创建一个数组,
int nums[]={1,2,3,4,5};
然后计算机就会寻找一块足够大的连续的空白地方来存放这个数据,像下面这样。数组的名字是可以当指针使用的,(当然还是有一点点不同,后面我会 举个小栗子~)
int* p=nums//或者不创建指针p直接使用 nums 也是可以直接使用的。
在上面那个图中,指针p 或者说nums 就需要标志出这个数组在计算机内存中的地址,不然计算机可找不到这个数组。
但是数组占了那么多的地址,到底用那个数字才比较合适呢,
答案当然是用第一个啦,也就是 4444
所以p和nums的值就应该是4444 。
然后要访问数组的内容只要根据这个地址去找就行了, 用符号 *
现在p/num 应该保存了这个数组第一个元素的地址
所以 *p=*nums =1;
p+1 就代表数组的下一个值的地址
所以 *(p+1) = *(nums+1) = 2;
还有一种方式查看数组的其他元素,就是我们常见的 num[0] 或者p[0]
这可以看作是用 与数组第一个元素地址的差值 来找。(专业术语-偏移地址)
数组的第一个元素的地址现在已经保存在了num/p中
数组的第一个值的地址与第一个值地址差0,所以查看数组的第一个元素就用nums[0] 或者p[0]
数组的第二个元素 与第一个元素的地址差1,所以是nums[1] /p[1] …
当然,数组的名字和指针还是有点不同的,举个栗子。
one
char* name1="zhang san";
char name2[]="zhang san";
//这两种方式看起来差不多吧,接下来,让我们开始实验。
#include <stdio.h>
int main()
{
char* name1="zhang san";
char name2[]="zhang san";
name1[2]='c';
printf("name1[2]='c' completed!");
name2[2]='c';
printf("name2[2]='c' completed!");
}
结果竟然出错了,好吧,
让我们注释一部分试试
#include <stdio.h>
int main()
{
char* name1="zhang san";
char name2[]="zhang san";
//name1[2]='c';
//printf("name1[2]='c' completed!");
name2[2]='c';
printf("name2[2]='c' completed!");
}
运行成功了,
所以,所以,问题出在了下面这个表达式
name1[2]='c';
这是怎么回事呢?
其实问题就出现在了定义的时候
char* name1="zhang san";
char name2[]="zhang san";
嗯?怎么了,不是说数组可以当指针用么,怎么这里会有问题呢?
原因就在与
第一条定义的是 —字符串字面值,(迷迷糊糊记得上课老师好像说过字符串什么*什么 ,啊,对,字符串不能改变)
第二个定义的是----字符串数组,
它就等价与
char name2[]={‘z’,‘h’,‘a’,‘n’,‘g’,’ ‘,‘s’,‘a’,‘n’,’\0’};
其实就是个字符数组啦,当然想改就改咯~
two
还有一点不同:
那个红杠可不是我画上的,是编译器提示错误,
为什么呢,因为name2是数组的名字,而name1是指针。
指针可以指来指去,这数组可不能这么干,至于为什么,我也不太清楚,指针就是用来 指这个指那个的嘛,数组还是老老实实当数组用吧。
等会~你刚才不是说字符串常量不能改变的么,现在怎么!?!?!
额,我好像是说过,
举个不恰当的例子。
我现在牵着一条狗,你跟我说不能杀它,,好,我照办,那我把他扔了可以吧,我有没杀它。
(无辜的眼神)
如果不想让它把可爱的狗扔了,那就给它带个紧箍咒吧 —— const
可能一开始想到的是这样
const int * num="zhang san";
这样可不行,这样其实没有任何效果,因为const 放在最前面是限定指针指向的那个内容不能更改,(是内容,内容,内容 )而"zhang san" 这已经是字符串常量,不能更改了,所以没用。
要放的近一点,想这样
int * const num="zhang san";
现在这个num只能老老实实的牵着 "zhang san" 这个字符串常量了 。
如果实在是记不住,想想那条无辜的小狗,你要是把这个紧箍咒放错了位置,那个小狗可要被这个野指针扔了!
(我没骂 "zhang san"是条狗, 滑稽保命~ )
现在让我们来点更刺激的。
你说指针是地址,是数字,
对,我是这么说的,看
确实是数字,我没骗你吧!
那好,既然指针是数字,那我可以用他来当数组下标吧
额。。。。。我试试。。。
不出所料,不行。。。
等等,我又想到了,你刚才说数组名字和指针用法很像,而且指针又是数字,所以。。
(坏笑)
我可不可以用一个数字代替指针,进而代替数组呢?
额。。。。
(害怕)
。。我试试。。。。
嗯?!?!上面两个编译竟然没有报错。
运行一下
好吧,不行。
我再注释一个试试
我***。。。。
竟然成功了而且这个结果 4 !!
慢着,那个数组中好像就有这个4。我再试试。
(目瞪口呆)这!这!这!好像就是数组中的那个数字。
让我们回想一下。指针和数组名字的值都是数组第一个元素的地址,访问其他元素,就是用和第一个元素地址的差值(偏移地址)来查找
那么当第一个元素的地址是44444的话,那么第二个元素的地址就是44444+2
就是44446,所以第二个元素的地址就是固定的44446,
现在这个元素的地址已经照告天下,谁想访问这个元素,只要到这个地址就行了,这个元素跑不了。所以不管数组的首地址在哪,只要首地址加上偏移地址得到的最终地址是444446就行。
现在假设num的值是5555 即数组的首地址是5555
那么这个数组的第二个元素的地址就是5556
那么num[1]=( 5555[1] )=(5555+1)=(1+5555)=( 1[5555] )=1[num];
哦,哦,哦 ,原来如此~
让我们多试几次
成功了!!
好了疯狂的实验到此结束,让我们回顾一下。
int n =88;
int* p =&n;
指针既然是数字 ,那我们定义指针的时候只要用一个long 或者int类型的变量不就行了,为什么要用和原来的那个值一样的类型呢。
慢这,上面还有一点细节这怎么了,有哪里奇怪的吗?
别慌,让我们把它输出来。
what!?!?!
p=1 p+1=5;
这个 1 好大啊!!
我们让他加一它竟然加了4。
让我们冷静分析一下。
我们让指针p加一的目的是为了获得数组的下一个元素的地址,而它直接加上4
并且我们知道数组是内存中一片连续的空间,而这个4的单位应该是4个字节
1个字节=8位,所以4个字节=32位,而我的电脑是64位,说明一个int型数据占32位,我们让指针加1,为了得到下一个元素的地址,而一个元素占32位(4字节),所以指针加 4 ;
所以我们为什么要为指针制定类型呢?
就是为了我们让指针+1 的时候,程序可以根据我们为指针指定的类型来判断指针的值是加几个字节才能到下一个元素的地址。
那为什么不是加32 ,而是要加4呢,为什么不用 位数而用字节数呢,
额,额,额,额,额
计算机就是这么干的。如果用位数的话,那数字也太大了吧,计算机就把8位弄成了一个字节。然后算字节数,这样地址数就比较小
(我瞎猜的,如果有大佬知道,请留言,我改, 抱头)
来点饭后甜点吧。 箭头所指的地方刚才试过了程序报错了,为什么报错呢,箭头上面那个他所找的元素我们是知道的,就是数组中的元素,但是下面那个num2 的个指针的值不一定是多少呢,所以要找的元素我们也不知道在哪,可能访问到了系统其他不该访问的地方,然后被叫停了,如果不叫停,继续访问,可是很危险的,想象一下如果不报错,那我们就可以随便获取一个未知的内存位置来改变它,万一这是系统运行文件的一个地址,那我们运行一次程序岂不是有可能把整个系统弄崩溃了,所以必须要叫停 。
好吧,就先说到这里吧。C语言的指针还有很多有趣的玩法的,想多重指针,指针数组,可不要被吓到呀,C语言还是很有趣的!
(如果有说错的地方,欢迎留言指正哦~)