第二章(略)
在C语言中,仅有4种基本数据类型——整型,浮点型,指针,聚合类型。
1.整型家族
整型家族包括字符、短整型、整型、长整型。它们都有有符号(singed)和无符号(unsigned)两种版本。
长整型至少一个和整型一样长,而整型至少和短整型一样长。
尽管设计char类型变量的目的是为了让它们容纳字符型值,但本质上是小整数型。缺省的char要么是signed char ,要么是unsinged char,这取决于编译器。这个事实意味着不同机器上的char可能拥有不同范围的值。所以,只有当程序所使用的char型变量的值位于signed char和unsigned char 的交集中,这个程序才可以被移植。例如ASCII字符集的字符都是位于这个范围之内的。
(1)整型字面值
(2)枚举类型
枚举(enumerated)类型是指它的值为符号常量而不是字面值的类型。它们以下面方式声明
enum Jar_Type( CUP, PINT ,QUART, HALF_GALLON,GALLON);
这里CUP是0,PINT是1,以此类推。适当时候你可以为这些符号名指定特定的整型值。如下:
enum Jar_Type( CUP = 8 , PINT = 16, QUART = 32 , HALF_GALLON = 64 ,GALLON = 128);
2.浮点类型
浮点数字面值在缺省的情况下都是double类型的,除非他的后面跟一个L或l表示它是这个long double类型的值,或者后面跟一个F或f表示它是一个float类型的值。
3.指针
(1)指针常量(pointer constant)
(2)字符串常量(string literal)
由于NUL字节是用于终结字符串的,所以在字符串内部不能有NUL字节。
之所以选择NUL作为字符串的终止符,是因为它不是一个可打印的字符。
之所以将字符串和指针放在一起讨论,是因为在程序中使用字符串常量会生成一个“指向字符的常量指针”。当一个字符串常量出现于一个表达式中时,表达式所使用的值就是这些字符所存储的地址,而不是这些字符本身。因此,你可以把字符串常量赋给一个“指向字符的指针”,后者指向这些字符所存储的地址,而不是这些字符本身。但是,你不能把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。
C语言支持一种叫typedef的机制,它允许你为各种数据类型定义新名字。
typedef声明的写法和普通声明基本相同,只是把typedef这个关键字出现在声明的前面。例如:
char *ptr_to_char;
把变量ptr_to_char 声明为一个指向字符的指针。很眼熟对不对,ptr_to_char 就是指向字符串常量的指针。所以有:
typedef char * string;
这就声明了一个字符串类型名。
string str;
提示:你应该使用typedef而不是#define来创建新的类型名,因为后者无法正确地处理指针类型。
ANSI C 允许你声明常量,你可以使用const 关键字来声明变量。
两种声明方法:
int const a;
const int a;
但涉及指针变量是,情况就变得有趣了,因为有两样东西可成为常量——指针变量和它所指向的实体。
int *pi;
pi 是一个普通的指向整型的指针。而变量
int const *pci;
则声明pci为一个指向整型常量的指针。此时你可以修改指针的值,但你不能修改它所指向的值。
int * const cpi;
cpi声明为一个指向整型的常量指针。此时指针是常量,它的值无法修改,但你可以修改它所指向的整型的值。
int const * const cpic;
在cpic这个例子中,无论是指针本身还是他所指向的值都是常量,不允许修改。
有四种不同的作用域
代码块作用域(block scope):位于一对花括号之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域。
文件作用域(file scope):任何在所以代码块声明的标识符都具有文件作用域
原型作用域(prototype scope):只适用于在函数原型中声明的参数名。
函数作用域(function scope):它只适用于语句标签,语句标签用于goto。基本上,函数作用域都可以简化为一条规则——一个函数中的所有语句标签必须唯一。我希望你永远不要用到这个知识。
1.标识符的链接属性(linkage)决定如何处理在不同文件中出现的标识符。
2.链接属性一共有3种——external(外部)、interal(内部)和none(无)。没有链接属性的标识符(none)总是被当做单独的个体,也就是说该标识符的多个声明被当做独立不同的实体。属于interal 链接数学的标识符在同一源文件内的所有声明都指同一个实体,但位于不同源文件的多个声明则分属不同实体。最后,属于external 链接属性的标识符不论声明多少次、位于几个源文件都表示同一个实体。
3.
typedef char *a;
int b;
int c( int g)
{
int e;
int f( int g );
}
在缺省情况下,标识符b、c和f的链接属性为external,其余的标识符的链接属性则为none。因此,如果另一个源文件也包含了标识符b的类似并调用函数c,它们实际上访问的是这个源文件所定义的实体。f 的链接属性之所以是external是因为它是个函数名。在这个源文件中调用函数 f 。它实际上将链接到其他源文件所定义的函数,甚至这个函数的定义肯出现在某个函数库。
4.如果某个声明在正常情况下具有external链接属性,在它的前面加上static关键字可以是它的链接属性变为internal。
5.如果你在一个地方定义变量,并在使用这个变量的其他源文件的声明中添加external关键字,可以使读者更加容易理解你的意图。
6.当extern关键字用于源文件中一个标识符的第1次声明时,它指定该标识符具有external链接属性。但是,如果他用于该标识符的第2次或以后声明时,它并不会更改有第1次声明所指定的链接属性。
1.变量的存储类型是指存储变量值的内存类型。有三个地方可以用于存储变量:普通内存,运行时堆栈、硬件寄存器。
2.在代码内部声明的变量的缺省存储类型是自动的(automatic),也就是说它在堆栈中,称为自动(auto)变量。一般很少使用它。
3.对于在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变为静态。
4.关键字register可以用于自动变量的声明,提示它们一个存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。
5.对于变量初始化问题。自动变量和静态变量的初始化存在一个重要的差别。在静态变量的初始化中,我们可以把可执行程序文件想要初始化的值放在当程序执行时变量将会使用的位置。当可执行文件载入到内存时,这个已经保存了正确初始值的位置将赋值给那个变量。如果不显示地指定其初始值,静态变量将初始化为0;
1.当static用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external变为internal,但标识符的存储类型和作用域不受影响。这种方式声明的函数或变量只能在声明它们的源文件中访问。
2.当static用于代码块内部的变量声明时,static关键字用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。
作用域、链接属性和存储类型总结
变量类型 | 声明的位置 | 是否存于堆栈 | 作用域 | 如果声明为static |
全局 | 所有代码块之外 | 否 | 从声明处到文件尾 | 不允许从其他源文件访问 |
局部 | 代码块起始处 | 是 | 整个代码块 | 变量不存储于堆栈中,它的值在程序整个执行期一直保持 |
形式参数 | 函数头部 | 是 | 整个函数 | 不允许 |
局部变量由函数内部使用,不能被其他函数通过名字引用。它在缺省的情况下的存储类型为自动,这是基于两个原因:其一,当这些变量需要时才为它们分配存储,这样可以减少内存的总需求量。其二,在堆栈上它们分配存在可以有效的实现递归。如果你觉得让变量的值在函数的多次调用中始终保持原先的值非常重要的话,你可以修改它的存储类型,把它从自动变量改为静态变量。