C_Primer_Plus03.Datatype
数据类型
- 要点
关键字:
int, short, long, unsigned, char, float, double, _Bool, _Complex, _Imaginary
运算符:sizeof()
整数型和浮点型的区别
如何书写整型和浮点型常数,如何声明这些类型的变量
如何使用printf()
和scanf()
函数读写不同类型的值
通过终端输入
scanf("%f", &weight)
浮点数是相对于定点数而言的,小数点是不固定的、浮动的,故称为浮点数
/* platinum.c -- your weight in platinum */
#include <stdio.h>
int main(void)
{
float weight; /* user weight*/
float value; /* platinum equivalent*/
printf("Are you worth your weight in platinum?\n");
printf("Let's check it out.\n");
printf("Please enter your weight in pounds: ");
/* get input from the user*/
scanf("%f", &weight);
/* assume platinum is $1700 per ounce*/
/* 14.5833 converts pounds avd. to ounces troy*/
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;
}
输出:
Are you worth your weight in platinum?
Let's check it out.
Please enter your weight in pounds: 156
Your weight in platinum is worth $3867491.25.
You are easily worth that! If platinum prices drop,
eat more to maintain your value.
%f
表示要读取用户从键盘输入一个浮点数,&weight
表示把输入的值赋给 weight 变量,使用 &
表示要找到 weight 变量的地址。
常量
有些数据在程序使用之前就已经预设好了,在整个程序的运行过程中没有变化,这些量叫做常量(constant)。
数据类型关键字
int: 基本的整数类型
long,short,unsigned:为基本整数类型提供变式。比如 unsigned short int, long long int
char:字母和其他字符(#,$,_ ,%,* 等),也可以表示较小的整数
float,double,long double:小数
_Bool:布尔型(true,false)
_Complex,_Imaginary:复数和虚数
通过这些关键字创建的类型,按计算机的存储方式可分为两大类:整数类型和浮点数类型。
对我们而言,整数和浮点数的区别是书写方式不同。对于计算机来说,他们的区别是存储方式不同。
存储单元
衡量计算机的数据单元或存储单元大小的术语(主要指存储单元):
位(bit):计算机最小的存储单元,可以存储0和1
字节(byte):几乎所有的机器,1字节等于8位
字(word):设计计算机时给定的自然存储单位。8位机的字长只有8位,后来有了16位、32位,目前是64位。字长越大,数据传输越快(计算机中总线带宽越大),理论可访问的内存越大(内存地址空间越大)
浮点数
计算机把浮点数分成小数部分和指数部分,分开存储。比如 7.0 会分成 0.7 和 1 两部分(0.7E1),1 是指数部分。计算机内部使用二进制和2的幂进行存储。
- 整数没有小数部分,浮点数有小数部分
- 浮点数的表示范围大
- 对于一些算术运算,浮点数损失的精度更多
- 因为在任何区间内(比如 1.0 到 2.0)都存在无数多个实数,所以计算机浮点数不能表示区间内所有的值。浮点数只是实际值的近似值
- 以前,浮点数运算比较慢,但现在许多CPU都包含浮点处理器,缩小了速度上的差距
C 语言基本数据类型
int
有符号整型:正整数、零、负整数。
一般情况下,一个 int 占用一个机器字长。早期的16位机使用 16 个位(两个字节)存储一个 int 值,现在个人计算机一般是 64 位机,但是用32个位(4个字节)存储一个 int。ISO C 规定 int 的取值范围最小是 -32768~36727(16位)。
int 类型变量示例:
#include <stdio.h>
int main(void){
int erns;
int hogs, cows, goats;
int horses = 10;
int dogs, cats = 94; /*语法没有问题,但这种写法容易让人迷惑。不推荐*/
printf("dogs:%d, cats:%d\n"); // dogs:0, cats:94
printf("sizeof int: %ld", sizeof(cats)); // sizeof int: 4
return 0;
}
- 可以一行声明一个变量,也可以同时声明多个变量
- 可以在声明的同时赋值
- 声明时未被赋值的值会被赋值 0
- printf 不寻常的设计,可以使其输入多个参数,但要确保转换符号的数量和要打印值的数量一致
%d
表示十进制 int,也可以用八进制(%o
或%#o
)或十六进制(%x
或%#x
)打印sizeof
函数的返回值是变量所占字节数,类型是 unsigned long int 类型,需要用%ld
或%lu
转换- C99 为类型大小(sizeof 函数的返回值)提供了
%zd
(无符号整型)的转换符
- C99 为类型大小(sizeof 函数的返回值)提供了
- long long 对应
%lld
,short 对应%hd
其他整数类型
C 语言提供3个附属关系字修饰基本整数类型:short, long 和 unsigned
- short (或 short int) 占用的存储空间可能比 int 少,常用于较小数值的场合以节省空间。short 是有符号类型
- long(或long int)占用存储空间可能比 int 大,适用于较大数值场合。long 是有符号类型
- long long int (或 long long)(C99 标准)占用的存储空间可能比 long 多,适用于更大数值的场合。该类型至少占 64 位。long long 是有符号类型
- unsigned int 或 unsigned 只用于非负值的场合。
- C90 标准新加入了 unsigned long int 或 unsigned long 和 unsigned int 或 unsigned short 类型。C99 标准又加入了 unsigned long long int 或 unsigned long long
- 在任何有符号类型前面添加关键字 signed,可强调使用有符号类型的意图。比如 short, short int, signed short, signed short int 都表示同一种类型
使用多种整数类型原因:考虑与以前的16位机和32位机的兼容性问题,以及满足不同需求
现在个人电脑大多是64位系统,最常见的设置是 long long 64位,long 32位,short 16位,int 16位或32位
- unsigned 类型常用于计数。因为计数不用负数,而且它能表示更大的正数
- 如无必要,尽量不用 long 类型。若在 long 和 int 类型占用相同大小的机器上编写代码,若确实需要32位整数,尽量使用 long 而不是 int,以便把程序移植到 16位系统后仍能正常工作
- 一般情况下,编译器会把整数看做int类型,若值太大,编译器会依次使用 long, unsigned long, long long, unsigned long long 来表示表示
- 有些情况,编译器会以 long 类型存储一个小数字。比如内存地址等。另外一些C 标准函数也要求使用 long 类型。要把一个较小的常数当做 long 类型对待,可以在值的末尾加上 l或L 后缀。(推荐L,因为 l 容易和数字 1 混淆)
- 类似地,可以用 ll 或 LL 表示 long long
- 后缀中出现 u 或 U 表示 unsigned。比如 5ull, 10LLU, 9Ull
整数溢出
/* toobig.c-exceeds maximum int size on our system */
#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);
return 0;
}
输出:
2147483647 -2147483648 -2147483647
4294967295 0 1
当 int 或 unsigned int 类型变量超出能表示的最大值时,会重新从起点开始(最小值)。
注意,当 i 溢出时,系统并未通知用户,需要自己注意这类问题。
无符号值 3000000000 和 有符号值 -129496296 在系统内存中的内部表示完全相同,而在打印时,由转换符
%d
或%u
来控制输出结果
对于short类型值的打印,C 编译器会将它转换成 int 类型的值,进行参数传递,然后由转换符来控制打印结果为什么?因为在 short 和 int 类型大小不同的计算机中,用 int 类型传递参数速度更快。
%hd
中的h
控制截断,将较大整数截断为 short 类型(16位)
char 类型
用于存储字符,但从技术角度看,char 是整数类型,因为它实际存储的是整数,而不是字符。
编码种类
- ASCII
- 0~127,只需7位二进制数表示,因此用一个字节(8位)容纳标准 ASCII 码绰绰有余
- 65 代表 A
- 其他系统还提供扩展 ASCII 码,也在8位表示范围之内
- ASCII 码并不能表示世界上所有的符号
- ISO/IEC 10646
- 国际标准化组织(ISO)和国际电工技术委员会(IEC)为字符集开发了 ISO/IEC 10646 标准
- Unicode (商用统一码)
- 目前包含的字符已超过 110000 个
- 与 ISO/IEC 10646 标准兼容
字符类型变量
char grade = 'A'; // 字符常量:用单引号括起来的单个字符
char grade1 = 65; // 对于 ASCII,这样做没问题,但不是一个好的编程风格
char grade2 = 'FATE'; // 只有最后8位有效,所以 grade2 的实际值是 E
非打印字符
单引号方法只适用于字符、数字和标点符号,有些ASCII字符打印不出来,比如 退格、换行、终端响铃或蜂鸣等。C 语言提供了三种方法表示这些字符:
1,使用 ASCII 码
char beep = 7; // 蜂鸣,编码为 7
2,使用转义序列 (escape sequence)
char nerf = '\n'; // 换行
这些转义序列不一定在所有显示设备上都起作用。比如换页符和垂直制表符在PC屏幕上会生成奇怪的符号,光标并不会移动。只有将其输出到打印机上时才会产生效果
char beep1 = '\a'; // 蜂鸣
char beep2 = '\007'; // 蜂鸣
char beep3 = '\7'; // 蜂鸣,省略0后编译器仍解释为8进制
printf("the code for %c is %d\n", beep1, beep1);
// the code for is 7
蜂鸣是常见的警报,在有些系统中,警报不起作用(有些字符是用于打印机等设备的)
3,用16进制形式表示字符常量,即反斜杠后跟一个x或X,再加上1~3位16进制数字
char ch1 = '7'; // 字符 7 的编码是 55
char ch2 = '\x41'; // 字符 A 的编码是 65
char ch3 = 0x41; // 字符 A 的编码是 65
#include <stdio.h>
int main(void){
char ch;
printf("Please 输入 a character.\n");
scanf("%c", &ch);
printf("The code for %c is %d.\n", ch, ch);
return 0;
}
output:
Please 输入 a character.
t
The code for t is 116.
&
符号表示把输入的字符赋值给变量 ch
。printf
函数中的两次打印,第一次是打印一个字符,第二次是打印一个十进制数值。转换符说明了数据的显示方式,而不是数据的存储方式。
实现方式
有些 C 编译器把 char 实现为有符号类型,即 char 的可表示范围是 -128~127。而有些编译器实现为无符号类型,即 0~255。
C90 标准允许在 char 前使用 signed 或 unsigned,这样可以使编译器强制使用有符号或无符号类型。这在用 char 类型处理小整数时很有用。如果只用 char 处理字符,则其前面无需使用任何修饰符。
_Bool
类型
C99 标准添加了布尔类型。1表示 true,0表示 false,所以 _Bool
原则上仅占一位存储空间。
可移植类型:stdint.h
和 inttypes.h
C 语言提供了许多有用的整数类型,而这些类型在不同系统中功能不同。C99 新增了两个头文件 stdint.h 和 inttypes.h,以确保C 语言的类型在各系统中功能相同。
对上述兼容性的实现有如下几种方法:
1,精确宽度整数类型 (exact-width integer type)
C 语言为现有类型创建了更多的类型名,他们都定义在 stdint.h 头文件中。比如 int32_t 表示32位有符号整数类型。在使用32位 int 的系统中,头文件会把 int32_t 作为 int 的别名,不同的系统也可以定义相同的类型名。例如,int 为16位、long 为32位的系统,会把 int32_t 作为 long 的别名。然后,使用 int32_t 类型编写程序,并包含 stdint.h 头文件,这样编译器会把 int 或 long 替换成与当前系统匹配的类型。
有些计算机底层可能不支持精确宽度整数类型,所以这只是一个可选项。
2,最小宽度类型 (minimum width type)
C99 和 C11 提供了第二类别名系统,保证某类型名所表示的类型一定是至少有指定宽度的最小整数类型。比如,int_least8_t 是可容纳8位有符号整数值的类型中宽度最小的类型的一个别名。如果某系统的最小整数类型是16位,可能不会定义 int8_t 类型。尽管如此,该系统仍可以使用 int_least8_t 类型,但可能把该类型实现为16位的整数类型。
3,最快最小宽度类型(fastest minimum width type)
有些程序员更关心速度而不是空间,为此 C99 和 C11 定义了一组可使计算达到最快的类型集合。例如,int_fast8_t 被定义为系统中对8位有符号值而言运算最快的整数类型的别名。
4,最大整数类型
C99 定义了最大的有符号整数类型 intmax_t,能存储任何有效的有符号整数值。unitmax_t 表示最大的无符号整数类型。这些类型可能比 long long 和 unsigned long 类型更大,因为 C 编译器除了实现标准规定的类型外,还可利用C语言实现其他类型。比如一些编译器在标准引入 long long 之前,就已提前实现了该类型。
C99 和 C11 不仅提供可移植的类型名,还提供相应的输入和输出。比如用 printf 打印特定类型时要求与相应的转换符匹配,如果要打印 int32_t,有的编译器使用 %d,有的使用 %ld,怎么办?C 标准针对这个问题提供了一些字符串宏(macro)来显示可移植类型。
#include <stdio.h>
#include <inttypes.h>
int main(void){
int32_t me32;
me32 = 45933945;
printf("First, assume int32_t is int: ");
printf("me32 = %d\n", me32);
printf("Next, let's not make any assumptions.\n");
printf("Instead, use a 'macro' from inttypes.h: ");
printf("me32 = %" PRId32 "\n", me32);
return 0;
}
输出:
First, assume int32_t is int: me32 = 45933945
PRId32: 4195986Next, let's not make any assumptions.
Instead, use a 'macro' from inttypes.h: me32 = 45933945
其中 "me32 = %" PRId32 "\n"
等价于 "me32 = %" "d" "\n"
,又等价于 "me32 = %d\n"
。多个连续的字符串可以用这种方式组合成一个字符串
PRId32
是字符串"d"
float, double 和 long double
C 标准规定,float 类型必须至少能表示6位有效数字,且取值范围至少是 \(10^{-37} \sim 10^{37}\)。通常一个浮点数要占用32位,其中前8位用于表示指数的值和符号,剩下的24位用于表示非指数部分(尾数、有效数)及其符号。
\(\pm3.4e38\)
double 类型和 float 类型的最小取值范围相同,但至少必须能保证10位有效数字,一般 double 占用64位。一些系统将多出的32位全部用来表示非指数部分(提高了精度),而另一些系统把其中一部分分配给指数部分(增加了可表示数的范围)。无论哪种方法,必须保证至少13位精度。
long double 类型是比 double 类型有更高的精度,但是 C 只保证 long double 类型至少与 double 类型的精度相同。
其中 double 以11位/53位为例
http://blog.sina.com.cn/s/blog_6ebd49350101gdgo.html
赋值
float pi = 3.1415;
double num = 4e16;
float a = .2;
float b = .8E-5;
float c = 100.;
float c = -19.E+12;
long double ld = 9.E30L;
计算
float some;
some = 4.0 * 2.0;
默认情况下,编译器假定浮点型常量是 double 类型的精度。上述运算中,4.0*2.0是按照 double 类型计算,然后将结果截断成 float 类型的宽度,这样做虽然精度更高,但是会减慢速度。在常量后加 f 或 F 可覆盖默认设置,强制使用float;使用 l 或 L 可以设置为 long double (建议 L 而不是 l)。
16进制表示
也叫作p计数法,用p表示16进制,相当于10进制中的e。C99 添加的一种新的浮点型常量格式
float a = 0xa.1fp10;
\((10 + \frac{1}{16} + \frac{15}{256}) * 1024 = 10364.0\),其中的1024来自
p10
,即 \(2^{10} = 1024\)
打印浮点值
%f
打印十进制 float 和 double%e
和%E
打印指数形式的浮点数%Lf
,%Le
和%La
打印 long double%a
和%A
打印16进制浮点数printf()
函数在传递参数时,会把 float 类型的值自动转换成 double 类型
浮点值的上溢和下溢
- 上溢 (overflow)
float toobig = 3.4E37 * 100.f;
printf("%e\n", toobig);
当计算导致数字过大,超过当前类型能表达的范围时,会发生上溢。C 语言规定,这种情况下会给 toobig 赋一个表示无穷大的特定值,而且 printf() 显示该值为 inf 或 infinity(或表示无穷大含义的其他内容)。
- 下溢 (underflow)
当存在一个很小的数(无穷小数,而不是很大的负数),它的指数部分是最小数(能表示的最大的负数),尾数部分也是用全部可用位表示的最小尾数。现在将它除以2,通常,这个操作会减小指数部分,但是这种情况下,指数部分已经是最小数了,所以计算机只好把尾数部分的位向右移动一位,空出一个二进制位,并丢弃最后一个二进制位。以十进制数 0.1234E-10 为例,将它除以10,得到的结果是 0.0123E-10。虽然得到了结果,但是损失了有效小数位数。这种情况叫做下溢,这种有效位数低于正常值的数叫做 低于正常的(subnormal) 浮点值。如果除以一个非常大的值,则会导致所有位数都是0。现在 C 库已经提供了用于检查计算是否会产生低于正常值的函数。
- NaN 或 nan
特殊的浮点值(not a number)。比如给 asin() (反三角函数)传递一个大于1的值,则会返回 NaN 值,printf() 函数打印时会显示为 nan 或 NaN 或其他类似的内容。
- 浮点数舍入误差
发生在很大的数与很小的数相加减时。
float a, b;
b = 2.e20 + 1.;
a = b - 2.e20;
printf("a: %e\n", a);
输出:
a: 4.008175e+12
原因:
计算机缺少足够的小数位数来完成正确的运算。2.e20 这个数加上1,它的第21位会发生变化,但 float 类型的数只有6位有效数字,超过6位的小数是不准确的,这种情况下,改变它的第21位小数会变得不准确。
不同的编译器、不同版本的编译器都可能会产生不同结果,但后来 IEEE 为浮点数的计算和表示方法开发了一套标准,该标准现为 C99 和 C11 的可选项
复数和虚数类型
C99 支持附属类型和虚数类型,但有所保留。有些系统,如嵌入式处理器的实现,不需要使用负数和虚数。一般来说,复数和虚数都是可选项。C11 把整个复数软件包都作为可选项。
三种复数类型和三种虚数类型:
float _Complex
,float _Imaginary
double _Complex
,double _Imaginary
long double _Complex
,long double_ Imaginary
每种类型的变量都包含两个 float 类型的值,分别表示实部和虚部
如果包含 complex.h 头文件,便可以用 complex
代替 _Complex
,用 imaginary
代替 _Imaginary
为何不内置复数类型,而要引入头文件才能使用复数?
因为在此前,已经有许多的程序中使用类似struct complex
的方式来表示复数或心理学程序中的心理状况。这样做是为了防止出现语法错误。
但是使用struct _Complex
的人很少(首字符是下划线的标识符作为预留字),因此标准委员会选定_Complex
作为关键字。在不用考虑名称冲突的情况下可选择使用complex
其他类型
- 字符串 -> 第4章
- 数组
- 指针
- 指针指向变量或其他数据对象位置
- 结构
- 联合
在 scanf()
函数中使用的前缀 &
创建了一个指针,告诉 scanf()
函数把数据放在何处。
小结
11个关键字
int, long, short, unsigned, char, float, double, signed, _Bool
, _Complex
, _Imaginary
有符号整型
- int
- 不小于16位
- short / short int
- 最大的 short 小于等于 int 位数,至少16位
- long / long int
- 大于等于最大的 int 类型位数,至少32位
- long long / long long int
- 大于等于最大的 long 类型整数,至少64位
一般来说,long 类型占用内存比 short 类型大,int 类型宽度要么和 long 类型相同,要么和 short 类型相同
ps - 我的电脑(字节): short:2, int:4, long:8, long long:8
无符号整型
只能用于表示0和正整数
- unsigned / unsigned int
- unsigned long
- unsigned short
字符类型
char,只占一个字节;但多个字节组合起来可以表示非基本字符,比如汉字一般占3个字节(Unicode编码格式)
布尔类型
1表示 true,0表示 false;布尔类型是无符号 int 类型
实浮点类型
- float
- 4 字节
- double
- 8字节
- long double
- 16字节
复数和虚数浮点数
虚数类型是可选类型。实部和虚部都是基于实浮点类型构成
float _Complex
double _Complex
long double _Complex
float _Imaginary
double _Imaginary
long long _Imaginary
规范使用变量类型
int apples = 3; // good
int pears = 3.0; // not good
int cost = 12.99; // cost is 12
float pi = 3.14159265357; // 只保留6位精度
变量命名:
变量名命名中保留类型信息,比如 i_ 前缀表示 int 类型,us_ 前缀表示 unsigned short 类型。
刷新输出
printf() 何时把输出发送到屏幕上?
printf() 语句把输出发送到一个叫做缓冲区(buffer)的中间存储区域,然后把缓冲区中的内容再不断被发送到屏幕上。
C 标准明确规定了何时把缓冲区中的内容发送到屏幕上:
- 缓冲区满
- 遇到换行字符
- 需要输入时
练习题
Ex.01
编写程序观察系统如何处理整数上溢、浮点数上溢和浮点数下溢的情况。
#include <stdio.h>
int main(void){
// integer overflow
int a = 2147483647;
int b = 1;
int c = a + b;
printf("integer overflow example:\n");
printf("%d + %d is %d\n\n", a, b, c);
// float overflow
float aa = 3.4E38;
float ab = 10.;
float ac = aa * ab;
printf("float overflow example:\n");
printf("%e multiply %e is %e\n\n", aa, ab, ac);
// float underflow
float ba = 1.234567E-38;
float bb = 10.;
float bc = ba / bb;
printf("float underflow example:\n");
printf("%e divided by %e is %e\n\n", ba, bb, bc);
return 0;
}
Ex.02
编写程序,要求提示输入一个 ASCII 码值(如,66),然后打印输入的字符。
#include <stdio.h>
int main(void){
int a;
printf("input a ascii number:\n");
scanf("%d", &a);
char b = a;
printf("number %d is %c\n", a, b); // number 66 is B
char c = (char)a;
printf("%d cast to char is %c\n", a, c);
return 0;
}