变量和常量是程序处理的两种基本数据对象。声明语句说明变量的名字及类型,也可以指定变量的初值。运算符指定要对变量进行的操作。表达式把变量与常量组合起来生成新的值。本章将详细讲述这些内容。
2.1 变量名名字可以由字母、数字或下划线("_")组成,必须以字母或下划线开头,区分大小写。if、for、int等关键字不能用作变量名。
习惯上,变量名使用小写字母,符号常量名全部使用大写字母。
变量名要尽量能够从字面上表达变量的用途。局部变量一般使用较短的变量名(循环控制变量习惯上使用i、j等),外部变量使用较长的名字。
2.2 数据类型及长度C语言只提供了以下几种基本数据类型:
char:字符int:整型float:单精度浮点型double:双精度浮点型
此外,还可以在这些基本数据类型前面加上一些限定符。short和long两个限定符用于限定整型,表示不同长度的整型数:
short int a; long int b;
在上述声明中,关键字int可以省略。
限定符signed和unsigned用于限定char类型或任何整型。signed表示“有符号”,即可以表示正数、0和负数,可省略;unsigned表示“无符号”,即只能表示非负数。
与类型长度有关的符号常量定义在标准头文件
注:计算机使用二进制表示整数,n位无符号二进制数的范围是0~11…1(n个1),即0~2n-1。例如,2位二进制数能表示0~3四个数:
二进制 十进制 00 0 01 1 10 2 11 3
为了表示负数,计算机使用最高位作为符号位,因此n位有符号二进制数的范围是-2n-1~2n-1-1。
例如,char类型的长度是1字节(8位),因此范围为-27~27-1,即-128~127;unsigned char类型的长度也是1字节(8位),范围是0~28-1,即0~255。
C语言基本类型的长度及范围如下:
| 类型 | 含义 | 长度 | 范围 |
|---|---|---|---|
| char | 字符 | 1字节 | -128~127 |
| unsigned char | 字符 | 1字节 | 0~255 |
| short | 短整型 | 2字节 | -32768~32767 |
| unsigned short | 短整型 | 2字节 | 0~65535 |
| int | 整型 | 4字节 | -2147483648~2147483647 |
| unsigned int | 整型 | 4字节 | 0~4294967295 |
| long | 长整型 | 4字节 | -2147483648~2147483647 |
| unsigned long | 长整型 | 4字节 | 0~4294967295 |
| float | 单精度浮点型 | 4字节 | -3.40282×1038~3.40282×1038 |
| double | 双精度浮点型 | 8字节 | -1.79769×10308~1.79769×10308 |
练习2-1 编写一个程序以确定分别由signed及unsigned限定的char、short、int与long类型变量的取值范围。采用打印标准头文件中的相应值以及直接计算两种方式实现。后一种方法的实现较困难一些,因为要确定各种浮点类型的取值范围。
注:使用直接计算的方法需要了解位运算(在2.9节介绍)。
2.3 常量 整型常量| 类型 | 格式 | 后缀 | 示例 |
|---|---|---|---|
| int | 十进制数字,可带正负号,不能以0开头(0除外) | 无 | 1234, -5678 |
| long | 同上 | l或L | 123456789L |
| unsigned | 十进制数字,不能以0开头(0除外) | u或U | 1234U |
| unsigned long | 同上 | ul或UL | 123456789UL |
| 进制 | 格式 | 前缀 | 示例 |
|---|---|---|---|
| 八进制 | 八进制数字(0~7),可带正负号 | 0 | 037 |
| 十六进制 | 十六进制数字(0~9, A~F或a~f),可带正负号 | 0x或0X | 0x1f |
八进制与十六进制常量也可以使用后缀L、U或UL。例如0xFUL是一个unsigned long类型的十六进制整型常量,其值等于十进制数15。
浮点数常量| 类型 | 格式 | 后缀 | 示例 |
|---|---|---|---|
| double | 由整数部分、小数点、小数部分、一个e或E和一个可选的带符号整数的指数部分组成 | 无 | 3.14159, 6.02e23, 5e-3, -1e5 |
| float | 同上 | f或F | 3.14159F |
| long double | 同上 | l或L | 1e100L |
字符常量是一个整数,表示字符集中的一个字符,其值等于字符的ASCII码。
| 类型 | 格式 | 示例 |
|---|---|---|
| 普通字符 | 单引号括起来的一个字符 | 'a', '0', '@' |
| 转义字符序列 | 单引号括起来的反斜杠加一个字符 | 'n', 't' |
| 八进制数 | 'ooo',其中ooo表示三个八进制数 | ' 12', '173' |
| 十六进制数 | 'xhh',其中hh表示两个十六进制数 | 'xa', 'x7b' |
注:后两种形式的含义是直接用八进制数或十六进制数表示字符的ASCII码,该字符常量的值也等于这个整数。例如,在数值上'n'等于' 12'等于'xa'等于10。
C语言中的全部转义字符序列:
a 响铃符 b 回退符 f 换页符 n 换行符 r 回车符 t 横向制表符 v 纵向制表符 \ 反斜杠 ? 问号 ' 单引号 " 双引号 空字符 ooo 八进制数 xhh 十六进制数
常量表达式是只包含常量的表达式。这种表达式在编译时求值,而不是在运行时求值。它可以出现在常量可以出现的任何位置。例如:
#define MAXLINE 1000 char line[MAXLINE + 1];字符串常量
字符串常量也叫字符串字面值(string literal),是用双引号括起来的0个或多个字符组成的字符序列,例如"hello, world"和""。字符常量中使用的转义字符序列也可以用在字符串中。编译器会自动将多个字符串常量连接起来,例如"hello," " world"等价于"hello, world"。
从技术角度看,字符串常量就是字符数组,编译器会自动在结尾添加一个' ',因此数组长度比字符串长度大1。例如,字符串"hello"的长度是5,但类型是char[6],声明char s[] = "hello";等价于char s[] = {'h', 'e', 'l', 'l', 'o', ' '};
C语言对字符串的长度没有限制,但程序必须扫描完整个字符串后才能确定字符串的长度。标准库函数strlen(s)返回字符串s的长度,不包括末尾的' '。下面是strlen函数的一个版本:
strlen函数
标准头文件
字符常量与仅包含一个字符的字符串之间的区别:'x'与"x"是不同的。前者是一个整数,其值是字符x的ASCII码(120);后者是一个包含x和 两个字符的字符数组。
枚举常量枚举(enumeration)是一个常量整型值的列表,例如:
enum boolean { NO, YES };
在没有显式说明的情况下,enum中第一个枚举名的值为0,第二个为1,以此类推。如果只指定了部分枚举名的值,未指定值的枚举名将从上一个指定的值向后递增。例如:
enum escapes {
BELL = 'a', BACKSPACE = 'b', TAB = 't',
newline = 'n', VTAB = 'v', RETURN = 'r'
};
enum months {
JAN = 1, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC
};
不同枚举中的名字必须互不相同。同一枚举中不同的名字可以具有相同的值。
枚举为建立常量值与名字之间的关联提供了一种便利的方式。相对于#define语句来说,枚举的优势在于常量值可以自动生成。
尽管可以声明enum类型的变量,但编译器不检查这种类型的变量中存储的值是否为该枚举的有效值。例如:
enum months m = DEC; ++m;
注意:
声明枚举变量时要加上enum关键字,但枚举常量名是全局的(这也是不同枚举中的名字必须互不相同的原因)枚举值本质上就是一个整数,因此可以进行整型运算、使用%d说明符打印枚举最常见的用法是检查枚举变量的值是否等于该枚举类型声明的某个常量值,因此虽然枚举变量的取值不限于该枚举声明的值,但这样就失去了使用枚举的意义 2.4 声明
所有变量都必须先声明后使用,尽管全局变量可以通过上下文隐式声明。声明指定一种类型,以及一个或多个这种类型的变量。例如:
int lower, upper, step; char c, line[1000];
一个声明语句中的多个变量可以拆开在多个声明语句中声明。上面的两个声明语句也可以等价地写成下列形式:
int lower; int upper; int step; char c; char line[1000];
这种形式占用更多空间,但便于向各声明语句后添加注释,也便于以后修改。
可以在声明的同时对变量进行初始化。如果变量名的后面紧跟一个等号以及一个表达式,该表达式就充当变量的初始值。例如:
char esc = '\'; int i = 0; int limit = MAXLINE + 1; float eps = 1.0e-5;
每次进入函数或程序块时,显式初始化的自动变量(局部变量)都将被初始化一次,其初始化表达式可以是任何表达式,未显式初始化的自动变量的值是未定义值(无效值)。
外部变量(全局变量)和静态变量只进行一次初始化(在程序开始执行之前),初始化表达式必须为常量表达式,默认初始化为0。
任何变量的声明都可以使用const限定符指定该变量的值不能被修改。对数组而言,const限定符指定数组所有元素的值都不能被修改。例如:
const double e = 2.71828182845905; const char msg[] = "warning: ";
const限定符也可配合数组参数使用,表示函数不能修改数组元素的值:
int strlen(const char s[]);
注:
const的含义是“常量(constant)”,这里是指变量的值不能被修改,而2.3节所说的“常量”是指“字面值(literal)”声明const常量时必须初始化 2.5 算术运算符
二元算术运算符包括:+、-、*、/、%(取模运算符)。整数除法会截断结果中的小数部分。
表达式x % y的结果是x除以y的余数,当x能被y整除时其值为0。例如,如果某一年的年份能被4整除但不能被100整除,或者能被400整除,那么这一年就是闰年。因此,可用下列语句判断闰年:
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
printf("%d is a leap yearn", year);
else
printf("%d is not a leap yearn", year);
取模运算符%不能用于float或double类型。如果有负操作数,则余数的符号与除数相同(见 除法舍入问题)。
运算符+和-的优先级比*、/和%的优先级低,相同优先级的算术运算符采用从左到右的结合规则。例如,a + b - c等价于(a + b) - c,a + b * c等价于a + (b * c)。
完整的运算符优先级和结合律见2.12节。
2.6 关系运算符与逻辑运算符关系运算符包括>、>=、<、<=、==、!=。关系运算符的优先级低于算术运算符,因此i < lim - 1等价于i < (lim - 1)。
逻辑运算符&&和||采用短路逻辑:由&&和||连接的表达式按从左到右的顺序进行求值,并且在知道结果为真或假后立即停止计算。运算符&&的优先级高于||,但两者都比关系运算符的优先级低。
在关系表达式和逻辑表达式中,如果结果为真,则表达式的值为1;如果为假,则值为0。
逻辑运算符!的作用是将非0操作数转换为0,将操作数0转换为1。
注:在if、while、for等语句的测试部分中,“真”等价于“非0”。因此if (expr != 0)等价于if (expr);if (expr == 0)等价于if (!expr)(此时将!读作 “not” 会更直观)
练习2-2 在不使用运算符&&或||的条件下编写一个与1.9节getline函数中的for循环等价的循环语句。
for (i = 0; (i < maxline - 1) * ((c = getchar()) != EOF) * (c != 'n'); ++i)
line[i] = c;
2.7 类型转换
隐式类型转换
如果二元运算符的两个操作数具有不同的类型,那么在进行运算之前要先把“较低”的类型提升为“较高”的类型,运算的结果为较高的类型。
简单来说,按照以下规则转换(不考虑无符号类型):
如果一个操作数是long double,则将另一个转换为long double;否则,如果一个操作数是double,则将另一个转换为double;否则,如果一个操作数是float,则将另一个转换为float;否则,将char和short转换为int;之后,如果一个操作数是long,则将另一个转换为long
由于char类型就是较小的整型,因此在算术表达式中可以自由使用char类型的变量,这就为实现某些字符转换提供了很大的灵活性。例如,下面的函数atoi将字符串转换为相应的整数:
atoi函数
注:atoi函数在遇到第一个非数字字符时就返回,因此该函数只考虑参数字符串的数字前缀,例如,atoi("123abc")返回123,atoi("abc")返回0
函数lower是将char类型转换为int类型的另一个例子,它将字符转换为小写形式,如果待转换的字符不是大写字母则返回字符本身:
lower函数
注:lower函数依赖于在ASCII字符集中大写字母和小写字母都是连续的,并且具有固定的间隔,A~Z的ASCII码分别是65~90,a~z的ASCII码分别是97~122,每个小写字母与对应的大写字母之间的间隔都是'a' - 'A' = 97 - 65 = 32,因此大写字母加上这个值就是对应的小写字母,例如'B' + 32 = 66 + 32 = 98 = 'b'
标准头文件
赋值时也要进行类型转换,赋值运算符右边的值需要转换为左边变量的类型。针对可能导致信息丢失的转换,编译器可能给出警告,但这些转换并不非法。当把较长的整数转换为较短的整数时,超出的高位部分将被丢弃。例如:
int i = 12345678; char c = i; i = c;
这是因为int值12345678有4个字节,其二进制是00000000 10111100 01100001 01001110,将其赋值给char类型的变量将被截断,只保留最低1个字节01001110,转换为十进制即78。
在把参数传递给函数时也可能进行类型转换。
强制类型转换 2.8 自增运算符与自减运算符 2.9 按位运算符 2.10 赋值运算符与表达式 2.11 条件表达式 2.12 运算符优先级与求值次序


