当前位置:   article > 正文

《C Primer Plus》第三章——数据与C(数据类型声明关键字,整型,浮点型,可移植类型转义序列等,复习题与编程练习)_c primerplus关于转义序列

c primerplus关于转义序列

声明:加粗部分只是我注明了我以前忽略的部分,没有特殊含义。

数据与C

本章内容

本章介绍以下内容:

  • 关键字——int、short、long、unsigned、char、float、double、_Bool、_Complex、_Imaginary;
  • 运算符——sizeof();
  • 函数——scanf();
  • 整数类型和浮点数类型的区别;
  • 如何书写整型和浮点型常数,如何声明这些类型的变量;
  • 如何使用printf()和scanf()函数读写不同类型的值。

示例程序

/* 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: ");
    /* 获取用户的输入 */
    scanf("%f", &weight);
    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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

如果输入程序时打错(如,漏了一个分号),编译器会报告语法错误消息。然而,即使输入正确无误,编译器也可能给出一些警告,如“警告:从double类型转换成float类型可能会丢失数据”。错误消息表明程序中有错,不能进行编译。而警告则表明,尽管编写的代码有效,但可能不是程序员想要的。警告并不终止编译。特殊的警告与C如何处理1700.0这样的值有关。本例不必理会这个问题,本章稍后会进一步说明。

按下Enter键是告知计算机,你已完成输入数据。该程序需要你输入一个数字(如,155),而不是单词(如,too much)。如果输入字母而不是数字,会导致程序出问题。这个问题要用if语句来解决(详见第7章)

  1. 程序中的新元素
    • 代码中使用了一种新的变量声明。前面的例子中只使用了整数类型的变量(int),但是本例使用了浮点数类型(float)的变量,以便处理更大范围的数据。float类型可以存储带小数的数字。
    • 为了打印新类型的变量,在printf()中使用%f来处理浮点值。%.2f中的.2用于精确控制输出,指定输出的浮点数只显示小数点后面两位。
    • scanf()函数用于读取键盘的输入。%f说明scanf()要读取用户从键盘输入的浮点数,&weight告诉scanf()把输入的值赋给名为weight的变量。scanf()函数使用&符号表明找到weight变量的地点。
    • 本章着重解释上述新特性中的前两项:各种数据类型的变量和常量。第4章将介绍后3项。

变量与常量数据

  • 数据,即承载信息的数字和字符。有些数据类型在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化,这些称为常量(constant)。其他数据类型在程序运行期间可能会改变或被赋值,这些称为变量(variable)。
  • 示例程序中,weight是一个变量,14.5833是一个常量。

数据:数据类型关键字

基础知识

  • C通过识别一些基本的数据类型来区分和使用这些不同的数据类型。如果数据是常量,编译器一般通过用户书写的形式来识别类型(如,42是整数,42.100是浮点数)。

  • 但是,对变量而言,要在声明时指定其类型。

  • 在这里插入图片描述

  • 后3个关键字(long、short和unsigned)和C90新增的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位。计算机的字长越大,其数据转移越快,允许的内存访问也更多。

整数和浮点数

  • 对我们而言,整数和浮点数的区别是它们的书写方式不同。对计算机而言,它们的区别是存储方式不同。
  • 计算机以二进制数字存储整数,例如,整数7以二进制写是111。因此,要在8位字节中存储该数字,需要把前5位都设置成0,后3位设置成1
  • 7是整数,7.00是浮点数。显然,书写浮点数有多种形式。稍后将详细介绍e记数法,这里先做简要介绍:3.16E7表示3.16×107(3.16乘以10的7次方)。其中,107=10000000,7被称为10的指数。
  • 整数没有小数部分,浮点数有小数部分。
  • 浮点数可以表示的范围比整数大。
  • 对于一些算术运算(如,两个很大的数相减),浮点数损失的精度更多。
  • **因为在任何区间内(如,1.0到2.0之间)都存在无穷多个实数,所以计算机的浮点数不能表示区间内所有的值。浮点数通常只是实际值的近似值。例如,7.0可能被存储为浮点值6.99999。**稍后会讨论更多精度方面的内容。
  • 过去,浮点运算比整数运算慢。不过,现在许多CPU都包含浮点处理器,缩小了速度上的差距。

C语言基本数据类型

int类型

  • int类型是有符号整型,即int类型的值必须是整数,可以是正整数、负整数或零。其取值范围依计算机系统而异。
  • 存储一个int要占用一个机器字长。
  • 目前的个人计算机一般是32位,因此用32位存储一个int值。
声明int变量:
  • 第2章中已经用int声明过基本整型变量。先写上int,然后写变量名,最后加上一个分号。要声明多个变量,可以单独声明每个变量,也可在int后面列出多个变量名,变量名之间用逗号分隔。
  • 可以分别在4条声明中声明各变量,也可以在一条声明中声明4个变量。两种方法的效果相同,都为4个int大小的变量赋予名称并分配内存空间。
