Bootstrap

3.1 常量与变量

(作者:徐诚 http://blog.csdn.net/shizhebsys 保留版权)

C语言程序中用于运算的数据可以分为常量与变量两种基本类型。常量是直接在代码中所出现的数据,运算过程中不能修改常量值。变量是C语言程序在内存中为数据动态划分出的定长存储空间,运算过程中可以修改变量值。为了让读者能够更深入的了解常量与变量的本质,在介绍常量与变量前,我们首先需要认识计算机内部数据存储机制。

3.1.1  内部存储器、寄存器和数据存储形式

在计算机的电路中,用于存放运算数据的设备有内部存储器(内存)和寄存器。内存是主要的存储设备,对应的电路模块称之为内存条。寄存器的容量非常小,但是处于CPU的内部,因此访问速度非常快。

 

数据以二进制形式存储在内存或寄存器中,最小的存储单位为位(bit),每次可操作的最小存储单位为字节(byte)。例如十进制整数87对应的二进制数为01010111,至少需要1字节的存储空间,在内存中的存储形式如图3.1所示。

3.1  1字节存储空间模拟图

 

内存地址用以表示字节单元在内存中的位置,计算机可通过内存地址访问相应的内存单元。由于计算机的结构差异非常大,每次运行程序时内存的状况也不相同,因此存放数据的位置也不一样。在C语言程序中,变量是程序运行时动态划分的内存单元,其本质是某一内存单元在程序中的映射。这样,设计程序时不用考虑数据具体存放的位置,只用变量的名称就可以访问相应的内存地址。变量的声明形式为:

[modifier] type name [= value];

其中,modifier是修饰符,name是变量的名称,value是在声明时为变量赋予的初始值。声明语句结束后,必须使用分号结束一行。

例如为了保存十进制整数87声明了一个字符型变量,变量名为a。该变量规定的长度为1字节,程序运行时,操作系统会为变量a划分出1字节的存储空间,通过名称a可对相应的存储空间进行数据的读或写操作。如下列源代码所示:

#include <stdio.h>                                                   // 包含基本输入输出头文件

int main()                                                                   // 主函数

{

   char a;                                                                           // 声明字符型变量a

   a = 87;                                                                           // 向变量a写入数据

   printf("%d", a);                                                    // 输出变量a的数值到终端

   return 0;                                                               // 退出程序

}

代码运行时,“char a”表达式进行声明操作,要求操作系统为字符型变量a分配内存。假设分配的内存地址为0x30,名称a就可以代表以该地址开始,长度为1字节的存储空间。操作系统会将这一段内存空间保护起来,不会将同样的空间分配给其他程序或变量。“a = 87”表达式对a进行赋值操作,实际上就是把数据写入到相应的内存单元中。程序结束后,操作系统会进行内存回收,分配给变量a的内存空间被标为空闲,其他程序可以获得该空间。

 

不同数据类型的变量差别在于对应存储空间的长度,该长度还会因为计算机硬件结构和编译器类型的差别而不同。例如整型变量的长度对应于凌动处理器和GCC编译器的长度为4字节,存放负整数-87的整型变量在内存中的存储形式如图3.2所示。

3.2  4字节存储空间模拟图

 

整型变量的长度为4个字节,但是只需要第1个字节的地址就能访问到整个空间。因为变量类型已经为变量定义了存储空间长度的信息,第1个字节的地址称为首地址,只用通过偏移量就能得到其他字节的地址。

为了保存正负符号,存储空间中的第1位是符号位。正数对应0,负数对应1。被符号占用1位存储空间后,字符型变量可储存的最小值为-27,最大值为27-1,即-128127;整型变量可存储的最小值为-231,最大值为231-10被作为正数保存,因此正数最大值的数值比负数最大值的数值要少1

存储到寄存器的原理与存储到内存非常相似,在变量声明表达式前加入register标识符可将变量声明为寄存器变量。如下例所示:

   register int a;                                                       // 声明寄存器整型变量a

上述语句声明了寄存器整型变量a,其长度同样是4字节,并且有自己的地址。但是由于寄存器的资源非常有限,通常只将需要高频率访问的变量声明为寄存器变量。操作系统和编译器考虑到程序性能优化的问题,并不一定会将用户声明的寄存器变量保存在寄存器中,而是转换为普通变量。

一切在代码中直接出现的数据都是常量,例如“a = 87”表达式中的数值87即常量。常量在内存中的存储位置不被程序设计者关心,程序中也无法直接得到常量的地址,因此常量是不可修改的。由此我们可以用内存地址是否能被程序得到来区别常量与变量,这也是常量与变量的本质性区别。

 

3.1.2  数据类型

认识了数据存储形式后,数据类型就比较容易理解。本小节所讨论的数据类型指的是C语言中原始的数据类型,实际上数据类型直接的差别在于存储空间长度。另外,还将涉及是否保存正负数符号,以及是否使用浮点方式来保存小数和指数。有正负符号的数称为有符号数,没有正负符号的数称为无符号数。有符号数可以存储负数,无符号数只能存储正数。不使用浮点形式的数称之为整数,使用浮点形式的数称之为浮点数。除此以外还有一种空值类型,它不能保存任何数据,存储空间长度为0

C语言的所有类型都是从5种最原始的类型发展而来的,见表3.1所示。

3.1  ANSI C标准基本类型的字长与范围表

类型

说明符

长度

值域

字符型

char

1字节

-128127

整型

int

4字节

- 2147483648 2147483647

单精度浮点型

float

4字节

约精确到6位数

双精度浮点型

double

8字节

约精确到12位数

空值型

void

0字节

无值

其中字符型和整型有无符号数和有符号数的差别,区别在于说明符前加入了signedunsigned修饰符,见表3.2所示。

3.2  无符号与有符号类型的字长与范围表

类型

说明符

长度

值域

无符号字符型

unsigned char

1字节

0255

有符号字符型

signed char

1字节

-128127

无符号整型

unsigned int

4字节

04294967295

有符号整型

signed int

4字节

- 2147483648 2147483647

由于字符型和整型默认为有符号数,所以通常在声明时可以省略signed修饰符。另外,整型数据可以使用shortlong修饰符来定义为短整型和长整型,见表3.3所示。

3.3  短整型和长整型的字长与范围表

类型

说明符

长度

值域

短整型

short int

2字节

-3276832767

整型

long int

4字节

- 2147483648 2147483647

长整型

long long int

8字节

-9.223372e+189.223372e+18

在使用短整型和长整型时,可省略int说明符。因此,声明短整型可使用short修饰符作为说明符,声明长整型可使用long long作为说明符,而longint说明符是等价的。unsignedsigned修饰符与shortlong说明符可以同时使用,如下例所示:

   unsigned long long a;                                                                   // 声明无符号长整型变量a

该行代码声明了无符号长整型变量a,其值域范围为0264-1。在不使用科学计数法的条件下,变量a可以用来保存C语言中最大的正整数264-1

3.1.3  常量的形式

C语言中,常量出现的形式共有4种,分别是直接常量、符号常量、枚举常量和常量变量。其中,前3种是严格意义上的常量,而常量变量是一种特殊的常量。

1.直接常量

所有在C语言源代码中直接出现的数值、字符和字符串都是直接常量。如下列源代码所示:

   float pi = 3.141593;                                                                       // 声明单精度浮点型变量pi并赋值

   char c = 'a';                                                                                      // 声明字符型变量c并赋值

   printf("a cup of coffee");                                                               // 在终端上输出一行字符串

代码中定义了2个变量,并使用直接常量为其赋值。其中,数值3.141593在类型上属于浮点型常量,“a”属于字符型常量。最后一行使用printf()函数输出了字符串“a cup of coffee”,此处使用的是字符串常量。

注意:字符型常量必须使用单引号包围,如'a'。字符串常量必须使用双引号包围,如"a cup of coffee"。如果使用双引号包围一个字符,如"a",那么编译器会认为这是一个字符串,并在其后自动加上字符串结束符。如果用单引号包围一个字符串,如'a cup of coffee',编译器将认为这是语法错误,并抛出错误提示信息。

2.符号常量

使用“#define”定义的常量称之为符号常量。符号常量定义的形式为:

#define NAME value

其中,NAME是符号常量的名称,value必须是一个直接常量。通常,符号常量声明在源代码的最上方,并且用大写字母作为其名称。如下例所示:

#include <stdio.h>                                                                               // 包含基本输入输出头文件

#define PI 3.141593                                                                                    // 定义符号常量PI

#define C 'a'                                                                                         // 定义符号常量C

#define S "a cup of coffee"                                                               // 定义符号常量S

 

int main()                                                                                               // 主函数

{

   printf("%f/n", PI);                                                                            // 输出符号常量所代表的数值

   printf("%c/n", C);

   printf("%s/n", S);

   return 0;                                                                                           // 主函数结束

}

代码中定义了3个符号常量,在主函数中,这些符号常量所代表的数值被printf()函数输出。我们可以简单的认为,符号常量所做的仅仅是在源代码中进行的字符串替换。编译器编译时,所以常量PI都会被替换为3.141593

使用符号常量有三点好处,其一是易于记忆,例如我们可以为某个常量定义一个较容易理解的名称,如PI。其二是表达简洁,在上例的代码中,仅仅用一个字母S就能代理整个字符串“a cup of coffee”。其三是数值容易修改,如果某个直接常量需要多次使用,一旦该常量的值必须被调整时,往往需要修改多处代码;而使用符号常量代替了源代码中所有直接常量后,修改时只用在符号常量定义部分修改。因此我们建议读者尽量在代码中使用符号常量。

注意:符号常量定义的行尾不需要使用分号“;”结束该语句,否则会造成语法错误。

3.枚举常量

使用enum定义的常量称之为枚举常量,它是一种聚合类型。枚举常量定义的形式为:

enum name {CON1 [= INT], CON2 [= INT], …};

其中,nume为枚举类型名称,CON1CON2为枚举成员名称。枚举成员的数值在定义后不可改变,并且能作为常量使用,被称为枚举常量。枚举成员可用整型常量赋值,第1个枚举成员默认值为0,其后枚举成员的默认值为前一个枚举成员值加1的结果。如下例所示:

enum week {MON = 1, TUE, WED, THU, FRI, SAT, SUN}; // 定义枚举类型和成员,将MON的值设置为1

printf("%d", SAT);                                                                                 // 输出成员SAT的值

代码中定义了枚举类型week,其中有7个成员,第1个成员MON的值设置为1。然后,printf()函数输出了成员SAT的值。根据枚举类型的默认值规则可知,SAT的值为6

4.常量变量

使用const修饰符声明的变量称之为常量变量。从本质上来说,常量变量依然属于变量的一种,但是程序运行过程中不能修改其值。如下例所示:

   const int id = 15;                                                                             // 声明常量变量id并赋值

代码中声明了常量变量id,并且为其赋值。声明语句以后,任何赋值或修改常量变量数值的语句,都将造成编译错误。

;