浅谈指针(一)——数据

4种基本数据类型——整型、浮点型、指针和聚合类型(如数组和结构等)。所有其他的类型都是从这4中基本类型的某种组合派生而来。首先让我们来介绍整型和浮点型。

1.整型

整型包括字符,短整型,整号和长整型,他们都分为有符号和无符号两种版本。“长整型”所能表示的值应该比“短整型”所能表示的值要打,长整型至少应该和整型一样长,而整型至少应该和短整型一样长。

short int至少16位,long int至少32位,至于缺省的int究竟16位还是32位,或者是其他值,则由编译器设计者决定。通常这个选择的缺省值是这种机器最为自然(高效)的位数。同时你还应该注意到标准也没有轨道这3个值必须不一样,某种机器的环境的字长是32位,而且没有什么指令能够更有效地处理更短的整型值,它可能把这3个整型值都设定位32位。

字面值这个术语是字面值常量的缩写,这是一种实体,制订了自身的值,并且不允许发生改变,因为其允许命名常量(const)区别在于当它被初始化以后,它的值便不能改变。

枚举类型就是指它的值位符号常量而不是字面值的类型。枚举里面的变量事宜整型存储。

2.浮点数

诸如3.14159和6.023x10^{23}这样的数值无法按照整数存储,但是,他们可以用浮点数的形式存储。它们通常以一个小数以及一个以某个假定为基数的指数组成。

整体呈现

float占用4个字节(32位),其中符号位(sign)占1位,指数位(exponent)占8位,尾数位(mantissa)占23位。double占用8个字节(64位),其中数值符号位(sign)占1位,指数位(exponent)占11位,尾数位(mantissa)占52位。符号位表示浮点数的正负,0表示正,1表示负。

符号位
        符号位表示浮点数的正负,0表示正,1表示负。
 

指数位
        指数又称阶码,阶码指明了小数点在数据中的位置。指数必须是有符号数才能表达很大或很小的数值,但是有符号数通常的表示法——补码,将会使电路设计变得复杂,使比较变得困难。为了解决这一问题,计算机将实际指数加上一个偏移量作为浮点数的阶码, 将其调整到一个无符号数的范围内以便进行比较,也就是说实际指数等于阶码减去偏移量。若浮点数有n位指数位,则偏移量=2^(n-1)-1,阶码范围为[0,2^(n)-1](0和2^(n)-1具有特殊用途,不遵守一般的指数修正规则,稍后再做解释)。
例如,float的指数偏移量为+127,阶码范围为[0,255],实际指数的范围为-126~127;double的指数偏移量为+1023,阶码范围为[0,2047],实际指数范围为-1022~1023。

尾数位

•规约形式的浮点数
       

阶码在0<e<2^(n)-1内的浮点数被称为规约形式的浮点数,规约形式的浮点数的尾数位之前隐含了一位1(1.111...),因此实际上float和double的尾数位数分别为24和53,该形式的浮点数的尾数是一个[1,2)范围内的数,如规约形式的float的尾数范围为[1,2-2^(-23))。

非规约形式的浮点数
     

  若一浮点数的阶码为0,尾数不为0,那么该浮点数被称为非规约形式的浮点数。除了一般浮点数,IEEE754-1985标准采用非规约浮点数来解决填补绝对值意义下最小规约数与零的距离。IEEE 754标准规定:非规约形式的浮点数的指数偏移量比规约形式的浮点数小1。例如,最小的规约形式的单精度浮点数的阶码为1,实际指数为-126;而非规约形式的单精度浮点数的阶码为0,对应的实际指数也是-126而不是-127。非规约形式的浮点数的尾数位之前隐含了一位0(0.111...),该形式的浮点数的尾数的是一个(0,1)范围内的数,如非规约形式的float的尾数范围为(0,1-2^(-23))。

3.指针

变量的值存储于计算机内存中,每一个变量都占据一个特定的位置。每个内存位置都由地址唯一确定并引用,就像一条街道上的房子由它们的门牌号码标识一样。指针只是地址的另一个名字罢了。指针变量就是一个其值为另一个内存地址的变量。我们可以获得一个变量的地址,也可以通过一个指针变量取得它所指向的值或数据。