变量初始化
  • 第1种途径是赋值:
    cows = 112;
    第2种途径是,通过函数(如,scanf())获得值。
  • 初始化变量:初始化(initialize)变量就是为变量赋一个初始值。在C语言中,初始化可以直接在声明中完成。只需在变量名后面加上赋值运算符(=)和待赋给变量的值即可。
int hogs = 21;
int cows = 32, goats = 14;
int dogs, cats = 94; /* 有效,但是这种格式很糟糕 */
  • 1
  • 2
  • 3
  • 以上示例的最后一行,只初始化了cats,并未初始化dogs。这种写法很容易让人误认为dogs也被初始化为94,所以最好不要把初始化的变量和未初始化的变量放在同一条声明中
int类型常量
  • 上面示例中出现的整数(21、32、14和94)都是整型常量或整型字面量。C语言把不含小数点和指数的数作为整数。
  • C语言把大多数整型常量视为int类型,但是非常大的整数除外。详见后面“long常量和long long常量”小节对long int类型的讨论。
打印int值
  • 可以使用printf()函数打印int类型的值。第2章中介绍过,%d指明了在一行中打印整数的位置。%d称为转换说明,它指定了printf()应使用什么格式来显示一个值。格式化字符串中的每个%d都与待打印变量列表中相应的int值匹配。这个值可以是int类型的变量、int类型的常量或其他任何值为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;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 在第二行输出中,第1个%d对应ten的值,但是由于没有给后两个%d提供任何值,所以打印出的值是内存中的任意值
  • 你可能会抱怨编译器为何不能捕获这种明显的错误,但实际上问题出在printf()不寻常的设计。大部分函数都需要指定数目的参数,编译器会检查参数的数目是否正确。但是,printf()函数的参数数目不定,可以有1个、2个、3个或更多,编译器也爱莫能助。
  • 记住,使用printf()函数时,要确保转换说明的数量与待打印值的数量相等。
  • 在C语言中,用特定的前缀表示使用哪种进制。0x或0X前缀表示十六进制值,所以十进制数16表示成十六进制是0x10或0X10。
  • 0前缀表示八进制。例如,十进制数16表示成八进制是020。
显示八进制和十六进制
  • 在C程序中,既可以使用也可以显示不同进制的数。不同的进制要使用不同的转换说明。以十进制显示数字,使用%d;以八进制显示数字,使用%o;以十六进制显示数字,使用%x。另外,要显示各进制数的前缀0、0x和0X,必须分别使用%#o、%#x、%#X。
