C Primer Plus(第五版)4

第四章 字符串和格式化输入输出

 

4.2 字符串简介

字符串(character string)就是一个或多个字符的序列。下面是一个字符串的例子:

“Zing went the strings of my heart,”

双引号不是字符串的一部分。它们只是通知编译器其中包含了一个字符串,正如单引号标识着一个字符一样。


4.2.1 char 数组类型和空字符

C 没有为字符串定义专门的变量类型,而是把它存储在 char 数组中

------------------------------------
Zing went the strings of my heart!\0
------------------------------------
每个单元是1个字节 \0为空字符

数组的最后一个位置显示字符\0.这个字符就是空字符(null character),C用它来标记字符串的结束。空字符不是数字 0;它是非打印字符,其ASCII码的值为(或者等同于)0 。C的字符串存储时通常以这个空字符结束。该字符的存在意味着数组的单元数必须至少比要存储的字符数多1 。

4.2.2 使用字符串

试着运行程序清单 4.2中的程序,你会看到使用字符串很简单。

程序清单4.2 praise1.c 程序
--------------------------------------------------------------------------
/* praisel.c ---使用不同类型的字符串 */
#include
#define PRAISE "What a super marvelous name!" //使用预处理器定义了字符常量
int main (void)
{
char name[40];

printf ("What's your name?\n");
scanf ("%s",name);
printf ("Hello,%s. %s\n",name,PRAISE);
getchar();
getchar();
return 0;
}

%s 告诉 printf()要打印一个字符串, %s出现两次是因为该程序要打印两个字符串:一个被存储在 name数组中,另一个由 PRAISE(预处理器定义的字符常量)来代表。 praisel.c 的运行输出应该像下面所示:

What's your name?
Hilary Bubbles
Hello,Hilary.What a super marvelous name!

你无须亲自把空字符插入 name数组中。scanf()在读取输入时会替你完成这项任务。也无须在字符串常量 PRAISE中包含一个空字符。我们很快就会解释 #define 语句,暂时只须注意 PRAISE 后面的一对双引号表示其中的文本是一个字符串。编译器插入空字符这件事情。

请注意(这很重要的),scanf()只读取了 Hilary Bubbles 的名字 Hilary。scanf()开始读着取输入以后,会在遇到的第一个空白字符空格(blank),制表符(tab)或者换行符(newline)处停止读取。因此,它在遇到 Hilary 和 Bubbles之间的空格时,就停止了扫描。一般情况下,使用 %s的 scanf()只会把一个单词而不是把整个语句作为字符串读入。C 使用其他读取输入函数(例如 gets())来处理一般的字符串,后面的几章将更全面地研究字符串函数。

---------------------------------------------------------------------------------

字符串和字符

字符串常量“x”与字符常量‘x’不同,其中一个区别是‘x’属于基本类型(char),而“x”属性派生类型(char数组)。第二个区别是“x”实际上由两个字符(‘x’和空字符“\0”)组成。


4.2.3 srtlen ()函数

上一章提到了 sizeof 运算符,它以字节为单位给出数据的大小。strlen()函数以字符为单位给出字符串的长度。因为一个字符只占用一个字节,所以你可以认为把这两个函数应用到同一个字符串时可以得到相同的结果,然而事实并非如此。请像程序清单 4.3中那样向前面的示例程序中添加几行,看看为什么会是这样。

程序清单 4.3 praise2.c程序
--------------------------------------------------------------
/* praise2.c */
#include
#include // 提供 strlen()函数的原型 (头文件)
#define PRAISE "What a super marvelous name !"
int main (void)
{
char name[40];
printf("What's your name?\n");
scanf("%s",name);
printf ("Hello,%s. %s\n",name,PRAISE);
printf ("Your name of %d letters occupies %d memory cells.\n",strlen(name),sizeof name);
printf ("The phrase of praise has %d letters",strlen(PRAISE));
printf ("and occupies %d memory cells.\n",sizeof PRAISE);
getchar();
getchar();
return 0;
}

如果你使用的是 ANSI C 之前的编译器,那么可能需要删除下面这一行:
#include

string.h 文件包含许多与字符串相关的函数的原型,包括 strlen()。第11章“字符串和字符串函数”更全面地讨论了这个头文件(随便提一下,一些 ANSI 之前的 UNIX系统使用头文件 strings.h 而非 string.h 来包含对字符串函数的声明)。

更一般的情况下,C 把 C函数库分成多个相关函数的系列,并为每个系列提供一个头文件。例如,printf()和 scanf()属于标准输入和输出函数系统,使用 stdio.h 头文件。strlen()函数和其他一些与字符串相关的函数(例如字符串复制和字符串搜索的函数)同属一个系列,并在 string.h头文件中定义。

请注意,程序清单 4.3使用两个方法来处理很长的 printf()语句。第一个方法是让一个 printf()语句占用两行(你可以在参数之间断开一行,但不要在一个字符串的中间,例如在一对引号之间断开一行)。第二个方法使用两个 printf()语句来输出一行,换行符(\n)只出现在第二个语句中。运行该程序会产生如下交互信息:

What's your name?
Morgan Buttercup
Hello,Mergan,What a super marvelous name!
Your name of 6 letters occupies 40 memory cells.
The phrase of praise has 28 letters and occupies 29 memory cells.

看看发生了什么。根据 sizeof 运算符的报告,数组 name 有40个内存单元。不过只用了其中前 6人单元来存放 Morgan,这是 strlen()所报告的。数组 name的第7个单元中放置空字符,它的存在告诉 strlen()在哪里停止计数。

对于 PRAISE,你会发现 strlen()再一次给出了字符串中的字符(包括空格和标点符号)的准确数目。sizeof 运算符提供给你的数目比前者大1,这是因为它报导用来标志字符串结束的不可见的空字符也计算在内。你并没有告诉计算机为存储该语句分配多大内存,它必须自己计算出双引号之间的字符的数目。

还有一点,前一章在 sizeof中使用了圆括号,但是本例却没有。是否使用圆括号取决于你是想获取一个类型的大小还是想获取某个具体量的大小。圆括号对于类型是必需的,而对于具体量则是可选的。也就是说,你应该使用 sizeof(char)或 sizeof(float),但是可以使用 sizeof name 或
sizeof 6.28 。不过,在所有情况下都使用圆括号会更好,例如 sizeof(6.28) 。

上一个示例程序中使用 strlen() 和 sizeof只是为了满足用户潜意识中的好奇心。然而在实际应用中,strlen()和 sizof都是重要的编程工具。例如,strlen()在各种类型的字符串程序都很有用,你将在第 11章中看到这一点。

下面我们来看 #define 语句

4.3 常量和 C 预处理器

有时需要在程序中使用常量。例如,可以按照如下形式给出圆的周长:

circumference = 3.14159 * diameter;

这里,常量 3.14159代表著名的常量 pi(∏)。要使用常量,只须像本例这样键入一个衬的值即可。不过,有一些强有力的理由可以说服我们使用符号常量来代替这种方法。也就是说,可以使用如下语句,并由计算机在稍后用实际值完成替换:

circumference = pi * diameter;

为什么使用符号常量比较好呢?首先,一个名字比一个数字告诉你的信息多。请比较如下两个语句:

owed = 0.015 * housevalue;
owed = taxrate * housevalue

如果你在通读很长的程序,那么第二种形式的意义更加清楚。

而且,假设你在多个地方使用了同一个常量,并且必须改变它的值。别忘了,税率是会变动的。那么你只需要改变这个符号常量的定义,而不用在程序中查找出这个常量的每个地方并做修改。

那么,如何建立一个符号常量呢?一个方法是声明一个变量,并设置该变量等于所需的常量。
你可以这样编写:

float taxrate;
taxrate = 0.015;

这就提供了一个符号名。但 taxrate 是一个变量,所以你的程序可能会意外地改变它的值。幸运的是, C还有更好的方法。

C 原来就提供了的一个更好的方法是 C预处理器。在第2章“C语言概述”中,你已经看到预处理器如何使用 #include 加入另一个文件中的信息。预处理器也允许定义常量。只须在程序文件的顶部添加如下信息即可:

#define TAXRATE 0.015

当编译你的程序时,值0.015将会在 TAXRATE 出现的每个地方替代它。这称为编译时代入法(copmile-time substitution)。到你运行程序的时候,所有的替代都已经完成了。这样的定义常量通常被称为明显常量(manifest constant)。

请注意格式: 首先是 #define,其次是常量的符号名(TAXRATE),接着是常量的值(0.015)(请注意,这个结构中没有用 = 符号)。所以一般形式如下:

#define NAME value

你可以用自己选择的符号名和合适的值来代替 NAME 和 value。没有使用分号是因为这是一种替代机制,而不是 C的语句。为什么 YAXRATE 要大写呢?键入大写的常量是一个明智的 C传统。这样,当你在程序中间遇到大写的符号名时,你会立即知道这是一个常量而非变量。大写常量只觉过是使程序更易阅读的技术之一。如果你没有大写常量,程序也会照常工作,但是应该培养大写常量的好习惯。

另外还有一个不常用的命名约定,就是名字前面加上前缀 c_ 或者 k_来表示常量,从而得到像 C_level 或者 K_line 这样的名字。

符号常量所用的名字必须满足变量命名规则。可以使用大写和小写字母,数字和下划线字符。第一个字符不能是数字。程序清单 4.4给出了一个简单的示例。


