【C语言初阶】初学C语言指针必看,探索你眼中抽象的东西,让它不再神秘

【C语言初阶】初学C语言指针必看,探索你眼中抽象的东西,让它不再神秘,第1张

前言

       很多朋友学习C语言时,都会对数据的存储以及什么是地址,都只有一个模糊的概念。而遇到这种情况的朋友,要么是老师觉得太难,故意一笔带过,要么是老师讲的太罗嗦让你们找不到重点。但是在学指针时,这些都是必不可少的基本功。如果这些概念不清楚的话,指针将会学的一塌糊涂!接下来小编就用通俗易懂的语言让你们直视这些东西,缕清你的思路,让你不再对它模糊,甚至害怕

目录

​前言

一、地址是什么?

二、数据是怎样存储的,与地址有何关系 

​1.整形数据的存储

2.浮点型数据的存储 

三、指针 

​1.指针是什么?

2.指针变量

3. 指针类型 

      (1)指针类型的意义是什么? 

      (2)指针类型的意义就这些吗?当然不是,请往下看。 

总结


地址是什么?

   不知道什么是地址?觉得地址很神秘很抽象?

        其实地址就是一串实实在在的数字,只是内存为这片空间所分配的编号而已。在内存中,每一个最小单元(字节)都有着属于它自己的编号,绝对不会重复!就像身份z编号一般,独一无二。话不多说,上图!

每一条地址中都能存储1字节的数据

         嘿嘿,这就是地址,是不是让你大吃一惊。32位机器每个地址都是用8位16进制表示,即4个字节(64位机器用16位16进制表示,即8个字节)并且是连续的。char类型变量所占1字节,所以会分配1个地址。int类型变量占4字节,所以会分配4个地址。float也是4字节,所以也是4个地址......既然知道地址了,那接下来就为大家解释一下数据是怎么存进去的吧~

数据是怎样存储的,与地址有何关系  1.整形数据的存储

        众所周知计算机有三种码值表示整形数据原码、反码、补码。存在计算机内存中的是补码,计算机运算也是用补码,但补码不能直接得到,而是需要在原码的基础上转换的。想要明白数据怎样存储,必须知道三种码值之间的关系与转换方法,所以还请大家耐心看完以下内容。

        有符号(signed)整形码值最高位代表符号位,正数为0,负数为1(这就是 signed char 取值范围是-128~+127而 unsigned char 取值范围是0~256的原因)。正数的原码就是它的反码和补码。

        负数原码是由数值直接转换成二进制最高位取值为1得来的,负数反码是原码符号位不变其他位取反得到的,负数反码加1就是补码。原码转补码用一个词来记就行——取反加1。补码转原码就是原码转补码的逆运算——减1取反。(补码转原码直接用原码转补码的方式也行——取反加1)。注意,取反时符号位千万不能改变。

        例:char类型的数据所占空间是1字节也就是1bit,1bit需要用8位二进制进行存储。那么 char a=-1在内存中是怎样的呢?

        因为是char类型,所以需要把1转成8位二进制,转换后为0000 0001,然后在最高位取1代表负数,所以-1的原码是 1000 0001,原码符号位不变,其他取反,得到反码为1111 1110。反码加1,得到补码1111 1111。此时存在内存中的就是补码1111 1111,如下图。(补充一下小知识:两位16进制数表示8位二进制数,也就是1字节。内存展示时是用16进制所呈现的,实际上存的还是二进制。)

1111 1111转成16进制就是 f f

       

          上面提到过,每一个字节都有一个地址。而char正好占1个字节,所以char a的内存的编号(地址)就是0x00EFF82F,至于它右边的cc和我们的char a没有任何关系。但是它的地址为0x00EFF82F+1也就是0x00EFF830。和我们char a的地址是连续的

        既然计算机是用补码进行计算的,那么-10减2在计算机是怎么实现的呢?本次用int类型的数据举例。如int a=-10,int b=2,int c=a-b

        因为int类型的数据占4字节,所以需要用32位二进制来存储。-10的原码为1000 0000 0000 0000 0000 0000 0000 1010,补码为1111 1111 1111 1111 1111 1111 1111 0110。因为2是正数,所以它的补码就是原码,为0000 0000 0000 0000 0000 0000 0000 0010。用它们补码相减,得-10减2的补码为1111 1111 1111 1111 1111 1111 1111 0100。(16进制为FF FF FF F4)此时存在内存中就是这一串数字,也就是它的补码,如下图。(用8位16进制数表示32位二进制数)

