第4章 字符串和格式化输入/输出
4.1 前导程序
talkback.c程序:
// talkback.c -- 演示与用户交互
#include <stdio.h>
#include <string.h> //提供strlen()函数的原型
#define DENSITY 62.4 //人体密度(单位:磅 / 立方英尺)
int main()
{
float weight, volume;
int size, letters;
char name[40]; // name是一个可容纳40个字符的数组
printf("Hi!What's your first name?\n");
scanf("%s", name);
printf("%s, what's your weight in pounds?\n", name);
scanf("%f", &weight);
size = sizeof name;
letters = strlen(name);
volume = weight / DENSITY;
printf("Well, %s, your volume is %2.2f cubic feet.\n",name,volume);
printf("Also, your first name has %d letters,\n",letters);
printf("and we have %d bytes to store it.\n",size);
return 0;
}
运行结果:
该程序包含以下新特性:
- 用数组储存字符串。该数组占用内存中40个连续的字节,每个字节储存一个字符值。
- 使用%s来说明来处理字符串的输入和输出。
- 用C预处理器把字符常量DENSITY定义为62.4。
- 用C函数strlen()获取字符串的长度。
4.2 字符串简介
字符串是一个或多个字符的序列。注意,双引号仅是用来告知编译器它括起来的是字符串,并不是字符串的一部分。
4.2.1 char 类型数组和null字符
在C语言中,字符串都被储存在char类型的数组中。数组由连续的存储单元组成,字符串中的字符被储存在相邻的存储单元中,每个单元储存一个字符。
数组末尾位置的字符\0。这是空字符,标记字符串的结束。空字符不是数字0,是非打印字符。C中一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多1。4.1的前导程序中有40个存储单元的字符串,只能储存39个字符,剩下一个字节留给空字符。
数组是同类型数据元素的有序序列。可以把数组看作是一行连续的多个存储单元。
4.2.2 使用字符串
praise1.c程序:
/* praise1.c -- 使用不同类型的字符串 */
#include <stdio.h>
#define PRAISE "You are an extraordinary being."
int main(void)
{
char name[40];
printf("What's your name? ");
scanf("%s", name);
printf("Hello, %s. %s\n", name, PRAISE);
return 0;
}
运行结果:
程序说明:
%s 打印字符串。scanf()只读取了Angela Plains中的Angela,它在遇到第1个空白(空格、制表符或换行符)时就不再读取输入。根据%s转换说明,scanf()只会读取字符串中的一个单词,而不是一整句。C语言还有其他的输入函数(如,fgets()),用于读取一般字符串。
字符串和字符:
字符串常量"x"和字符常量’x’不同。
区别之一:‘x’ 是基本类型,“x” 是派生类型
区别之一:“x” 实际上由两个字符组成:‘x’ 和空字符 \0。
4.2.3 strlen()函数
sizeof 运算符,以字节为单位给出对象的大小。
strlen() 函数,给出字符串中的字符长度。
praise2.c程序:
/* praise2.c */
//如果编译器不识别 %zd,尝试换成 %u或 %lu。
#include <stdio.h>
#include <string.h> /* 提供strlen()函数的原型 */
#define PRAISE "You are an extraordinary being."
int main(void)
{
char name[40];
printf("What's your name? ");
scanf("%s", name);
printf("Hello, %s. %s\n", name, PRAISE);
printf("Your name of %zd letters occupies %zd memory cells.\n", strlen(name), sizeof name);
printf("The phrase of praise has %zd letters ", strlen(PRAISE));
printf("and occupies %zd memory cells.\n", sizeof PRAISE);
return 0;
}
运行结果:
程序说明:
C把函数库中相关的函数归为一类,并为每类函数提供一个头文件。string.h头文件包含了strlen()函数和其他一些与字符串相关的函数。
sizeof 运算符报告,name 数组有40个存储单元。但是,只有前11个单元用来储存Serendipity,所以strlen()得出的结果是11。name 数组的第12个单元储存空字符串,strlen()并未将其计入。
对于 PRAISE,用 strlen() 得出的也是字符串中的字符数(包括空格和标点符号)。然而,sizeof 运算符给出的数更大,因为它把字符串末尾不可见的空字符也计算在内。该程序并未明确告诉计算机要给字符串预留多少空间,所以它必须计算双引号内的字符数。
sizeof 何时使用圆括号取决于运算对象是类型还是特定量。对象是类型时,圆括号必不可少;对于特定量,可有可无。建议所有情况下都使用圆括号。
4.3 常量和C预处理器
假设程序中的多处使用一个常量,有时需要改变它的值。如果程序使用符号常量,则只需更改符号常量的定义,不用在程序中查找使用常量的地方,然后逐一修改。
如何创建符号常量?方法一:声明一个变量,然后将该变量设置为所需的常量。例如,float taxrate = 0.015。方法二:C预处理器。例如,#define TAXRATE 0.015。
格式:首先是#define,接着是符号常量名(TAXRATE),然后是符号常量的值。
通用格式:#define NAME value
符号常量要大写。还有一个命名约定,即在名称前带c_或k_前缀来表示常量(如,c_level)。
符号常量的命名规则与变量相同。可以使用大小写字母、数字、下划线字符,首字母不能为数字。
pizza.c程序:
/* pizza.c -- 在比萨饼程序中使用已定义的常量 */
#include <stdio.h>
#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 = 2.0 * PI * radius;
printf("Your basic pizza parameters are as follows:\n");
printf("circumference = %1.2f, area = %1.2f\n", circum, area);
return 0;
}
运行结果:
程序说明:
%1.2f 表明,结果被四舍五入为两位小数输出。
#define指令可以定义字符和字符常量。前者使用单引号,后者使用双引号。
4.3.1 const 限定符
const 关键字,用于限定一个变量为只读。
const int MONTHS = 12; //MONTHS在程序中不可更改,值为12
const 用起来比 #define更灵活。
4.3.2 明示常量
limits.h 和 float.h 分别提供了与整数类型和浮点类型大小限制相关的详细信息。每个头文件都定义了一系列供实现使用的明示常量。例如,limits.h 头文件包含以下类似的代码:
#define INT_MAX +32767
#define INT_MIN -32768
下表中所列都与float类型相关。把明示常量名中的FLT分别替换成DBL和LDBL,即可分别表示double和Long double类型对应的明示变量(表中假设系统使用2的幂来表示浮点数)。
defines.c程序:演示如何使用float.h和limits.h中的数据
// defines.c -- 使用limits.h和float.h头文件中定义的明示常量
#include <stdio.h>
#include <limits.h> //整数限制
#include <float.h> //浮点数限制
int main(void)
{
printf("Some number limits for this system:\n");
printf("Biggest int: %d\n", INT_MAX);
printf("Smalleat long long: %lld\n", LLONG_MIN);
printf("One byte = %d bits on this system.\n", CHAR_BIT);
printf("Largest double: %e\n", DBL_MAX);
printf("Smalleat normal float: %e\n", FLT_MIN);
printf("float precision = %d digits\n", FLT_DIG);
printf("float epsilon = %e\n", FLT_EPSILON);
return 0;
}
运行结果:
4.4 printf() 和 scanf()
printf() 函数和 scanf() 函数,是输入\ 输出函数,或简称为I \ O函数。
4.4.1 printf() 函数
请求printf()函数打印数据的指令要与待打印数据的类型相匹配。
4.4.2 使用printf()
printout.c程序:
/* printout.c -- 使用转换说明 */
#include <stdio.h>
#define PI 3.141593
int main(void)
{
int number = 7;
float pies = 12.75;
int cost = 7800;
printf("The %d contestants ate %f berry pies.\n", number,pies);
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);
return 0;
}
运行结果:
程序说明:
待打印项可以是变量、常量、乘法表达式。
格式字符串是双引号括起来的内容。
格式字符串包含两种形式不同的信息:1. 实际要打印的字符 2. 转换说明
4.4.3 printf()的转换说明修饰符
在%和转换字符之间插入修饰符可修饰基本的转换说明。如果要插入多个字符,其书写顺序应该与表4.4中列出的顺序相同。
注意 类型可移植性
sizeof 运算符以字节为单位返回类型或值的大小。 这应该是某种形式的整数, 但是标准只规定了该值是无符号整数。 在不同的实现中, 它可以是unsigned int、 unsigned long甚至是unsigned long long。
因此, 如果要用printf()函数显示sizeof表达式, 根据不同系统, 可能使用%u、 %lu或%llu。 这意味着要查找你当前系统的用法, 如果把程序移植到不同的系统还要进行修改。
鉴于此, C提供了可移植性更好的类型。 首先, stddef.h头文件(在包含stdio.h头文件时已包含其中) 把size_t定义成系统使用sizeof返回的类型, 这被称为底层类型 。
其次, printf()使用z修饰符表示打印相应的类型。 同样, C还定义了ptrdiff_t类型和t修饰符来表示系统使用的两个地址差值的底层有符号整数类型。
注意 float参数的转换
对于浮点类型, 有用于double和long double类型的转换说明, 却没有float类型的。
这是因为在K&R C中, 表达式或参数中的float类型值会被自动转换成double类型。 一般而言, ANSI C不会把float自动转换成double。 然而, 为保护大量假设float类型的参数被自动转换成double的现有程序,printf()函数中所有float类型的参数(对未使用显式原型的所有C函数都有效) 仍自动转换成double类型。 因此, 无论是K&R C还是ANSI C, 都没有显示float类型值专用的转换说明。
width.c程序:字段宽度在打印整数时的效果
/* width.c -- 字段宽度 */
#include <stdio.h>
#define PAGES 959
int main(void)
{
printf("*%d*\n", PAGES);
printf("*%2d*\n", PAGES);
printf("*%10d*\n", PAGES);
printf("*%-10d*\n", PAGES);
return 0;
}
运行结果:
程序说明:
第一个转换说明:%d 不带任何修饰符,其对应的输出结果与带整数字段宽度的转换说明的输出结果相同。
第二个转换说明:%2d 对应的输出结果应该是2字段宽度。因为待打印的整数有3位数字,所以字段宽度自动扩大以符合整数的长度。
第三个转换说明:%10d,其对应的输出结果有10个空格宽度,实际上在两个星号之间有7个空格和3位数字,并且数字位于字段的右侧。
第四个转换说明:%-10d,其对应的输出结果同样是10个空格宽度,- 标记说明打印的数字位于字段的左侧。
floats.c程序:
// floats.c -- 一些浮点型修饰符的组合
#include <stdio.h>
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);
return 0;
}
运行结果:
程序说明:
第一个转换说明:%f 字段宽度和小数点后面的位数均为系统默认设置,即字段宽度是容纳待打印数字所需的位数和小数点后打印6位数字。
第二个转换说明:%e 默认情况下,编译器在小数点的左侧打印1个数字,在小数点的右侧打印6个数字。
第七个转换说明:+ 标记,使打印的值前面多了一个代数符号(+)。
第八个转换说明:0 标记,使打印的值前面以0填充以满足字段要求。%010.2f 的第1个0是标记,句点(.)之前、标记之后的数字(本例为10)是指定的字段宽度。
flags.c程序:
/* flags.c -- 演示一些格式标记 */
#include <stdio.h>
int main(void)
{
printf("%x %X %#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);
return 0;
}
运行结果:
程序说明:
第一行输出:1f 是十六进制数,等于十进制数31。根据 %x 打印出 1f,%X 打印出 1F, %#x 打印出 0x1f。
第二行输出:演示了如何在转换说明中用空格在输出的正值前面生成前导空格,负值前面不产生前导空格。
第三行输出:演示了 如何在整数格式中使用精度(%5.3d)生成足够的前导0以满足最小位数的要求(本例是3)。然而,使用0标记会使得前导0填充满整个字段宽度。最后,如果0标记和精度一起出现,0标记会被忽略。
stringf.c程序:
/* stringf.c -- 字符串格式*/
#include <stdio.h>
#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);
return 0;
}
运行结果:
程序说明:
第一行输出:虽然第1个转换说明是%2s,但是字段被扩大为可容纳字符串中的所有字
符。
第三行输出:精度限制待打印字符的个数。.5表示只打印5个字符。
第四行输出:- 标记使得文本左对齐输出。
4.4.4 转换说明的意义
转换说明把以二进制格式储存在计算机中的值转换成一系列字符(字符串)以便于显示。
转换说明是翻译说明,%d的意思是“把给定的值翻译成十进制整数文本并打印出来”。
1. 转换不匹配
转换说明应该与待打印值的类型相匹配。例如,打印 int 类型的值,可以使用%d、%x或%o;打印 double 类型的值,可以用%f、%e或%g。
intconv.c程序:演示一些不匹配的整型转换示例
/* intconv.c -- 一些不匹配的整型转换 */
#include <stdio.h>
#define PAGES 336
#define WORDS 65618
int main(void)
{
short num = PAGES;
short mnum = -PAGES;
printf("num as short and unsigned short: %hd %hu\n", num, num);
printf("-num as short and unsigned 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);
return 0;
}
运行结果:
程序说明:
第二行输出:short int 的大小是2字节,系统使用二进制补码来表示有符号整数。这种方法,数字0 ~ 32767代表它本身,而数字32768 ~ 65535则表示负数。65535表示-1,66534表示-2,以此类推-336表示为65200(65536-336)。所以,被解释成有符号int时,65200代表-336;而被解释成无符号int时,65200则代表65200。
第三行输出:演示了把一个大于225的值转换成字符会发生什么情况。short int 是2字节,char 是1字节。使用%c打印336时,只会查看储存336的2字节中的后1字节。这种截断相当于用一个整数除以256,只保留其余数(以256为模)。在这种情况下,余数是80,对应的ASCII值是字符P。
第四行输出:在该系统中打印比 short int 类型最大整数(32767)更大的整数(65618)。计算机进行求模运算。在本系统中,把数字65618储存为4字节的 int 类型值。用%hd打印时,只使用最后2个字节。相当于65618除以65536(256*256)的余数,此例为82。如果余数在32767 ~ 65618范围内会被打印成负数。
floatcnv.c程序:
/* floatcnv.c -- 不匹配的浮点型转换 */
#include <stdio.h>
int main(void)
{
float n1 = 3.0;
double n2 = 3.0;
long n3 = 2000000000;
long n4 = 1234567890;
printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
printf("%ld %ld\n", n3, n4);
printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
return 0;
}
运行结果:
程序说明:
第1行输出显示,%e没有把整数转换成浮点数。 如果使用%e转换说明打印n3(long类型) 会发生什么情况。 首先,%e转换说明让printf()函数认为待打印的值是double类型(本系统中double为8字节)。当printf()查看n3(本系统中是4字节的值) 时, 除了查看n3的4字节外, 还会查看查看n3相邻的4字节,共8字节单元。 接着,它将8字节单元中的位组合解释成浮点数(如, 把一部分位组合解释成指数)。因此,即使n3的位数正确,根据%e转换说明和%ld转换说明解释出来的值也不同。 最终得到的结果是无意义的值。
第1行:在本系统中,float是4字节,但是为了printf()能正确地显示该值,n1被扩成8字节。
第2行输出显示,只要使用正确的转换说明,printf()就可以打印n3和n4。
第3行输出显示,如果printf()语句有其他不匹配的地方, 即使用对了转换说明也会生成虚假的结果。 用%ld转换说明打印浮点数会失败, 但是在这里, 用%ld打印long类型的数竟然也失败了! 问题出在C如何把信息传递给函数。 具体情况因编译器实现而异。
参数传递:程序把传入的值放入称为栈的内存区域。计算机根据变量类型把值放入栈中。因此,n1被储存在栈中,占8字节(float类型被转换成double类型)。 n2也在栈中占8字节,而n3和n4在栈中分别占4字节。printf()函数根据转换说明从栈中读取值。%ld转换说明表明printf()应该读取4字节, 所以printf()读取栈中的前4字节作为第1个值。这是n1的前半部分, 将被解释成一个long类型的整数。 根据下一个%ld转换说明, printf()再读取4字节, 这是n1的后半部分, 将被解释成第2个long类型的整数。根据第3个和第4个%ld, printf()读取n2的前半部分和后半部分, 并解释成两个long类型的整数。 因此,对于n3和n4,虽然用对了转换说明,但printf()还是读错了字节。
2. printf()的返回值
printf()函数返回打印字符的个数。 如果有输出错误,printf()则返回一个负值 。
printf()的返回值是其打印输出功能的附带用途,在检查输出错误时可能会用到(如,在写入文件时很常用)。如果一张已满的CD或DVD拒绝写入时,程序应该采取相应的行动, 例如终端蜂鸣30秒。
prntval.c程序:演示如何确定函数的返回值
/* prntval.c -- printf()的返回值 */
#include <stdio.h>
int main(void)
{
int bph2o = 212;
int rv;
rv = printf("%d F is water's boiling point.\n", bph2o);
printf("The printf() function printed %d characters.\n", rv);
return 0;
}
运行结果:
程序说明:
rv = printf(…); 该语句执行两项任务:打印信息和给变量赋值。注意计算针对所有字符数,包括空格和不可见的换行符(\n) 。
3. 打印较长的字符串
longstrg.c程序: 字符串断行有3种方法
/* longstrg.c ––打印较长的字符串 */
#include <stdio.h>
int main(void)
{
printf("Here's one way to print a ");
printf("long string.\n");
printf("Here's another way to print a \
long string.\n");
printf("Here's the newest way to print a "
"long string.\n"); /* ANSI C */
return 0;
}
运行结果:
程序说明:
方法1:使用多个 printf()语句。因为第1个字符串没有以\n字符结束,所以第2个字符串紧跟第1个字符串末尾输出。
方法2:用反斜杠()和 Enter(或 Return)键组合来断行。这使得光标移至下一行,而且字符串中不会包含换行符。其效果是在下一行继续输出。但是,下一行代码必须和程序清单中的代码一样从最左边开始。如果缩进该行,比如缩进5个空格,那么这5个空格就会成为字符串的一部分。
方法3:ANSI C引入的字符串连接。 在两个用双引号括起来的字符串之间用空白隔开, C编译器会把多个字符串看作是一个字符串。要记得在字符串中包含所需的空格。如,“young”“lovers"会成为"younglovers”,而"young " “lovers"才是"young lovers”。
4.4.5 使用scanf()
C库包含了多个输入函数,scanf()是最通用的一个,因为它可以读取不同格式的数据。从键盘输入的都是文本, 因为键盘只能生成文本字符: 字母、 数字和标点符号。
scanf()把输入的字符串转换成整数、 浮点数、 字符或字符串, 而printf()正好与它相反, 把整数、 浮点数、 字符和字符串转换成显示在屏幕上的文本。
scanf()函数使用指向变量的指针。
- scanf()读取基本变量类型的值, 在变量名前加上一个&;
- scanf()把字符串读入字符数组中, 不加&。
input.c程序:
// input.c -- 何时使用 &
#include <stdio.h>
int main(void)
{
int age; // 变量
float assets; // 变量
char pet[30]; // 字符数组,用于储存字符串
printf("Enter your age, assets, and favorite pet.\n");
scanf("%d %f", &age, &assets); //这里要使用 &
scanf("%s", pet); // 字符数组不使用&
printf("%d $%.2f %s\n", age, assets, pet);
return 0;
}
运行结果:
程序说明:
scanf()函数使用空白(换行符、制表符和空格)把输入分成多个字段。只要在每个输入项之间输入至少一个换行符、制表符或空格即可。
唯一例外的是%c转换说明。 根据%c, scanf()会读取每个字符, 包括空白。
scanf()函数所用的转换说明与printf()函数几乎相同。 主要的区别是, 对于float类型和double类型, printf()都使用%f、 %e、 %E、 %g和%G转换说明。 而scanf()只把它们用于float类型, 对于double类型时要使用 l 修饰符。
ASCI C中scanf()的转换说明:
可以在百分号和转换字符之间使用修饰符。如果要使用多个修饰符,必须按下表所列顺序书写。
scanf()转换说明中的修饰符:
1. 从 scanf() 角度看输入
假设scanf()根据一个%d转换说明读取一个整数。 scanf()函数每次读取一个字符, 跳过所有的空白字符, 直至遇到第1个非空白字符才开始读取。如果找到一个数字或符号, 它便保存该字符, 并读取下一个字符。scanf()不断地读取和保存字符, 直至遇到非数字字符。然后,scanf()把非数字字符放回输入。这意味着程序在下一次读取输入时, 首先读到的是上一次读取丢弃的非数字字符。 最后, scanf()计算已读取数字(可能还有符号) 相应的数值, 并将计算后的值放入指定的变量中。
如果使用字段宽度, scanf()会在字段结尾或第1个空白字符处停止读取(满足两个条件之一便停止)
如果第1个非空白字符是A而不是数字, 会发生什么情况? scanf()将停在那里, 并把A放回输入中, 不会把值赋给指定变量。 程序在下一次读取输入时, 首先读到的字符是A。 如果程序只使用%d转换说明, scanf()就一直无法越过A读下一个字符。 另外, 如果使用带多个转换说明的scanf(), C规定在第1个出错处停止读取输入。
如果使用%s 转换说明,scanf()会读取除空白以外的所有字符。scanf()根据%s转换说明读取一个单词,即不包含空白字符的字符串。
当scanf()把字符串放进指定数组中时, 它会在字符序列的末尾加上’\0’, 让数组中的内容成为一个C字符串。
C 语言还有其他的输入函数, 如 getchar()和 fgets()。 这两个函数更适合处理一些特殊情况, 如读取单个字符或包含空格的字符串。使用scanf()函数,可以读取整数、小数、字符和字符串。
2. 格式字符串中的普通字符
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()便会跳过空格, 从第1个非空白字符开始读取。 也就是说, scanf(“%c”, &ch)从输入中的第1个字符开始读取, 而scanf(” %c”, &ch)则从第1个非空白字符开始读取。
3. scanf()的返回值
scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()便返回0。当scanf()检测到“文件结尾”时,会返回EOF(EOF是stdio.h中定义的特殊值,通常用#define指令把EOF定义为-1)。
4.4.6 printf() 和 scanf() 的*修饰符
printf()和scanf()都可以使用 * 修饰符来修改转换说明的含义,但用法不太一样。
printf()的 * 修饰符:如果不想预先指定字段宽度, 希望通过程序来指定, 那么可以用 * 修饰符代替字段宽度。 但是要用一个参数告诉函数, 字段宽度应该是多少。也就是说, 如果转换说明是 % * d, 那么参数列表中应包含*和 d对应的值。 这个技巧也可用于浮点值指定精度和字段宽度。
varwid.c程序:
/* varwid.c -- 使用变宽输出字段 */
#include <stdio.h>
int main(void)
{
unsigned width, precision;
int number = 256;
double weight = 242.5;
printf("Enter a 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("Weight = %*.*f\n", width, precision, weight);
printf("Done!\n");
return 0;
}
运行结果:
程序说明:
变量width提供字段宽度, number是待打印的数字。 因为转换说明中*在d的前面, 所以在printf()的参数列表中, width在number的前面。 同样, width和precision提供打印weight的格式化信息。用户首先输入6, 因此6是程序使用的字段宽度。 类似地, 接下来用户输入8和3, 说明字段宽度是8, 小数点后面显示3位数字。
scanf()的 * 修饰符:把*放在%和转换字符之间时, 会使得scanf()跳过相应的输出项。
skip2.c程序:
/* skip2.c -- 跳过输入中的前两个整数 */
#include <stdio.h>
int main(void)
{
int n;
printf("Please enter three integers:\n");
scanf("%*d %*d %d", &n);
printf("The last integer was %d\n", n);
return 0;
}
运行结果:
程序说明:
程序中的scanf()指示:跳过两个整数, 把第3个整数拷贝给n。
在程序需要读取文件中特定列的内容时,这项跳过功能很有用。
4.4.7 printf() 的用法提示
想把数据打印成列,使用足够大的固定字段宽度可以让输出整齐美观。例如:printf("%9d %9d %9d\n", val1, val2, val3);
输出: 12 234 1222
在两个转换说明中间插入一个空白字符,可以确保即使一个数字溢出了自己的字段,下一个数字也不会紧跟着该数字一起输出。这是因为格式字符串中的普通字符(包括空格)会被打印出来。
如果要在文字中嵌入一个数字, 通常指定一个小于或等于该数字宽度的字段会比较方便。 这样, 输出数字的宽度正合适, 没有不必要的空白。 例如,printf(“Count Beppo ran %.2f miles in 3 hours.\n”, distance);
输出:Count Beppo ran 10.22 miles in 3 hours.
如果把转说明改为%10.2f
输出:Count Beppo ran 10.22 miles in 3 hours.
4.5 关键概念
- char 类型表示单个字符
- 字符串表示字符序列。可以把字符串储存在字符数组中。字符串无论表示成字符常量还是储存在字符数组中,都以一个叫做空字符的隐藏字符结尾。
- 字符常量是一种字符串形式,即用双引号把字符括起来。
- #define 定义数值常量
- const 关键字声明的变量为只读变量
- 第一个参数中的转换说明必须与后续参数中的值相匹配,确保转换说明的数量和类型与函数的其余参数相匹配。
- scanf(),一定要在变量名前面加上地址运算符(&)。
- 除了%c 模式(读取下一个字符),scanf()在读取输入时会跳过非空白字符前的所有空白字符(制表符、空格、换行),然后一直读取字符,直至遇到空白字符或与正在读取字符不匹配的字符。
- 例子:假如有如下输入行
-13.45e12# 0 //负号前面有一个空格
如果其对应的转换说明是%d, scanf()会读取3个字符(-13) 并停在小数点处, 小数点将被留在输入中作为下一次输入的首字符。
如果其对应的转换说明是%f, scanf()会读取-13.45e12, 并停在#符号处, 而#将被留在输入中作为下一次输入的首字符; 然后, scanf()把读取的字符序列-13.45e12转换成相应的浮点值, 并储存在float类型的目标变量中。
如果其对应的转换说明是%s, scanf()会读取-13.45e12#, 并停在空格处, 空格将被留在输入中作为下一次输入的首字符; 然后, scanf()把这 10个字符的字符码储存在目标字符数组中, 并在末尾加上一个空字符。
如果其对应的转换说明是%c, scanf()只会读取并储存第1个字符, 该例中是一个空格 。 - strlen()函数(声明在 string.h头文件中)可用于获得字符串的长度(末尾的空字符不计算在内)。
- scanf()函数中的转换说明是%s时,可读取一个单词。
- C预处理器为预处理器指令(以#符号开始)査找源代码程序,并在开始编译程序之前处理它们。
- 处理器根据 #include 指令把另一个文件中的内容添加到该指令所在的位置。
- limits.h 和 float.h头文件用#define定义了一组表示整型和浮点型不同属性的符号常量。