通过地址而不是名字来访问数据的想法常常会引起混淆,我们可以把计算机内存想象成一条长街的一间间房子,每一间房子都用一个唯一的号码进行标识。每个位置包含一个值,这和它的地址是独立且显著不同的,即使它们都是数字。

指针常量与非指针常量在本质上是不同的,因为编译器把变量赋值给计算机内存中的位置。因此,可以通过操作符获得一个变量的地址而不是直接把它的地址写成字面值常量的形式。因为我们不知道这是不是编译器实际存放这个变量的内存位置。事实上,当一个函数每次被调用时,它的自动变量可能每次分配的内存位置都不相同。因此,把指针常量表达为数值字面值的形式几乎没有用处。

之所以把字符串常量和指针放在一起讨论,是因为在程序中使用字符串常量会生成一个“指向字符的常量指针”。当一个字符串常量出现在一个表达式中时,表达式所使用的值就是这些字符所存储的地址,而不是这些字符本身。因此,可以把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。

在声明指针时,*为表达变量的一部分。

而char *message = "hello world!"相当于char *message;message="hello world!"

4.隐式声明

函数如果不显示地声明返回值的类型,它就默认返回整型。当你使用旧风格声明函数的形式参数时,如果省略了参数的类型,编译器就会默认它们为整型。最后,如果编译器可以得到充足的信息,推断出一条语句实际上是一个声明时,如果它缺少类型名,编译器会假定它为整型。

5.typedef

使用typedef声明类型可以减少使声明变得又臭又长的危险,尤其是那些复杂的声明。而且,如果修改程序所使用的一些数据的类型时,修改一个typedef声明比修改程序中与这种类型有关的所有变量(和函数)的所有声明要容易得多。

//声明为一个指向字符的指针
typedef char* ptr_to_char
ptr_to_char a;

//a声明正确,b却为一个字符
#define d_ptr_to_char char*
d_ptr_to_char a,b;


在定义更为复杂的类型名字时,如函数指针或指向数组的指针,使用typedef更为合适。

6.常量

对于声明常量,下面两条语句所声明的值不能被修改。

int const a;
const int a;

当涉及到指针变量时,有两样东西都有可能成为常量——指针变量和它所指向的实体。

int const *pci;//不能修改指向的值
int * const cpi;//不能修改指针
int const * const cpi;//都不能修改

#define是另一种创建名字常量的机制。允许使用字面值常量的地方都可以使用前者,比如声明数组的长度。const变量只能用于允许使用变量的地方。

7.链接属性

编译器可以确认4种不同类型的作用域——文件作用域、函数作用域、代码块作用和原型作用域。标识符声明的位置决定它的作用域。

当组成一个程序的各个源文件分别被编译之后,所有的目标文件已经那些从一个或多个函数库中引用的函数链接在一起,形成可执行程序。然而,如果相同的标识符出现在几个不同的源文件中时,标识符的链接属性决定如何处理在不同文件中出现的标识。标识符的作用域与它的链接属性有关,但这两个属性并不相同。

链接属性一个有三种——外部、内部、无、没有链接属性的标识符总算被当作单个的个体,也就是说该标识符的多个声明被当作独立不同的实体,属于内部链接属性的标识符在同一个源文件内的所有声明都指同一个实体,但位于不同源文件的多个声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。最后,属于外部链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。

关键字extern和ststic用声明中修改标识符的链接属性。如果某个声明在正常情况下具有外部链接属性,在它前面加上static关键字可以使它的链接属性变为内部。那么该变量就将为这个源文件私有。在其他源文件中,如果也链接到一个相同名称的变量,那么它所引用的是另一个不同的变量。

static只对缺省链接属性为外部的声明才有改变链接属性的效果。

extern为一个标识符指定外部链接属性,这样就可以访问在其他任何位置定义的这个实体。这样一来,函数就可以访问在其他源文件声明的外部变量了。当extern关键字用于源文件中一个标识符的第一次声明时,它指定该标识符具有外部链接属性。但是,如果它用于该标识符的第二次或以后的声明时,它并不会更改由第一次声明所指定的链接属性。