/* 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);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

该程序以3种不同记数系统显示同一个值。printf()函数做了相应的转换。

其他整数类型

short、long和unsigned

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都表示同一种类型。
细节说明
  • 为什么说short类型“可能”比int类型占用的空间少,long类型“可能”比int类型占用的空间多?因为C语言只规定了short占用的存储空间不能多于int,long占用的存储空间不能少于int。这样规定是为了适应不同的机器。
  • 例如,过去的一台运行Windows 3.x的机器上,int类型和short类型都占16位,long类型占32位。后来,Windows和苹果系统都使用16位存储short类型,32位存储int类型和long类型(使用32位可以表示的整数数值超过20亿)。现在,计算机普遍使用64位处理器,为了存储64位的整数,才引入了long long类型。
  • 现在,个人计算机上最常见的设置是,long long占64位,long占32位,short占16位,int占16位或32位(依计算机的自然字长而定)。原则上,这4种类型代表4种不同的大小,但是在实际使用中,有些类型之间通常有重叠。
  • unsigned类型。这种类型的数常用于计数,因为计数不用负数。而且,unsigned类型可以表示更大的正数。
  • 如果一个数超出了int类型的取值范围,且在long类型的取值范围内时,使用long类型。然而,对于那些long占用的空间比int大的系统,使用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类型存储一个小数字。例如,编程时要显式使用IBM PC上的内存地址时。
  • 把一个较小的常量作为long类型对待,可以在值的末尾加上l(小写的L)或L后缀。
  • 类似地,在支持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);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2147483647 -2147483648 -2147483647

4294967295 0 1

  • 当达到它能表示的最大值时,会重新从起始点开始。
  • 注意,当i超出(溢出)其相应类型所能表示的最大值时,系统并未通知用户。因此,在编程时必须自己注意这类问题。
打印short、long、long long和unsigned类型
  • 打印unsigned int类型的值,使用%u转换说明;
  • 打印long类型的值,使用%ld转换说明。
  • 如果系统中int和long的大小相同,使用%d就行。但是,这样的程序被移植到其他系统(int和long类型的大小不同)中会无法正常工作。
  • 在x和o前面可以使用l前缀,%lx表示以十六进制格式打印long类型整数,%lo表示以八进制格式打印long类型整数。
  • 对于short类型,可以使用h前缀。%hd表示以十进制显示short类型的整数,%ho表示以八进制显示short类型的整数。
  • h和l前缀都可以和u一起使用,用于表示无符号类型。例如,%lu表示打印unsigned long类型的值。对于支持long long类型的系统,%lld和%llu分别表示有符号和无符号类型。
/* 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);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在特定的系统中输出如下(输出的结果可能不同):

un = 3000000000 and not -1294967296

end = 200 and 200

big = 65537 and not 1

verybig= 12345678908642 and not 1942899938

  • 对于无符号变量un,使用%d会生成负值!其原因是,无符号值3000000000和有符号值−129496296在系统内存中的内部表示完全相同(详见第15章)。因此,如果告诉printf()该数是无符号数,它打印一个值;如果告诉它该数是有符号数,它将打印另一个值。

  • 在待打印的值大于有符号值的最大值时,会发生这种情况。对于较小的正数(如96),有符号和无符号类型的存储、显示都相同。

  • 第2行输出,对于short类型的变量end,在printf()中无论指定以short类型(%hd)还是int类型(%d)打印,打印出来的值都相同。这是因为在给函数传递参数时,C编译器把short类型的值自动转换成int类型的值。
  • 你可能会提出疑问:为什么要进行转换?h修饰符有什么用?第1个问题的答案是,int类型被认为是计算机处理整数类型时最高效的类型。因此,在short和int类型的大小不同的计算机中,用int类型的参数传递速度更快。第2个问题的答案是,使用h修饰符可以显示较大整数被截断成short类型值的情况。
  • 第3行输出就演示了这种情况。把65537以二进制格式写成一个32位数是00000000000000010000000000000001。使用%hd,printf()只会查看后16位,所以显示的值是1。
  • 与此类似,输出的最后一行先显示了verybig的完整值,然后由于使用了%ld,printf()只显示了存储在后32位的值。
  • 程序员必须根据待打印值的类型使用正确的转换说明。

使用字符:char类型

char类型用于存储字符(如,字母或标点符号),但是从技术层面看,char是整数类型。因为char类型实际上存储的是整数而不是字符。

计算机使用数字编码来处理字符,即用特定的整数表示特定的字符。

美国最常用的编码是ASCII编码,本书也使用此编码。例如,在ASCII码中,整数65代表大写字母A。因此,存储字母A实际上存储的是整数65

声明char类型变量

char类型变量的声明方式与其他类型变量的声明方式相同。下面是一些例子:

char response;

char itable, latan;

字符常量和初始化
  • 如果要把一个字符常量初始化为字母A,不必背下ASCII码,用计算机语言很容易做到。通过以下初始化把字母A赋给grade即可:char grade = ‘A’;

  • 在C语言中,用单引号括起来的单个字符被称为字符常量

  • 如果省略单引号,编译器认为T是一个变量名;如果把T用双引号括起来,编译器则认为"T"是一个字符串。

  • 单引号只适用于字符、数字和标点符号,浏览ASCII表会发现,有些ASCII字符打印不出来。例如,一些代表行为的字符(如,退格、换行、终端响铃或蜂鸣)。C语言提供了3种方法表示这些字符。

  • 第1种方法前面介绍过——使用ASCII码。例如,蜂鸣字符的ASCII值是7,因此可以这样写:
    char beep = 7;

  • 第2种方法是,使用特殊的符号序列表示一些特殊的字符。这些符号序列叫作转义序列(escape sequence)。把转义序列赋给字符变量时,必须用单引号把转义序列括起来。例如,假设有下面一行代码:char nerf = ‘\n’;

  • 稍后打印变量nerf的效果是,在打印机或屏幕上另起一行。

  • 在这里插入图片描述

    标准中的活跃位置(active position)指的是显示设备(屏幕、电传打字机、打印机等)中下一个字符将出现的位置。

    • 换页符(\f)把活跃位置移至下一页的开始处;
    • 换行符(\n)把活跃位置移至下一行的开始处;
    • 回车符(\r)把活跃位置移动到当前行的开始处;
    • 水平制表符(\t)将活跃位置移至下一个水平制表点(通常是第1个、第9个、第17个、第25个等字符位置);
    • 垂直制表符(\v)把活跃位置移至下一个垂直制表点。
    • 接下来的3个转义序列(\\、\’、\")用于打印\、’、"字符(由于这些字符用于定义字符常量,是printf()函数的一部分,若直接使用它们会造成混乱)。如果打印下面一行内容:
    • Gramps sez, “a \ is a backslash.”
      应这样编写代码:
      printf(“Gramps sez, “a \ is a backslash.”\n”);
  • 在这里插入图片描述

  • 使用ASCII码时,注意数字和数字字符的区别。例如,字符4对应的ASCII码是52。'4’表示字符4,而不是数值4。

打印字符
  • printf()函数用%c指明待打印的字符。

  • 前面介绍过,一个字符变量实际上被存储为1字节的整数值。因此,如果用%d转换说明打印char类型变量的值,打印的是一个整数。而%c转换说明告诉printf()打印该整数值对应的字符。

/* charcode.c-显示字符的代码编号 */
#include<stdio.h>
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • Please enter a character.C

    The code for C is 67.

    printf()函数打印ch的值两次,第1次打印一个字符(对应代码中的%c),第2次打印一个十进制整数值(对应代码中的%d)。