程序清单 4.4 pizza.c 程序
----------------------------------------------------------
/* pizza.c --- 在这个比萨饼的例子中使用定义常量 */
#include
#define PI 3.14159
int main (void)
{
float area, circum,radius;

printf ("What is the radius of your pizza?\n");
scanf ("%f",&radius);
area = PI * radius * radius;
circum = PI * radius * 2.0;
printf ("Your basic pizza parameters are as folows:\n");
printf ("circumference = %1.2f. area = %1.2f\n",circum,area);
getchar();
getchar();
return 0;
}

printf()语句中的 %1.2F使输出结果四舍五入为保留两位小数。当然,该程序并不能真正反映你对比萨饼主要关注的方面,但是它确实能说明比萨饼程序设计领域中的一个小问题。下面是一个运行示例:

What is the radius your pizza?
6.0
your basic pizza parameters are as follows;
circumference = 37.70, area = 113.10

#define 语句也可以用于定义字符和字符串常量。前者用单引号。后面用双引号。下面的例子是正确的:

#define BEEP '\a'
#define TEE 'T'
#define ESC '\033'
#define OOPS "Now you have done it !"

请记住,符号名后的所有内容都被用来代替它。不要犯这样常见的错误:
/* 下面的定义是错误的 */
#efine TOES = 20;

如果你这样做,TOES 将会被 =20而不是 20所代替。如果这样,那么下面的语句:

digits = fingers + TOES;

经转换后会变成下面所示的错误表示方法:

digits = fingers +=20;

 

4.3.1 const 修饰符

C90 新增了一种创建符号常量的第二种方法,即可以使用 const关键字把一个变量声明转换成常量声明:

const int MONTHS = 12; // MONTHS 是一个代表 12的符号常量

这就使 MONTHS成为一个只读值。也就是说,你可以显示 MONTHS,并把它用于计算中,但是你不能改变 MONTHS 的值。这个新方法比使用 #define 更灵活; 第12章“存储类,链接和内存管理”讨论了该方法以及 const 的其他用法。实际上,C还有第三种方法可以创建符号常量,那就是第14章“结构和其他数据形式”所讨论的枚举(enum)功能

4.3.2 系统定义的明显常量

C 头文件 limits.h 和 float.h 分别提供有关整数类型和浮点类型的大小限制的详细信息。每个文件都定义了一系列应用于你的实现的明显常量。例如,linmits.h 文件包含与下类似的行:

#define INT_MAX +32767
#define INT_MAN -32768

