好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

变量与内存

变量与内存

变量与内存

在前面一节中简单介绍了 变量 的使用,当我们定义一个变量的时候,系统就会为变量分配一块存储空间。而变量的数值在内存中是以二进制的形式存储的,这讲来深入研究变量在内存中的一些存储细节。

回到顶部

一、字节和地址

为了更好地理解变量在内存中的存储细节,先来认识一下内存中的“字节”和“地址”。

1.计算机中的内存是以 字节 为单位的存储空间。内存的每一个字节都有一个唯一的编号,这个编号就称为 地址 。就好像酒店是以房间为单位的,每个房间都有一个唯一的房号,我们根据房号就能找到对应的房间。

里面的每个小框框就代表着内存中的一个字节,白色数字就是每个字节的地址(这里采取十六进制来显示,地址值是随便写的,仅作为参考,真实情况中的地址值不一定是这个),可以发现,内存中相邻字节的地址是连续的。

2.大家都知道,一个字节有8位,所能表示的数据范围是非常有限的,因此,范围较大的数据就要占用多个字节,也就是说,不同类型的数据所占用的字节数是不一样的。

回到顶部

二、变量的存储

1.变量类型的作用

跟其他编程语言一样,C语言中用 变量 来存储计算过程使用的值,任何变量都必须先定义类型再使用。为什么一定要先定义呢?因为变量的类型决定了变量占用的存储空间,所以定义变量类型,就是为了给该变量分配适当的存储空间,以便存放数据。比如char类型,它是用来存储一个字符的,一个字符的话只需要1个字节的存储空间, 因此系统就只会给char类型变量分配1个字节,没必要分配2个字节、3个字节乃至更多的存储空间。

2.变量占用多少存储空间

1> 一个变量所占用的存储空间,不仅跟变量类型有关,而且还跟编译器环境有关系。同一种类型的变量,在不同编译器环境下所占用的存储空间又是不一样的。我们都知道操作系统是有不同位数的,比如Win7有分32位、64位,编译器也是一样的,也有不同位数:16位、32位、64位(Mac系统自带的GCC编译器是64bit的)。由于我们是Mac系统下开发,就以64位编译器为标准。

2> 下面的表格描述了在64位编译器环境下,基本数据类型所占用的存储空间,了解这些细节,对以后学习指针和数组时是很有帮助的。

3> 下面的表格描述了在不同编译器环境下的存储空间占用情况

3.变量示例

当定义一个变量时,系统就会为这个变量分配一定的存储空间。

 1   int   main()
  2   {
  3       char  a =  '  A  '  ; 
  4      
 5       int  b =  10  ;
  6      
 7       return   0  ;
  8  }

1> 在64bit编译器环境下,系统为变量a、b分别分配1个字节、4个字节的存储单元。也就是说:

变量b中的10是用4个字节来存储的,4个字节共32位,因此变量b在内存中的存储形式应该是0000 0000 0000 0000 0000 0000 0000 1010。 变量a中的'A'是用1个字节来存储的,1个字节共8位,变量a在内存中的存储形式是0100 0001,至于为什么'A'的二进制是这样呢,后面再讨论。

2> 上述变量a、b在内存中的存储情况大致如下表所示:

(注:"存储的内容"那一列的一个小格子就代表一个字节,"地址"那一列是指每个字节的地址)

从图中可以看出,变量b占用了内存地址从ffc1~ffc4的4个字节,变量a占用了内存地址为ffc5的1个字节。每个字节都有自己的地址,其实变量也有地址。变量存储单元的 第一个字节的地址就是该变量的地址 。 变量a的地址是ffc5,变量b的地址是ffc1。 内存寻址是从大到小的 ,也就是说做什么事都会先从内存地址较大的字节开始,因此系统会优先分配地址值较大的字节给变量。由于是先定义变量a、后定义变量b,因此你会看到变量a的地址ffc5比变量b的地址ffc1大。 注意看表格中变量b存储的内容,变量b的二进制 形式是:0000 0000 0000 0000 0000 0000 0000 1010 。由于内存寻址是从大到小的,所以是从内存地址最大的字节开始存储数据,存放顺序是ffc4 -> ffc3 -> ffc2 -> ffc1,所以把前面的0000 0000都放在ffc2~ffc4中,最后面的八位0000 1010放在ffc1中。

4.查看变量的内存地址

在调试过程中,我们经常会采取打印的方式查看变量的地址

 1  #include <stdio.h>
 2  
 3   int   main()
  4   {
  5       int  a =  10  ;
  6      printf( "  变量a的地址是:%p  " , & a);
  7       return   0  ;
  8  }

第6行中的&是一个地址运算符,&a表示取得变量a的地址。格式符%p是专门用来输出地址的。输出结果是:

