14.2 高层I/O

C++中的高层I/O是从C继承而来的,使用头文件<cstdio>。本节先从其标准的输入输出讲起,再来学习高层I/O中文件的操作。原则上流类库已经能胜任所有的输入输出处理,但高层I/O的介绍很有必要,这有助于读者阅读源代码。

14.2.1 标准输出函数printf

printf函数用于将字节流复制到标准输出设备,其使用方式十分灵活,有两个突出的优点,一是其参数表的长度是可变的;二是其转换说明和格式控制十分简单。printf函数的参数表分为两部分,即控制字符串和参数表,基本格式如下所示。


int printf(const char*,para1,para2……);

注意

这里使用的概念是函数而不是对象,注意printf和cout的不同,这也许就是C和C++思维方式的不同。

printf函数用于将字符流复制到标准输出设备,在进行深入讲解前,先看以下示例代码14.1。

代码14.1 printf函数使用范例PrintfSample


<————————————文件名:example1401.cpp———————————————> 01 #include<cstdio>//使用printf要包含的头文件 02 int main() 03 { 04 printf("%s is%d years old\n","Deco",24);'\n'是换行符 05 return 0; 06 }

输出结果如下所示。


Deco is 24 years old

【代码解析】代码第4行演示了printf函数最基本的用法,注意下述代码。


控制字符串:最前面的一个字符串,即"%s is%d years old\n",其中%s和%d是格式要求 参数表:两个参数"Deco"和24。

细心的读者可能会发现,代码14.1并没有引入名称空间std,这是因为头文件<cstdio>中实体的定义并没有采用名称空间的形式,也就是说,其中的函数和实体都定义在全局空间中,所以在包含了<cstdio>头文件后,直接使用函数名printf调用即可。

printf函数对参数表中的表达式进行求值,并按照控制字符串中的格式要求进行转换,把结果放在标准输出流中,控制字符串中符号%标记格式说明符的开始,而由转换字符结尾(诸如代码14.1中跟在%后的s或d)标记格式说明符的结束,也就是说,“%s”整体是一个格式说明符,格式说明符将被参数表中对应位置的参数值替换,控制字符串中不是格式说明符的部分将字节放入标准输出流中,如代码14.1中的“is”和“years”以及单词之间的空格等,printf返回当前已成功写入标准输出流的字符数。

printf函数支持的转换字符如表14.1所示,%标记着某个格式转换符的开始,而转换字符标记着其所在格式转换符的结束,它是必不可少的,且在单个格式转换符中只能有一个转换字符。

14.2 高层I/O - 图1

特别说明,%g或者%G表示系统决定对应浮点数采取常规或科学记数法中最短的方式输出该数据,且不输出无意义的0,如下所示。


printf("%g",123.456);//显示123.456; printf("%g",0.0000123456);//显示1.23e-008

当浮点数%f(%F)或%e(%E)形式的小数位数小于6,补足0使其达到6位,若大于6位,不予截断,而是保留,再比较两种形式下的数据长度,选择合适的一种,进行6位有效数字的截断,如123.456的%f形式为:123.456000,占10个字符空间,%e形式为:1.234560e-002,占12位字符空间,因此,%g取%f的形式进行输出,且不输出没有意义的0,结果为123.456。0.0000123456的%f形式为:0.0000123456(不截断),占12个字符空间,%e形式为1.234560e-005,同样占据12个字符空间,在这种情况下,优先使用%e的形式。

除了开头和结尾的标记,printf函数还提供了其他一些可选的控制标记,使输出形式更为灵活,充分满足程序的需求,在%与转换字符之间依次可以有下列标记。

1.域宽(Field Width)M

单个字符占用的显示空间称为一格,域宽M是用以指明被转换参数要使用的最少格数,用一个正整数来表示,如果被转换参数的字符数小于M,那么需要对该参数对应的显示区进行填充,使满足域宽要求。如果参数的字符数大于M,那么域宽的设定便失效,按参数的实际字符数显示。

域宽是可选的,默认情况下等同于参数的实际字符数。

如何填充取决于调整方式(左调整还是右调整),是用0还是空格填充,这可以由用户根据其他标记设定,稍后会进行介绍。默认情况下是右调整并以空格填充,如下所示。


printf("%5d",10);//输出"□□□10",右调整,左边添加空格以满足域宽要求 printf("%5f",12.123456789);//原来的字符数已经超过域宽5,限制失效,但f转换默认为 //6位小数,所以输出为"12.123457"

注意

为了说明更形象直观,书中使用□表示空格符,这是一种人为替换而不是真实输出。

