程序对数据进行操作,本章对数据进行描述。
整型包括:字符、短整型、整型、长整型。他们都分为有符号和无符号两个版本。
听上去,长整型所能表示的范围要比短整型的大,但是这个假设并不一定正确。规定整型值的大小规则很简单:
长整型至少应该和整型一样长,而整型至少应该和短整型一样长。
尽管设计char类型变量的目的是为了让他们容纳字符型值,但是字符在本质上是小整型值。缺省的char要不是signed char;要不就是unsigned char;,这取决于编译器。这个事实意味着不同机器上的char可能拥有不同范围的值。所以,只有当程序所使用的char型变量的值位于signed char 和unsigned char 的交集中,这个程序才是可移植的。
在一个把字符当做小整型值的程序中,如果显式的把这类变量声明为signed或unsigned,可以提高这类程序的可移植性。这类做法可以确保不同的机器中在字符是否为有符号值方面保持一致。
下表显示了所有这些变量声明的类型。同一个框内的所有声明都是等同的。signed关键字一般只用于char类型,因为其他整型类型在缺省情况下都是有符号数。至于char是否是signed,则因编译器而异。所以char可能等同于unsigned char,也可能等同于signed char。浮点数在这方面要简单一些,因为除了long double之外,其余几个说明符(short, signed, unsigned)都是不可用的。
short ;signed sort ;short int ;signed short int; | unsigned short; unsigned short int; |
int ; signed int;; signed; | unsigned int; unsigned; |
long ;signed long ;long int ; signed long int; | unsigned long;unsigned long int; |
对于C数组来说,有一个值得关注的地方,编译器并不检查程序对数组下标的应用是否在数组的合法范围之内。
typedef 允许为各种数据类型定义新的名字。比如:
char *point_to_ char;
可以写成:
typedef char *point_to_char; //把标识符point_to_char作为指向字符的指针类型的新名字。
point_to_char a;
关于const:
int const *ptr;
意为:这是一个指向整型常量的指针,它所指向的值不能修改,但是他的指向可以修改;
int * const ptr;
意为:这是一个指向整型的常量指针,它所指向的值可以修改,但是他的指向不能修改;
int const *const ptr;
意为:两者都不能修改。
变量存储类型是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及它的值将保持多久。
有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。在这三个地方存储的变量具有不同的特性。
(1)
变量的缺省存储类型取决于它的声明的位置。凡是在任何代码块(位于一对花括号之间的所有语句称为一个代码块)之外声明的变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态变量。这类变量在程序运行之前创建,在程序整个运行期间始终存在。
(2)
在代码块内部声明的变量的缺省存储类型是自动的,也就是说它存储于堆栈中,称为自动变量。在程序执行到声明的自动变量的代码块时,自动变量才被创建,当程序的执行流离开该代码块时,这些自动变量边自行销毁。如果该代码块执行多次,这些自动变量每次都将重新创建。在代码块执行时,这些自动变量在堆栈中所占据的内存位置有可能与原先的位置相同,也有可能不同。
(3)
对于在代码块内部声明的变量,如果给他加上关键字static,可以是它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中一直存在,而不仅仅在声明它的代码块的执行时存在。注意,修改变量的存储类型并不表示修改该变量的作用域。函数的形参不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。
最后,关键字register可以用于自动变量的声明,提示他们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常,寄存器变量比存储于内存的变量访问起来效率更高。但是编译器并不一定要理睬register关键字,如果你有很多的自动变量声明为register,那么她只选取前几个实际存储于寄存器中,其余的就按照普通自动变量处理。
寄存器变量的创建和销毁时间和自动变量相同,但是需要一些额外的工作。
当它用于函数定义时,或用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但是标识符的存储类型和作用域不受影响。用这种方式声明的函数或变量只能在声明他们的源文件中访问。
当它用于代码块内部的变量声明时,static关键字用于修改变量的存储类型。从自动变量修改为静态变量,但是变量的链接属性和作用域不受影响。用这种方式声明的变量在程序执行之前创建,并在程序的整个运行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕之后销毁。
1......int a = 5;
2......extern int b;
3......static int c;
4......int d( int e )
5......{
6...... int f = 15;
7...... register int b;
8...... static int g = 20;
9...... extern int a;
10...... ...
11..... {
12..... int e;
13..... int a;
14..... extern int h;
15..... ...
16..... }
17..... ...
18..... {
19..... int x;
20..... int e;
21..... ...
22..... }
23........
24.....}
25.....static int i()
26.....{
27..... ...
28.....}
29........
上图中,属于文件作用域的声明在缺省情况下为external链接属性,所以第1行的a的链接属性为external。如果b的定义在其他地方,第二行的extern关键字在技术上并非必须,但是在风格上加上这个关键字较好。第三行的static关键字修改了c的缺省链接属性,把它修改为internal。声明了变量a和b(具有external链接属性)的其他源文件在使用这两个变量时,实际所访问的是声明于此处的这两个变量。但是c只能由这个源文件访问,因为它具有internal链接属性。
变量a,b,c,的存储类型为静态,表示他们的并不是存储于堆栈中。因此这些变量在程序执行之前创建,并一直保持它们的值,直到程序结束。
这些变量的作用域一直延伸到这个源程序结束为止,但是第7行和第13行声明的局部变量a和b在那部分程序中将隐藏同名的静态变量。因此,这三个变量的作用域为:
a:第1行至12行, 第17行至29行;
b:第2行至第6行,第25行至29行;
c:第3至29行;
第4行声明了两个标识符,d的作用域从第4行直到文件结束。
参数e不具有链接属性,所以我们只能从函数内部通过名字访问它。它具有自动存储的类型,调用函数时创建它,当函数返回时消失。到函数结束为止。
第6至8行声明局部变量,所以他们的作用域导函数结束时为止,不具有链接属性。
f的存储类型是自动,当函数每次被调用时赋值初始化为15;
b的存储类型为寄存器类型,所以它的初始值是垃圾。
g的存储类型是静态,所以它在整个程序执行过程中一直存在。当函数每次被调用时,他并不会被重新初始化。
第9行的声明并不重要。这个代码块位于第一行声明的作用域之内。
第12行和第13行是代码块声明局部变量。自动存储类型,不具有连接属性。作用域延伸至第16行。这些变量和先前声明的a和e不同,而且由于名字冲突,在这个代码块中,以前声明的同名变量是不能被访问的。
第14行使全局变量h在这个代码块内可以被访问。它具有external属性,存储于静态内存中。
第19行和20行用于创建局部变量(自动、无连接属性、作用域限于本代码块)。这个e和参数e是不同的变量,它和第12行声明的e也不相同。从11行到第18行并无嵌套,所以编译器可以使用相同的内存来存储两个代码块中不同的变量e。如果你想让这两个代码块中的e表示同一个变量,那么你就不应该把它声明为局部变量。
最后,第25行声明了函数i,具有静态链接属性,静态链接属性可以防止他被这个源文件之外的任何函数调用。事实上,其他的源文件也可能声明自己的函数i,它与这个源文件的i是不同的函数。i的作用域从他声明的位置到这个源文件结束。函数d不可以调用函数i,因为d之前不存在函数i的原型。
例题:
(1)确认下面程序中存在的错误。
(2)去除错误之后,确定所有标识符的存储类型、作用域和链接属性。
(3)每个变量的初始值是什么?
(4)程序中存在许多同名标识符,他们所代表的是相同的变量还是不同的变量?
(5)程序中的每个函数从哪个位置起可以被调用?
1......static int w = 5;
2......extern int x;
3......static float
4......func1( int a, int b, int c)
5......{
6...... int c, d, e = 1;
7...... ...
8...... {
9...... int d, e, w;
10..... ...
11..... {
12..... int b, c, d;
13..... static int y = 2;
14..... ...
15..... }
16..... }
17..... ...
18..... {
19..... register int a, d, x;
20..... extern int y;
21..... ...
22..... }
23.....}
24.....static int y;
25.....float
26.....func2( int a )
27.....{
28..... extern int y;
29..... static int z;
30..... ...
31.....}
解答:
(1)第6行中c的声明与函数的形参有冲突;
(2)所有标识符的存储类型、作用域、链接属性和初始值见下表:
名字(所在行数) | 存储类型 | 作用域(行数) | 链接属性 | 初始值 |
w(1) | 静态存储 | 1-7,17-31 | internal | 5 |
x(2) | 静态存储 | 2-17,24-31 | external | 如果该变量未在任何其他声明中初始化,则其初始值为0。 |
func1(4) | 静态存储 | 4-31 | interanal | - |
a(4) | 自动存储 | 4-17 | 无 | 函数参数的初始值是调用函数时传递的参数。 |
b(4) | 自动存储 | 4-10,17-23 | 无 | 函数参数的初始值是调用函数时传递的参数。 |
c(4) | 自动存储 | 4-10,17-23 | 无 | 函数参数的初始值是调用函数时传递的参数。 |
d(6) | 自动存储 | 6-7,17 | 无 | 垃圾 |
e(6) | 自动存储 | 6-7,17-23 | 无 | 1 |
d(9) | 自动存储 | 9-10 | 无 | 垃圾 |
e(9) | 自动存储 | 9-16 | 无 | 垃圾 |
w(9) | 自动存储 | 9-16 | 无 | 垃圾 |
b(12) | 自动存储 | 12-15 | 无 | 垃圾 |
c(12) | 自动存储 | 12-15 | 无 | 垃圾 |
d(12) | 自动存储 | 12-15 | 无 | 垃圾 |
y(13) | 静态存储 | 13-15 | 无 | 2 |
a(19) | 寄存器存储 | 19-22 | 无 | 垃圾 |
d(19) | 寄存器存储 | 19-22 | 无 | 垃圾 |
x(19) | 寄存器存储 | 19-22 | 无 | 垃圾 |
y(20) | 静态存储 | 20-22 | external | 如果该变量未在任何其他声明中初始化,则其初始值为0。 |
y(24) | 静态存储 | 24-31 | internal | 0 |
func2(26) | 静态存储 | 26-31 | external | - |
a(26) | 自动存储 | 26-31 | 无 | 函数参数的初始值是调用函数时传递的参数。 |
y(28) | 自动存储 | 28-31 | extern关键字不会更改第24行中声明的y的链接属性。 | 0 |
z(29) | 静态存储 | 29-31 | 无 | 0 |