变量a的地址是: 0x7fff5fbff8f8 

这个0x7fff5fbff8f8就是变量a的内存地址

回到顶部

三、负数的二进制形式

 1   int   main()
  2   {
  3       int  b = - 10  ;
  4       return   0  ;
  5  } 

在第3行定义了一个整型变量,它的值是-10。-10的二进制形式是怎样的呢? 一个负数的二进制形式,其实就是对它的正数的二进制形式进行取反后再+1 。(取反的意思就是0变1、1变0)

1> 先算出10的二进制形式 :0000 0000 0000 0000 0000 0000 0000 1010

2> 对10的二进制进行取反: 1111 1111  1111 1111  1111 1111 1111 0101

3> 对取反后的结果+1: 1111 1111  1111 1111  1111 1111 1111  0110

因此,整数-10在内存中的二进制形式是: 1111 1111  1111 1111  1111 1111 1111  0110 。这里还能得出一个结论: 负数的二进制形式的最高位一定是1 。

回到顶部

四、变量的作用域

1.作用域简介

变量的作用域就是指变量的作用范围。先来看看下面的程序:

 1   int   main()
  2   {
  3       int  a =  7  ;
  4      
 5       return   0  ;
  6  }

在第3行定义了一个变量a,当执行到这行代码时,系统就会为变量a分配存储空间 当main函数执行完毕,也就是执行完第5行代码了,变量a所占用的内存就会被系统自动回收 因此,变量a的作用范围是从定义它的那行开始,一直到它所在的大括号{}结束,也就是第3~6行,一旦离开这个范围,变量a就失效了

2.代码块

1> 代码块其实就是用大括号{}括住的一块代码。

  1   int   main()
   2   {
   3       {
   4           int  a =  10  ;
   5          
  6          printf( "  a=%d  "  , a);
   7       }
   8      
  9      a =  9  ;
  10      
 11       return   0  ;
  12  }

注意第3~7行的大括号,这就是一个代码块 当执行到第4行时,系统会分配内存给变量a 当代码块执行完毕,也就是执行完第6行代码后,变量a所占用的内存就会被系统回收 因此,变量a的作用范围是从定义它的那行开始,一直到它所在的大括号{}结束,也就是第4~7行,离开这个范围,变量a就失效了 所以,上面的程序是编译失败的,第9行代码是错误的,变量a在第7行的时候已经失效了,不可能在第9行使用

2> 如果是下面这种情况

  1   int   main()
   2   {
   3       int  a =  9  ;
   4      
  5       {
   6           int  a =  10  ;
   7          
  8          printf( "  a=%d  "  , a);
   9       }
  10      
 11       return   0  ;
  12  }

注意第3、6行,各自定义了一个变量a,这种情况是没问题的。C语言规定:在不同作用域中允许有同名变量,系统会为它们分配不同的存储空间。 在第3行定义的变量a的作用域是:第3~12行;在第6行定义的变量a的作用域是:第6~9行。 最后注意第8行:尝试输出变量a的值。那这里输出的是哪一个变量a呢?先看输出结果:

a= 10 

这里采取的是“就近原则”,也就是第8行访问的是在第6行定义的变量a,并不是在第3行的变量a。

回到顶部

五、变量的初始化

变量在没有进行初始化之前,不要拿来使用,因为它里面存储的是一些垃圾数据

 1  #include <stdio.h>
 2  
 3   int   main()
  4   {
  5       int   c;
  6      
 7      printf( "  %d  "  , c);
  8       return   0  ;
  9  }

注意第5行的变量c,只是定义了变量,并没有给它赋初值。输出结果:

 1606422622 

可以发现,变量c里面存储的是一些乱七八糟的数据

 

 

分类:  零基础学习iOS开发

标签:  ios ,  iphoe应用开发 ,  ipad应用开发 ,  objective-c

05-进制

上一讲简单介绍了 常量和变量 ,这讲补充一点计算机的基础知识---进制。

我们先来看看平时是如何表示一个整数的,最常见的肯定是用阿拉伯数字表示,比如“十二”,我们可以用12来表示,其实这种表示方式是基于一种叫做“十进制”的计数方式。“进制”是一种计数方式,编程中常见的进制有4种:十进制、二进制、八进制、十六进制。也就是说,同一个整数,我们至少有4种表示方式。

回到顶部

一、十进制

1.概念

十进制是我们最熟悉、最常用的一种计数方式,它有两个特点:由0、1、2….9十个基本数字组成;运算规则是“逢十进一”。

2.运算

所谓“逢十进一”,似乎在小学数学中都已经学过了,也就是当数值满十时,就要向前进一位

个位数为9+1,满十了,十位数就进一。

回到顶部

二、二进制

1.概念