int a=-10存在内存中的样子(补码)

        因为int类型变量占4个字节,所以地址0x008FF9C8~0x008FF9CB的空间都是属于a的。0x008FF9C8存放它的后8位1111 0110,0x008FF9C9存放它的第8位到第16位1111 1111,以此类推。低地址存低位数据,高地址存放高位数据。这就是它地址与它数据存储之间的关系。

int b=-2存在内存中的样子(补码)
int c=a-b存在内存中的样子(补码)

        有些朋友可能会问,数据为什么要以这种顺序存储呢,为什么不用低地址存储高位数据,高地址存储低位数据?这样最高位就在前面,最低位就在后面,我们来读取的时候不是会方便许多吗?

        嘿嘿,其实是有的,开发环境不同,也就是编译器不同,它所存储的方式也会有所改变。图片中所展示的低地址存低位数据,高地址存放高位数据这种存储方式叫小端存储,低地址存储高位数据,高地址存储低位数据这种存储方式叫大端存储。小编写了个大小端字节序存储测试程序,好奇的朋友可以测试一下自己的编译器是大端存储还是小端存储~

​
#include 
int main()
{
    int x = 0;
    char* p = &x;
    printf("请输入一个整数:");
    scanf("%d", &x);
    if (*p==x%256)
    {
        printf("当前机器是小端存储");
    }
    else
    {
        printf("当前机器是大端存储");
    }
    getch();
    return 0;
}

​

 2.浮点型数据的存储 

        既然整形数据的存储介绍完了,那么就来进行浮点形的介绍吧。浮点数可不是用原码、反码、补码来表示了。

        IEEE(电气和电子工程协会)754标准中规定,任意浮点数都能用(-1)^S*M*2*E(可看作2进制的科学计数法)来表示S用来表示正负,0为正,1为负,M用来表示科学计数法中的有效数字2是科学计数法中的底数,E表示科学计数法中的指数,其中E为 unsigned(无符号) 类型的数,即非负数

        IEEE(电气和电子工程协会)754标准中对有效数字M和指数E在计算机内部保存时还有一些特别的规定。M的取值范围是1<=M<=2,也就是1.xxxxxx的形式。所以在计算机保存时,默认这个数最高位总是1 ,因此可以被舍去,只保存后面的小数部分。 等到读取的时候再加上去,这样做的目的是为了节省一位有效数字。而对于指数E在科学计数法中是可以出现负数的,而我们的E是unsigned类型的,所以为了避免它出现负数,IEEE754规定指数E存进内存中必须在真实值的基础上再加上一个中间数。

        例1:float a=10.5用(-1)^S*M*2*E表示,方法如下,先将10.5转成二进制得1010.1,小数点向左移动3位,得有效数字M为1.0101,指数E为3,因为10.5为正数,所以符号位S为0。转换之后就是这个样子(-1)^0*1.0101*2*3。 知道了浮点数的表示方法,那就来看看它是如何存进内存当中的吧。

        因为float类型占4个字节,所以需要用32位二进制来表示,下图是单精度浮点数存储模型,其中S占1位,E占8位,M占23位。

单精度浮点数存储模型

         (-1)^0(S)*1.0101(M)*2*3(E)存进内存中时,已知S=0,而因为 a 是 float 类型,所以根据存储模型分配给指数E8位空间,而E为无符号数(unsigned),所以E的取值范围为0~256,即中间变量为127,故E=3+127=130,转成8位2进制为1000 0010。因为规定M只保留小数部分,所以得M=0101但是M需要占23位,由于M是小数部分,为了不改变它的值,所以需要在0101后面补0,得M为0101 0000 0000 0000 0000 000。组合在一起的0100 0001 0010 1000 0000  0000 0000 0000(16进制为41 28 00 00) 就是float a存在内存中的样子了,如图。

float a=10.5用(-1)^S*M*2*E即 (-1)^0*1.0101*2*3存进内存中的样子

        例2: 将float a=0.5存进内存中。先用(-1)^S*M*2*E把0.5表示出来,方法如下,0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,所以将小数点右移1位,则为 (-1)^0*1.0*2^(-1)。

        接下来将它存进内存中,则E=-1+127=126,二进制表示为 01111110,而有效数(M)1.0去掉整数部分为0,补齐0到23位得0000 0000 0000 0000 0000 000,则其二进制表示形式为:0011 1111 0000 0000 0000 0000 0000 0000(16进制为3F 00 00 00),即内存中的样子,如图。

