输入与输出并不是c本身的功能
一般讨论的输入输出都是建立在标准输入输出之上的,标准输入(stdin)和标准输出(stdout)是操作系统自动提供给程序访问的
ANSI标准精准地定义了包括输入\输出函数、字符串处理函数、存储管理函数以及数学函数等标准库函数
在任何可以使用c的系统中都有这些函数兼容形式,如果程序的系统交互部分仅用了标准库提供的功能,则可以不经修改地从一个系统移植到另一个系统
这些库函数的属性分别在十多个头文件中声明,如<stdio.h>、<string.h>、<ctype.h>等

标准输入输出

标准库实现了简单的文本输入和输出模式
文本流由一系列行组成,每行结尾是一个换行符,如果系统没有遵循这种模式,则标准库将通过一些措施使得该系统适应这种模式

如:标准库可以在输入端将回车符和换页符都转换为换行符,而在输出端进行反向转换

字符输入函数getchar()

使用getchar函数从标准输入(一般为键盘)中一次读取一个字符

函数原型:int getchar(void);

getchar函数每次在被调用时返回下一个输入字符,若遇到文件结尾则返回EOF,符号常量EOF在头文件<stdio.h>中定义,其值一般为-1
不过一般在程序中应使用符号常量EOF来测试文件是否结束,这样才能保证程序与EOF的特定值无关
在许多环境中,可以使用符号’<'来实现输入重定向,它把键盘输入替换为文件输入

如:在程序prog中使用了函数getchar(),则命令行prog <infile将使得程序prog从文件infile(而不是键盘)中getchar()
并且在事实上程序prog本身并不在意输入方式的改变,字符串"<infile"也不包含在argv的命令行参数中

如果输入通过管道机制来自于另一个程序,那么这种输入切换也是不可见的

如:在某些系统中,命令行otherprog|prog将运行两个程序otherprog和prog
并将程序otherprog的标准输出通过管道重定向到程序prog的标准输入上

字符输出函数putchar

函数原型:int putchar(int);

用于输出数据,在程序中往往可以交叉调用函数putchar和printf,输出将按照函数调用的先后顺序依次产生

格式化输出函数printf

输出函数printf将内部数值转换为字符的形式

函数原型:int printf(char*format,arg1,arg2,…);

基本格式:printf(“格式字符串”,输出项列表)
printf在输出格式format的控制下将其参数进行转换与格式化,并在标准输出设备上打印,返回值为打印的字符数
输出项可以为变量,常量,表达式,其间用逗号分隔
格式字符串包含两种类型的对象:普通字符和转换说明(以及%)
输出时,普通字符将原样不动地复制到输出流中,而转换说明不直接复制到输出流中,而是用于控制printf中参数的转换与打印
每个转换说明都由一个百分号字符(即%)开始,并以一个转换字符结束,使用修饰符可以控制输出宽度,精度,小数位数,对齐方式等
在字符%和转换说明中间可能按顺序依次包含下列修饰符:

  • 负号:用于指定被转换的参数按照左对齐的形式输出
  • 数(小数点前):用于指定最小字段宽度,转换后的参数将打印不小于最小字段宽度的字段
  • 如果有必要,字段左边(如果使用左对齐,则为右边)多余字符位置用空格填充以保证最小字段宽度
  • 小数点:用于将字段宽度和精度分开
  • 数(小数点后):用于指定精度,即字符串中要打印的最大字符数、浮点数小数点后的位数、整型最少输出的数字数目
  • 字母h或l:字母h表示将整数作为short类型打印,字母l表示将整数作为long类型打印

printf转换说明(如果%后面的字符不是一个合法的转换说明则该行为是未定义的):

  • %d或%i:参数类型为int,用于输出十进制整型数(整型指本质为整型的数,如int、short、char等)
  • %c:参数类型为int,用于输出单个字符
  • %s:参数类型为char*,顺序打印字符串中的字符,直到遇到’\0’或已打印了事先指定的精度数的字符数为止
  • %o:参数类型为int,以无符号八进制形式输出整数值(没有前导0)
  • %x或%X:参数类型为int,以无符号十六进制形式输出整数值(没有前导0或0x),10-15分别用abcdef或ABCDEF表示
  • %u:以无符号形式输出十进制数
  • %f:参数类型为double,用于输出十进制小数[-]m.dddddd,d的个数由精度指定(缺省值为6)
  • %e或%E:参数类型为double,以科学计数的形式输出十进制小数[-]m.dddddde±xx,[-]mm.ddddddE±xx,d的个数由精度指定(缺省值为6)
  • %g或%G:参数类型为double,如果指数小于等于-4或大于等于精度,用%e或%E格式输出,否则用%f格式输出,尾部的0和小数点不打印
  • %p:参数类型为void*,指针的值(取决于具体实现)
  • %%:参数为’%’,直接打印一个%
    此外,在转换说明中,宽度和精度可以用*表示,这时宽度或精度的值通过转换下一参数(必须为int类型)进行计算