二进制是计算机内部使用的一种计数方式,它有两个特点:由0、1两个基本数字组成;运算规则是“逢二进一”。

2.细节

1> 有人可能会问:为什么二进制里面没有2~9这些数字呢?都说了,逢二进一,因此当数字满二的时候就会进位。

2> 如果我写个1010,你能看出它是二进制还是十进制么?为了跟其他进制区分开来,书写二进制数的时候,需要以 0b 或者 0B 开头。比如 0b 1010是个二进制数,而1010则还是我们熟悉的十进制数,就是“一千零一十”

3.运算

个位数是1+1,值满二了,于是十位数要进一。因此,在二进制中:1 + 1 =  0b 10,这里的 0b 10表示的是十进制中的“二”,并不是“十”。

 

4.二进制转为十进制

1> 用二进制表示数值,虽然简单、方便,但是不容易阅读,比如随便给出个二进制数 0b 110111101010,你能立刻看出它表示的是什么数值吗?一般需要将二进制数转为十进制数后才能知道代表的什么数值。

进制里面有个“基数”的概念,基数是用来计算数值的,比如十进制的基数是10,因此十进制是这样算数的:

二进制的基数是2,以此类推:

2> 一位二进制数能表示的最大值是1,而n位二进制数所能表示的最大值是 0b 111...1111,也就是说n位二进制数都是1,那么它的十进制数值为:

1 * 2 0  + 1 * 2 1  + 1 * 2 2  + 1 * 2 3  + .... + 1 * 2 n-1  = 2 n  - 1

因此,n位二进制数所能表示的最大值是 2 n  - 1 。也就是说,4位二进制数能表示的最大值是 0b 1111,十进制数值为:2 4  - 1 = 15;5位二进制数能表示的最大值是2 5  - 1 = 31。

回到顶部

三、八进制

1.概念

八进制有两个特点:由0~7八个基本数字组成;运算规则是“逢八进一”。

2.细节

由于十进制和八进制中都包含了0~7,为了区分开来,在书写八进制数的时候,需要在前面加个 0 。比如 0 76是个八进制数,76则是个十进制数。

3.运算

个位数是7+1,值满八了,于是十位数要进一。因此,在八进制中,7 + 1 =  0 10。 0 10则表示十进制中的“八”,而不是“十”

4.八进制转十进制

5.二进制转八进制

不难发现一个十进制数可以表示的最大值是9,而一个八进制数可以表示的最大值是7,恰好3个二进制数可以表示的最大值 0b 111也是7。因此,我们可以用一个八进制数来代替3个二进制数。

如果是八进制转为二进制,那就反过来,用3位二进制数来表示1位八进制数

回到顶部

四、十六进制

1.概念

十六进制有两个特点:由0~9和A~F组成,A~F分别表示10~15;运算规则是“逢十六进一”。

2.细节

由于十进制、八进制、十六进制中都包含了0~7,为了区分开来,在书写十六进制数的时候,需要在前面加个 0x 或者 0X 。比如 0x 76是个十六进制数,076是个八进制数,76则是个十进制数。

3.运算

个位数是B+5,也就是11+5,值满十六了,于是十位数要进一。因此,在十六进制中,B + 5 = 0x 10。 0x 10则表示十进制中的“十六”,而不是“十”

4.十六进制转十进制

5.二进制转十六进制

一个十六进制数可以表示的最大值是15,恰好4个二进制数可以表示的最大值 0b 1111也是15。因此,我们可以用一个十六进制数来代替4个二进制数。

如果是十六进制转为二进制,那就反过来,用4位二进制数来表示1位十六进制数

0x 25 =  0b  0010  0101= 0b 100101

回到顶部

五、进制总结

1.一个整数“十二”的4种表示方式分别如下:

十六进制: 0x C

2.其实也可以利用Mac中的计算器软件来测试进制之间的转换。

设置计算机的显示模式为“编程器”

先选择10进制,然后输入12,底部显示的二进制数为1100,这里省略最前面的 0b

选择八进制,显示为14,这里省略了最前面的 0

选择十六进制,显示为 0x C

回到顶部

六、变量与进制

1.上一讲学习了 变量 的使用,简单回顾一下

 1   int   main()
  2   {
  3       int  a =  10  ;
  4       return   0  ;
  5  }

在第3行定义了一个变量a,存储的是十进制整数10。其实,这个变量a在内存中是以二进制数的形式存储的,10的二进制形式是1010。

2.除了十进制整数,还可以将其他进制的整数赋值给整型变量

  1   int   main()
   2   {
   3       int  a = 0b110;  //   十进制数:6 
  4      
  5       int  b =  021 ;  //   十进制数:17 
  6      
  7       int  c =  12 ;  //   十进制数:12 
  8      
  9       int  d =  0x1D ;  //   十进制数:29 
 10      
 11       return   0  ;
  12  }