这些常量代表 int 类型的最大和最小的可能值。如果你的系统使用 32位的 int,那么该文件将会为这些符号常量提供不同的值。该文件定义了所有整数类型的最小和最大值。如果你包含了 linmits.h文件,(如: #include )那么可以使用如下代码:

printf("Mzximum int value on this system = %d\n", INT_MAX);

如果你的系统使用 4字节的 int,那么该系统的 linmits.h文件就会提供符合 4个字节整数限制的 INT_MAX 和 INT_MIN的定义。表 4.1 列出了 linmits.h 中的一些常量。

表 4.1 limits.h 中的一些符号常量

-----------------------------------------
符号常量 含义
-----------------------------------------
CHAR_BIT 一个 char 的位数
-----------------------------------------
CHAR_MAX char 类型的最大值
-----------------------------------------
CHAR_MIN char 类型的最小值
-----------------------------------------
SCHAR_MAX signed char 类型的最大值
-----------------------------------------
SCHAR_MIN signed char 类型的最小值
-----------------------------------------
UCHAR_MAX unsigned char 类型的最大值
-----------------------------------------
UCHAR_MIT unsigned char 类型的最小值
-----------------------------------------
SHRT_MAX short 类型的最大值
-----------------------------------------
SHRT_MIN short 类型的最小值
-----------------------------------------
USHRT_MAX unsigned short 类型的最大值
------------------------------------------
USHRT_MIN unsigned short 类型的最小值
------------------------------------------
INT_MAX int 类型的最大值
------------------------------------------
INT_MIN int 类型的最小值
------------------------------------------
UINT_MAX unsigned int 类型的最大值
------------------------------------------
UINT_MIN unsigned int 类型的最小值
------------------------------------------
LONG_MAX long 类型的最大值
------------------------------------------
LONG_MIN long 类型的最小值
------------------------------------------
ULONG_MAX unsigned long 类型的最大值
------------------------------------------
LLONG_MAX long long 类型的最大值
------------------------------------------
LLONG_MIN long long 类型的最小值
------------------------------------------
ULLONG_MAX unsigned long long 类型的最大值
----------------------------------------------
ULLONG_MIN unsigned long long 类型的最小值
----------------------------------------------


同样,float.h文件定义了诸如 FLT_DIG 和 DBL_BIG 之类的常量,这些常量分别代表 float类型 和
double 类型支持的有效的个数。 表4.2列出了 float.h 中定义的一些常量(可以使用文本编辑器来打开和查看系统使用的 float.h 头文件)。本示例与 float类型相关。为 double 和long long double类型也定义了相对应的常量,只是常量名中的 FLT 被 LDBL所代替(该表假设系统用 2 的幂表示浮点数)


表 4.2 float.h 中的一些符号常量
-----------------------------------------------------------
符号常量 含义
-----------------------------------------------------------
FLT_MANT_DIG float 类型的尾数位数
-----------------------------------------------------------
FLT_DIG float 类型的最少有效数字位数 (十进制)
-------------------------------------------------------------------------
FLT_MIN_10_EXP 带有全部有效数字的 float 类型的负指数的最小值(以10为底)
-------------------------------------------------------------------------
FLT_MAX_10_EXP 带有全部有效数字的 float 类型的负指数的最大值(以10为底)
-------------------------------------------------------------------------
FLT_MIN 保留全部精度的 float 类型正数的最小值
-------------------------------------------------------------------------
FLT_MAX float 类型正数的最大值
-------------------------------------------------------------------------
FLT_EPSILON 1.00 和比 1.00 大的最小的 float类型值之间的差值
-------------------------------------------------------------------------

PS : 如果要定义 double 的符号常量 用 LDBL 代替 FLT (如 LDBL_MAX)。


程序清单 4.5 示意了如何使用 float.h 和 limits.h 中的数据(请注意,许多当前的编译器还不完全支持 C99标准也许还不接受 LLONG_MIN 标识符)

程序清单 4.5 defines.c 程序
---------------------------------------------------------------
/* defines.c --- 使用 linmit.h 和 float.h 中定义的常量 */
#include
#include
#include
int main (void)
{
printf ("Some number linmits for this system:\n");
printf ("Biggest int: %d \n",INT_MAX);
printf ("Smallest unsigned long: %lld \n",LLONG_MIN);
printf ("One byte = %d bits on this system \n",CHAR_BIT);
printf ("Largest double: %e \n",DBL_MAX);
printf ("Smallest normal float: %e \n",FLT_MIN);
printf ("Float precistion = %d digits \n", FLT_DIG);
printf ("Float epsilon = %e \n",FLT_EPSILON);
getchar();
return 0;
}

下面是示例的输出

Some number linmits for this system:
Biggest int: 2147483647
Smallest unsigned long: -9223372036854775808
One byte = 8 bits on this system
Largest double: 1.797693e+308
Smallest normal float: 1.175494e-038
Float precistion = 6 digits
Float epsilon = 1.192093e-007

C 预处理器是个极其有用的工具,所以在可能的时候要尽量利用它。在本书的后面的章节中你会看一更多的相关应用。

 


4.4 研究和利用 printf() 和 scanf()

printf()和 scanf()函数使你能够与程序通信。它们被称为输入/输出函数,或者被简称为 I/O函数。它们不是只有你可以使用的 C I/O函数,而且是通用的 I/O函数。在历史上,这些函数就像 C函数库中的所有其他函数一样,不是 C 的定义的一部分。最初的时候 C 把输入/输出的实现留给编译器的编写者,这样可以更好地使 I/O与特定的机器相匹配。为了兼容起见,不同的实现中都带有各自的
scanf()和 printf(),但它们之间偶尔有一些差异。C90 和 C99 标准描述了这些函数的标准版本,我们将遵循这个标准。

虽然 printf()是输出函数,scanf()是输入函数,但是它们的工作几乎相同,都使用了控制字符串和参数列表。我们将依次给出 printf()和 scanf()函数的工作原理。


4.4.1 printf(函数)

请求 printf()打印变量的指令取决于变量的类型,例如,在打印整数时使用 %d符号,而在打印字符时使用 %c 符号。这些符号被称为转换说明,因为它们指定了如何把数据转换成可显示的形式。我们将会列出 ASCI C 标准为 printf()提供的各种转换说明,然后示例如何使用一些较为常用的转换说明。表 4.3给出了转换说明符和用这些转换说明符打印的输出类型。


表 4.3 转换说明符及作为结果的打印输出
---------------------------------------------------------------
转换说明 输出
---------------------------------------------------------------
%a (%A) 浮点数,十六进制数字和 p- P-记数法 (C99)
---------------------------------------------------------------
%c 一个字符
---------------------------------------------------------------
%d 有符号十进制整数
--------------------------------------------------------------
%e (%E) 浮点数 e- E- 记数法
--------------------------------------------------------------
%f 浮点数,十进制记数法
----------------------------------------------------------------------------------
%g (%G) 根据数值不同自动选择 %f 或 %e。 %e格式在指数小于-4 或者大于等精度时使用
----------------------------------------------------------------------------------
%i 有符号十进制整数 (与 %d相同)
-------------------------------------------------------------
%o 无符号八进制整数
-------------------------------------------------------------
%p 指针
-------------------------------------------------------------
%s 字符串
-------------------------------------------------------------
%u 无符号十进制整数
-------------------------------------------------------------
%x (%X) 使用十六进制数字 0f 0F 的无符号十六进制整数
-------------------------------------------------------------
%% 一个百分号
-------------------------------------------------------------


4.4.2 使得 printf()

程序清单 4.6 中包含的程序使用了一些转换说明

程序清单 4.6 printout.c 程序
--------------------------------------------------
/* printfout.c -- 使用转换说明符 */
#include
#define PI 3.141593
int main (void)
{
int number = 5;
float expresso = 13.5;
int cost =3100;
printf (" The %d CEOs drank %f cups of expresso.\n",number,expresso);
printf ("The value of pi is %f \n",PI);
printf ("Farewell thou art too dear for my possessing,\n");
printf ("%c %d\n",'$',2*cost);
getchar();
return 0;
}

显然,输出就是是:
The 5 CEOs drank 13.500000 cups of expresso.
The value of pi is 3.141593
Farewell thou art too dear for my possessing,
$ 6200


以下是 printf()的使用格式:

printf (Control - string, item1,item2...);

item1,item2 等等都是要打印的项目。它们可以是变量,也可以是常量,甚至可以是在打印之前进行计算的表达式。控制字符串(control-string)是一个描述项目如何打印的字符串。正好第3章“数据和 C”中所提及的,控制字符串应该为每个要打印的项目包含一个转换说明符。例如,考虑如下语句:

printf ("The %d CEOs drank %f cups of expresso.\n",number,expresso);

控制字符串就是双引号内的语句。它包含了分别对应于 number 和 expresso (要显示的两个项目)的两个转换说明符。

下面是示例程序中的另外一行:

printf ("The value of pi is %f \n",PI);

这一次,项目列表中只有一个成员,即符号常量 PI。

控制字符包含了两种形式截然不同的信息:
·实际要打印的字符
·转换说明


警告:
不要忘记给控制字符串后面的列表中的每个项目都使用一个转换说明。若忘记这个基本的要求是一件可怕的事情。千万不要写出下面这样的语句:

printf ("The score was squids %d,slugs %d \n",scorel);

这里没有值使用第二个 %d.这种错误语句的结果取决于你的系统,就算在最好的情况下,你也只能得到无意义的值。


如果你只想打印一个语句,那么你就不需要任何转换说明,如果你只想打印数据,你无须加入任何说明内容。程序清单 4.6中,下列每个语句都是可接受的:

printf ("Farewell thou art too dear for my possessing \n");
printf (%c %d \n",'$',2 * cost);

请注意在第二个语句中,打印列表的第一项是一个字符常量而非常量,而第二项则是一个乘法表达式。这就说明了 printf()使用的是值,无论该值的变量,常量还是表达式。

因为 printf()函数使用 % 符号来标识转换说明,所以打印 % 符号本身就成了一个小问题。如果你单独使用一个 % 符号,那么编译器就会认为你千年是一个转换说明符号。解决办法很简单,就是使用两个 % 符号。

pc = 2 * 6;
printf ("only %d%% of sally's gribbles were edible.\n",ps);

下面是输出结果:

only 12% of sally's gribbles were edible.


4.4.3 printf() 转换说明修饰符

可以在 % 和定义转换字符之间通过插入修饰符对基本的转换说明加以修改。表 4.4 和 4.5列出了可以插入的合法字符。如果使用了一个以上的修饰符,那么它们应该与其表 4.4 中出现的顺序相同。并不是所有的组合都是可能的。该表中有些是 C99新增加的部分,你的 C实现也许还不支持这里所示的所有选项。

表 4.4 printf()修饰符
----------------------------------------------------------------
修饰符 意义
--------------------------------------------------------------------------------
标志 五种标志(-,+,空格,# 和 0)都奖在表4.5中描述。可以使用零个或者多个
标志。 示例 “%-10d”
--------------------------------------------------------------------------------
digit(s) 字段宽度的最小值。如果该字段不能容纳要打印的数或者字符串,系统就会使用
更宽的字段。 示例 “%4d”
---------------------------------------------------------------------------------
.digit(s) 精度。对于 %e,%E 和 %f 转换,是将要在小数的右边打印的数字的位数。对于
%g 和 %G 转换,是有效的数字的是大位数。对于 %s 转换,是将要打印的字符的最大
数目。对于整数转换,是将要打印的数字的最小位数,如果必要,要使用前导零来达到这
个位数。只使用“.”表示其后跟随一个零,所以 %.f 与 %.0f相同。示例:
“%5.2f”打印一个浮点数,它的字段宽度为 5个字符,小数点后有两个数字
-----------------------------------------------------------------------------------
h 和整数转换说明符一起使用,表示一个 short int 或 unsigned short int 类型数值
示例:“%hu”,“%hx”和 “%6.4hd”
-----------------------------------------------------------------------------------
hh 和整数转换说明符一起使用,表示一个 signed char 或 unsigned char 类型数值
示例:“%hhu”,“%hhx”和 “%6.4hhd”
------------------------------------------------------------------------------------
j 和整数转换说明符一起使用,表示一个 intmax_t 或 uintmax_t 值
示例:“%jd”和 “%8jx”
------------------------------------------------------------------------------------
l 和整数转换说明符一起使用,表示一个 long int 或 unsigned long int 类型值
示例:“%ld”和“%8lu”
-----------------------------------------------------------------------------------
ll 和整数转换说明符一起使用,表示一个 long int 或 unsigned long long int 类型值
示例:“%lld”和 “%8llu”
-------------------------------------------------------------------------------------
L 和浮点转换说明符一起使用,表示一个 long double值
示例:“%Lf”和 “%10.4Le”
-------------------------------------------------------------------------------------
t 和整数转换说明符一起使用,表示一个 ptrdiff_t 值(与两个指针之间的差相对应的类型 )示例:“%td”和 “%12ti”
---------------------------------------------------------------------------------------
z 和整数转换说明符一起使用,表示一个 size_t值(sizeof返回的类型)
示例:“%zd”和 “%12zx”
----------------------------------------------------------------------------------------


PS: 浮点型参数的转换

有用于打印浮点类型 double 和 long double 的转换说明符,但没有用于 float的说明符。原因是在 K&R C中 float值在被用于表达式中或者被用作参数之前,会被自动转换为 double类型。一般情况下,ANSI C不会自动把 float 转换成 double。不过,为了保护大量现有的假设 float参数会被转换成 double的程序,printf()和其他任何不使用显式原型的 C函数的所有float参数仍然会自动被转换成 double。因此,不管是 K&R C 还是 ANSI C,都无需专门的转换说明符来显示 float类型。

 

printf()的标志
--------------------------------------------------------------------------------
标志 意义
-----------------------------------------------------------------------------------------
- 项目的左对齐的,也就是说,会把项目打印在字段的左侧开始处 示例:“%-20s”
-----------------------------------------------------------------------------------------
+ 有符号的值若为正,则显示带加号的符号;若为负,则带减号的符号 示例:“%+6.2f”
------------------------------------------------------------------------------------------
空格 有符号的值若为正,则显示时带前导空格(但是不显示符号);若为负,则带减号的符号。
+标志会覆盖空格标志。示例: “%6.2f”
------------------------------------------------------------------------------------------
# 使用转换说明的可选形式。若为 %o格式,则以 0 开始;若为 %x 和 %X格式,则以0x或0X开始
对于所有的浮点形式,#保证了即使不跟任何数字,也打印一个小数点字符。对于 %g 和 %G格式
它防止尾随零被删除 示例:“%#o”,“%#8.0f”和 “%+#10.3E”
-----------------------------------------------------------------------------------------
0 对于所有的数字格式,用前导零而不是有填充字段宽度。如果出现-标志或者指定了精度(对于 整数)则忽略该标志。示例:“%010d”和 “%08.3f”
-----------------------------------------------------------------------------------------


一·使用修饰符和标志示例

我们开始使用这些修饰符,先来看看打印整数时字段宽度修饰符的作用。考虑程序清单 4.7中的程序。

程序清单 4.7 width.c程序
---------------------------------------------------------------------------
/* width.c --- 字段宽度 */
#include
#define PAGES 931
int main (void)
{
printf ("*%d*\n",PAGES);
printf ("*%2d*\n",PAGES);
printf ("*%10d*\n",PAGES);
printf ("*%-10d*\n",PAGES);
getchar();
return 0;
}

程序清单 4.7使用 4个不同的转换说明把相同的值打印了 4次。它使用一个星号(*)来标识出每个字段的开始和结尾。输出结果如下:

*931*

*931*

*       931*

*931       *

第一个转换说明是不带任何修饰符号的 %d,它生成一个与要打印的整数宽度相同的字段。这是显示文件选项,也就是说,如果你没有进一步给出命令,这就是打印的结果。第二个转换说明是 %2d。它指示应该产生 %10d。这将产生一个有 10个空格那么宽的字段,于是在星号之间有 7个空白和 3个数字,并且数字位于整个字段的右端。最后一个说明是 %-10d。它也产生宽 10个空格的字段,标志“-”把数字放在左端,就像广告中那样。你习惯后,该系统就易于使用,并使你能够很好地控制输出结果的外观。试着改变PAGES的值,看看不同位数的数字是如何打印的。

现在来看一些浮点格式。请输入,编译并运行程序清单 4.8的程序。

程序清单 4.8 floats.c 程序
--------------------------------------------------------------
// floats.c --- 一些浮点数的组合
#include
int main (void)
{
const double RENT = 3852.99; //以 const方法定义的常量
printf ("*%f*\n",RENT);
printf ("*%e*\n",RENT);
printf ("*%4.2f*\n",RENT);
printf ("*%3.1f*\n",RENT);
printf ("*%10.3f*\n",RENT);
printf ("*%10.3e*\n",RENT);
printf ("*%+4.2f*\n",RENT);
printf ("*%010.2f*\n",RENT);
getchar();
return 0;
}

这一次,程序使得关键字 const 创建了一个符号常量。输出为:

*3852.990000*
*3.852990e+03*
*3852.99*
*3853.0*
*  3852.990*
* 3.853e+03*
*+3852.99*
*0003852.99*

本例以显示文件格式 %f 开始。在这种情形下,有两个默认项目:字段宽度和小数点右边的数字的数目。第二个默认项目的值是 6个数字,字段宽度就是容纳数字所用的空间。

接下来是 %e 的默认格式。它在小数点左侧打印一个数字,在小数点的右侧打印 6个数字。我们得到了一堆数字!解决方法是指定小数点右边的小数位的数目,本段中接下来的 4个示例就是这样做的。请注意,第 4个和第 6个示例对输出进行了四舍五入。

最后,+标记使得结果数字和它的代数符号一起打印,在这里该符号就是加号符号:0标志产生前导零以使结果填充整个字段。请注意,在说明符 %010 中第一个 0 是一个标志,剩余的数字(0)指定字段宽度。

你可以修改 RENT 值来看看不同大小的值如何打印。程序清单 4.9给出了另外的一些组合。


程序清单 4.9 flags.c程序
----------------------------------------------------------------------------
/* flags.c --- 一些格式标志的使用示例 */
#include
int main (voic)
{
printf ("%x %X 5#x \n",31,31,31);
printf ("**%d**% d**% d**\n",42,42,-42);
printf ("**%5d**%5.3d**%05d**%05.3d**\n",6,6,6,6);
getchar();
return 0;
}

输出如下:

1f 1F 5#x
**42** 42**-42**
** 6** 006**00006** 006**

首先,1f 等于 31的十六进制。x说明符输出 1f,而 X 说明符输出 1F。使用 #标志使输出以 0x开始。第二行示范了如何在说明符中使用空格以在正值之前产生一个前导空格(在负值之前不产生前导空格)。这将使有效位相同的正值和负值以相同字段宽度打印输出,因此结果看起来会令人舒服一些。

第三行说明如何在整数格式中使用精度说明符(%5.3d)来产生足够的前导零以填满要求的最小数字位数(这里是3),而使用 0 标志将会用前导零填满整个字段宽度,最后,如果 0 标志和精度说明符同时出现,那么 0 标志就会被忽略。

现在我们查看一些有关字符串的选项。考虑程序清单 4.10的示例:

程序清单 4.10 strngs.c 程序
----------------------------------------------
// strngs.c -- 字符串的格式化
#include #define BLURB "Authentic imitation"
int main (void)
{
printf ("/%2s/\n",BLURB);
printf ("/%24s/\n",BLURB);
printf ("/%24.5s/\n",BLURB);
printf ("/%-24.5s/\n",BLURB);
getchar();
return 0;
}

下面是输出

/Authentic imitation/
/ Authentic imitation/
/ Authe/
/Authe /


请注意系统如何扩展字段以包含所有指定的字符。同时请注意:精度说明符是如何限制所打印的字符
的数目的。格式说明符中的 .5 告诉 printf()只打印 5个字符。另外,“-”修饰符使文本左对齐输出。


二 应用你的知识

现在,你已经看到了几个示例。如何用一个语句打印如下形式的内容?

The NAME family just may be $XXX.XX dolbuarw richer!

这里,NAME 和 XXX.XX 代表由程序中的变量(比如说 name[40]和 cash)所提供的值。

解决方法如下:

printf ("The %s family just may be $%.2f richer \n",name,cash);

 

4.4.4 转换说明的意义

我们深入探讨一下转换说明的意义。它把存储在计算机中的二进制格式的数值转换成一系列字符(一个字符串)以便于显示。例如,数字 76 的内部存储形式可能是二进制的 01001100. %d 转换说明符将之转换成字符 7 和 6,并显示成 76 。 %x 转换则把相同的值(01001100)转换成十六进制的表示法 4c 。 %c 把相同的值转换成字符表示法 L

术语“转换”(conversion)可能会带来误导,因为它可能意味着用转换后的值代替原值。转换说明实际上就是翻译说明: %d 意为“把给定的值翻译成十进制整数文本表示,并打印出来”。

一·不匹配的转换

显然,应该使转换说明与要打印的值的类型相匹配。通常情况下都有多种选择。例如,如果想打印一个 int 类型值,可以使用 %d %x 或 %o, 所有这些说明符都假定打印一个 int 类型的值,它们仅仅提供该值不同的表示形式。同样,也可以使用 %f %e 或 %g 来表示 double类型的值。

如果转换说明与类型不匹配会怎样呢?上章中你已看到不匹配会导致一些问题。这一点是非常重要的,一定要牢记。所以程序清单 4.11 又给出了整数系列内几个不匹配的示例。

程序清单 4.11 intconv.c 程序
-----------------------------------------------------------------
/* intconv.c --- 一些不匹配的整数转换 */
#include
#define PAGES 336
#define WORDS 65618
int main (void)
{
short num = PASES;
short mnum = -PASES;

printf ("num as short and unsigned short : %hd %hu \n",num,num);
printf ("-num as short and unsinged short : %hd %hu \n",mnum,mnum);
printf ("num as int and char: %d %c \n",num,num);
printf ("WORDS as int,short,and char: %d %hd %c\n",WORDS,WORDS,WORDS);
getchar();
return 0;
}

我们的系统产生如下结果
num as short and unsigned short : 336 336
-num as short and unsinged short : -336 65200
num as int and char: 336 P
WORDS as int,short,and char: 65618 82 R

请看第一行,你会看到 %hd 和 %hu 产生 336 作为变量 num的输出,这没有任何问题。然而,mmun的 %u (无符号)版本的输出结果则为 65200,而非你所期望的 336 。这是由于有符号 short int 值在我们的参考系统中的表示方式所造成的。首先,它们的大小为 2字节。其次,该系统使用一种被称为 2 的补码(two's complement)的方法来表示有符号整数。在这种方法中,数字 0 到 32767 代表它们本身,而数字 32768 到 65535 则代表负数, 65535代表 -1 ,65534代表 -2,以此类推。因此,-336 由65536-336,也即 65200来表示,所以当被解释成一个有符号整数时, 65200代表 -336,而被解释成无符号整数时,65200则代表65200 。一宁要谨慎!一个数字可以被解释成两个不同的值。不是所有的系统都用该方法来表示负整数。虽然如此,还是有一个准则:不要期望 %u 转换能把数字和符号分开。

第三行显示如果你试图把一个 大于 255的值转换成字符,将会发生什么事情。在该系统上,一个 short int 占用 2个字节,一个 char占用 1个字节。当 printf()使用 %c 打印 336时,它只查看用于存放 336的两字节中的 1个字节。这种截断(育参见图 4.8)相当于用 256 除一个整数,并取其余数。在这种情况下,余数是 80 也就是字符 P的ASCII码值。更技术一些,你可以说,该数字被解释成“以 256为模”(modulo 256),意即使用数字被256 除的余数。

-----图 4.8 --- 把 336 读作一个字符

0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0
| | |
二进制数 336 | 二进制数字 80 ,(被取一半)字符 P 的ASCII 码值
----------------------------------------------------------------------

最后,我们试着打印一个比系统允许的最大的 short int (65618)。这次计算机又进行了模运算。我们的系统根据数字 65618 的大小,将它存储为 4 个字节的整数值。当我们使用 %hd 说明打印它时,printf()只使用最后 2个字节。这相当于使用被 65536 除后得到的余数。在这里,余数是 82.鉴于负数的存储方法,在 32767 和 65536 之间的余数会被打印成负数。整数大小不同的系统将会做出相同的动作,但是会产生不同的数值。

当混淆了整数和浮点类型时,结果更是千奇百怪。例如,考虑程序清单4.12

程序清单 4.12 floatcnv.c程序
-----------------------------------------------------
/* floatcnv.c --- 不匹配的浮点数转换 */
#include
int main (void)
{
float n1 = 3.0;
double n2 = 3.0;
long n3 = 2000000000;
long n4 = 1234567890;

printf ("%.le %.le %.le %.le\n",n1,n2,n3,n4);
printf ("%ld %ld\n",n3,n4);
printf ("%ld %ld %ld %ld\n",n1,n2,n3,n4);
getchar();
return 0;
}

在我们的系统上,程序清单 4.12 产生如下输出
3e+000 3e+000 3e+046 3e-307
2000000000 1234567890
0 1074266112 0 1074266112

输出的第一行显示,使用 %e 说明符并没有把整数转换成浮点数。例如,考虑当你试图使用 %e说明符打印 %3(long)类型时,发生了什么。首先,%e 说明符使 printf()期望一个 double类型值,在本系统这是一个 8字节的值。当 printf()查看 n3(它在本系统中是一个 4字节值)时,它也查看邻近的 4个字节因此,它查看了一个 8字节的单元,其中只有一部分是真正的 n3 。 接着,它把该单元内的位解释成一个浮点数。例如,一些位将会解释成指数。所以即使 n3 的位数正确,在 %e作用下和 %ld 作用下它们的解释也是不同的。最终结果是无意义的。

第一行也就是说明了前面所提到的:当被用作 printf()的参数时,float被转换成 double。在本系统中,float是 4字节的,但是 n1 会扩展到 8字节以使 printf()能够正确显示它。

输出的第二行显示,如果使用的说明符正确,printf()就可以正确打印 已经 n3 和 n4

输出的第三行表明,如果 printf()语句在其他地方出现不匹配错误,即使正确的说明符也会产生虚假的结果。正如你能料到的,试图用 %ld 说明符打印一个浮点数会失败。但是在这里,试图使用 %ld 一个 long 类型居然也失败了!问题出在 C把信息传递给函数的方式中。失败过程的确切细节是依赖于实现的,下面这段文字针对一个具有代表性的系统进行了讨论。


PS: 参数传递

参数传递的机制随之实现不同而不同。下面是参数传递在我们的系统中的工作原理。函数调用如下:

printf ("%ld %ld %ld %ld \n",n1,n2,n3,n4);

该调用告诉计算机把变量 n1,n2,n3 和 n4的值传递给计算机,计算机把它们放置到被称为堆栈(stack)的一块内存区域中来实现。计算机根据变量的类型而非转换说明符把这些值放到堆栈中。所以,n1 在堆栈中占有用 8个字节(float 被转换成 double)。同样,n2 占有用 8个字节,而 n3 和 n4则分别占用 4个字节。然后控制转移到 printf()函数。该函数从堆栈把值读出来,但是在读取时,它根据转换说明符去读取。%ld 说明符指出,printf()应该读取 4个字节,所以 printf()在堆栈中读取前 4 个字节作为它的第一个值。这就是 n1 的前半部分,它被解释成一个长整数(long integer)。下一个 %ld 说明符再读取 4个字节,这就是 n1 的后半部分,它被解释成第二个长整数
(long integer)(请参见图 4.9)。同样,%ld 的第三个和第四个实例使得 n2 的前部分和后半部分被读出,并被解释成两个长整数(long integer)。所以,虽然 n3 和 n4 的说明符都正确,但是 printf()仍然读取了错误的字节。



二·printf ()的返回值

正如第2章中所提到的,C函数一般都有一个返回值。这就是由函数计算并返回给调用程序的值。例如,C 函数库包含一个 sqrt()函数,它接受一个数字作为参数,并返回该参数的平方根。返回值可以赋给一个变量,也可以用在计算中,还可以作为参数传递。总之,它可以像任何其他值那样使用。printf()函数也有一个返回值,它返回所打印的字符的数目。如果输出错误,那么 printf()会返回一个负数(printf()的一些老版本会有不同的返回值)。

printf()的返回值是其打印输出用途的附带功能,通常很少被用到。使用返回值的一个可能原因是要检查输出错误。在向文件中输出而非向屏幕上输出的时候,这是很常用的。如果一张已满的软盘拒绝写入,那么你的程序应该采取正确的行动,例如使终端蜂鸣 30秒钟。不过,在那样做之前,你必须了解 if 语句。 程序清单 4.13简单示例给出了确定返回值的方法


程序清单 4.13 prmtval.c 程序
--------------------------------------------------------------------------
/* protval.c --- 发现 printf()函数的返回值 */

#include
int main (void)
{
int bph2o = 212;
int rv;

rv = printf ("%d F is water's boiling point.\n",bph2o);
printf ("The printf() Function printfed %d characters.\n", rv);
getchar();
return 0;
}

输出如下:
212 F is water's boiling point.
The printf() Function printfed 32 characters.

首先,程序使用了 rv = printf(...);形式将返回值赋给 rv。因此,该语句执行两项任务:打印信息和对变量赋值。其次,请注意计数针对所有的打印字符,包括空格和不可见的换行字符。

三· 打印较长的字符串

有时,printf()语句会很长,以至于不能在一行被放下。因为空白字符(空格,制表符和换行符)如果不是用来分隔元素,那么 C将忽略它们,所以可以在多行放置一个语句,只要在元素之间放置换行符即可。例如,程序清单 4.13 使用两行来放置一个语句。

printf ("The printf() function printed %d charactes.\n",rv);

该行在逗号和 rv 之间断开。为了让读者知道该行末完,示例缩进了 rv。C 忽略了多余的空格。不过,你不能在引号括起来的字符串中间断行。如果你试图执行如下语句:

printf ("The printf() Function printfed %d characters.\n", rv);

C 将抱怨你在字符串常量中使用了一个非法字符。你可以在字符串中使用\n 符号来表示换行字符,但是在字符串中不能通过回车 (Enter 或 Return)键产生实际的换行字符。

如果你必须分割一个字符串,有三个选项可供选择,如程序清单4.14所示

程序清单 4.14 longstrg.c 程序
----------------------------------------------------------------
/* longstrg.c ---- 打印较长的字符串 */

#include
int main (void)
{
printf ("Here's one way to printf a");
printf ("long string.\n");
printf ("Here's the another way to printf a \ long string.\n");
printf ("Here's the newest way to printf a""long string \n");
getchar();
return 0;
}

下面是输出

Here's one way to printf along string.
Here's the another way to printf a long string.
Here's the newest way to printf a long string


方法1 是使用多个 printf()语句。因为打印的第一个字符没有以 \n字符结束,所以每二个字符串紧跟第一个字符串输出。

方法2 是用反斜线符号(/)和回车键的组合来结束第一行。这就使得屏幕上的文本另起一行,并且在字符串中不会包含换行字符。其效果就是在下一行继续该字符。不过,下一行必须像代码中那样从行的最左边开始。如果缩进了该行,比如说缩进了 5个空格,那么这 5个空格会变成字符串的一部分。

方法3 采用字符串连接的方法,它是 ANSI C的新方法。如果在一个用双引号引起来的字符串后面跟有另一个用双引号引起来的字符串,而且二都之间仅用空白字符分隔,那么 C会把该组合当作一个字符串来处理,所以以下三种形式是等同的:

在所有这些方法中,你应该在字符串内部包含所有必需的空格:“young”“lovers”变成“younglovers”,但组合“young”“lovers” 就是“young lovers”

 

4.4.5 使用 scanf()

现在,我们从输出转到输入,来看一下 scanf()函数。C函数包含了多个输入函数,scanf()是其中最常用的一个,因为它可以读取各种格式的数据。当然,从键盘输入的是文本,因为那些键生成文本字符:字母,数字和标点。比如说,当你想输入整数 2004时,你键入字符2,0,0和 4 。如果想把它们存储成一个数值而非字符串,那么你的程序必须把这个字符串逐个字符地转换成数值,就这是 scanf()所做的工作!它把输入的字符串转换成各种形式:整数,浮点数,字符和 C的字符串。它是
printf()的逆操作,后者把整数,浮点数,字符和 C的字符串转换成要在屏幕上显示的文本。

跟 printf()一样,scanf()使用控制字符串和参数列表。控制字符串指出输入将被转换的格式。主要的区别是在参数列表中。printf()函数使用变量名,常量和表达式,而 scanf()函数使用指向变量的指针。幸运的是,要使用该函数,不必对指针有任何了解,只需要记住下面这些简单规则:

·如果使用 scanf()来读取前面讨论过的某种基本变量类型的值,请在变量名之前加上一个 &。

·如果使用 scanf()把一个字符串读进一个字符数组中,请不要使用 &。


程序清单 4.15 给出一个短程序来说明这些规则

程序清单4.15 input.c 程序
--------------------------------------------------------------------
/* input.c ---- 什么情况下使用 & */
#include
int main (void)
{
int age;
float assets;
char pet [30];

printf ("Enter your ,assets,and favorite pet.\n");
scanf ("%d %f", &age,&assets); //此处需要使用 &
scanf ("%s",pet); // 对 char 数组不需要使用 &
printf (" %d $%.2f %s\n", age,assets,pet);
getchar();
getchar();
return 0;
}

下面是一个交互结果的示例;
Enter your ,assets,and favorite pet.
38
92360.88
11ama
38 $92360.88 11ama


scanf()函数使用空格(换行,制表符和空格)来决定怎样把输入分成几个字段。它依次把转换说明与字段相匹配,并跳过它们之间的空格。请注意我们分两行进行了输入。同样,也可以分一行或 5行输入,只要在每个输入项目之间至少键入一个换行符,空格或者制表符。唯一的例外就是 %c 说明,即使下一个字符是空白字符,它也会读取那个字符。我们很快还会再讨论这个主题。

scanf()函数所用的转换说明符与 printf()所用的几乎完全相同。主要的区别在于 printf()把 %f, %e ,%E,%g 和 %G 同时用于 float类型和 double类型,而 scanf()只把它们用于 float类型,而用于 double类型时要求使用 l 修饰符。表 4.6列出了 C99标准中描述的主要转换说明符。


表4.6 ANSI C 中 scanf()的转换说明符
-------------------------------------------------------------------------------------
转换说明符 意义
---------------------------------------------------------------------------------------
%c 把输入解释成一个字符
---------------------------------------------------------------------------------------
%d 把输入解释成一个有符号十进制整数
---------------------------------------------------------------------------------------
%e,%f,%g,%a 把输入解释成一个浮点数(%a 是 C99标准)
----------------------------------------------------------------------------------------
%E,%F,%G,%A 把输入解释成一个浮点数(%A 是 C99标准)
----------------------------------------------------------------------------------------
%i 把输入解释成一个有符号十进制整数
----------------------------------------------------------------------------------------
%o 把输入解释成一个有符号八进制整数
----------------------------------------------------------------------------------------
%p 把输入解释成一个指针 (一个地址)
----------------------------------------------------------------------------------------
%s 把输入解释成一个字符串,输入的内容以第一个非空白字符作为开始,并且包含直到
下一个空白字符的全部字符
-----------------------------------------------------------------------------------------
%u 把输入解释成一个无符号十进制整数
-----------------------------------------------------------------------------------------
%x %X 把输入解释成一个有符号十六进制整数
-----------------------------------------------------------------------------------------


也可以在表 4.6所示的转换说明符中使用修饰符。修饰符出现在百分号和转换字符之间。如果在一个说明符内使用多个修饰符,那么它们出现的顺序应该与在表 4.7中出现的顺序相同。


表 4.7 scanf()的转换修饰符
-------------------------------------------------------------------------------------
修饰符 意义
--------------------------------------------------------------------------------------
* 滞后赋值(请参见书中的文字部分) 示例: ‘%*d’
---------------------------------------------------------------------------------------
digit(s) 最大字段宽度:在达到最大字段宽度或者遇到第一个空白字符时(不管哪个先发生都一样 )停止对输入项的读取。 示例: “%10s”
----------------------------------------------------------------------------------------
hh 把整数读作 signed char 或 unsigned char
示例: “%hhd” “%hhu”
------------------------------------------------------------------------------------------
ll 把整数读作 long long 或者 unsigned long long (c99)
示例 “%lld” “llu”
------------------------------------------------------------------------------------------h,l或L “%hd”和“%hi”指示该值会存储在一个 short int 中。“%ho”,“%hx”和“%hu”指 示该值将会存储在一个 unsigned short int中。“%ld”和“%li”指示该值会存储在一
个 long中。“%le”“%lf”和“%lg”指示该值以 double类型存储。将 L(而非l)与
e,f和g一起使用指示该值以 long double类型存储。如果没有这些修饰符,d,i,o 和 x 指示 int 类型,而 e,f 和 g指示 float类型
-----------------------------------------------------------------------------------------

正如你所看到的,转换说明符的使用比较复杂,而且这些表活力了一些特性。这些省略了的特性主要便于从高度格式代的源(例如穿孔卡或者其他数据记录媒介)读取选定的数据。因为本书主要是把 scanf() 和作向程序提供交互式数据的一种使得方式,所以我们就不再讨论这些更深奥的特性。

 

一·从 scanf()的角度看输入

我们更仔细地研究 scanf()怎么读取输入。假定你使用了一个 %d 说明符来读取一个整数。scanf()函数开始每次读取一个输入字符,它跳过空白字符(空格,制表符和换行符)直到遇到一个非空白符。因为它试图读取一个整数,所以 scanf()期望发现一个数字字符或者一个符号(+或者-)。如果它发现了一个数字或一个符号,那么它就保存之并读取下一个字符,如果接下来的字符是一个数字,它保存这个数字,并读取下一个字符。就这样,scanf()持续读取和保存字符直到它遇到一个非数字的字符。如果遇到一个非数字的字符,它就得出结论,已读到整数的尾部。scanf()把这个非数字字符放回输入。就这意味着当程序下一次开始读取输入时,它将从前面被放弃的那个非数字字符开始。最后,scanf()计算它读取到的数字的相应数值,并将该值放到指定的变量中。

如果你使用了字段宽度,那么 scanf()在字段结尾或者在第一个空白字符处(二者最先到达的一个)终止。

如果第一个非空白字符不是数字,将会发生什么叱?比如说,是 A 而非一个数字?这时 scanf()会停在那里,并把 A(或者不管是什么)放回输入。没有把任何的值赋给指定的变量,程序下一次读取输入时,它就在 A处重新开始。如果程序中只有 %d 说明符,scanf()永远也不会越过那个 A (去读下一个)。而且,如果你使用带有多个说明符的 scanf()语句,ANSI C 要求函数在第一个出错的地方停止读取输入。

使用其他数字说明符读取输入与使用 %d 的情况相同。主要的区别在于 scanf()也许会把更多的字符看作数字的一部分。例如,%x 说明符要求 scanf()识别十六进制数字 a 到 f 和 A 到 F。浮点说明要求 scanf()识别小数点指数记数法(e-notation),新的 p 记数法(p-notation)。

如果使用 %s 说明符,那么空白字符以外的所有字符都是可接受的,所以 scanf()跳过空白字符直到遇到第一个非空白字符,然后保存再次遇到空白字符之前的所有非空白字符。这就意味着 %s 使 scanf()读取一个单词,也就是说,一个不包含空白字符的字符串。如果使用字段宽度,scanf()在字段的结尾或者第一个空白字符处停止。不过通过字段宽度使用 scanf()用一个 %s 说明符读取多于一个字的输入。最后一点:当 scanf()把字符串放在一个指定的数组中时,它添加终止的‘\0’使得数组内的内容成为一个 c 字符串。

如果使用 %c 说明符,那么所有的输入字符都是平等的。如果下一个输入字符是一个空格或都换行符,将会把这个空格或换行符赋给指定的变量,不会跳过空白字符。

实际上,scanf()不是 C最常用的输入函数。这里提到它是因为它的用途很多(它可以读取所有不同的数据类型),但是 C还有其他几个输入函数,例如 getchar(),和 gets()。它们更适用于专门的任务,例如读取一个字符或者读取包含空格的字符串。我们将在第 7章“C控制语句:分支和跳转”,第 11章“字符串和字符串函数”和第13章“文件输入/输出”中讨论这样的一些函数。同时,如果需要一个整数,小数,字符或者字符串,你都可以使用 scanf()。


二·格式字符串中的常规字符

scanf()函数允许你把普通字符放在格式字符串中。除了空格字符之外的普通字符一定要与输入字符串准确匹配。例如,如果无意间把逗号放在两个说明符之间:

scanf("%d,%d",&n,&m);

scanf()函数将其解释成,你将键入一个数字,键入一个逗号,再键入一个数字。也就是说,你必须像下面这样输入两个整数:

88, 121

因为在格式字符串中逗号紧跟在 %d 后面,所以你必须紧跟 88 输入一个逗号。不过,因为 scanf()会跳过整数前面的空白字符,所以在输入时可以在逗号后面键入一个空格或换行符。也就是说,下面的两种输入方式也可以接受:

88, 121
或者
88,
121

格式字符串中的空格意味着跳过下一个输入项之前的任何空格。例如下面的语句: 交换

scanf("%d, %d",&n,&m);

将会接受下列任何一个输入行
88,121
88 ,121
88, 121

请注意,“任何空格”的概念包括没有空格的特殊情况。

除了 %c 以外的说明符会自动跳过输入项之前的空格,所以 scanf("%d%d",&n,&m)与 scanf("%d %d",&n,&m)的行为是相同的。对于 %c 来说,向格式字符串中添加一个空格导致一些区别。例如,如果在格式字符串中 %c 之前有一个空格,那么 scanf()会跳到第一个非空白字符处。也就是说,命令 scanf("%c",&ch);读取在输入中遇到的第一个字符,而 scanf("%c",&ch)则读取遇到的第一个非空白字符。

 

三· scanf()的返回值

scanf()函数返回成功读入的项目的个数。如果它没有读取任何项目(当它期望一个数字而你却键入一个非数字字符串时就会发生这种情况),scanf()会返回值 0 。当它检测到“文件结尾”(end of file)时,它返回 EOF(EOF是文件在 stdio.h中定义的特殊值。一般,#define指令把EOF的值定义为-1)。我们将会在第6章“C控制语句:循环”中讨论“文件结尾”,并在本书稍后 scanf()的返回值。在你学会 if语句和 while 语句后,可以使用 scanf()返回值来检测和处理不匹配的输入。

 

4.4.6 printf() 和 scanf()的 * 修饰符

printf() 和 scanf()都可以使用 *修饰符来修饰说明符的意义,但是它们的方式不同。首先,我们看看 *修饰符能为 printf()做什么。

假定你不想事先指定字段宽度,而是希望由程序来指定该值,那么你可以在字段宽度部分使用 * 代替数字来达到目的,但是你也必须使用一个参数来告诉函数字段宽度应该是什么。也就是说如果转换说明符是 %*d,那么参数列表中应该包括一个 *的值和一个 d 的值。该技术也可以和浮点值一起使用来指定精度和字段宽度。程序清单 4.16 就是表明这个工作原理的简短示例。

程序清单 4.16 varwid.c 程序
-------------------------------------------------------------
/* varwid.c -- 使用可变宽度的输出字段 */

#include
int main (void)
{
unsigned width,precision;
int number = 256;
double weight = 242.5;

printf (" What field width? \n");
scanf ("%d", &width);//指定字段宽度
printf ("The number is: %*d: \n", width,number);字段宽度写前面
printf ("Now enter a width and a precision :\n");
scanf ("%d %d",&width, &precision);
printf (" weignt = %*.*f \n",width,precision,weight);
getchar();
getchar();
return 0;
}

变量 width 提供字段宽度,而 number 就是要打印的数字。因为在说明符中 * 在 d 前面,所以在 printf()的参数列表中 width 在 number 前面。同样, width 和 precision 共同提供了打印 weight 的格式代信息。下面是一个运行示例:

What field width?
6
The number is: 256:
Now enter a width and a precision :
8 3
weignt = 242.500


在这里,第一个输入是 6 ,所以 6就是所用的字段宽度。与之类似,第二个答案指示字段宽度为8,并且小数点右边有 3位数字。更一般地,一个程序应根据 weight 的值来决定这些变量的值。


在 scanf()中 *提供截然不同的服务。当把它放在 % 和说明符字母之间时,它使函数跳过相应的输入项目。程序清单 4.17 提供了一个示例。

程序清单 4.17 skip2.c程序
------------------------------------------------------------------------------
/* skip2.c --- 跳过输入的头两个整数 */
#include
int main (void)
{
int n;

printf ("Please enter three integers :\n");
scanf ("%*d %*d %d",&n);
printf ("the last integer was %d \n",n);
getchar();
getchar();
return 0;
}

程序清单中 4.17 中的 scanf()命令指示:跳过两个整数,并把第三个整数复制给 n。下面是一个运行示例:

Please enter three integers :
2004 2005 2006
the last integer was 2006

如果程序需要读取一个文件中某个特定的列 (该文件的数据以统一的列排列),那么该功能将非常有用。

 

4.47 printf 的用法提示

在想打印几列数据时,指定固定字段宽度是很有用的。因为默认的字段宽度是数字的宽度,所以如果同一列中的数字大小不同,那么下列语句:

printf ("%d %d %d \n",val1,val2,val3);

如果被执行多次,将会输出参差不齐的列。例如,输出可能是这样的:

12 234 1222
4 5 23
22334 2322 10001

(这里假定变量的值在各个 printf 语句之间发生了变化)。

如果通过指定足够大的固定字段宽度使输出更加整齐清晰。例如,使用下列语句:

printf ("%9d %9d %9d \n",val1,val2,val3);

产生如下结果;

12 234 1222
4 5 23
22334 2322 10001

在两个转换说明之间放一个空白字符,可以确保即使一个数字溢出了自己的字段,它也不会闯入下一个数字一起输出。这是因为控制字符串中的常规字符(包括空格)会被打印出来。

另一方面,如果语句中要嵌入一个数字,那么指定一个和期望的数字宽度同样小或者更小的字段宽度通常会比较方便。这使得数字的宽度正合适,而无需不必要的空白符。例如,下列语句:

printf ("Count Beppo ran %.2f miles in 3 hours. \n",distance)'

可能产生

Count Beppo ran 10.22 miles in 3 bours。

把转换说明更改为 % 10.2f 会产生如下结果

Count Beppo ran 10.22 miles in 3 bours。


 

 


4.5 关键概念

C 的 char 类型表示一个字符。要表示一个字符序列, C使用字符串。字符串的一种形式是字符常量,其中字符用双引号括起来,例如“Good luck,my friend”。也可以在字符数组中存储一个字符串,字符数组由内存中相邻的字节组成。字符串,无论是表达成一个字符常量还是存储一个字符数组中,都要以一个被称为空字符的隐藏字符来结束。

在程序中最好使用 #define 或是关键字 const 以符号代表数字常量。符号常量使程序可读取性更强,更易于维护和修改。

标准 C输入和输出函数 scanf()和 printf()都使用一个系统,在这个系统中必须使第一个参数中的类型说明符与后续参数中的值相匹配。比如说,把诸如 %d 这样的 int 说明符与一个浮点值相匹配会产生奇怪的结果。必须小心谨慎,以确保说明符的数目和类型与函数的其余参数相匹配。如果是 scanf(),一定要记得给变量名加上地址运算符前缀 (&)

空白字符(制表符,空格和换行符)对于 scanf()如何处理输入起着至关重要的作用。除了在 %c 模式(它读取下一个字符)下外,在读取输入时,scanf()会跳过空白字符直到第一个非空白字符处。然后它会一直读取字符,直到遇到空白字符,或遇到一个不符合正在读取的类型的字符。我们考虑如果让几个不同的 scanf()输入模式读取相同的输入行,将会产生什么情况。假设有如下输入行:

-13.45e12 # 0

首先,假定我们使用 %d 模式,scanf()会读取三个字符(-13)并在小数点处停止,将小数点作为下一个输入字符。然后,scanf()将会把字符序列 -13 转换成相应的整数值,并将该值存储在目标整形变量中,

接着,假定 scanf()以 %f 模式读取相同的行,它将会读取字符 -13.45e12 ,并在 # 符号处停止,将它作为下一个输入字符。然后它把字符序列 -13.45e12 转换成相应的浮点数值,并将该值存储在目标浮点型变量。

假定 scanf()以 %s 模式读取相同的行,它将会读取 -13.45E12#,并在空格处停止,将这个空格作为下一个输入字符。然后它将把这 10 个字符的字符代码存储到目标字符数组中,并在结尾附加一个空字符。

最后,假定 scanf()使用 %c 说明符读取相同的行,它将会读取并存储第一个字符,在这里是一个空格。


4.6 总结

字符串是作为一个单位处理的一系列字符。在 C 中,以空字符结束的一系列字符代表一个字符串,空字符就是 ASCII 码为 0 的字符。字符串可以存储在字符数组中。一个数组就是一系列项目或元素,并且所有这些项目或元素的类型都相同。要声明为 name 并有 30个 char 类型的元素的数组,请使用以下语句:

char name[30];

请确保分配足够多的元素来存放整个字符串(包括空字符)。


字符串常量用双引号引起来的字符串表示 “This is an example of a string”


srtlen()函数(在 string.h 头文件中定义)可以用于获得一个字符串的长度(不包括标示缠上的空字符)。


scanf()函数在使用 %s 说明符时,可以用于读取包含一个单词的字符串。


strcpy()函数用于交换二个字符串的数据 例如 :

char a[10],b[10];
......
strcpy(a,b);


C 预处理器在源代码程序中搜索预处理器指令(预处理器指令以 # 符号开始),并在程序开始编译之前它们。#include 指令使处理器把另一个文件的内容添加到文件中该指令所在的位置。使用#define 指令可以创建明显常量,也就是代表常量的符号。limits.h 和 float.h 头文件使用 #define
定义了一套表示整数和浮点类型的各个属性的常量。也可以使用 const 修饰符来创建符号常量。


printf()和 scanf()函数对输入和输出提供多种支持。二者都使用一个包含内嵌转换说明符的控制字符串来指示将要读取或打印的数据项的类型和数目。还可以使用转换说明会来控制输出的外观:字段宽度,小数点位置和字段内的布局。


4.7 复习题

1. 再将运行程序清单 4.1,但是在需要你输入名字时,请输入你的名字和姓氏。发生了什么?为什么?

答:程序不能正常工作。第一个 scanf()语句只是读入你的名而没有读入你的姓,你的姓依然存储在输入“缓冲区”(缓冲区只是一块用来存放输入的临时存储区域)中。当下一个 scanf()语句想要读入你的体重时,它从上次读入结束的地方开始,这样就试图把你的姓作为体重来读取。这会使 scanf()失败。另一方面,如果你对姓名请求给出像 Lasha 144 这样的响应,程序会使用 144 作为你的体重,虽然你是在程序请求体重之前输入 144的。


------------------------------------------------------------------------------------------

2.假定下列每个示例都是某个完整程序的一部分。它们的打印结果分别是什么?
a. printf("He sold the painting for $%2.2f. \n",2.345e2);
b. printf("%c%c%c \n",'H',105,'\41');
c. #define Q "His Hamlet was funny without being vulgar"
printf ("%s \nhas %d character.\n",Q,strlen(Q));
d. printf ("Is %2.2e the same as %2.2f? \n",1201.0,1201.0);

------------------------------------------------------------------------------------------
答:a. He sold the painting for $234.50.
b. Hi! (注意:第一个字符是一个字符常量,第二个字符是由一个十进制整数转换而来的,第三 个字符是一个八进制字符常量的 ASCII表示)。
c. His Hamlet was funny without being vulgar
has 41 character.
d. Is 1.20e+003 the same as 1201.00?
-----------------------------------------------------------------------------------------

3. 在问题 2c中,应进行哪些更改以使字符串 Q 引在双引号中输出?

答 c. #define Q "His Hamlet was funny without being vulgar"
printf ("%s \nhas %d character.\n",Q,strlen(Q));

修改为:printf ("\"%s\" nhas %d character.\n",Q,strlen(Q)); (使用 \" )

----------------------------------------------------------------------------------------

4. 找出下列程序中的错误

define B booboo
define X 10
main (int)
{
int age;
char name;

printf ("please enter your first name".);
scanf ("%s",name);
printf ("All right, %c,what's your age?\n,name);
scanf ("%f",age);
xp = age + X:
printf ("That's a %s! You must be at least %d.\n",B,xp);
rerun 0;
}

答: 正确的应该如下:

#include /* 不要忘记包含要用的头文件 */
#define B "booboo" /* 添加了# 和 双引号*/
#define X 10 /*添加了 # */
int main (void) /* 不是 main (int)*/
{
int age,xp; /* 声明所有变量 */
char name[40]; /* 字符串 要是一个数组 */

printf ("Please enter your first name.\n");
scanf ("%s",name);
printf ("All right, %s,what's your age? \n",name); /* 打印字符串 应该用 %s */
scanf ("%d",&age); /* 年龄一般为整数 应为 %d 少了 &符号 */
xp = age + X;
printf ("that's a %s! You must be at least %d.\n",B,xp);
getchar();
getchar();
return 0; /* 不是 rerun */
}

------------------------------------------------------------------------------------------

5. 假设一个程序这样开始:

#define BOOK "war and peace"
int main (void)
{
float cost = 12.99;
float prercent = 80.0;

请构造一个 printf()语句,使用 BOOK cost 和 prercent 打印下列内容:

This copy of "war and peace" sells for $12.99.
That is 80% of list.

答: printf (" This copy of \"%s\" sells for $%0.2f.\n ",BOOK,cost);
printf (" That is %0.0f%% of list\n",prercent);


-----------------------------------------------------------------------------------------

6. 你会使用什么转换说明来打印下列各项内容?

a. 一个字段宽度等于数字位数的十进制整数。
b. 一个形如 8A,字段宽度为 4 的十六进制整数
c. 一个形如 233.346 字段宽度为 10 的浮点数
d 一个形如 2.33e+002 字段宽度为 12的浮点数
e 一个字段宽度为30, 左对齐的字符串。


答 a %d
b %4X
c %10.3f
d %12.2e
e %-30s

-----------------------------------------------------------------------------------------

7. 你会使用哪个转换说明来打印下列各项内容?

a. 一个字段宽度为 15 的 unsigned long 整数
b. 一个形如 0x8a 字段宽度为 4 的十六进制数
c. 一个形如 2.33E+02 ,字段宽度为12 左对齐的浮点数
d. 一个形如 +232.346 字段宽度为 10 的浮点数
e. 一个字符串的前 8个字符,字段宽度为 8字符


a. %15lu
b. %#4x
c. %-12.2e
d. %+10.3f
e %8.8s


------------------------------------------------------------------------------ -------

8. 你会使用什么转换说明来打印下列各项内容?

a. 一个字段宽度为6 最少有 4位数字的十进制整数
b. 一个字段宽度在参数列表中给定的 八进制整数
c. 一个字段宽度为 2 的字符
d. 一个形如 +3.13, 字段宽度等于数字中字符个数的浮点数
e. 一个字符串的前 5个字符,字段宽度为7. 左对齐

a. %6.4d
b. %*o
c. %2c
d. %+0.2f
e %-7.5s

--------------------------------------------------------------------------------------

9. 为下列每个输入行提供一个对其进行读取的 scanf()语句,并声明语句中用到的所有变量或数组

a. 101
b. 22.32 8.34E-09
c. linguini
d. catch 22
e. catch 22(但是跳过 catch)

答 a. int num;
scanf("%d",&num);

b. float num1,nmu2;
scanf("%f %f", &num1,&num2);

c. char string[40];
scanf("%s",string);

d char string[40];
int num;
scanf ("%s %d",stting,&num);

e. int num;
scanf ("%s %d",action,&num);

-----------------------------------------------------------------------------------------

10. 什么是空白字符

答:空白字符包括空格,制表符和推行符。 C 使用空白字符分隔各个语言符号,scanf()使用空白字符分隔相邻的输入项。

-----------------------------------------------------------------------------------------

11. 假设你想在程序中使用圆括号代替花括号。以下方法可以么?

#define ){
#define )}

答: 会发生替换。但不幸的是,预处理器不能区别哪些圆括号应该被替换成花括号,哪些圆括号不应该被替换成花括号。

------------------------------------------------------------------------------------------

 

 



4.8 编程练习

---------------------------------------------------------------------------------------
1. 编写一个程序,要求输入名字和姓氏,然后以 “名字,姓氏”的格式打印

答:

#include
int permute (void); /* 下面要定义的函数 要在头文件这里申请 后面要有分号*/

char name1[40],name2[40],x[40]; /* 定义三个全局变量的字符串数组,x 为交换变量 */

int premute (void) /* 定义一个交换函数 */
{
strcpy(x,name1); /* strcpy 为字符串 交换函数 注意交换的顺序 */
strcpy(name1,name2);
strcpy(name2,x);
}

int main (void)
{
printf (" 请输入你的姓氏 \n");
scanf ("%s%s",name1,name2); /* scanf 的数组 获取 前缀不用放 & 说明符 */
printf (" 你的姓氏是 %s %s \n",name1,name2);
premute();
printf (" 交换后你的姓氏变为 %s %s \n", name1,name2);
getchar();
getchar();
return 0;
}

----------------------------------------------------------------------------------------

2. 编写一个程序,要求输入名字,并执行以下操作;

a. 把名字引在双引号中打印出来。
b. 在宽度为 20个字符的字段内打印名字,并且整个字段引在引号内。
c. 在宽度为 20个字符的字符的左端打印名字,整个字段引在引号内。
d. 在比名字宽 2个字符的字段内打印它。

答:
#include
int main (void)
{
char name1[40]; /* 定义字符串数组 */
int d;

printf("请输入你的名\n");
scanf("%s",name1);
printf ("\"%s\" \n",name1); /* a */
printf ("\"%20s\" \n",name1); /* b */
printf ("\"%-20s\" \n",name1); /* c */
d = strlen(name1); /* 使用 strlen 函数 计算字符串中字符的长度 */
printf ("%*s",d+3,name1); /* 注意: %*s 和 d+3 name1 的含义 */
getchar();
getchar();
return 0;
}

------------------------------------------------------------------------------------------

3. 编写一个程序,读取一个浮点数,并且首先以小数点记数法,然后以指数记数法打印之。输出使用以下形式:

a. The input is 21.3 or 2.1e+001
b. The input is + 21.290 or 2.219E+001

答:

#include
int main (void)
{
float num1;

printf ("请输入一个浮点数 \n");
scanf ("%f",&num1);
printf(" The input is %0.1f or %0.1e \n",num1,num1);
printf(" The input is %+0.3f of %0.3E \n",num1,num1);
getchar();
getchar();
return 0;
}

------------------------------------------------------------------------------------------

4. 编写一个程序,要求输入身高(以英寸为单位)和名字,然后以如下形式显示:

Dabney, you are 6.208 feet tall

答:

#include
int main (void)
{
char name[40];
float tall;

printf ("请输入你的身高 (英寸)和名字 \n");
scanf ( "%f %s",&tall,name);
printf (" %s 你的身高为 %0.3f(英寸) \n",name,tall);
getchar();
getchar();
return 0;
}

使用 float 类型,使用 /作为除号。如果你愿意,可以要求以厘米为单位输入身高,并以米为单位进行显示

答:
#include
int main (void)
{
char name[40];
float tall;

printf ("请输入你的身高 (厘米)和名字 \n");
scanf ( "%f %s",&tall,name);
printf (" %s 你的身高为 %0.3f(米) \n",name,tall/100);
getchar();
getchar();
return 0;
}
------------------------------------------------------------------------------------------

5. 编写一个程序,首先要求用户输入名字,然后要求用户输入姓氏。在一行打印输入的姓名,在下一行打印每个名字中字母的个数。把字母个数与相应名字的结尾对齐,如下所示:

Melissa Honeybee
7 8

然后打印相同的信息,但是字母个数与相应的单词开始对齐

Melissa Honeybee
7 8

答:

#include
int main (void)
{
char fa_name[40],fi_name[40]; /* 定义 字符串数组 */
int fa_len,fi_len; /* 定义 字符串长度的整数变量 */

fa_len = 0; /* 初始化 长度变量 */
fi_len = 0;
printf (" Please input your family name \n");
scanf ("%s",fa_name); /* 数组无须前缀 & */
printf (" Please input your first name \n");
scanf ("%s",fi_name);
fa_len = strlen(fa_name); /* strlen()函数可以计算字符串长度 */
fi_len = strlen(fi_name);
printf ("%s %s \n",fa_name,fi_name);
printf ("%*d %*d\n",fa_len,fa_len,fi_len,fi_len); // 说明符中 * 后面的参数要放在首位
printf ("%s %s \n",fa_name,fi_name);
printf ("%d %*d\n",fa_len,fa_len,fi_len);
getchar();
getchar();
return 0;
}


---------------------------------------------------------------------------------------

6. 编写一个程序,设置一个值为 1.0 /3.0 的 double 类型变量和一个值为 1.0 /3.0的 float类型变量。每个变量的值显示三次:一次在小数点右侧显示 4个数字,一次在小数点左侧显示12个数字,另一次在小数点右侧显示16个数字。同时要让程序包括 float.h 文件,并显示 FLT_DIG 和 DBL_DIG 的值。1.0 /3.0 的显示值与这些值一致么?

答:

#include
#include /* 定义 float 头文件 主要支持 直接显示 DBL_DIG,FLT_DIG */
int main (void)
{
double do_num = 1.0/3.0;
float fl_num = 1.0/3.0;

printf ("右侧4位:%0.4f %0.4f \n",do_num,fl_num);
printf ("左侧12位:%015.2f %015.2f \n",do_num,fl_num); /* 要显示小数点左边 记得放0 显示 左侧12位 如果要小数点后几位 必须相加起来 如015.2 显示的12位+小数点+小数后二位 = 15位*/
printf ("右侧16位:%0.16f %0.16f \n",do_num,fl_num);
printf ("DBL_DIG:%f \n",DBL_DIG);
printf ("FLT_DIG:%f \n",FLT_DIG);
getchar();
return 0;
}

------------------------------------------------------------------------------------------
7. 编写一个程序,要求用户输入行驶的英里数和消耗汽油的加仑数。接着应该计算和显示消耗每加仑汽油行驶的英里数,显示方式是小数点右侧显示一个数字。然后,根据 1 加仑约等于 3.785升,1英里约等于 1.609公里的规则,它应该把每加仑英里数转换成每100公里的升数并显示结果,显示方式是在小数点右侧显示一个数字。用符常量表示两个转换系数 (使用 const 或 #define)

答;

#include

#define KILOMETRE 1.609 /* 注意:用符号常量的话 不用定义类型 也不用 = 号 */
#define LITRE 3.785
int main (void)
{
float fl_mile,fl_gal;
float fl_kil,fl_lit;

fl_mile = 0;
fl_gal = 0;
printf (" 请输入行驶的英里 \n");
scanf ("%f",&fl_mile);
printf (" 请输入消耗汽油的加仑数 \n");
scanf ("%f",&fl_gal);
printf("一加仑可以行驶%0.1f 英里 \n",fl_mile/fl_gal);
fl_kil = fl_mile * KILOMETRE;
fl_lit = fl_gal * LITRE;
printf("一升可以行驶 %0.1f 公里 \n",fl_kil/fl_lit);
getchar();
getchar();
return 0;
}

转载于:https://www.cnblogs.com/dream-for/p/5124405.html

你可能感兴趣的:(C Primer Plus(第五版)4)