_Bool类型

C99标准添加了_Bool类型,用于表示布尔值,即逻辑值true和false。因为C语言用值1表示true,值0表示false,所以_Bool类型实际上也是一种整数类型。但原则上它仅占用1位存储空间,因为对0和1而言,1位的存储空间足够了。

可移植类型:stdint.h和inttypes.h

声明
  • C99新增了两个头文件stdint.h和inttypes.h,以确保C语言的类型在各系统中的功能相同。
  • C语言为现有类型创建了更多类型名。这些新的类型名定义在stdint.h头文件中。
  • 例如,int32_t表示32位的有符号整数类型。在使用32位int的系统中,头文件会把int32_t作为int的别名。不同的系统也可以定义相同的类型名。例如,int为16位、long为32位的系统会把int32_t作为long的别名。然后,使用int32_t类型编写程序,并包含stdint.h头文件时,编译器会把int或long替换成与当前系统匹配的类型。
  • 上面讨论的类型别名是精确宽度整数类型(exact-width integer type)的示例。int32_t表示整数类型的宽度正好是32位。
  • 如果系统不支持精确宽度整数类型怎么办?
  • C99和C11提供了第2类别名集合。一些类型名保证所表示的类型一定是至少有指定宽度的最小整数类型。这组类型集合被称为最小宽度类型(minimum width type)。
  • 例如,int_least8_t是可容纳8位有符号整数值的类型中宽度最小的类型的一个别名。如果某系统的最小整数类型是16位,可能不会定义int8_t类型。尽管如此,该系统仍可使用int_least8_t类型,但可能把该类型实现为16位的整数类型。
  • 当然,一些程序员更关心速度而非空间。为此,C99和C11定义了一组可使计算达到最快的类型集合。这组类型集合被称为最快最小宽度类型(fastst minimum width type)。例如,int_fast8_t被定义为系统中对8位有符号值而言运算最快的整数类型的别名。
  • 另外,有些程序员需要系统的最大整数类型。为此,C99定义了最大的有符号整数类型intmax_t,可存储任何有效的有符号整数值。类似地,uintmax_t表示最大的无符号整数类型。
输出
  • C标准针对这一情况,提供了一些字符串宏(第4章中详细介绍)来显示可移植类型。例如,inttypes.h头文件中定义了PRId32字符串宏,代表打印32位有符号值的合适转换说明(如d或l)。