上面的代码中,分别将4种不同进制的数值赋值给不同的整型变量。对应的十进制数值已经写在右边的注释中。它们最终都是以二进制的形式存储在内存中。

回到顶部

七、printf的简单使用

1.用printf输出整型变量

前面给变量赋值了各种进制的整数,究竟这些整数的十进制形式为多少呢?我们自己可以换算出来,不过总是自己去算,太麻烦了,我们可是程序员,应该利用程序帮我们算出来。前面已经学过printf("Hello World");语句的作用是在屏幕输出Hello World这一串内容。我们其实也可以利用printf将一个变量输出到屏幕,看看这个变量的值究竟为多少。

这个printf看起来好像是你往它的小括号里面放什么内容,它就会在屏幕上输出什么内容,但是printf的用法有讲究的,像下面的写法就是 错误 的:

  1  #include <stdio.h>
  2  
  3   int   main()
   4   {
   5       int  a =  0x1D  ;
   6      
  7       printf(a);
   8      
  9       return   0  ;
  10  }

有人可能会疑惑,为什么有时需要#include <stdio.h>,有时又不需要#include <stdio.h>?这个暂时不去详细讨论,你先记住只要用了printf,就要添加#include <stdio.h>。

在第5行定义了变量a,初值为一个十六进制数。在第7行想通过printf输出变量a的数值,但是第7行的写法是 错误 的。 要想利用printf输出一个整型变量,就必须先说明输出的格式,比如是以十进制格式输出还是以八进制格式输出?

下面的写法才是正确的:

  1  #include <stdio.h>
  2  
  3   int   main()
   4   {
   5       int  a =  0x1D  ;
   6      
  7      printf( "  变量a的值为%d  "  , a);
   8      
  9       return   0  ;
  10  }

注意看第7行,左边双引号括住的内容代表着要输出到屏幕的内容,不过并不是直接将 " 变量a的值为 %d"  输出到屏幕。%d是一种格式符,它的意思是 用右边变量a的值替代%d的位置进行输出,并且以十进制格式输出 。说白了,格式符是用来控制输出格式的。

程序在终端上的运行结果是:

可以发现,输出了"变量a的值为29",0x1D的十进制数值确实是29,说明这个输出是正确的。但是这个29跟后面的英文连在一起了,非常难看,这时候我们可以在%d的后面加一个\n表示回车换行。

  1  #include <stdio.h>
  2  
  3   int   main()
   4   {
   5       int  a =  0x1D  ;
   6      
  7      printf( "  变量a的值为%d\n  "  , a);
   8      
  9       return   0  ;
  10  }

注意第5行的改变:在%d的后面加了个\n,表示将变量a以十进制格式输出后进行回车换行。

这时候的运行结果是:

这样就可以明显地看到这个29了

2.printf支持的格式符

除开%d,printf还支持很多格式符,如下表所示(红色表示常用),这份表格的内容不用去死记,用到时再回来查资料即可

接下来简单演示一下%x的使用,它的功能是以十六进制形式输出整数

  1  #include <stdio.h>
  2  
  3   int   main()
   4   {
   5       int  a =  17  ;
   6      
  7      printf( "  %x\n  "  , a);
   8      
  9       return   0  ;
  10  }

在第5行定义了变量a,存储的是十进制整数17,在第7行让变量a以十六进制形式输出,运行结果是:

可以发现,十进制整数17以十六进制形式输出就是11,这是正确的。

3.printf可以同时输出多个数值

看下面代码,利用printf同时输出多个数值

  1  #include <stdio.h>
  2  
  3   int   main()
   4   {
   5       int  age =  17  ;
   6      
  7       int  no =  10  ;
   8      
  9      printf( "  age = %d, no = %d\n  "  , age, no);
  10      
 11       return   0  ;
  12  }

注意看第9行,左边的双引号内有2个%d,age的值会代替第一个%d进行输出,no的值会代替第二个%d进行输出,并且都是以十进制形式输出。输出结果:

4.printf也可以输出常量

 1  #include <stdio.h>
 2  
 3   int   main()
  4   {
  5      
 6      printf( "  输出的常量为%d\n  " ,  11  );
  7      
 8       return   0  ;
  9  }

注意第6行,右边的是一个整型常量11,它会代替%d的位置进行输出。输出结果:

用了半天,似乎还是没有说到printf究竟是个什么东西,这个会放到后面详细讨论,它还有其他复杂用法。

 

 

分类:  零基础学习iOS开发

标签:  ios ,  iphoe应用开发 ,  ipad应用开发 ,  objective-c

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于变量与内存的详细内容...

  阅读:38次