数据与C
位(bit)是计算机最小的存储单位。它可以容纳两个值(0或1)之一,是计算机存储的基本单位。
字节(byte)是常见的计算机存储单位,一般一个字节由八个位组成,可包含256种0、1的组合。
字(word)取决于计算机本身的位数,现代操作系统中一般为32位机和64位机。
变量与常量数据
变量的值可以在程序执行过程中改变与指定,而常量不可以。
数据类型与关键字
K&R关键字 | C90关键字 | C99关键字 |
---|---|---|
int | signed | _Bool |
long | void | _Complex |
short | _Imaginary | |
unsigned | ||
char | ||
float | ||
double |
int
关键字是基本的整数类型,long
short
unsigned
signed
用于提供基础类型的变种。
char
关键字用于表示字母及符号等字符,也可以表示小的整数。(char本质是按int存储的)
float
double
和 long double
表示带小数点的数。(float本质是按double存储的)
_Bool
类型表示布尔值true
和false
, _Complex
和 _Imaginary
分别表示复数和虚数。
这些类型按照存储方式分为两个系列,即整数(integer)类型和浮点数(floating-point)类型。
整数类型和浮点类型
对于人,整数和浮点数的区别在于它们的书写。
对于计算机,区别在于它们的存储方式。
两种数之间的区别
- 整数没有小数部分,浮点数可以有小数部分。
- 浮点数可以表示比整数范围大得多的数。
- 对于算术运算(比如两个很大的数相减),使用浮点数会损失更多精度。
- 因为在任何区间内,都存在无穷多个实数所以无法表示所以值,往往只能是实际值的近似。
- 浮点运算通常比整数运算慢。
整数
整数(integer)就是没有小数部分的数。
浮点数
浮点数(floating-point)差不多是可以和数学中的实数(real number)概念相对应。实数包含了整数之间的那些数。
C数据类型
int类型
int
类型是有符号整数,它的值可以是正、负的整数或是0,其取值依赖于计算机系统。
ISO/ANSI C规定 int
类型的最小范围是-32768到32767,系统可以使用一个指示正负号的特定位来表示有符号整数。
声明与初始化
int erns;
int hogs.cows,goats = 14; // 这种情况容易造成误解可读性差
cows = 112;
变量的声明可以多个,也可以分别声明每个变量(可读性好)。
变量的初始化(initialize)就是为变量赋一个初始值,可以在声明时进行初始化,也可以在随后赋值如第三行。
打印整数:
int one = 1;
int two = 2;
printf("The number is %d and %d\n",one,two);
注意:
int
类型打印时使用%d
格式说明符(format specifier),注意格式说明符的书目和要显示的值数目一定要相同,要显示的值都必须对应自己的格式说明符。
八进制与十六进制
计算机中使用二进制存储,所以使用八进制和十六进制可以更方便表示与计算机相关的值。
在C语言中,使用前缀0X
或者0x
表示使用十六进制值,前缀0(零)表示使用八进制值。
打印八进制和十六进制数
/* 以十进制、八进制和十六进制输出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
可以看出,使用%o
和%x
可以打印出十六进制数,加上#
可以打印出前缀。
-
signed
关键字,可以和任何有符号类型一起使用,基本默认使用,使用它可以使数据类型更加明确。 -
unsigned int
类型,用于非负值的场合,在十六位机子中取值范围为0到65535。 -
short int
类型,可能占用比int
类型更少的存储空间。 -
long int
类型,可能占用比int
类型更多的存储空间。 -
long long int
类型,在C99中引入,占用比long
类型更多的存储空间 - 在C90中引入了
unsigned long int
和unsigned short int
类型。C99又增加unsigned long long int
类型。
C仅保证
short
类型不会比int
类型长,long
类型不会比int
类型短。
目前的一般情况是,long long
类型为64位,long
类型为32位,short
类型为16位,int
类型为16位或32位(依机器自然字大小而定)。
类型 | 最小取值范围 |
---|---|
short和int | -32767~32767 |
unsigned short和unsigned int | 0~65535 |
long | -2147483647~ 2147483647 |
unsigned long | 0~4294967295 |
long long | -9223372036854775807 ~9223372036854775807 |
unsigned long long | 0~18446744073709551615 |
如何选取数据类型
- 优先考虑
unsigned
类型,用于计数比较自然,不需要负数所以能容纳更大的值。 - 当
int
类型无法表示一个数而long
类型可以时,使用long
类型。然而long
会减慢计算,当确实需要32位整数时再使用。 - 同上,如果需要64位整数时,使用
long long
类型,当int
为32位系统时,如果需要16位整数,使用short
类型可以节省空间。
整数溢出
// 超出系统上的最大int值
#include
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);
return 0;
}
// 此程序的输出为:
2147483647 -2147483648 -2147483647
4294967295 0 1
由上可知,整数溢出时会回到初始值,就好像时钟一样走完了一圈回到一开始的地方(就像循环单链表)。
具体原理可以参考下面整数类型的存储方式一图
无符号数当达到最大值时再加1的话会往前进位直到最高位时溢出(无法存储到最高位前面的数据),导致可存储的位数都为0,结果也为0。
而有符号整数达到最大值时再加1的话会变成它的最大负数(也就是正数的负值加一,因为正数有一个给了零)。
编译器如何确定常量类型大小
一般像2345这样的数字时,它会以int
存储,当使用1000000 这样的数字时,编译器会视为long int
类型,当大于long
类型时 会视为 unsigned long
,如果仍然不够C会视为long long
或者unsigned long long
八进制和十六进制常量通常视为int
类型。如果值过大会按上面的顺序依次试用。
一般操作系统会选择较小的类型存储,如果希望把小的常量作为long
类型对待,可以使用l
或L
,这个同样适用于八进制与十六进制。
与之类似,在支持long long
的系统中,可以使用ll
或者LL
后缀标识long long
类型值,如3LL
。
u
或U
后缀用于标识 unsigned long long
类型值,比如5ull
,10LLU
等等。
打印short、long、long long和unsigned类型数
类型 | 说明符 |
---|---|
unsigned int | %u |
long | %ld(十进制) %lx(十六进制) %lo(八进制) |
short | %hd(十进制) %hx(十六进制) %ho(八进制) |
long long | %lld(有符号) %llu(无符号) |
注意:h 和 l 都可以与 u 结合使用标识无符号类型
常量后缀支持大小写而类型说明符只能使用小写。
printf 函数根据类型说明符读取该类型大小的内存区,并根据类型说明符解析该内存区内的数据并打印出来。
int 类型是计算机处理起来最方便有效的整数类型,所以在 short 和 int 长度不相同的系统中,使用 int 类型值进行参数传递会更快。(书中41页 print2.c程序)
如果类型说明符与类型不匹配,大的类型可以显示小的类型,如果小的类型显示大的就会对数据截断显示出截断部分的数据。
使用字符:char类型
char
类型的技术实现是整数类型,存储的是整数,计算机使用一种数字编码,用特定的整数表示特定字符。(比如ASCII码)
声明及初始化
char response;
char itable,latan;
char grade = 'A'; // 与char grade = 65 是相同的, 不过这样可读性差。
itable = 'T' // 只要是在char类型范围内的整数就允许赋值。
A
作为数值65存储在32位内存单元内,而赋值给grade
则把66存储在一个8位单元中。
使用单引号表示一个字符,如果是双引号的字符串需要存放在char
数组内。
非打印字符
非打印字符例如一些动作描述:退格、换行或者让终端响铃。
C提供了三种方法来表示:
- 使用 ASCII 码,例如蜂鸣器的 ASCII 值为7,可以这样写:
char beep = 7;
- 使用特殊的符号序列,即转义序列(Escape Sequence),使用转义序列易于记忆且可移植性好。
序列 | 意义 |
---|---|
\a | 警报(ASCII C) |
\b | 退格 (使活动位置在当前行上退回一个空格) |
\f | 走纸 (将活动位置移到下一页开始处) |
\n | 换行 (将活动位置移到下一行开始处) |
\r | 回车 (将活动位置移到当前行的开始处) |
\t | 水平制表符 |
\v | 垂直制表符 |
\ | 反斜杠(\) |
' | 单引号 (') |
" | 双引号 (") |
? | 问号 (?) |
\0oo | 八进制值(o表示一个八进制数字) |
\xhh | 十六进制值(h表示一个十六进制数字) |
给一个字符变量赋值时,转义序列必须使用单引号:char nerf = '\n';
,在双引号中的字符集合则无需单引号。
- C90开始提供了使用十六进制形式表示字符常量,例如Ctrl+P字符的十六进制ASCII码为10,表示为
\x10
或\X010
。
当使用数值编码时使用\032
而不是032
,更能表现使用字符编码的意图且方便嵌入到字符串中。
打印字符
#include
int main(void)
{
char ch;
printf(" Please enter a character.\n");
scanf("%c",&ch);
printf(" The code for %c is %d.\n", ch, ch);
return 0;
}
/* 程序运行如下:
Please enter a character.
C
The code for C is 67.
*/
printf()
说明符决定数据的显示方式而不是决定数据的存储方式。
有符号还是无符号
根据C90标准,C允许在关键字char
前使用signed
和unsigned
。无论默认的char
类型是什么,signed char
就是有符号类型,而unsigned char
则是无符号类型。这对于使用字符类型处理小整数十分有用。如处理字符,则只须使用不带修饰词的标准char
类型。
_Bool类型
是由C99引入的,用于标识布尔值,即逻辑值true(真)与false(假)。因为C用值 1 表示 true 用值 0 表示 false,所以_Bool
类型实际上是用一位来存储的整数类型。
可移植的类型: inttypes.h
C99提供了几组可选的名字合集,使用时要包含inttypes.h
头文件。
“确切长度类型” 例如int16_t
表示16位有符号整数 、uint32_t
表示32位无符号整数。
“最小长度类型” 例如int_least8_t
是可以容纳8位有符号数的那些类型中长度最小的一个的别名。
“最快最小长度类型”(fastest minimum width type)如int_fast8_t
定义为系统中对8位有符号数而言计算最快的整数类型的别名。
“最大的整数类型”例如intmax_t
定义为最大的无符号整数类型。
详细其他的内容参考书中P614 参考资料6 “扩展的整数类型”。
如何打印
inttypes.h
提供了打印需要的宏定义。
#include
#include
int main(void)
{
int16_t me16;
me16 = 4593;
printf(" Use a \" macro \" from inttypes.h: ");
printf(" me16 = %" PRId16 "\n" ,me16);
return 0;
}
必须在C99之后才提供该支持。
float
、double
和 long double
类型
类型 | 最小位数 |
---|---|
float | 6 |
double | 10 |
long double | 只保证至少同double一样 |
声明浮点变量
float noah,jonah;
double trouble;
float planck = 6.63e-34;
long double gnp;
浮点常量
一般的常量如-1.56E+12
或2.87e-3
,可以省略正号,可以没有小数点(2E5)或指数部分(19.28),当不能都没有。
默认情况下,编译器将浮点常量当作double
类型,如 some = 4.0 * 2.0
其中 4.0 和 2.0 被存储为double类型,通常使用64位存储。乘积使用双精度结果被截成正常的 float
长度。这能保证精度但会减慢程序执行。
C可以使用 f 或 F 后缀使编译器把浮点常量当 float
类型,比如 2.3f
和 9.11E9F
。
l 和 L 后缀使一个数字成为 long double
类型,比如 54.31 和 4.32e4L。
C99增加了一种十六进制格式。这种格式使用0x
或0X
前缀,接着是十六进制数字,然后是字母 p 或 P,最后是二的指数。如 0xa.1fp10
a 是10, .1f 表示1/16 加上 15/256,p10 表示二的十次方。十进制为10364.0
。
打印浮点数
说明符 | 输出类型 |
---|---|
%f | 小数形式 |
%e或%E | 指数计数法 |
%a或%A | 十六进制格式浮点数 |
long double
类型需要加上 L 如 %Lf
,%Le
和%La
float
和double
使用同样的类型符是因为 printf()
在参数传递时,C自动将 float
类型的参数转化成 double
类型。
浮动值的上溢和下溢
假设计算的数大到计算机不能表达的数时,会发生上溢(overflow)。printf()
函数显示此值为inf
或infinity
(或其他个含义的名称)。
假设计算的数已经是能表示的最小数字,将此数除以 2。通常计算机只好将尾数部分的位进行右移,空出首位二进制数,并丢弃最后一位二进制。以十进制为例,把一个包含四位有效数字的数字的数 0.1234E-10
除以 10
,将得到结果 0.0123E-10
,损失了一位有效数字。此过程称为下溢(underflow)。
还有一个特殊的浮动值 NaN (Not-a-Number)例如 asin();
输入参数不能大于 1 否则函数返回 NaN 值。
复数和虚数类型
C99支持 complex.h
头文件。
_Complex
的三种:float _Complex
、double _Complex
、long double _Complex
。
_Imaginary
的三种:float _Imaginary
、double _Imaginary
、long double _Imaginary
。
各种类型所占大小
C99提供了 %zd
说明符来打印数据大小。
// todo:程序代码。
注意的内容
C对待类型不匹配现象更宽容。
int apples = 3; // 正确
int oranges = 3.0; // 不好的形式
C会自动对该值进行类型转换以便和变量类型相匹配,但是可能会丢失一部分数据。
int cost = 12.99; // 把一个 int 变量初始化为一个 double 值
float pi = 3.1415926536;// 把一个 float 变量初始化为一个 double 值
第一个会丢弃小数部分不进行四舍五入,第二个会损失部分精度,因为 float
类型只能保证前 6 位是精确的。
有的变量命名规则,其中变量名可以表达它的类型。例如: 使用 i_
前缀表示 int
变量,使用 us_
表达 unsigned short
变量。
#include
int main(void)
{
int f = 4;
int g = 5;
float h = 5.0f;
printf("%d\n", f, g); // 参数太多
printf("%d %d\n", f); // 参数太少
printf("%d %f\n", h, g); // 值的类型不对应
return 0;
}
当参数过多或过少时,它的值会随平台不同而不同。
使用 %d
显示 float
值不会把该 float
值转换为近似的 int
值,而是显示垃圾值。与之类似,使用 %f
显示 int
值也不会把 int
值转换为浮点值。
转义序列的应用
#include
int main(void)
{
float salary;
printf("\aEnter your desired monthly salary: ");
printf(" $_______\b\b\b\b\b\b\b");
scanf("%f",&salary);
printf("\n\t$%.2f a mouth is $%.2f a year.",salary, salary * 12.0);
printf("\rGee!\n");
return 0;
}
/*
输入:2000.00
Enter your desired monthly salary: $2000.00
Gee! $2000.00 a mouth is $24000.00 a year.
*/
演示了退格(\b)、制表符(\t)和回车符(\r)的工作方式。
刷新输出
pintf()
语句将输出传递给一个被称为缓冲区(buffer)的中介存储区域。缓冲区中的内容再不断地传递给屏幕。
标准 C 规定在以下几种情况下将缓冲区传给屏幕
- 缓冲区满的时候
- 遇到换行符的时候
- 需要输入的时候(scanf)
将缓冲区传输给屏幕或文件称为刷新缓冲区(flushing the buffer)。
早期的C语言版本遇到
scanf()
不会强迫缓冲区刷新,为防止此情况可以使用换行符刷新缓冲区。