8.存储类型

变量的存储类型是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁已经它的值保持多久。有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。在这三个地方存储的变量具有不同的特性。

应该避免嵌套的代码块中出现相同的变量名,我们并没有很好的理由使用这种技巧,它们只会在程序的调试或维护期间引起混淆。

变量的缺省存储类型取决于它的声明位置。凡是在任何代码块之外声明的变量总算存储于静态内存中,也就是不属于堆栈的内存,这里变量称为静态变量。对于这类变量,你无法为它们指定其他存储类型。静态变量在程序运行之钱创建,在程序的整个执行期间始终存在。它是始终存在。它始终保持原先的值,除非给他赋一个不同的值或者程序结束。

在代码块内部声明的变量的缺省存储类型是自动的,也就是说它存储与堆栈中,称为自动变量。有一个关键字auto就是用于修饰这种存储类型的,但它极少使用,因为代码块中的变量在缺省情况下就是自动变量。在程序执行到声明自动变量的代码块时,自动变量才被创建,当程序的执行流离开该代码块时,这些自变量便自行销毁。如果该代码块被数次执行,例如一个函数被返回调用,这些自动每次都将重新创建。在代码块再次执行时,这些自动变量在堆栈中所占据的内存位置有可能和原先的位置相同,也可能不同。

对于在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中一直存在,而不仅仅在声明它的代码块的执行时存在。

最后,关键字register可以用于自动变量的声明,提示它们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常,寄存器变量比存储于内存的变量访问起来效率更高。但是,编译器并不一定要理睬register,它只选取前几个实际存储于寄存器中,其余的就按普通自动变量处理。如果一个编译器自己具有一套寄存器优化方法,它也可能忽略register关键字,其依据是由编译器决定那些变量存储于寄存器中比人脑的决定更为合理一些。

在典型情况下,把使用频率最高的那些变量声明为寄存器变量。在有些计算机中,如果把指针声明为寄存器变量,程序的效率将能得到提高,尤其是那些频繁执行间接访问操作的指针。你可以把函数的形式参数声明为寄存器变量,编译器会在函数的起始位置生成指令,把这些值从堆栈复制到寄存器中。但是,完全有可能,这个优化措施所节省的时间和空间的开销还抵不上复制这几个值所用的开销。

寄存器变量的创建和销毁时间和自动变量相同,但他需要一些额外的工作。在一个使用寄存器变量的函数返回之前,这些寄存器先前存储的值必须恢复,确保调用者寄存器变量并未被破坏。许多机器使用运行时堆栈来完成这个任务。当函数开始执行时,它把需要使用的所有寄存器的内容都保存在堆栈中,当函数返回时,这些值再复制回寄存器中。在许多机器的硬件实现中,并不为寄存器指定地址。同样,由于寄存器的保存和恢复,某个特定的寄存器在不同的时刻所保存的值不一定相同。

当static用于函数定义时,用于修改标识符的链接属性。当用于变量声明时,用于修改变量的存储类型。

9.初始化

自动变量和静态变量的初始化存在一个重要的差距。在静态变量的初始化中,我们可以把可执行程序文件想要初始化的值放在当程序执行时变量将会使用的位置。当可执行文件载入到内存时,这个已经保存了正确初始值的位置将赋值给那个变量。完成这个任务并不需要额外的时间,也不需要额外的指令,变量将会得到正确的值。如果不显式地指定其初始化,静态变量将初始化为0。

自动变量初始化需要更多的开销,因为当程序链接时还无法自动变量的存储位置。事实上,函数的局部变量在函数的每次调用中可能占据不同的位置。

自动变量的初始化除了声明为const较之赋值效率并无提高。其次,这条隐式的赋值语句使自动变量在程序执行到它们所声明的函数时,每次都将重新初始化。自动变量在初始化运行时,可以用任何表达式作为初始化值。除非对自动变量进行显式的赋值,否则当自动变量创建时,它们的值总是垃圾。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值