第三周 数据和C
3.1 示例程序
程序清单3.1 platinum.c
程序
/* platinum.c -- your weight in platinum */
#include <stdio.h>
int main(void)
{
float weight; /* 你的体重 */
float value; /* 相等重量的白金价值 */
printf("Are you worth your weight in platinum?\n");
printf("Let's check it out.\n");
printf("Please enter your weight in pounds: ");//输入你的体重,然后按下Enter或Return键
/* 获取用户的输入 */
scanf("%f", &weight); //scanf()函数读取用户从键盘输入的数据,并把数据传递给程序
/* 假设白金的价格是每盎司$1700 */
/* 14.5833用于把英磅常衡盎司转换为金衡盎司[1]*/
value = 1700.0 * weight * 14.5833;
printf("Your weight in platinum is worth $%.2f.\n", value);
printf("You are easily worth that! If platinum prices drop,\n");
printf("eat more to maintain your value.\n");
return 0;
}
3.1.1 程序中的新元素
float
类型可以存储带小数的数字。printf()
中使用%f
来处理浮点值。%.2f
中的.2
用于精确控制输出,指定输出的浮点数只显示小数点后面两位。scanf()
函数用于读取键盘的输入。%f
说明scanf()
要读取用户从键盘输入的浮点数,&weight
告诉scanf()
把输入的值赋给名为weight
的变量。scanf()
函数使用&
符号表明找到weight
变量的地点。- 本程序最突出的新特点是它的交互性。计算机向用户询问信息,然后用户输入数字。
3.2 变量与常量数据
数据,即承载信息的数字和字符。
常量(constant
):在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化的数据类型。
变量(variable
):在程序运行期间可能会改变或被赋值的数据类型。
3.3 数据:数据类型关键字
C通过识别一些基本的数据类型来区分和使用不同的数据类型。
最初K&R给出的关键字 | C90标准添加的关键字 | C99标准添加的关键字 |
---|---|---|
int | signed | _Bool |
long | void | _Complex |
short | _Imaginary | |
unsigned | ||
char | ||
float | ||
double |
int
:表示基本的整数类型。
long
、short
、unsigned
、signed
:用于提供基本整数类型的变式,例如unsigned short int
和long long int
。
char
关键字用于指定字母和其他字符(如,#
、$
、%
和*
)。另外,char
类型也可以表示较小的整数。
float
、double
和long double
表示带小数点的数。
_Bool
类型表示布尔值(true
或false
)。
_Complex
和_Imaginary
分别表示复数和虚数。
位、字节和字
位(bit)是最小的存储单元,可以存储0或1(或者说,位用于设置“开”或“关”)。位是计算机内存的基本构建块。
字节(byte)是常用的计算机存储单位。对于几乎所有的机器,1字节均为8位。
字(word)是设计计算机时给定的自然存储单位。对于8位的微型计算机(如,最初的苹果机),1个字长只有8位。从那以后,个人计算机字长增至16位、32位,直到目前的64位。计算机的字长越大,其数据转移越快,允许的内存访问也更多。
3.3.1 整数和浮点数
对我们而言,整数和浮点数的区别是它们的书写方式不同。
对计算机而言,它们的区别是存储方式不同。
3.3.2 整数
整数是没有小数部分的数。例如,2、−23和2456都是整数。而3.14、0.22和2.000都不是整数。
计算机以二进制数字存储整数,例如,整数7以二进制写是111。因此,要在8位字节中存储该数字,需要把前5位都设置成0,后3位设置成1
3.3.3 浮点数
浮点数与数学中实数的概念差不多。2.75、3.16E7、7.00和2e-8都是浮点数。
注意,在一个值后面加上一个小数点,该值就成为一个浮点值。所以,7是整数,7.00是浮点数。
浮点数和整数的存储方案不同:
计算机把浮点数分成小数部分和指数部分来表示,而且分开存储这两部分。因此,虽然7.00和7在数值上相同,但是它们的存储方式不同。
两种类型的实际区别。
- 整数没有小数部分,浮点数有小数部分。
- 浮点数可以表示的范围比整数大。
- 对于一些算术运算(如,两个很大的数相减),浮点数损失的精度更多。
- 因为在任何区间内(如,1.0到2.0之间)都存在无穷多个实数,所以计算机的浮点数不能表示区间内所有的值。浮点数通常只是实际值的近似值。例如,7.0可能被存储为浮点值6.99999。
- 过去,浮点运算比整数运算慢。不过,现在许多CPU都包含浮点处理器,缩小了速度上的差距。
3.4 C语言基本数据类型
3.4.1 int
类型
int
类型是有符号整型,即int
类型的值必须是整数,可以是正整数、负整数或零。
其取值范围依计算机系统而异。一般而言,存储一个int
要占用一个机器字长。因此,早期的16
位IBM PC
兼容机使用16
位来存储一个int
值,其取值范围(即int
值的取值范围)是-32768
~32767
。
ISO C
规定int
的取值范围最小为-32768
~32767
。
1.声明int
变量
先写上int
,然后写变量名,最后加上一个分号。
int erns; // 可以单独声明变量
int hogs, cows, goats; // 也可在int后面列出多个变量名,变量名之间用逗号分隔。
给变量赋值:
第1
种途径是赋值。例如,cows = 112;
第2
种途径是,通过函数(如,scanf()
)获得值;
第3
种途径初始化变量
2.初始化变量
初始化(initialize)变量就是为变量赋一个初始值。
在C语言中,初始化可以直接在声明中完成。只需在变量名后面加上赋值运算符(=)和待赋给变量的值即可。
int hogs = 21;
int cows = 32, goats = 14;
int dogs, cats = 94; /* 有效,但是这种格式很糟糕 */
简而言之,声明为变量创建和标记存储空间,并为其指定初始值
.
3.int
类型常量
上面示例中出现的整数(21
、32
、14
和94
)都是整型常量或整型字面量。
22
和-44
都是整型常量,但是22.0
和2.2E1
则不是。
C语言把大多数整型常量视为int
类型,但是非常大的整数除外。
4.打印int
值
可以使用printf()
函数打印int
类型的值。
/* print1.c - 演示printf()的一些特性 */
#include <stdio.h>
int main(void)
{
int ten = 10;
int two = 2;
printf("Doing it right: ");
printf("%d minus %d is %d\n", ten, 2, ten - two);
printf("Doing it wrong: ");
printf("%d minus %d is %d\n", ten); // 遗漏2个参数
return 0;
}
编译并运行该程序,输出如下:
Doing it right: 10 minus 2 is 8 ①
Doing it wrong: 10 minus 16 is 1650287143 ②
①在第一行输出中,第1
个%d
对应int
类型变量ten
;第2
个%d
对应int
类型常量2
;第3
个%d
对应int
类型表达式ten - two
的值。
②在第二行输出中,第1
个%d
对应ten
的值,但是由于没有给后两个%d
提供任何值,所以打印出的值是内存中的任意值
记住,使用printf()
函数时,要确保转换说明的数量与待打印值的数量相等。
5.八进制和十六进制
通常,C语言都假定整型常量是十进制数。
在C语言中,用特定的前缀表示使用哪种进制。
0x或0X前缀表示十六进制值,所以十进制数16表示成十六进制是0x10或0X10。
0前缀表示八进制。例如,十进制数16表示成八进制是020。
6.显示八进制和十六进制
在C程序中,既可以使用也可以显示不同进制的数。不同的进制要使用不同的转换说明。
-
以十进制显示数字,使用
%d
; -
以八进制显示数字,使用
%o
; -
以十六进制显示数字,使用
%x
。
要显示各进制数的前缀0
、0x
和0X
,必须分别使用%#o
、%#x
、%#X
。
程序清单3.3 bases.c
程序
/* bases.c--以十进制、八进制、十六进制打印十进制数100 */
#include <stdio.h>
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);//在八进制和十六进制值前显示0和0x前缀,要分别在转换说明中加入#。
return 0;
}
编译并运行该程序,输出如下:
dec = 100; octal = 144; hex = 64
dec = 100; octal = 0144; hex = 0x64
3.4.2 其他整数类型
C语言提供3
个附属关键字修饰基本整数类型:short
、long
和unsigned
。应记住以下几点。
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 short int
或unsigned short
类型。C99
标准又添加了unsigned long long int
或unsigned long long
。 - 在任何有符号类型前面添加关键字
signed
,可强调使用有符号类型的意图。例如,short
、short int
、signed short
、signed short int
都表示同一种类型。
1.声明其他整数类型
其他整数类型的声明方式与int
类型相同,下面列出了一些例子。不是所有的C编译器都能识别最后3
条声明
long int estine;
long johns;
short int erns;
short ribs;
unsigned int s_count;
unsigned players;
unsigned long headcount;//C90标准新增
unsigned short yesvotes;//C90标准新增
long long ago;//C99标准新增
2.使用多种整数类型的原因
C语言只规定了short
占用的存储空间不能多于int
,long
占用的存储空间不能少于int
。这样规定是为了适应不同的机器。
现在,个人计算机上最常见的设置是,
-
long long
占64
位, -
long
占32
位, -
short
占16
位, -
int
占16
位或32
位(依计算机的自然字长而定)。
原则上,这4
种类型代表4
种不同的大小,但是在实际使用中,有些类型之间通常有重叠。
C标准对基本数据类型只规定了允许的最小大小。
-
对于
16
位机,short
和int
的最小取值范围是[
−32768,32767]
; -
对于
32
位机,long
的最小取值范围是[
−2147483648,2147483647]
。 -
对于
unsigned short
和unsigned int
,最小取值范围是[0,65535]
; -
对于
unsigned long
,最小取值范围是[0,4294967295]
。 -
long long
类型是为了支持64
位的需求,最小取值范围是[
−9223372036854775808,9223372036854775807]
; -
unsigned long long
的最小取值范围是[0,18446744073709551615]
。
(这个数是一千八百亿亿六千七百四十四万亿零七百三十七亿零九百五十五万一千六百一十五)
int
类型那么多,应该如何选择?
首先,考虑unsigned
类型。这种类型的数常用于计数,因为计数不用负数。而且,unsigned
类型可以表示更大的正数。
- 如果一个数超出了
int
类型的取值范围,且在long
类型的取值范围内时,使用long
类型。
然而,对于那些long
占用的空间比int
大的系统,使用long
类型会减慢运算速度。如非必要,请不要使用long
类型。
-
如果在
long
类型和int
类型占用空间相同的机器上编写代码,当确实需要32
位的整数时,应使用long
类型而不是int
类型,以便把程序移植到16
位机后仍然可以正常工作。类似地,如果确实需要64
位的整数,应使用long long
类型。 -
如果在
int
设置为32
位的系统中要使用16
位的值,应使用short
类型以节省存储空间。
通常,只有当程序使用相对于系统可用内存较大的整型数组时,才需要重点考虑节省空间的问题。
使用short
类型的另一个原因是,计算机中某些组件使用的硬件寄存器是16
位。
3.long
常量和long long
常量
程序代码中使用的数字(如,2345
)都被存储为int
类型。
如果使用1000000
这样的大数字,超出了int
类型能表示的范围,编译器会将其视为long int
类型(假设这种类型可以表示该数字)。
如果数字超出long
可表示的最大值,编译器则将其视为unsigned long
类型。
如果还不够大,编译器则将其视为long long
或unsigned long long
类型(前提是编译器能识别这些类型)。
八进制和十六进制常量被视为int
类型。
如果值太大,编译器会尝试使用unsigned int
。
如果还不够大,编译器会依次使用long
、unsigned long
、long long
和unsigned long long
类型。
把一个较小的常量作为long
类型对待,可以在值的末尾加上l
(小写的L
)或L
后缀。
因此,在int
为16
位、long
为32
位的系统中,会把7
作为16
位存储,把7L
作为32
位存储。
l
或L
后缀也可用于八进制和十六进制整数,如020L
和0x10L
。
类似地,在支持long long
类型的系统中,也可以使用ll
或LL
后缀来表示long long
类型的值,如3LL
。
另外,u
或U
后缀表示unsigned long long
,如5ull
、10LLU
、6LLU
或9Ull
。
整数溢出
如果整数超出了相应类型的取值范围会怎样?
/* toobig.c-- 超出系统允许的最大int值*/
#include <stdio.h>
int main(void)
{
int i = 2147483647;
unsigned int j = 4294967295;
printf("%d %d %d\n", i, i+1, i+2);
printf("%u %u %u\n", j, j+1, j+2);//使用%u说明显示unsigned int类型的值
return 0;
}
在本地系统下输出的结果是:
2147483647 -2147483648 -2147483647
4294967295 0 1
可以把无符号整数j
看作是汽车的里程表。当达到它能表示的最大值时,会重新从起始点开始。整数i
也是类似的情况。
它们主要的区别是,在超过最大值时,unsigned int
类型的变量j
从0
开始;而int
类型的变量i
则从−2147483648
开始。
注意,当i
超出(溢出)其相应类型所能表示的最大值时,系统并未通知用户。因此,在编程时必须自己注意这类问题。
4.打印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
类型的值。
程序清单3.4 print2.c
程序
/* print2.c--更多printf()的特性 */
#include <stdio.h>
int main(void)
{
unsigned int un = 3000000000; /* int为32位和short为16位的系统 */
short end = 200;
long big = 65537;
long long verybig = 12345678908642;
printf("un = %u and not %d\n", un, un);//①
printf("end = %hd and %d\n", end, end);//②
printf("big = %ld and not %hd\n", big, big);//③
printf("verybig= %lld and not %ld\n", verybig, verybig);//④对于支持long long类型的系统,%lld和%llu分别表示有符号和无符号类型。
return 0;
}
在特定的系统中输出如下(输出的结果可能不同):
un = 3000000000 and not -1294967296
end = 200 and 200
big = 65537 and not 1
verybig= 12345678908642 and not 1942899938
使用错误的转换说明会得到意想不到的结果:
①第1
行输出,对于无符号变量un
,使用%d
会生成负值!
原因是,无符号值3000000000
和有符号值−1294967296
在系统内存中的内部表示完全相同
因此,如果告诉printf()
该数是无符号数,它打印一个值;如果告诉它该数是有符号数,它将打印另一个值。在待打印的值大于有符号值的最大值时,会发生这种情况。对于较小的正数(如96
),有符号和无符号类型的存储、显示都相同。
②第2
行输出,对于short
类型的变量end
,在printf()
中无论指定以short
类型(%hd
)还是int
类型(%d
)打印,打印出来的值都相同。
因为在给函数传递参数时,C编译器把short
类型的值自动转换成int
类型的值。
为什么要进行转换?
int
类型被认为是计算机处理整数类型时最高效的类型。因此,在short
和int
类型的大小不同的计算机中,用int
类型的参数传递速度更快。
h
修饰符有什么用?
使用h
修饰符可以显示较大整数被截断成short
类型值的情况。第 3
行输出就演示了这种情况。
③把 65537
以二进制格式写成一个 32
位数是00000000000000010000000000000001
。使用%hd
,printf()
只会查看后16
位,所以显示的值是1
。
④与此类似,输出的最后一行先显示了verybig
的完整值,然后由于使用了%ld
,printf()
只显示了存储在后32
位的值。