示例程序
如果输入程序时打错(如, 漏了一个分号) , 编译器会报告语法错误消息。 然而, 即使输入正确无误, 编译器也可能给出一些警告, 如“警告: 从double类型转换成float类型可能会丢失数据”。 错误消息表明程序中有错, 不能进行编译。
而警告则表明, 尽管编写的代码有效, 但可能不是程序员想要的。 警告并不终止编译。
程序调整
在程序中添加getchar();本例, 需要调用两次getchar()函数:
getchar()函数读取下一个输入字符, 因此程序会等待用户输入。 在这种情况下, 键入 156 并按下Enter(或Return) 键(发送一个换行符) , 然后scanf()读取键入的数字, 第1个getchar()读取换行符, 第2个getchar()让程序暂停, 等待输入。
代码中使用了一种新的变量声明。 前面的例子中只使用了整数类型的变量(int) , 但是本例使用了浮点数类型(float) 的变量, 以便处理更大范围的数据。 float 类型可以储存带小数的数字。
为了打印新类型的变量, 在printf()中使用%f来处理浮点值。 %.2f中的.2用于精确控制输出, 指定输出的浮点数只显示小数点后面两位。
scanf()函数用于读取键盘的输入。 %f说明scanf()要读取用户从键盘输入的浮点数, &weight告诉 scanf()把输入的值赋给名为 weight 的变量。 scanf()函数使用&符号表明找到 weight变量的地点。
计算机向用户询问信息, 然后用户输入数字。 与非交互式程序相比, 交互式程序用起来更有趣。 更重要的是, 交互式使得程序更加灵活。
可以根据不同体重进行计算。scanf()和printf()函数用于实现这种交互。 scanf()函数读取用户从键盘输入的数据, 并把数据传递给程序; printf()函数读取程序中的数据, 并把数据显示在屏幕上。
变量与常量数据
有些数据类型在程序使用之前已经预先设定好了, 在整个程序的运行过程中没有变化, 这些称为常量(constant) 。 其他数据类型在程序运行期间可能会改变或被赋值, 这些称为变量(variable) 。
示例程序中, weight 是一个变量, 14.5833 是一个常量。 在现实生活中, 白金的价格不会是常量, 但是在程序中, 像1700.0这样的价格被视为常量。
数据: 数据类型关键字
不仅变量和常量不同, 不同的数据类型之间也有差异。 一些数据类型表示数字, 一些数据类型表示字母(更普遍地说是字符) 。 C通过识别一些基本的数据类型来区分和使用这些不同的数据类型。 如果数据是常量, 编译器一般通过用户书写的形式来识别类型(如, 42是整数, 42.100是浮点数) 。但是, 对变量而言, 要在声明时指定其类型。
C语言的基本类型关键字。 K&C给出了7个与类型相关的关键字。 C90标准添加了2个关键字, C99标准又添加了3个关键字
在C语言中, 用int关键字来表示基本的整数类型。 后3个关键字(long、short和unsigned) 和C90新增的signed用于提供基本整数类型的变式, 例如unsigned short int和long long int。 char关键字用于指定字母和其他字符(如,#、 $、 %和*) 。 另外, char类型也可以表示较小的整数。 float、 double和long double表示带小数点的数。 _Bool类型表示布尔值(true或false) ,_complex和_Imaginary分别表示复数和虚数。
通过这些关键字创建的类型, 按计算机的储存方式可分为两大基本类型: 整数类型和浮点数类型。
位、 字节和字位、
字节和字是描述计算机数据单元或存储单元的术语。 这里主要指存储单元。
最小的存储单元是位(bit) , 可以储存0或1(或者说, 位用于设置“开”或“关”) 。 位是计算机内存的基本构建块。
字节(byte) 是常用的计算机存储单位。 对于几乎所有的机器, 1字节均为8位。 这是字节的标准定义, 至少在衡量存储单位时是这样。
1位可以表示0或1, 那么8位字节就有256(2的8次方) 种可能的0、 1的组合。 通过二进制编码(仅用0和1便可表示数字) , 便可表示0~255的整数或一组字符
字(word) 是设计计算机时给定的自然存储位。
整型和浮点型
整数和浮点数的区别是它们的书写方式不同。 对计算机而言, 它们的区别是储存方式不同。
整数
在C语言中, 整数是没有小数部分的数。 例如,2、 −23和2456都是整数。 而3.14、 0.22和2.000都不是整数。 计算机以二进制数字储存整数, 例如, 整数7以二进制写是111。 因此, 要在8位字节中储存该数字, 需要把前5位都设置成0, 后3位设置成1
浮点型
2.75、 3.16E7、 7.00 和 2e-8 都是浮点数。 注意, 在一个值后面加上一个小数点, 该值就成为一个浮点值。 所以, 7是整数, 7.00是浮点数。 显然, 书写浮点数有多种形式。3.16E7 表示3.16×107(3.16 乘以10 的7次方) 。 其中, 107=10000000, 7被称为10的指数。
计算机把浮点数分成小数部分和指数部分来表示, 而且分开储存这两部分。
整数没有小数部分, 浮点数有小数部分。
浮点数可以表示的范围比整数大。 参见本章末的表3.3。
对于一些算术运算(如, 两个很大的数相减) , 浮点数损失的精度更多。
因为在任何区间内(如, 1.0 到 2.0 之间) 都存在无穷多个实数, 所以计算机的浮点数不能表示区间内所有的值。
C语言基本数据类型
int类型
int类型是有符号整型, 即int类型的值必须是整数, 可以是正整数、 负整数或零。 其取值范围依计算机系统而异。 一般而言, 储存一个int要占用一个机器字长。C规定int的取值范围最小为-32768~32767。 一般而言, 系统用一个特殊位的值表示有符号整数的正负号。
声明int变量
第2章中已经用int声明过基本整型变量。 先写上int, 然后写变量名, 最后加上一个分号。 要声明多个变量, 可以单独声明每个变量, 也可在int后面列出多个变量名, 变量名之间用逗号分隔。
int erns;
int hogs, cows, goats;
可以分别在4条声明中声明各变量, 也可以在一条声明中声明4个变量。两种方法的效果相同, 都为4个int大小的变量赋予名称并分配内存空间。
程序中获取值的两种途径。 第1种途径是赋值:
cows = 112;
第2种途径是, 通过函数(如, scanf()) 获得值。
初始化变量
初始化(initialize) 变量就是为变量赋一个初始值。 在C语言中, 初始化可以直接在声明中完成。 只需在变量名后面加上赋值运算符(=) 和待赋给变量的值即可。
int hogs = 21;
int cows = 32, goats = 14;
int dogs, cats = 94; /* 有效, 但是这种格式很糟糕 */
以上示例的最后一行, 只初始化了cats, 并未初始化dogs。 声明为变量创建和标记存储空间, 并为其指定初始值
int类型常量
整数(21、 32、 14和94) 都是整型常量或整型字面量。 C语言把不含小数点和指数的数作为整数。22和-44都是整型常量, 但是22.0和2.2E1则不是。
C语言把大多数整型常量视为int类型, 但是非常大的整数除外。
打印int值
可以使用printf()函数打印int类型的值。%d指明了在一行中打印整数的位置。 %d称为转换说明, 它指定了printf()应使用什么格式来显示一个值。 格式化字符串中的每个%d都与待打印变量列表中相应的int值匹配。 这个值可以是int类型的变量、 int类型的常量或其他任何值为int类型的表达式。
在第一行输出中, 第1个%d对应int类型变量ten; 第2个%d对应int类型常量2; 第3个%d对应int类型表达式ten - two的值。 在第二行输出中, 第1个%d对应ten的值, 但是由于没有给后两个%d提供任何值, 所以打印出的值是内存中的任意值
八进制和十六进制
八进制和十六进制记数系统在表达与计算机相关的值时很方便。十进制数65536经常出现在16位机中, 用十六进制表示正好是10000。 另外, 十六进制数的每一位的数恰好由4位二进制数表示。
在C语言中, 用特定的前缀表示使用哪种进制。 0x或0X前缀表示十六进制值, 所以十进制数16表示成十六进制是0x10或0X10。
显示八进制和十六进制
在C程序中, 既可以使用和显示不同进制的数。 不同的进制要使用不同的转换说明。 以十进制显示数字, 使用%d; 以八进制显示数字, 使用%o;以十六进制显示数字, 使用%x。
要显示各进制数的前缀0、 0x和0X,必须分别使用%#o、 %#x、 %#X。
该程序以3种不同记数系统显示同一个值。 printf()函数做了相应的转换。 注意, 如果要在八进制和十六进制值前显示0和0x前缀, 要分别在转换说明中加入#。
其他整数类型
short int类型(或者简写为short) 占用的存储空间可能比int类型少, 常用于较小数值的场合以节省空间。 与int类似, short是有符号类型。
long int或long占用的存储空间可能比int多, 适用于较大数值的场合。 与int类似, long是有符号类型。
long long int或long long(C99标准加入) 占用的储存空间可能比long多,适用于更大数值的场合。 该类型至少占64位。 与int类似, long long是有符号类型
unsigned int或unsigned只用于非负值的场合。 这种类型与有符号类型表示的范围不同。 例如, 16位unsigned int允许的取值范围是0~65535, 而不是-32768~32767。 用于表示正负号的位现在用于表示另一个二进制位, 所以无符号整型可以表示更大的数。
在C90标准中, 添加了unsigned long int或unsigned long和unsigned int或unsigned short类型。 C99标准又添加了unsigned long long int或unsigned longlong。
在任何有符号类型前面添加关键字signed, 可强调使用有符号类型的意图。 例如, short、 short int、 signed short、 signed short int都表示同一种类型。
声明其他整数类型
其他整数类型的声明方式与int类型相同, 不是所有的C编译器都能识别最后3条声明, 最后一个例子所有的类型是C99标准新增的。
long int estine;
long johns;
short int erns;
short ribs;
unsigned int s_count;
unsigned players;
unsigned long headcount;
unsigned short yesvotes;
long long ago;
C语言只规定了short占用的存储空间不能多于int,long占用的存储空间不能少于int。 这样规定是为了适应不同的机器。为了储存64位的整数, 才引入了long long类型。
如果一个数超出了int类型的取值范围, 且在long类型的取值范围内时,使用long类型。对于那些long占用的空间比int大的系统, 使用long类型会减慢运算速度。如果在long类型和int类型占用空间相同的机器上编写代码, 当确实需要32位的整数时, 应使用long类型而不是int类型, 以便把程序移植到16位机后仍然可以正常工作。
如果在int设置为32位的系统中要使用16位的值, 应使用short类型以节省存储空间。只有当程序使用相对于系统可用内存较大的整型数组时,才需要重点考虑节省空间的问题。
long常量和long long常量
通常, 程序代码中使用的数字(如, 2345) 都被储存为int类型。 如果使用1000000这样的大数字, 超出了int类型能表示的范围, 编译器会将其视为long int类型(假设这种类型可以表示该数字) 。如果数字超出long可表示的最大值, 编译器则将其视为unsigned long类型。 如果还不够大, 编译器则将其视为long long或unsigned long long类型(前提是编译器能识别这些类型) 。
八进制和十六进制常量被视为int类型。 如果值太大, 编译器会尝试使用unsigned int。 如果还不够大, 编译器会依次使用long、 unsigned long、 longlong和unsigned long long类型
有些情况下, 需要编译器以long类型储存一个小数字。 例如, 编程时要显式使用IBM PC上的内存地址时。 另外, 一些C标准函数也要求使用long类型的值。 要把一个较小的常量作为long类型对待, 可以在值的末尾加上l(小写的L) 或L后缀。
整数溢出
可以把无符号整数j看作是汽车的里程表。 当达到它能表示的最大值时, 会重新从起始点开始。 整数 i 也是类似的情况。
它们主要的区别是, 在超过最大值时, unsigned int 类型的变量 j 从 0开始; 而int类型的变量i则从−2147483648开始。 注意, 当i超出(溢出) 其相应类型所能表示的最大值时, 系统并未通知用户。
溢出行为是未定义的行为, C 标准并未定义有符号类型的溢出规则。
打印short、 long、 long long和unsigned类型
打印unsigned int类型的值, 使用%u转换说明; 打印long类型的值, 使用%ld转换说明。 如果系统中int和long的大小相同, 使用%d就行。
这样的程序被移植到其他系统(int和long类型的大小不同) 中会无法正常工作。 在x和o前面可以使用l前缀, %lx表示以十六进制格式打印long类型整数, %lo表示以八进制格式打印long类型整数。
C允许使用大写或小写的常量后缀, 但是在转换说明中只能用小写。
C语言有多种printf()格式。 对于short类型, 可以使用h前缀。 %hd表示以十进制显示short类型的整数, %ho表示以八进制显示short类型的整数。 h和l前缀都可以和u一起使用, 用于表示无符号类型。
如, %lu表示打印unsigned long类型的值。 程序演示了一些对于支持long long类型的系统, %lld和%llu分别表示有符号和无符号类型。
使用错误的转换说明会得到意想不到的结果。 第 1 行输出,对于无符号变量 un, 使用%d会生成负值! 其原因是, 无符号值 3000000000和有符号值−129496296 在系统内存中的内部表示完全相同如果告诉printf()该数是无符号数, 它打印一个值; 如果告诉它该数是有符号数, 它将打印另一个值。 在待打印的值大于有符号值的最大值时, 会发生这种情况。对于较小的正数(如96) , 有符号和无符号类型的存储、 显示都相同。
第2行输出, 对于short类型的变量end, 在printf()中无论指定以short类型(%hd) 还是int类型(%d)打印, 打印出来的值都相同。在给函数传递参数时, C编译器把short类型的值自动转换成int类型的值。
int类型被认为是计算机处理整数类型时最高效的类型。 因此, 在short和int类型的大小不同的计算机中, 用int类型的参数传递速度更快。
使用h修饰符可以显示较大整数被截断成 short 类型值的情况。 第 3 行输出就演示了这种情况。 把 65537 以二进制格式写成一个 32 位数是00000000000000010000000000000001。 使用%hd, printf()只会查看后 16位, 所以显示的值是 1。 与此类似, 输出的最后一行先显示了verybig的完整值, 然后由于使用了%ld, printf()只显示了储存在后32位的值。
在使用 printf()函数时, 切记检查每个待打印值都有对应的转换说明,还要检查转换说明的类型是否与待打印值的类型相匹配。
char类型
char类型用于储存字符(如, 字母或标点符号) , 但是从技术层面看,char是整数类型。 因为char类型实际上储存的是整数而不是字符。计算机使用数字编码来处理字符, 即用特定的整数表示特定的字符。
标准ASCII码的范围是0~127, 只需7位二进制数即可表示。 通常, char类型被定义为8位的存储单元, 因此容纳标准ASCII码绰绰有余。
声明char类型变量
char类型变量的声明方式与其他类型变量的声明方式相同。
char response;
char itable, latan;
以上声明创建了3个char类型的变量: response、 itable和latan。
字符常量和初始化
如果要把一个字符常量初始化为字母 A, 不必背下 ASCII 码, 用计算机语言很容易做到。 通过以下初始化把字母A赋给grade即可:
char grade = 'A';
在C语言中, 用单引号括起来的单个字符被称为字符常量(characterconstant) 。 编译器一发现'A', 就会将其转换成相应的代码值。 单引号必不可少。
char broiled; /* 声明一个char类型的变量 */
broiled = 'T'; /* 为其赋值, 正确 */
broiled = T; /* 错误! 此时T是一个变量 */
broiled = "T"; /* 错误! 此时"T"是一个字符串 */
如果省略单引号, 编译器认为T是一个变量名; 如果把T用双引号括起来, 编译器则认为"T"是一个字符串。
字符是以数值形式储存的, 所以也可使用数字代码值来赋值。
C语言将字符常量视为int类型而非char类型。
非打印字符
单引号只适用于字符、 数字和标点符号, 浏览ASCII表会发现, 有些ASCII字符打印不出来。 例如, 一些代表行为的字符(如, 退格、 换行、 终端响铃或蜂鸣) 。 C语言提供了3种方法表示这些字符。
第1种方法——使用ASCII码。 例如, 蜂鸣字符的ASCII值是7, 因此可以这样写:
char beep = 7;
第 2 种方法是, 使用特殊的符号序列表示一些特殊的字符。 这些符号序列叫作转义序列(escape sequence) 。
把转义序列赋给字符变量时, 必须用单引号把转义序列括起来。
假设有下面一行代码:
char nerf = '\n';
稍后打印变量nerf的效果是, 在打印机或屏幕上另起一行。
C90新增的警报字符(\a)是否能产生听到或看到的警报, 取决于计算机的硬件, 蜂鸣是最常见的警报(在一些系统中, 警报字符不起作用) 。
C标准规定警报字符不得改变活跃位置。 标准中的活跃位置(active position) 指的是显示设备(屏幕、 电传打字机、 打印机等) 中下一个字符将出现的位置。
转义字符\b、 \f、 \n、 \r、 \t和\v是常用的输出设备控制字符换页符(\f) 把活跃位置移至下一页的开始处; 换行符(\n) 把活跃位置移至下一行的开始处; 回车符(\r) 把活跃位置移动到当前行的开始处; 水平制表符(\t) 将活跃位置移至下一个水平制表点(通常是第1个、 第9个、 第17个、 第25个等字符位置) ; 垂直制表符(\v) 把活跃位置移至下一个垂直制表点。
转义序列字符不一定在所有的显示设备上都起作用。换页符和垂直制表符在PC屏幕上会生成奇怪的符号, 光标并不会移动。 只有将其输出到打印机上时才会产生前面的效果。
3个转义序列(\\、 \'、 \") 用于打印\、 '、 "字符(由于这些字符用于定义字符常量, 是printf()函数的一部分, 若直接使用它们会造成混乱)。
如果打印下面一行内容:
Gramps sez, "a \ is a backslash."
应这样编写代码:
printf("Gramps sez, \"a \\ is a backslash.\"\n");
最后两个转义序列(\0oo和\xhh) 是ASCII码的特殊表示。 如果要用八进制ASCII码表示一个字符, 可以在编码值前面加一个反斜杠(\)并用单引号括起来。 例如, 如果编译器不识别警报字符(\a) , 可以使用ASCII码来代替:
beep = '\007';
可以省略前面的 0, '\07'甚至'\7'都可以。 即使没有前缀 0, 编译器在处理这种写法时, 仍会解释为八进制
C语言还提供了第3种选择——用十六进制形式表示字符常量, 即反斜杠后面跟一个x或X, 再加上1~3位十六进制数字。
使用ASCII码时, 注意数字和数字字符的区别。
无论是普通字符还是转义序列, 只要是双引号括起来的字符集合, 就无需用单引号括起来。 双引号中的字符集合叫作字符串
打印字符
printf()函数用%c指明待打印的字符。 一个字符变量实际上被储存为1字节的整数值。 因此, 如果用%d转换说明打印 char类型变量的值, 打印的是一个整数。 而%c转换说明告诉printf()打印该整数值对应的字符。
scanf()函数会读取用户输入的字符, &符号表示把输入的字符赋给变量ch。接着, printf()函数打印ch的值两次, 第1次打印一个字符(对应代码中的%c) , 第2次打印一个十进制整数值(对应代码中的%d) 。
printf()函数中的转换说明决定了数据的显示方式, 而不是数据的储存方式
有符号还是无符号有些C编译器把char实现为有符号类型, 这意味着char可表示的范围是-128~127。 而有些C编译器把char实现为无符号类型, 那么char可表示的范围是0~255。
C语言允许在关键字char前面使用signed或unsigned。 这样, 无论编译器默认char是什么类型, signed char表示有符号类型, 而unsigned char表示无符号类型。 这在用char类型处理小整数时很有用。 如果只用char处理字符, 那么char前面无需使用任何修饰符。
Bool类型
C99标准添加了_Bool类型, 用于表示布尔值, 即逻辑值true和false。 因为C语言用值1表示true, 值0表示false, 所以_Bool类型实际上也是一种整数类型。 但原则上它仅占用1位存储空间, 因为对0和1而言, 1位的存储空间足够了。
程序通过布尔值可选择执行哪部分代码。