float a=0.5进内存中的样子

        下图是双精度浮点数存储模型,双精度浮点数在内存中占8个字节也就是64位,如图可知其中指数E占11位,有效数M占52位,符号位S占1位。因为E占11位,所以E的取值范围是0~2047,故指数的中间变量为1023。因为双精度浮点数的存储方式与单精度存储方式一样,所以我便不再示例了,感兴趣的朋友们可以根据模型与公式自己算一算。

双精度浮点数存储模型

        浮点数的数据从内存中取出来的方式就是存数据的逆运算,但是对于指数E从内存中取出还可以再分成三种情况:

         E不全为0或不全为1 这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。

        E全为0:这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。 

        E全为1:这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)。

        数据怎么存储的在这里就已经介绍完了,大家都明白了吗?有什么疑问可以询问小编。如小编有地方说错了,或者说的不好,欢迎各位批评改正~~

三、指针 1.指针是什么?

         指针是内存中一个最小单元的编号,也就是我们上面所说的地址,是用来存放内存地址的变量。所以说指针就是地址,而口语中说的指针就是指针变量

2.指针变量         我们可以通过 &(取地址 *** 作符) 取出变量的内存 起始地址(第一个地址) ,把地址可以存放到一个 变量 中, 这个变量就是指针变量。
#include 
int main()
{    
     int a = 10;//在内存中开辟一块4字节的空间
     int *p = &a;//这里我们对变量a,使用& *** 作符取出它的起始地址,即首地址放在p中。                     
     return 0;//p就是一个之指针变量。               
}
3. 指针类型          (1)指针类型的意义是什么? 

        char* 类型的指针是为了存放char类型变量的地址。int* 类型的指针是为了存放int类型变量的地址。float *类型的指针是为了存放float类型变量的地址。但为什么要这样呢?注:指针变量所存的都是其变量的首地址。

        因为char*只能管辖1个地址,对应的是1个字节。int*能管辖四个地址,对应的是4个字节。那么有朋友会问,既然是按管辖地址个数来区分,那么用int*代替float*不行吗?

        当然不行,虽然float*与int*一样,都是管辖4个地址,但是解引用时(指针解引用,就是通过地址去访问,或者改变地址所对应的,那一块内存里面存放的数据)读取或者改变内存中数据的方式不同,所以也不能混为一谈。就像float与int一样,虽然都是占四个字节,但是它们是完全不同的两个东西,谁也不能代替谁,如图。      

#include 

int main()
{
	int n = 10;
	float i = 20;
	float* pfn = &n;
	float* pfi = &i;
	int* pi = &i;
	int* pn = &n;
	printf("%-15d float *n\n", *pfn);
	printf("%-15f float *i\n", *pfi);
	printf("%-15d int *n\n", *pn);
	printf("%-15f int *i\n", *pi);
	return 0;
}
        (2)指针类型的意义就这些吗?当然不是,请往下看。

        不同指针类型的指针不止管辖的地址个数不同,不同指针类型的指针还决定了指针向前,或者向后走一步的距离有多大。

         如int*类型的指针加1,则代表这个指针加了1个int*的距离,也就是四个字节,对应着四个地址,如本来的地址为0x00EFFE74,那么加1之后就为0x00EFFE78。

        如char*类型的指针加1,则代表这个指针加了1个char*的距离,也就是1个字节,对应着1个地址,如本来的地址为0x00EFFE74,那么加1之后就为0x00EFFE75。

#include 

int main()
{
    int n = 10;
    char* pc = &n;
    int* pi = &n;
    printf("%p int n的地址", &n);
    printf("%p pc", pc);
    printf("%p pc+1", pc + 1);
    printf("%p pi", pi);
    printf("%p pi+1", pi + 1);
    return 0;
}
         指针的类型还决定对指针解引用时,有多大的权限(能 *** 作与访问几个字节的数据)。                   例:  char* 的指针解引用就只能访问一个字节, 而 int* 的指针的解引用就能访问四个字节。 如用char*类型的指针解引用int型的变量,实际上只能访问与改变int型变量的最低位1个字节数据,也就是它的首地址所对应那一字节的数据, 而剩下3个字节的数据访问不到,如下图。

        本次内容到这就结束啦~,如果感觉不错的话还请点个赞,谢啦~

       加油,技术人~


总结

        以上就是今天要讲的内容,本文仅仅简单介绍了地址,数据的存储与指针。很多地方为了通俗易懂都未深入的去讲。入需更深入的学习,还需要自己去查看更多的资料。本篇文章只适合指针初学者阅读。码字不易,希望友友们能够支持支持,十分感谢~~ 

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1294643.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-10
下一篇2022-06-10

发表评论

登录后才能评论

评论列表(0条)

    保存