2.精度(Precision).N

用一个小数点加一个整数N来表示,对e、E和f转换用以指明非整型参数转换后的小数位数目,如果参数的小数位数多于N,对参数进行近似处理,若参数位数小于N,用0进行填充使其小数位数为N,对d、i、o、u、x和X等整型转换来说,N描述了转换参数被显示数字的最小个数。举例如下所述。


printf("%.3f",1.23456789);//小数位数多于精度3,近似处理,输出"1.235" printf("%.6f",1.23);//小数位数不足精度6,用0填充,输出结果为"1.230000" printf("%.6d",567);//d型转换,精度用于指定被显示数字的最小个数,至少应有 //6个数字被显示,输出结果为"000567"对s转换来说,精度N描述了要显示的最大字符数,如果参数字符数大于N,字符串会被截断,当字符数小于N时,限制失效,如下所示。 printf("%.4s","Computer");//参数字符数大于4,输出结果为"Comp" printf("%.10s","Hello");//参数字符数小于10,输出结果为"Hello",限制失效

对g和G转换来说,精度M描述了有效数字的最大位数,默认为6。

精度可以和域宽结合使用,如下例所示。


printf("%7.3f",1.23456789);//输出结果为"□□1.235",注意小数点也占1格 printf("%6.4d",567);//输出结果为"□□0567" printf("%10.4s","Hello");//输出结果为"□□□□□□Hell"

3.可选项h

h是short修饰符,如果将h放在转换字符d、i、o、u、x和X的前面,那么转换描述适用于short int或unsignedt short int参数,如果h后跟转换字符n,那么相应的参数是指向short int或unsigned short int的指针。

4.可选项l

l是long修饰符,如果将l放在转换字符d、i、o、u、x和X的前面,那么转换描述适用于long int或unsignedt long int参数,如果将l放在转换字符e、E、f、g或G前,那么转换描述适应于double型参数,如果l后跟转换字符n,那么相应的参数是指向long int或unsigned long int的指针。

5.可选项L

L也是一个long修饰符,如果将L放在转换字符e、E、f、g或G前,那么转换描述适用于long double参数。

说明

对于单精度数,使用%f格式符输出时,仅前7位是有效数字,小数为6位。对于双精度数,使用%lf格式符输出时,前16位是有效数字,小数为6位。

6.减号-

减号的放置位置有一定要求,必须在%和域宽M之间,用以指定参数是左对齐(左调整),如果没有减号,参数右对齐(右调整),如以下域宽中的例子所示。


printf("%-5d",10);//输出"10□□□",左调整,右边添加空格以满足域宽要求

7.加号+

加号要放置在%和域宽M(如果已设定了M,否则应放在精度前)之间,但和减号并没有先后顺序要求,加号只能用来修饰d、i、e、E、f、g和G等有符号类型,如果没有指明加号,负数输出以减号开头,非负前没有符号,通过指明加号非负数输出以正号(加号)开头,如下所示。


printf("%+d",10);//输出结果为"+10"

8.空格

空格的位置要求和加号一样,所起作用也相似。如果输出为非负数,则在其前面加一个空格,如果用加号和空格同时指定,空格被忽略,空格同样只能用于修饰d、i、e、E、f、g和G等有符号类型,如下所示。


printf("%.3f",1.23);//输出"□1.230"

9.#

的位置要求同加号以及空格一样,用于指定其他输出格式,对于o格式,第一个数字必须为零;对于x/X格式,指定在输出的非0值前加0x或0X;对于e/E/f/g/G格式,指定输出总有一个小数点,即便精度设置为0;对于g/G格式,还指定输出值后面无意义的0将被保留,如下所示。


printf("%o",127);//输出177 printf("%#o",127);//输出0177 printf("%x",127);//输出7f printf("%#x",127);//输出0x7f printf("%.0f",3.1415);//输出3 printf("%#.0f",3.1415);//输出3.(小数点也被显示)

10.0

0的位置要求和#一样,如果设定了0标记,则当输出长度小于域宽M时,用0进行填充,而不是用空格来填充,如下所示。


printf("%010d",256);//输出0000000256在一个格式中,可以用*代表整数来说明域宽或精度,这需要从参数表中获取值,下面是个简单的示例。 char ch[20]; printf("%.s\n",m,n,ch);

上述代码输出对应域宽为m,精度为n,此时,如果m为负数,将该参数看成是减号后跟着域宽;如果n为负数,没有意义,将其抛弃。

说明

printf函数的标记符系统比较繁杂,需要在使用中仔细体会。