数据
3.1基本数据类型
3.1.1整型家族
整型家族包括字符、短整型、整型和长整型,它们都分为有符号(signed)和无符号(unsigned)两种版本。
长整型至少因该和整型一样长,整型至少因该和短整型一样长。
头文件limits.h说明了各种不同整数类型的特点。
字面值(literal)这个术语是字面值常量的缩写——这是一种实体,指定了自身的值,并且不允许发生改变。
3.1.2浮点类型
头文件float.h定义了名字FLT_MAX、DBL_MAX和LABL_MAX,分别表示float、double和long double所能存储的最大值。
3.7存储类型
变量的存储类型(storage class)是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及他的值将保存多久。有三个地方可以保存变量:普通内存、运行时堆栈、硬件寄存器。在这三个地方存储的变量具有不同的特性。
变量的缺省存储类型取决于它的声明位置。凡是在任何代码块之外声明的变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态(static)变量。对于这类变量,你无法为它们指定其他存储类型。静态变量在程序运行之前创建,在程序的整个执行期间始终存在。它始终保持原先的值,除非给他赋一个不同的值或者程序结束。
在代码块内部声明的变量的缺省存储类型是自动的(automatic),也就是说它存储于堆栈中,称为自动(auto)变量。在程序执行到声明自动变量的代码块时,自动变量才被创建,当程序执行流程离开该代码块时,这些自动变量便自行销毁。如果该代码块被数次执行,这些自动变量每次都要重新创建。在代码块再次执行时,这些自动变量在堆栈中所占据的内存位置有可能和原先的位置相同,也可能不同。即使他们所占据的位置相同,你也不能保证这块内存同时不会有其他用途。
对于在代码块内部声明的变量,如果给他加上关键字static,可以使它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行的过程中一直存在,而不仅仅在声明他的代码块执行时存在。注意:修改变量的存储类型并不表示修改该变量的作用域,他仍然只能在该代码块内部按名字访问。函数的形式参数不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。
关键字register可以用于自动变量的声明,提示它们因该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常,寄存器变量比存储于内存中的变量访问起来效率更高。但是,编译器并不一定要理睬register关键字,如果有太多的变量被声明位register,它只选取前几个实际存储于寄存器中,其余的就按普通自动变量处理。如果一个编译器自己具有一套寄存器优化方案,它也可以忽略register关键字,其依据是由编译器决定哪些变量存储于寄存器中比人脑的决定更为合理一些。
寄存器变量的创建和销毁时间和自动变量相同,但它需要一些额外的工作,在一个使用寄存器变量的函数返回之前,这些寄存器先前存储的值必须恢复,确保调用者的寄存器变量未被破坏。许多机器使用运行时堆栈来完成这个任务。当函数开始执行时,它把需要使用的所有寄存器的内容都保存到堆栈中,当函数返回时,这些值再复制回寄存器中。
在许多机器的硬件实现中,并不为寄存器指定地址。同样,由于寄存器值得保存和恢复,某个指定的寄存器在不同的时刻保存的值不一定相同。基于这个理由,机器并不向你提供寄存器变量的地址。
初始化
自动变量和静态变量的初始化存在一个重要的差别。在静态变量的初始化中,我们可以把可执行文件想要初始化的值放在当程序执行时变量将会使用的位置。当可执行文件载入到内存时,这个已经保存了正确初始值的位置将赋值给那个变量。完成这个任务并不需要额外的时间,也不需要额外的指令,变量将会得到正确的值。如果不显式地指定其初始值,静态变量将会初始化为0。
自动变量初始化需要更多的开销,因为当程序连接时还无法判断自动变量存储的位置。事实上,函数的局部变量在函数的每次调用中可能占据不同的位置。基于这个理由,自动变量没有缺省的初始值,而显式的初始化将在代码的起始处插入一条隐式的赋值语句。
这个技巧造成4种后果。首先,自动变量的初始化较之赋值语句效率并无提高。除了声明位const的变量外,在声明变量的同时进行初始化和先声明后赋值只有风格之差,并无效率之别。其次,这条隐式的赋值语句使自动变量在程序执行到它们所声明的函数(或代码块)时,每次都将重新初始化。这个行为与静态变量大不相同,后者只是在程序开始执行前初始化一次。第3个后果则是个优点,由于初始化在运行时执行,你可以用任何表达式作为初始化的值,例如:
1: int
2: func( int a )
3: {
4: int b = a + 3;
最后一个后果是,除非你对自动变量进行显式的初始化,否则当自动变量创建时,它们的值总是垃圾。
3.8static关键字
当用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标志符的连接属性,从external改为internal,但标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明它们的原文件中访问。
当它用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量改为静态变量,但变量的连接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个执行周期一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕后销毁。
3.10总结
具有external连接属性的实体在其它语言的术语里称为全局(global)实体,所有源文件中的所有函数均可以访问它。只要变量并非声明于代码块或函数定义内部,它在缺省情况下的连接属性即为external。如果一个变量声明于代码块内部,在它前面添加extern关键字将使它所引用的是全局变量而非局部变量。
具有external连接属性的实体总是具有静态存储类型。全局变量在程序开始执行前创建,并在程序的整个执行过程中一直存在。从属于函数的局部变量在函数开始执行前创建,在函数执行完毕后销毁,但用于执行函数的机器指令在程序的生命周期内一直存在。
局部变量有函数内部使用,不能由其它函数通过名字引用。它在缺省情况下的存储类型为自动,这是基于两个原因:其一,当这些变量需要时才为它们分配存储,这样可以减少内存的总需求量。其二,在堆栈上为它们分配存储可以有效地实现递归。如果你觉得让变量的值在函数的多次调用中始终保持原先的值非常重要的话,你可以修改它的存储类型,把它从自动变量改为静态变量。
变量类型 | 声明的位置 | 是否存于堆栈 | 作用域 | 如果声明为static |
全局 | 所有代码块之外 | 否 | 从声明处到文件尾 | 不允许从其它源文件访问 |
局部 | 代码块起始处 | 是 | 整个代码块 | 变量不存储于堆栈中,它的值在程序整个执行期一直保持 |
形式函数 | 函数头部 | 是 | 整个函数 | 不允许 |
3.12编成提示的总结
- 为了保持最佳的可移植性,把字符的值限制在有符号和无符号字符范围的交集之内,或者不要在字符上执行算术运算。
- 用它们在使用时最自然的形式来表示字面值。
- 不要把整型值和枚举值混在一起使用。
- 不要依赖隐式声明。
- 在定义类型的新名字时,使用typedef而不是#define。
- 用const声明其值不会修改的变量。
- 使用名字常量而不是字面值常量。
- 不要在嵌套的代码块之间使用相同的变量名。
- 除了实体的具体定义位置之外,在它的其它声明位置都使用external关键字。