对标题和序号稍加修改。
本章仍从一简单的程序开始。
/*将您的体重换算同等白金价值的程序*/
#include
int main(void)
{
float weight;/*你的体重*/
float value;/*相等重量的白金价值*/
printf("Are you worth your weight in platinum?\n");
printf("Let`s check it out.\n");
printf("Plaese enter your weight in pounds:");
/*获取用户输入*/
scanf("%f",&weight);
/*假设白金的价格是每盎司$1700*/
/*14.5833用于将常衡盎司转换为金衡盎司*/
value=1700.0*weight*14.5833;
printf("Your weight in platinum is worth $.2f.\n",value);
printf("Your easily worth that! If platinum prices drop,\n");
printf("eat more to maintain your value.\n");
return 0;
}
在集成环境中编辑时,可能会提示程序中有错误或警告。错误表示存在的问题导致无法编译,警告表明代码有效且可以编译,但是结果却不是想要的。
代码中“enter your weight”表示在输入体重,输入后需要按下Enter键,目的是告知计算机已完成输入。至于输入错误问题第7章详解。
%f
转换说明输出float类型值。scanf()
读取输入。数据即承载信息的数字和字符,有两种表现形式:变量(variable)和常量(constant)。常量即在程序使用之前已预先设定好的,在程序运行期间无变化的数据类型。变量即在程序运行期间可能发生变化的数据类型。
C通过识别一些基本的数据类型来区分不同的数据类型。对于常量来说,编译器一般通过用户的书写形式识别类型(42为整数,42.100为浮点数)。对与变量,要求在声明时指定类型。下表为C语言的数据类型关键字。
K&R | C90添加 | C99添加 |
---|---|---|
int | signed | _Bool(布尔) |
long | void | _Complex(复数) |
short | _Imaginary(虚数) | |
unsigned | ||
char | ||
float | ||
double |
char也可以表示较小的整数。
按计算机的存储方式将数据类型分为两大基本类型:整数类型和浮点数类型。
位、字节和字
位、字节和字是描述计算机数据单元或存储单元的术语,这里主要指存储单元。
位(bit)是最小的存储单元,可以存储0或1(或是说用于设置“开”或“关”),位是计算机内存的基本构建模块。
字节(byte)是常用的计算机存储单位。1字节为8位。1字节有256种0、1的组合。通过二进制编码可以表示0~255的整数或一组字符。第15章详解。
字(word)是设计计算机时给定的自然存储单位。计算机的字长越大,其数据转移越快,允许的内存访问也更多。
除了基本类型,C语言还有许多派生类型,以下为C语言的数据类型结构图:
C语言中整数是没有小数部分的数。计算机以二进制数字储存整数。例如二进制数11001100用十进制整数表示为27+26+23+22,即204。
浮点数和数学中的实数概念差不多。在一个值后加上一个小数点即可变为浮点值。计算机把浮点数分成小数部分和指数部分来表示,而且分开存储这两部分。例如,7.0可以写作0.7E1(e计数法,稍后解释),即0.7X101。0.7为小数部分,1为指数部分,计算机在内部使用二进制和2的幂进行储存,而不是10的幂,15章讲述相关内容。
int类型是有符号整型,即int类型的值必须是整数,可以是正整数、负整数或0。其取值范围依计算机系统而异,一般储存一个int要占用一个机器字长。早期的16位机用16位储存一个int值,目前一般用32位储存一个int值,随着64位计算机的出现,能储存的数值越来越大。ISO C规定int的取值范围最小为-32768~32767,即216=65536个数。一般而言,系统用一个特殊位的值表示有符号整数的正负号,第15章详解。
先写int,然后写变量名,最后加一个分号。有效声明示例:
int erns;
int hogs,cows,goats;
声明后如何获取值?有三种方式:
初始化(initialize)变量就是为变量赋一个初始值,可在声明中完成,在变量名后加赋值运算符=
和待赋值即可。如下所示:
int hogs=21;
int cows=32,goats=14;
int dogs,cats=94;/*有效,但并不推荐此类格式*/
建议不要将初始化的变量和未初始化的变量放在同一条声明中。
以上示例出现的数均为整型常量或整型字面常量,字面常量亦称为字面值。
可以使用printf()
函数打印int类型值,之前的几个示例程序中printf()
函数使用%d
来显示一个int值,它称为转换说明,每个转换说明都与一个int值向匹配,这个值可以是int变量、int常量或任何值为int的表达式。确保转换说明的数量与待打印值的数量一致(编译器不会捕捉该类型错误)。
八进制和十六进制在表达与计算机相关的值时很方便,因为8和16都是2的幂,10却不是。例如,八进制数的每一位由3位二进制数表示,同理十六进制每一位由4位二进制数表示。例如,十进制数314的八进制数为472,它的每一位分别对应二进制数100、111、010,所以八进制数472的位组合(bit pattern)就是100111010,而314的二进制数就是100111010。对于十六进制数来说,它的每一位可以用4为二进制数来表示。
在C语言中用特定前缀来表示八进制与十六进制,用0前缀表示八进制值,用0x或0X表示十六进制值。所以十进制数16可以表示成八进制020,十六进制0x10或0X10。
尽管表示方式不同,但计算机内部储存方式却是相同的,都已二进制编码进行储存。
十六进制每一位取值范围是0至15内的数,用a~ f(大小写都可以)表示10~15。
C语言中不同的进制需要使用不同的转换说明。以十进制显示数字使用%d
,以八进制显示数字使用%o
,以十六进制显示数字使用%x
。另外,如果要显示前缀,需要使用%#o
、%#x
和%#X
。示例程序:
/*分别以十进制、八进制、十六进制打印十进制数100*/
#include
int main(void)
{
int x=100;
printf("dec=%d;octal=%o;hex=%x;\n",x,x,x);
printf("dec=%#d;octal=%#o;hex=%#x;\n",x,x,x);
return 0;
}
运行后输出结果:
dec=100;octal=144;hex=64;
dec=100;octal=0144;hex=0x64;
C语言提供3个附属关键字修饰基本整数类型:short、long、unsigne。注意以下几点:
以下示例中的最后三条声明并不是所有编译器都能识别的,最后一条声明是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;
上面说到short类型肯能比int占用的存储空间少,long可能比int占用的存储空间多等等,这是因为C标准对基本数据类型规定了最小大小。它只规定了short占用空间不能多于int,long占用空间不能少于int。现在PC上最常见设置是:long long占64位,long占32位,short占16位,int占16位或32位(依计算机自然字长而定)。原则上4种类型代表4种不同大小,但是实际使用中有些类型之间会有重叠。根据计算机自然字长以及取值范围选择合适的数据类型有利于提高程序的可移植性。例如,当使用一种在所有系统上都保证至少是32位的类型时,选择long类型。
八进制和十六进制常量被视为int类型。如果值太大可以考虑使用unsigned int,甚至时long int类型等。也可以将一个较小的数存储位long类型。只需在常量后加l或L(推荐大写)后缀。八进制和十六进制数也可以使用这种后缀,例如:7L、020L、0x10L。在支持long long类型的系统中也可以使用ll或LL后缀。而u和U后缀表示unsigned类型,配合L和LL使用,例如:5ull、10LLU、6LLU或9ULL等。
整数溢出
数据溢出是未定义的行为,C标准未定义有符号类型的溢出规则。同时,不同类型的变量在溢出后会从不同的数值开始,比如usigned int类型从0开始,int类型(4字节)从-2147483648开始。
打印usigned int类型值使用%u
转换说明;打印long类型值使用%ld
转换说明(若系统种int和long大小相同均使用%d
,但是移植到大小不同的系统中无法使用),%lo
打印八进制long值,%lx
打印十六进制long值;打印short类型值使用%hd
转换说明,h和l前缀都可以配合u使用,用于表示无符号整型。接下来演示一个因错误使用转换说明导致截断现象的示例程序:
#include
int main(void)
{
short end=200;
long int big=6557L;
printf("end = %hd and %d\n",end,end);
printf("big = %ld and not %hd\n",big,big);
return 0;
}
运行后输出结果:
end = 200 and 200
big = 65537 and not 1
输出的第一行无论用%hd
还是%d
打印一个short int值都是相同的。这是因为在调用printf()
传递参数时,C编译器会把short类型转换为int类型,int类型被认为是计算机处理整数类型最高效的类型。因此在short和int大小不同的系统中,用int类型参数传递速度更快。
输出的第二行用%hd
输出将一个long值截断成一个short值。long值65537的32位二进制式为00000000000000010000000000000001。然而使用%hd
让函数只看到了后16位,即十进制值1。
在使用printf()
时,不仅要注意转换说明与待打印值的个数是否匹配,还要检查转换说明是否与待打印值的类型匹配。
注意,整数类型可以使用小写或大写常量后缀,但是转换说明中只能使用小写修饰符。
char类型用于储存字符。但它实际是整数类型,实际储存的整数。因为计算机使用数字编码处理字符,即用特定的整数表示字符。最常用的就是ASCII编码,标准ASCII码的范围是0~127,只需要7位二进制数即可表示。通常char类型占用8位(1B)存储空间。
一般而言,C语言会保证char类型足够大,以储存实现C语言的系统的基本字符集。
代码示例:
char response;
char itable,latan;
在C语言中,用''
括起来的单个字符被称为字符常量(character constant)。''
必不可少,但''
不是字符常量的一部分。代码示例:
char broiled; /*声明一个char变量*/
broiled='T'; /*赋值正确,'T'是字符常量*/
broiled=T; /*赋值错误,T是变量*/
broiled="T"; /*赋值错误,"T是字符串(第4章详解)*/
由于char实际储存的是整数数值,所以可以使用数字代码来赋值。
char grade=65;
65是int值,在ASCII中65代表字符’A’,因此是把’A’赋值给grade,注意,系统必须使用ASCII码才可以如此赋值,因此最好使用字符常量代替数值赋值。但是C语言将字符常量视为int类型而非char类型,在int为32位,char为8位的系统中,可能会有如下现象:
#include
int main(void)
{
char grade='B';
printf("grade is %c\n",grade);
grade='FATE';
printf("grade is %c\n",grade);
return 0;
}
运行后输出结果:
grade is B
grade is E
输出结果第一行没有问题,但是第二行有问题。本来’B’对应的数值66储存在32位存储单元中,但是却可以储存在8位存储单元(grade)中,所以利用这一特性对其赋一个4个字符组成的字符常量,即把4个独立的8位ASCII码储存在一个32位存储单元中。但是这一的字符常量在赋值时只有最后8位有效,因此grade值是’E’。
单引号只适用于字符、数字和标点符号。同时,有些ASCII字符是打印不出来的,C语言提供了3中方法表示这些字符:
在将转义序列赋给字符变量时,必须用''
将转义序列括起来。下表为转义序列。
转义序列 | 含义 |
---|---|
\a | 警报(ANSI C) |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\|反斜杠(\) | |
\’ | 单引号 |
\" | 双引号 |
\? | 问号 |
\0oo | 八进制值(oo必须是有效的八进制数,即每个o可表示0~7中的一个数) |
\xhh | 十六进制值(hh必须是有效的十六进制数,即每个h可表示0~f中的一个数) |
打印转义序列有时会改变活跃位置(active position,光标位置),有时则不会改变。警报字符(\a)就不会改变活跃位置。换页符(\f)把活跃位置移至下一页开始处;换行符(\n)把活跃位置移至下一行开始处;回车符(\r)把活跃位置移至当前行开始处;水平制表符(\t)把活跃位置移至写一个水平制表点(第1个、第9个、第17个等字符位置);垂直制表符(\v)把活跃位置移至下一个垂直制表点。
表中三个特殊转义序列\\、\’、\"用于打印\、’、"字符,因为这些字符用于定义字符常量,是printf()
的一部分,若直接使用会造成混乱,所以要用转义序列打印出来。若要输出:
Gramps sez, “a \ is a backslash.”
应该这样编写代码:
printf("Gramps sez, \"a \\ is a backslash.\"");
\0oo和\xhh是ASCII码的特殊表示。例如,可用’\007’代替’\a’赋给字符变量。
关于转义序列,还需注意以下三个问题:
打印char类型值要用%c
转换说明。由于char实际储存的是整数值,所以用%d
打印char类型值,实际打印的是字符对应的整数值,用%c
告诉printf()
打印该整数值对应的字符。
有些编译器把char实现为有符号类型,而有些则实现为无符号类型,C99中允许用signed或usigned前缀修饰char类型,这在用char类型处理小整数时很有用。如果只处理字符,则无需使用任何修饰符。
C99新增_Bool类型表示布尔值,即逻辑值true和false。因为C语言中1用表示true,0表示false,所以该类型实际上也是一种整数类型。原则上该类型变量占用1为存储空间。第6、7章详解。
C99新增两个头文件stdint.h和inttypes.h以确保C语言的类型名在各系统中的功能相同。C语言为现有类型创建了更多类型名,定义在了stdint.h头文件中。
精确宽度整数类型(exact-width integer type)。例如,int32_t表示宽度正好是32位的有符号整数类型。在int为32位的系统中,int32_t作为int的别名;在int 为16位,long为32位的系统中,int32_t作为long的别名。
最小宽度类型(minimum width type)。例如,int_least8_t是可容纳8位有符号整数值类型中宽度最小的一类型的别名。
最小最快宽度类型(fastest minmum width type),该类型可以使计算速度达到最快。例如,int_fast8_t被定义为系统中对8位有符号值而言运算最快的整数类型的别名。
可用inttypes.h头文件中的字符串宏(第4 章详解)代表打印某一类型值相应的转换说明来输出数据。
C语言中的浮点类型有float、double和long double类型。浮点数在计算机中的写法类似于科学计数法,即指数计数法或e计数法。下表为示例。
一般计数法 | 科学计数法 | 指数计数法 |
---|---|---|
1000000000 | 1.0X109 | 1.0e9 |
322.56 | 3.2256X102 | 3.2556e2 |
0.000056 | 5.6X10-5 | 5.6e-5 |
指数计数法中,e后面的数字代表10的指数,而且e也可以用大写E代替。
C标准规定,float(单精度)类型必须至少能表示6位有效数字,且取值范围至少是10-37~10+37。有效数字是指能够表示前6位数字,并非精确到小数点后6位。系统存储一个float值要占用32位。其中8位用于表示指数的值和符号,24位用于表示非指数部分(尾数或有效数)及其符号。
C标准规定,double(双精度)类型最小取值范围与float相同,至少保留10位有效数字,占用64位。实际使用中,double至少有13位有效数字。
long long double类型比double精度更高,但C只保证此类型至少与double的精度相同。
float noah,jonah;
double trouble;
float planck=6.63e-34;
long long double lld;
建议在代码中使用指数计数法书写浮点型常量。形式:有符号数字、小数点、e或E、10的有符号指数。示例:
-1.56E+12
2.87e-3
可以省略正号,负号不可以省略;可以省略小数点(小数点和小数部分都没有)或指数部分,但不能同时省略两者;可以省略整数部分或小数部分(保留小数点),但不能同时省略两者。示例:
3.14159 省略指数部分
.2 省略整数和指数部分
4e16省略小数点和小数部分
.8E-5省略整数部分
100.省略小数和指数部分
不要在浮点型常量中间加空格:1.56 E+12是错误的!
默认情况下,编译器假定浮点型常量是double类型的精度。示例:
float result;
result=2.0*4.0;
2.0和4.0被储存为64位double类型。使用双精度进行运算,然后再截断为float类型宽度。这样计算精度高,但是运行速度慢。在浮点值后加f或F后缀可以覆盖默认设置,编译器将其看作float类型,加l或L后缀就被识别为long double类型。无后缀就是double类型。
使用%f
打印float和double值(float类型没有专门的转换说明),使用%e
打印指数计数法值,使用%Lf
或%Le
打印long double值。给未在函数原型中显式说明参数类型的函数(如printf()
)传递参数时,编译器会把float类型值自动转换成double类型。示例:
#include
int main(void)
{
float aboat=3200.0F;
double abet=2.14e9;
long double dip=5.32e-5L;
printf("%f can be written %e\n",aboat,aboat);
printf("%f can be written %e\n",abet,abet);
printf("%Lf can be written %Le\n",dip,dip);
return 0;
}
运行后输出结果:
32000.000000 can be written 3.200000e+04
2140000000.000000 can be written 2.140000e+09
0.000053 can be written 5.320000e-05
当计算导致数字过大,超过当前类型能表达的范围时就会发生上溢(overflow),此时编译器会给指定的变量赋一个表示无穷大的特定值,显示为inf或infinity。
当计算导致损失了原末尾有效位上的数字就会发生下溢(underflow),C语言把损失了类型全精度浮点值称为低于正常的(subnormal)浮点值。
还有一个特殊的NaN(Not a Number)值,显示为nan或NaN。
C语言有3种复数类型:float_Complex、double_Complex和long double_Complex。例如,float_Complex类型变量应包含两个float值分别对应复数的实部和虚部。C语言的3种虚数类型为:float_Imaginary、double_Imaginary和long double_Imaginary。
类型说明符由一个或多个关键字组成。
,
分隔。sizeof
是C语言内置的以字节为单位计算指定类型大小的运算符。用%zd
匹配其返回类型size_t
,不支持可以用%u
或%lu
替代。示例;
#include
int main(void)
{
printf("Type int has a size of %zd bytes.\n",size(int));
printf("Type char has a size of %zd bytes.\n",size(char));
printf("Type long has a size of %zd bytes.\n",size(long));
printf("Type long long has a size of %zd bytes.\n",size(long long));
printf("Type double has a size of %zd bytes.\n",size(double));
printf("Type long double has a size of %zd bytes.\n",
size(long double));//不在双引号或单词内部断行是允许的
}
运行后输出结果:
Type int has a size of 4 bytes.
Type char has a size of 1 bytes.
Type long has a size of 8 bytes.
Type long long has a size of 8 bytes.
Type double has a size of 8 bytes.
Type long double has a size of 16 bytes.
注意,sizeof
运算符有两种形式:sizeof(数据类型)
或sizeof 常/变量
,建议任何时候都加()
。
stddef.h头文件(已包含在stdio.h中)定义了一系列底层类型(underlying type)。例如,将size_t定义为sizeof的返回类型,并使用z修饰符来打印;将ptrdiff_t定义为指针差值的有符号整型。
在以上示例程序中多次调用了printf()
函数,之前特别强调了要保证转换说明和参数的数量、类型要匹配。尽管目前C语言通过函数原型机制检查函数调用时参数数量与类型是否正确,但是对printf()
和scanf()
不起作用,因为两者参数个数可变。
第3章练习题