如:printf("%*s",max,s);表示从字符串s中至多打印max个字符
例: int i=123;
printf("%6d",i); 输出结果:(三个空格)123        输出结果字段宽度为6字节,不足位用空格填充
printf("%06d",i); 输出结果:000123            6前加0表示字段宽度为6字节,不足位用0填充
float j=1234.567;
printf("%9.3f",j); 输出结果: (空格)1234.567   输出结果共9字段,小数点后保留三位
printf("%-9.3f",j); 输出结果: 1234.5657(空格) 使用负号为左对齐,不用负号为右对齐
printf("%0*d",i); 输出结果: 000123
printf("%0*.*f"9,3,j); 输出结果: 01234.567

对字符串而言,其输出的字符个数由转换说明中的精度来指定,在宽度不足时用空格填充,空格填充的位置由对齐方式决定

例: 
cout<<printf("%s","hello, world")<<endl;        输出结果:hello, world12
cout<<printf("%10s","hello, world")<<endl;      输出结果:hello, world12
cout<<printf("%.10s","hello, world")<<endl;     输出结果:hello, wor10
cout<<printf("%-10s","hello, world")<<endl;     输出结果:hello, world12
cout<<printf("%.15s","hello, world")<<endl;     输出结果:hello, world12
cout<<printf("%-15s","hello, world")<<endl;     输出结果:hello, world   15
cout<<printf("%15.10s","hello, world")<<endl;   输出结果:     hello, wor15
cout<<printf("%-15.10s","hello, world")<<endl;  输出结果:hello, wor     15
注意,printf由第一个参数判断后面参数的个数及类型,如果参数的个数不够或者类型错误则将得到错误的结果
如:printf(s)中如果字符串包含%则输出错误,printf("%s",s)则输出一定正确

格式输入函数scanf

输入函数scanf对应于输出函数printf,它在后者相反的方向上提供同样的转换功能

函数原型:int scanf(char* format,…);

基本格式:scanf(“格式字符串”,输入项列表)
scanf函数从标准输入中读取字符序列,按照format中的格式说明对字符序列进行解释,并把结果保存到其余参数中
其他所有无名参数都必须是指针,用于指定经格式转换后的相应输入保存位置
当scanf函数扫描完其格式串,或者碰到某些输入无法与格式控制说明匹配的情况时,该函数将终止
同时成功匹配并赋值的输入项的个数将作为函数值返回,因此scanf函数的返回值可以用来确认已匹配的输入项的个数,如果到达文件的结尾,该函数将返回EOF
注意,返回EOF与0是不同的,0表示第一个输入字符与格式串中的第一个格式说明不匹配,下次调用scanf将从上一次转换的最后一个字符的下一个字符开始搜索
同printf一样,scanf的格式串通常也包含转换说明和普通字符,对普通字符而言分两种情况:
(格式串中加入可打印字符可忽略输入流中的相同字符)
空格或制表符:在处理过程中所有空白符(包括但不限于空格和制表符)均被忽略(一般用于处理最后的输入项后的空格,因为scanf本身忽略输入项以前和之间的空格)
普通字符(不包括%):用于匹配输入流中的下一个非空白符字符(该字符将被忽略,只有与输入流中全部字符均匹配成功时scanf函数才能进行赋值)
scanf的转换说明用于控制下一个输入字段的转换,一般来说,转换结果存放在相应的参数指向的变量中
同理,转换说明与%中间可能包含修饰符,用于控制输入的转换:

  • 星号(* ):赋值禁止字符,如果转换说明中有*则跳过一个(用以表示该输入项,读入后不赋予相应的变量,即跳过该输入值),输入字段不进行赋值
  • 数:指定最大字段宽度
  • 字母h、l或L:指定目标对象的长度(字节数),l表示输入长整型数据和双精度浮点数,h表示输入短整型数据,可以加在转换说明d、i、o、u或x的前面