/* altnames.c -- 可移植整数类型名 */ 
#include <stdio.h>
#include <inttypes.h> // 支持可移植类型
int main(void)
{
    int32_t me32; // me32是一个32位有符号整型变量
    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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

篇幅有限,无法介绍扩展的所有整数类型。本节主要是为了让读者知道,在需要时可进行这种级别的类型控制。

float、double和long double

基础知识
  • C标准规定,float类型必须至少能表示6位有效数字,且取值范围至少是10-37~10+37。前一项规定指float类型必须能够表示33.333333的前6位数字,而不是精确到小数点后6位数字。后一项规定用于方便地表示诸如太阳质量(2.0e30千克)、一个质子的电荷量(1.6e-19库仑)或国家债务之类的数字。

  • 通常,系统存储一个浮点数要占用32位。其中8位用于表示指数的值和符号,剩下24位用于表示非指数部分(也叫作尾数或有效数)及其符号。

  • double类型和float类型的最小取值范围相同,但至少必须能表示10位有效数字。

  • 一般情况下,double占用64位而不是32位。

  • C语言的第3种浮点类型是long double,以满足比double类型更高的精度要求。不过,C只保证long double类型至少与double类型的精度相同。

浮点型常量
  • 在代码中,可以用多种形式书写浮点型常量。浮点型常量的基本形式是:有符号的数字(包括小数点),后面紧跟e或E,最后是一个有符号数表示10的指数。下面是两个有效的浮点型常量:

    -1.56E

    +122.87e-3

  • 正号可以省略。可以没有小数点(如,2E5)或指数部分(如,19.28),但是不能同时省略两者。

  • 可以省略小数部分(如,3.E16)或整数部分(如,.45E-6),但是不能同时省略两者。下面是更多的有效浮点型常量示例:

    3.14159

    .2

    4e16

    .8E-5

    100.

  • 不要在浮点型常量中间加空格:1.56 E+12(错误!)

  • 默认情况下,编译器假定浮点型常量是double类型的精度。

    some = 4.0 * 2.0;
    通常,4.0和2.0被存储为64位的double类型,使用双精度进行乘法运算,然后将乘积截断成float类型的宽度。这样做虽然计算精度更高,但是会减慢程序的运行速度。

  • 在浮点数后面加上f或F后缀可覆盖默认设置,编译器会将浮点型常量看作float类型,如2.3f和9.11E9F。

  • C99标准添加了一种新的浮点型常量格式——用十六进制表示浮点型常量,即在十六进制数前加上十六进制前缀(0x或0X),用p和P分别代替e和E,用2的幂代替10的幂(即,p计数法)。如下所示:
    0xa.1fp10

  • printf()函数使用%f转换说明打印十进制记数法的float和double类型浮点数,用%e打印指数记数法的浮点数。

  • 如果系统支持十六进制格式的浮点数,可用a和A分别代替e和E。

  • 打印long double类型要使用%Lf、%Le或%La转换说明。

浮点值的上溢和下溢
  • 当计算导致数字过大,超过当前类型能表达的范围时,就会发生上溢。这种行为在过去是未定义的,不过现在C语言规定,在这种情况下会给toobig赋一个表示无穷大的特定值,而且printf()显示该值为inf或infinity

  • 以十进制为例,把一个有4位有效数字的数(如,0.1234E-10)除以10,得到的结果是0.0123E-10。虽然得到了结果,但是在计算过程中却损失了原末尾有效位上的数字。这种情况叫作下溢

  • C语言把损失了类型全精度的浮点值称为低于正常的(subnormal)浮点值。因此,把最小的正浮点数除以2将得到一个低于正常的值。如果除以一个非常大的值,会导致所有的位都为0。现在,C库已提供了用于检查计算是否会产生低于正常值的函数。

  • 还有另一个特殊的浮点值NaN(not a number的缩写)。例如,给asin()函数传递一个值,该函数将返回一个角度,该角度的正弦就是传入函数的值。但是正弦值不能大于1,因此,如果传入的参数大于1,该函数的行为是未定义的。在这种情况下,该函数将返回NaN值,printf()函数可将其显示为nan、NaN或其他类似的内容。

浮点数舍入错误
/* floaterr.c--演示舍入错误 */ 
#include<stdio.h>
int main(void)
{
    float a, b;
    b = 2.0e20 + 1.0;
    a = b - 2.0e20;
    printf("%f \n", a);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

0.000000 ←Linux系统下的老式gcc

-13584010575872.000000 ←Turbo C 1.5

4008175468544.000000 ←XCode

4.5Visual Studio 2012、当前版本的gcc

  • 得出这些奇怪答案的原因是,计算机缺少足够的小数位来完成正确的运算。
  • 2.0e20是2后面有20个0。如果把该数加1,那么发生变化的是第21位。要正确运算,程序至少要存储21位数字。而float类型的数字通常只能存储按指数比例缩小或放大的6或7位有效数字。在这种情况下,计算结果一定是错误的。
  • 另一方面,如果把2.0e20改成2.0e4,计算结果就没问题。因为2.0e4加1只需改变第5位上的数字,float类型的精度足够进行这样的计算。

复数和虚数类型

  • 简而言之,C语言有3种复数类型:float _Complex、double _Complex和long double _Complex。例如,float _Complex类型的变量应包含两个float类型的值,分别表示复数的实部和虚部。类似地,C语言的3种虚数类型是float _Imaginary、double _Imaginary和long double _Imaginary。
  • 如果包含complex.h头文件,便可用complex代替、_Complex,用imaginary代替、_Imaginary,还可以用I代替-1的平方根。
  • sizeof是C语言的内置运算符,以字节为单位给出指定类型的大小。C99和C11提供%zd转换说明匹配sizeof的返回类型。一些不支持C99和C11的编译器可用%u或%lu代替%zd。
/* typesize.c -- 打印类型大小 */
#include <stdio.h>
int main(void)
{
    /* C99为类型大小提供%zd转换说明 */
    printf("Type int has a size of %zd bytes.\n", sizeof(int));
    printf("Type char has a size of %zd bytes.\n", sizeof(char));
    printf("Type long has a size of %zd bytes.\n", sizeof(long));
    printf("Type long long has a size of %zd bytes.\n",
           sizeof(long long));
    printf("Type double has a size of %zd bytes.\n",
           sizeof(double));
    printf("Type long double has a size of %zd bytes.\n",
           sizeof(long double));
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

使用数据类型

把一个类型的数值初始化给不同类型的变量时,编译器会把值转换成与变量匹配的类型,这将导致部分数据丢失。例如,下面的初始化:

int cost = 12.99; /* 用double类型的值初始化int类型的变量 */

float pi = 3.1415926536; /* 用double类型的值初始化float类型的变量 */

参数和陷阱

  • 读者应该还记得,传递给函数的信息被称为参数。例如,printf(“Hello,pal.”)函数调用有一个参数:“Hello,pal.”。双引号中的字符序列(如,“Hello,pal.”)被称为字符串(string),第4章将详细讲解相关内容。现在,关键是要理解无论双引号中包含多少个字符和标点符号,一个字符串就是一个参数。
  • 与此类似,scanf("%d", &weight)函数调用有两个参数:"%d"和&weight。C语言用逗号分隔函数中的参数。
  • printf()和scanf()函数用第1个参数表明后续有多少个参数,即第1个字符串中的转换说明与后面的参数一一对应。例如,下面的语句有两个%d转换说明,说明后面还有两个参数:
  • 程序员要负责确保转换说明的数量、类型与后面参数的数量、类型相匹配。
  • 注意,用%d显示float类型的值,其值不会被转换成int类型。在不同的平台下,缺少参数或参数类型不匹配导致的结果不同。

转义序列示例

/* escape.c -- 使用转义序列 */
#include <stdio.h>
int main(void)
{
    float salary;
    printf("\aEnter your desired monthly salary:"); /* 1 */
    printf(" $_______\b\b\b\b\b\b\b");              /* 2 */
    scanf("%f", &salary);
    printf("\n\t$%.2f a month is $%.2f a year.", salary,
           salary * 12.0); /* 3 */
    printf("\rGee!\n");    /* 4 */
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 第1条printf()语句(注释中标为1)发出一声警报(因为使用了\a),然后打印下面的内容:
    Enter your desired monthly salary:
    因为printf()中的字符串末尾没有\n,所以光标停留在冒号后面。

  • 第2条printf()语句在光标处接着打印,屏幕上显示的内容是:

  • Enter your desired monthly salary: $_______

    冒号和美元符号之间有一个空格,这是因为第2条printf()语句中的字符串以一个空格开始。7个退格字符使得光标左移7个位置,即把光标移至7个下划线字符的前面,紧跟在美元符号后面。

  • 通常,退格不会擦除退回所经过的字符,但有些实现是擦除的,这和本例不同。
    假设键入的数据是4000.00(并按下Enter键),屏幕显示的内容应该是:

    Enter your desired monthly salary: $4000.00

  • 键入的字符替换了下划线字符。按下Enter键后,光标移至下一行的起始处。

  • 第3条printf()语句中的字符串以\n\t开始。换行字符使光标移至下一行起始处。水平制表符使光标移至该行的下一个制表点,一般是第9列(但不一定)。然后打印字符串中的其他内容。执行完该语句后,此时屏幕显示的内容应该是:

  • Enter your desired monthly salary: $4000.00

    ​ $4000.00 a month is $48000.00 a year.

  • 因为这条printf()语句中没有使用换行字符,所以光标停留在最后的点号后面。
    第4条printf()语句以\r开始。这使得光标回到当前行的起始处。然后打印Gee!,接着\n使光标移至下一行的起始处。

刷新输出
  • printf()语句把输出发送到一个叫作缓冲区(buffer)的中间存储区域,然后缓冲区中的内容再不断被发送到屏幕上。C标准明确规定了何时把缓冲区中的内容发送到屏幕:当缓冲区满、遇到换行字符或需要输入的时候(从缓冲区把数据发送到屏幕或文件被称为刷新缓冲区)。例如,前两个printf()语句既没有填满缓冲区,也没有换行符,但是下一条scanf()语句要求用户输入,这迫使printf()的输出被发送到屏幕上。

  • 还有一种刷新缓冲区的方法是使用fflush()函数,详见第13章。

本章小结

  • C有多种的数据类型。基本数据类型分为两大类:整数类型和浮点数类型。
  • 通过为类型分配的存储量以及是有符号还是无符号,区分不同的整数类型。最小的整数类型是char,因实现不同,可以是有符号的char或无符号的char,即unsigned char或signed char。但是,通常用char类型表示小整数时才这样显式说明。
  • 其他整数类型有short、int、long和long long类型。C规定,后面的类型不能小于前面的类型。上述都是有符号类型,但也可以使用unsigned关键字创建相应的无符号类型:unsigned short、unsigned int、unsigned long和unsigned long long。或者,在类型名前加上signed修饰符显式表明该类型是有符号类型。
  • 最后,_Bool类型是一种无符号类型,可存储0或1,分别代表false和true。
  • 浮点类型有3种:float、double和C90新增的long double。后面的类型应大于或等于前面的类型。
  • 有些实现可选择支持复数类型和虚数类型,通过关键字_Complex和_Imaginary与浮点类型的关键字组合(如,double _Complex类型和float _Imaginary类型)来表示这些类型。
  • 整数可以表示为十进制、八进制或十六进制。0前缀表示八进制数,0x或0X前缀表示十六进制数。例如,32、040、0x20分别以十进制、八进制、十六进制表示同一个值。l或L后缀表明该值是long类型,ll或LL后缀表明该值是long long类型。
  • 在C语言中,直接表示一个字符常量的方法是:把该字符用单引号括起来,如’Q’、‘8’和’$’。C语言的转义序列(如,’\n’)表示某些非打印字符。另外,还可以在八进制或十六进制数前加上一个反斜杠(如,’\007’),表示ASCII码中的一个字符。
  • 浮点数可写成固定小数点的形式(如,9393.912)或指数形式(如,7.38E10)。C99和C11提供了第3种指数表示法,即用十六进制数和2的幂来表示(如,0xa.1fp10)。
  • printf()函数根据转换说明打印各种类型的值。转换说明最简单的形式由一个百分号(%)和一个转换字符组成,如%d或%f。

复习题

  1. 指出下面各种数据使用的合适数据类型(有些可使用多种数据类型):
    a.East Simpleton的人口
    b.DVD影碟的价格
    c.本章出现次数最多的字母
    d.本章出现次数最多的字母次数

  2. 在什么情况下要用long类型的变量代替int类型的变量?

  3. 使用哪些可移植的数据类型可以获得32位有符号整数?选择的理由是什么?

  4. 指出下列常量的类型和含义(如果有的话):
    a.’\b’
    b.1066
    c.99.44
    d.0XAA
    e.2.0e30

  5. Dottie Cawm编写了一个程序,请找出程序中的错误。

    include <stdio.h>
    main
    (float g; h;float tax, rate;
     
     g = e21;
     tax = rate*g;)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  6. 写出下列常量在声明中使用的数据类型和在printf()中对应的转换说明:

  7. 写出下列常量在声明中使用的数据类型和在printf()中对应的转换说明(假设int为16位):

  8. 假设程序的开头有下列声明:

    int imate = 2;
    long shot = 53456;
    char grade = 'A';
    float log = 2.71828;
    
    • 1
    • 2
    • 3
    • 4

    把下面printf() 语句中的转换字符补充完整:
    printf(“The odds against the %__ were %__ to 1.\n”, imate, shot);
    printf(“A score of %__ is not an %__ grade.\n”, log, grade);

  9. 假设ch是char类型的变量。分别使用转义序列、十进制值、八进制字符常量和十六进制字符常量把回车字符赋给ch(假设使用ASCII编码值)。

  10. 修正下面的程序(在C中,/表示除以)。

    void main(int) / this program is perfect /
    {
        cows, legs integer;
        printf("How many cow legs did you count?\n);
        scanf("%c", legs);
        cows = legs / 4;
        printf("That implies there are %f cows.\n", cows)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  11. 指出下列转义序列的含义:
    a.\n
    b.\
    c."
    d.\t


  1. a.int类型,也可以是short类型或unsigned short类型。人口数是一个整数。
    b.float类型,价格通常不是一个整数(也可以使用double类型,但实际上不需要那么高的精度)。
    c.char类型。
    d.int类型,也可以是unsigned类型。

  2. 原因之一:在系统中要表示的数超过了int可表示的范围,这时要使用long类型。原因之二:如果要处理更大的值,那么使用一种在所有系统上都保证至少是32位的类型,可提高程序的可移植性。

  3. 如果要正好获得32位的整数,可以使用int32_t类型。要获得可存储至少32位整数的最小类型,可以使用int_least32_t类型。如果要为32位整数提供最快的计算速度,可以选择int_fast32_t类型(假设你的系统已定义了上述类型)。

  4. a.char类型常量(但是存储为int类型)
    b.int类型常量
    c.double类型常量
    d.unsigned int类型常量,十六进制格式
    e.double类型常量

  5. 第1行:应该是#include <stdio.h>
    第2行:应该是int main(void)
    第3行:把(改为{
    第4行:g和h之间的;改成,
    第5行:没问题
    第6行:没问题
    第7行:虽然这数字比较大,但在e前面应至少有一个数字,如1e21或1.0e21都可以。
    第8行:没问题,至少没有语法问题。
    第9行:把)改成}
    除此之外,还缺少一些内容。首先,没有给rate变量赋值;其次未使用h变量;而且程序不会报告计算结果。虽然这些错误不会影响程序的运行(编译器可能给出变量未被使用的警告),但是它们确实与程序设计的初衷不符合。另外,在该程序的末尾应该有一个return语句。
    下面是一个正确的版本,仅供参考:

    #include <stdio.h>
    int main(void)
    {
        float g, h;
        float tax, rate;
        rate = 0.08;
        g = 1.0e5;
        tax = rate * g;
        h = g + tax;
        printf("You owe $%f plus $%f in taxes for a total of $%f.\n", g, tax, h);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  6. 在这里插入图片描述

7.在这里插入图片描述

  1. printf(“The odds against the %d were %ld to 1.\n”, imate, shot);
    printf(“A score of %f is not an %c grade.\n”, log, grade);

  2. ch = ‘\r’;
    ch = 13;
    ch = '\015’
    ch = ‘\xd’

  3. 最前面缺少一行(第0行):#include <stdio.h>
    第1行:使用//把注释括起来,或者在注释前面使用//。
    第3行:int cows, legs;
    第4行:count?\n");
    第5行:把%c改为%d,把legs改为&legs。
    第7行:把%f改为%d。
    另外,在程序末尾还要加上return语句。

编程练习

  1. 通过试验(即编写带有此类问题的程序)观察系统如何处理整数上溢、浮点数上溢和浮点数下溢的情况。

  2. 编写一个程序,要求提示输入一个ASCII码值(如,66),然后打印输入的字符。

  3. 编写一个程序,发出一声警报,然后打印下面的文本:

    Startled by the sudden sound, Sally shouted,

    “By the Great Pumpkin, what was that!”

  4. 编写一个程序,读取一个浮点数,先打印成小数点形式,再打印成指数形式。然后,如果系统支持,再打印成p记数法(即十六进制记数法)。按以下格式输出(实际显示的指数位数因系统而异):

    Enter a floating-point value: 64.25

    fixed-point notation: 64.250000

    exponential notation: 6.425000e+01

    p notation: 0x1.01p+6

  5. 一年大约有3.156×107秒。编写一个程序,提示用户输入年龄,然后显示该年龄对应的秒数。

  6. 1个水分子的质量约为3.0×10−23克。1夸脱水大约是950克。编写一个程序,提示用户输入水的夸脱数,并显示水分子的数量。

  7. 1英寸相当于2.54厘米。编写一个程序,提示用户输入身高(/英寸),然后以厘米为单位显示身高。

  8. 在美国的体积测量系统中,1品脱等于2杯,1杯等于8盎司,1盎司等于2大汤勺,1大汤勺等于3茶勺。编写一个程序,提示用户输入杯数,并以品脱、盎司、汤勺、茶勺为单位显示等价容量。思考对于该程序,为何使用浮点类型比整数类型更合适?


  1. #include <stdio.h>
    int main(void)
    {
        char ch;
        printf("请输入一个ASII码:");
        scanf("%d", &ch);
        printf("%d对应的字符是%c", ch, ch);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  2. #include <stdio.h>
    int main(void)
    {
        printf("\aStartled by the sudden sound, Sally shouted,\n\"By the Great Pumpkin, what was that!\"");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  3. #include <stdio.h>
    int main(void)
    {
        float num;
        printf("Enter a floating-point value:");
        scanf("%f", &num);
        printf("fixed-point notation: %f\n", num);
        printf("exponential notation: %e\n", num);
        printf("p notation: %a\n", num);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  4. #include<stdio.h>
    int main(void)
    {
        int age;
        printf("请输入您的年龄(整数):");
        scanf("%d",age);
        float second_of_year = 3.156E7;
        printf("%d岁对应%e秒\n",age,age*second_of_year);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  5. #include <stdio.h>
    int main(void)
    {
        float weightOfAH2OMolecule = 3.E-23;
        double kuaTuo;
        // 经验证,若是float类型可以计算1E13夸脱的水,大于将溢出,未防止溢出,声明未double变量,可以计算1的283次方夸脱
        scanf("%f", &kuaTuo);
        double numOfMolecule = kuaTuo * 950 / weightOfAH2OMolecule;
        printf("%e夸脱的水中有%e个水分子", kuaTuo, numOfMolecule);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  6. #include <stdio.h>
    int main(void)
    {
        float inch;
        printf("请输入您的身高(英寸单位):");
        scanf("%f", &inch);
        printf("您的身高是%f", inch * 2.54);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  7. // 1品脱=2杯=16盎司=32大汤勺=96茶勺
    #include <stdio.h>
    int main(void)
    {
        float cup;
        scanf("%f",&cup);
        printf("%f杯等价于%f品脱,%f盎司,%f汤勺,%f茶勺\n", cup, cup / 2, 8 * cup, 16 * cup, 48 * cup);
         return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  8. #include <stdio.h>
    int main(void)
    {
        float inch;
        printf("请输入您的身高(英寸单位):");
        scanf("%f", &inch);
        printf("您的身高是%f", inch * 2.54);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  9. // 1品脱=2杯=16盎司=32大汤勺=96茶勺
    #include <stdio.h>
    int main(void)
    {
        float cup;
        scanf("%f",&cup);
        printf("%f杯等价于%f品脱,%f盎司,%f汤勺,%f茶勺\n", cup, cup / 2, 8 * cup, 16 * cup, 48 * cup);
         return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/76317
推荐阅读