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 (无符号整型)的转换符
  • 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.

& 符号表示把输入的字符赋值给变量 chprintf 函数中的两次打印,第一次是打印一个字符,第二次是打印一个十进制数值。转换符说明了数据的显示方式,而不是数据的存储方式。

实现方式

有些 C 编译器把 char 实现为有符号类型,即 char 的可表示范围是 -128~127。而有些编译器实现为无符号类型,即 0~255。

C90 标准允许在 char 前使用 signed 或 unsigned,这样可以使编译器强制使用有符号或无符号类型。这在用 char 类型处理小整数时很有用。如果只用 char 处理字符,则其前面无需使用任何修饰符。

_Bool 类型

C99 标准添加了布尔类型。1表示 true,0表示 false,所以 _Bool 原则上仅占一位存储空间。

可移植类型:stdint.hinttypes.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;
}
posted @ 2020-04-23 22:08  keep-minding  阅读(118)  评论(0编辑  收藏  举报