前缀h表明参数表的相应参数是一个指向short而非int类型的指针,前缀h表明参数表的相应参数是一个指向long类型的指针
类似的,转换说明e、f和g的前面也可以加上前缀l,它表明参数表的相应参数是指向double类型而非float类型的指针
输入字段定义为一个不包含空白符的字符串其边界定义为到下一个空白符或到达指定的字段宽度,这表明scanf函数将越过行边界读取输入,因为换行符也是空白符
(空白符包括空格符、横向制表符、换行符、回车符、纵向制表符以及换页符)
转换说明指定对输入字段的解释,对应的参数必须是指针,这是c通过值调用语义所要求的
scanf转换说明(如果%后面的字符不是一个合法的转换说明则该行为是未定义的):

  • %d:参数类型为int*,输入十进制整数
  • %i:参数类型为int*,输入整数,可以是八进制(以0开头)或十六进制(以0x或0X开头的)
  • %o:参数类型为int*,输入八进制整数(可以0开头也可以不0开头)
  • %u:参数类型为unsigned int*,输入无符号十进制整数
  • %x:参数类型为int*,输入十六进制整数(可以0x或0X开头也可以不0x或0X开头)
  • %c:参数类型为char*,输入字符,将接下来的多个输入字符(默认为1个字符)存放到指定位置,该转换规范通常不跳过空白符,如果需要读入下一个非空白符可使用%1s
  • %s:参数类型为char*,输入字符串(不加引号),指向一个足以存放该字符串的字符数组(包括尾部的字符’\0’的位置),字符串的末尾将被添加一个结束符’\0’
  • %e或%f或%g:参数类型为float*,输入浮点数,它可以包括正负号(可选)、小数点(可选)以及指数部分(可选)
  • %%:输入字符%,不进行任何赋值操作
例: *可以跳过对应数据赋值操作
scanf("%d%*d",&a,&b); 输入34 45时 a=34 b赋到的值是空格(跳过了第二个输入项45)
scanf("%d%c",&a,&b); 输入 45 a时 a=45 b值为空格
scanf("%d %c",&a,&b); 输入 45 a时 a=45 b=a 可以解决上面赋值空格的问题
scanf("%d,,,,%c",&a,&b); 输入 45,a时 a=45 b=a 同理,可以忽略输入流中的,,,,
double sum=0,v;while(scanf("%1f",&v))printf("\t.2f",sum+=v); 简易计算器
int day,year;char monthname[20];scanf("%d %s %d",&day,monthname,&year); 读取日期格式的输入如:25 Dec 1988
(数组名本身就是指针,因此monthname的前面 没有加取地址运算符&)
int day,month,year;scanf("%d/%d/%d",&day,&month,&year); 读取日期格式的输入如:dd/mm/yy
如果要读取格式不固定的输入,最好每次读一行,然后再用sscanf将合适的格式分离出来读入
如:假定需要读取一些包含日期数据的输入行,日期的格式可以是多种形式
while(getline(line,sizeof(line))>0)
{
    if(sscanf(line,"%d %s %d",&day,monthname,&year)==3)printf("valid:%s\n,line");  格式形如:25 Dec 1988
    else if(sscanf(line,"%d/%d/%d",&day,&month,&year)==3)printf("valid:%s\n,line");格式形如:dd/mm/yy
    else printf("invalid:%s\n,line");                                              日期格式无效
}
scanf函数可以和其他输入函数混合使用,无论调用哪个输入函数,下一个输入函数的调用将从scanf没有读取的第一个字符处开始读取数据
最后,再次强调注意scanf和sscanf函数的所有参数都必须是指针,最常见的错误是将输入语句scanf("%d",&n);写为:scanf("%d",n);
部分编译器在编译时可能是检测不到这类错误的

格式化输出函数sprintf与格式化输入函数sscanf

函数sprintf和printf执行的转换与函数printf相同,但它将输出保存到一个字符串中,函数sscanf用于从一个字符串中(而不是标准输入)中读取字符序列

函数原型:
int sprintf(char* string,char* format,arg1,arg2,…);
int sscanf(char* string,char* format,arg1,arg2,…);

sprintf函数和printf函数一样,按照format格式格式化参数序列arg1、arg2、…
区别在于sprintf将输出结果存放到string中,而不是输出到标准输出
存储字符串的变量string必须足够大以存放输出结果
函数sscanf用于从一个字符串中(而不是标准输入)中读取字符序列,并将结果分别保存在arg1、arg2、…这些参数中,这些参数必须是指针
许多程序只需要从一个数据流中读取数据,并且只向一个输出流中输出数据
对于这样的程序只需使用函数getchar()、putchar()、printf等实现输入输出即可,并且对程序而言一般已经足够
特别是,如果通过重定向将一个程序的输出连接到另一个程序的输入,仅使用这些函数就足够了

如:int main()
   {
        int c;
        while((c=getchar())!=EOF)putchar(tolower(c));
        //函数tolower定义在<ctype.h>,将大写字母转换小写形式,并降其余字符原样返回
        return 0;
    }

实际上,头文件<stdio.h>的getchar()和putchar()“函数”以及<ctype.h>中的tolower“函数”一般都是宏
因此可以避免对每个字符都进行函数调用的开销,并且无论<ctype.h>中的函数在给定机器上如何实现,使用这些函数的程序都不必了解字符集的知识