C语言输入与输出

调试环境:win10+vs2015
声明:本篇博客主要讲怎样使用C语言标准库里面的输入输出函数

输入输出简单机制

我们在使用输入输出函数的时候,不管是从文件还是控制台,它都是会先存放在缓冲区里面,但需要使用的时候才会在缓冲区里面提取。
例如:

#include<stdio.h>
int main() {int a, b;scanf("%d %d", &a, &b);printf("%d %d\n", a, b);scanf("%d %d", &a, &b);printf("%d %d\n", a, b);scanf("%d %d", &a, &b);printf("%d %d\n", a, b);return 0;
}

在控制台输入:
12 23 34 45 56 67 78 89
控制台输出结果:
12 23
34 45
56 67

由于输入在控制台的数据已经到了缓冲区,所以除了第一次调用scanf()的时候控制台会弹出,另两次则不会弹出,缓冲区不为空,直接从缓冲区里面拿取数据。
如果不想这样做可以选择清空缓冲区,我们可以使用fclose()函数清空缓冲区并关闭流,这样我们就无法继续使用流了。因此我们可以选择使用fflush()函数,在不关闭流的情况下清空缓冲区。(注:vs2015不支持这样做)

函数原型:int fflush ( FILE * stream );
参数:一个文件指针
返回值:整形。返回0,表示运行正常,返回EOF(-1)时说明已到文件尾或发生错误

如果给出的文件流是一个输出流,那么fflush()把输出到缓冲区的内容写入文件;
如果给出的文件流是输入类型的,结果未定义;
fflush(NULL)刷新所有的输出流;
fflush(stdin)刷新标准输入缓冲区,把输入缓冲区里的东西丢弃;
fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上。
例如

#include<stdio.h>
int main() {int a, b;scanf("%d %d", &a, &b);printf("%d %d\n", a, b);fflush(stdin);scanf("%d %d", &a, &b);printf("%d %d\n", a, b);fflush(stdin);scanf("%d %d", &a, &b);printf("%d %d\n", a, b);return 0;
}

在控制台输入:
12 23 34 45 56 67 78 89
控制台输出
12 23
然后会等待控制台输入

这就是C语言的输入输出的大致运行模式

标准输入输出

标准输入

在C语言中最简单的输入就是从控制台输入一个字符,所以就需要用到getchar()函数

函数原型: int getchar(void);
参数:无参函数
返回值:整形。如果是控制台输入末尾返回EOF(-1),如果不是则返回字符的ASCII码值。为什么返回int有待考究。
说明:从标准输入中(一般为键盘)一次读取一个字符。
例如

#include<stdio.h>
int main() {char c;c = getchar();return 0;
}

运行结果:c为控制台输入的第一个字符。

如果需要调试的话,每次输入都是一样的就可以使用输入重定向
运行程序时在命令行时输入prog

#include<stdio.h>
int main() {char c;c = getchar();c = putchar(c);return 0;
}

运行结果:控制台输出输入的第一个字符

同样的输出也可以进行重定向,使用prog>输出文件名,可已经将需要输出在控制台的东西存放至文件中,这样就可以永久保存。

ungetc()函数

在C语言中,输入判断时使用getchar()函数,但是我们有需要使用读取的这个字符,就需要使用ungetc()函数。
函数原型:int ungetc(int c,FILE* fp);
函数参数:整形,文件指针。
函数返回值:整形。如果执行成功返回c,否则返回EOF。
说明:每个文件只能接收一个写回字符,ungetc()函数可以和任何一个输入函数一起使用。
例如

#include<stdio.h>
int main() {char c;c = getchar();c = ungetc(c, stdin);c = getchar();c = putchar(c);return 0;
}

控制台输入:dh
控制台输出:d

格式化输入输出

可变参数列表

除了getchar外我们还经常还是用格式化输入输出。
如果想要了解格式化输入输出怎么用就有必要先了解什么是变长参数表。
要实现可变参数函数,定义时与其他函数就不一样
例如

void fun(int count,...){//dosomething
}

函数必须要有一个确定的参数,“…”表示参数是可变的。
想要实现它还需要了解三个宏

typedef char* va_list;
#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap)        ((void)(ap = (va_list)0))其中
#define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define _ADDRESSOF(v) (&(v))

例如

#include<stdarg.h>
#include<stdio.h>void fun(int a,...) {va_list ap;va_start(ap, a);for (int i = 0; i < a; i++) {printf("%d ", va_arg(ap, int));}va_end(ap);
}int main() {fun(3, 1, 2, 3);return 0;
}

运行程序:控制台打印 1 2 3

格式化输出

讲完可变参数列表我们就可以来看看格式化输出printf()函数了。
函数原型:int printf(char* format,...);
函数参数:传入字符串,可变参数列表。
函数返回值:整型。表示成功输出变量的个数。
format表示格式字符串,它包含两种类型的对象(普通字符和转换说明),在输出时普通字符原样复制到输出流中,而转换说明并不是直接输出到输出流中,而是用于控制printf中参数的转换和打印每个转换说明都有一个百分号字符开始,并以一个转换字符结束。在字符%和转换字符中间可以包含下列的组成部分:

  • 负号,用于指定被转换的参数按照左对齐的形式输出
  • 数字,用于指定最小字段宽度。转换后的参数将打印不小于最小字段宽度的字段。
  • 小数点,用于将字段宽度和精度分开。
  • 数,用于指定精度,即指定字符串中要打印的最大字符数、浮点数小数点后的位数、整型最小输出的数字数目。
  • 字母h或l,字母h表示不将整数作为short类型打印,字母l表示将整数作为long类型打印。

转换说明见文章尾点击此可快速跳转

例如

#include<stdio.h>
int main() {char* str = "1234567890123";printf("%s\n", str);printf("%10s\n", str);printf("%.10s\n", str);printf("%-10s\n", str);printf("%.15s\n", str);printf("%-15s\n", str);printf("%15.10s\n", str);printf("%-15.10s\n", str);return 0;
}

程序运行后结果:
1234567890123
1234567890123
1234567890
1234567890123
1234567890123
1234567890123
1234567890
1234567890

还有一个和printf()函数类似的函数–sprintf()函数
函数原型:int sprintf(char* str,char* format,...);
函数参数:两个字符串,可变参数列表
返回值:整形。
说明:它与printf()函数一样输出,只不过它将输出存放至一个字符串里面,而不是输出到控制台上面。只要str空间足够大就可以放完所有的输出。

格式化输入

有了输出,也就因该有输入,所以与printf()函数相对应的就是scanf()函数。
函数原型:int scanf(char* format,...);
函数参数:一个字符串,一个可变参数列表
函数返回值:返回匹配成功的个数
说明:scanf()函数从标准输入中读取字符串序列,按照format中的格式说明对字符序列进行解释,并把结果保存到其余的参数中,所有参数都必须是指针。如果遇到某些输入无法与格式控制说明匹配的情况时该函数将终止,同时将到此为止匹配成功参数的个数作为返回值返回,到达文件尾返回EOF。
格式化串包含的转换说明,用于控制输入的转换,包括:

  • 空格或制表符,在处理过程中忽略
  • 普通字符,用于匹配输入流中下一个非空白符字符
  • 转换说明, 依次由一个%,一个可选的赋值禁止字符*、一个可选的数值(指定最大字段宽度)、一个可选的h/l或L字符(指定目标对象的宽度)以及一个转换字符组成点击此处跳转查看

例如

#include<stdio.h>
int main() {char str[10] = { 0 };int a = 0;float f = 0;scanf("%s %d %f",str,&a,&f);printf("%s %d %f", str, a, f);return 0;
}

程序运行结果:
控制台打印
abc 10 1.0001
abc 10 1.000100

还存在与scanf()函数类似的函数–sscanf()函数
函数原型:int sscanf(char* str,char* format,...);
函数参数:两个字符串,一个可变参数列表,传入参数必须是指针
函数返回值:返回匹配的个数
说明:从str中读取字符串序列

注意:如果scanf()函数与其他输入函数混合使用时,无论哪种函数,下一个输入函数将从scanf()函数没有读取的第一个字符开始读取数据。

文件访问

上面讲的都是从标准输入读取数据,标准输出输出数据,这些是操作系统提供的接口。接下来我们从文件中进行输入输出。
在C语言中提供了一个FILE的结构体,里面包含了一个空指针。

typedef struct _iobuf{void* _Placeholder;} FILE;

声明方式:FILE* fp;
赋值方式:FILE* fp=fopen(char* name,char* mode);
函数参数:俩个字符串
函数返回值:返回一个FILE类型的指针
说明:将文件名为name的文件,以mode模式打开。允许模式包括:读(“r”)、写(“w”)和追加(“a”),还有的区分文本文件和二进制文件,对二进制操作需要在模式字符串中增加字符“b”。如果文件不存在用于写或者追加,该文件将会被创建。当以写的方式打开一个已存在的文件时,原内容将会被替换,如果以追加形式打开则不会覆盖。如果发生错误,函数将返回NULL。即:

  • r:打开文本文件用于读
  • w:创建文本文件用于写,并删除已存在的内容(如果有的话)
  • a:追加;打开或创建文本文件,并向文件末尾追加内容
  • r+:打开文本文件用于更新(即读和写)
  • w+:创建文本文件用于更新,并删除已存在的内容(如果有的话)
  • a+:追加;打开或创建文本文件用于更新,写文件时追加到文件末尾

后三种方式(更新方式)允许对同一文件进行读和写。在读写交叉过程中,需要使用fflush()函数进行刷新流操作或者使用文件重定位函数。在末尾加上b,就可以对二进制文件进行操作。文件名filename限定最多为FILENAME_MAX个字符,一次最多可以打开FOPEN_MAX个文件。
在vs2015中他们的定义为

#define FILENAME_MAX    260
#define FOPEN_MAX       20

注意:每次使用完文件流时,我们需要关闭流,使用fclose()函数进行流的关闭
函数原型:int fclose(FILE* fp);
函数参数:文件指针
函数返回值:整型,执行成功返回0,失败返回EOF
说明:它的作用与fopen()函数作用相反,它的作用是释放自动分配的全部缓冲区,它还可以将缓冲区的清空,如果是输出则将尚未写入文件的所有数据写入文件中,最后关闭流。当程序正常终止时,程序会为每个打开的文件调用fclose()函数。

在使用文件指针的时候,可能想更改当前文件指针指向的文件,这个时候就可以使用freopen()函数了
函数原型:FILE* freopen(const char* filename,const char* mode,FILE* stream);
函数说明:函数以mode指定的模式打开filename指定的文件,并将该文件关联到stream指定的流。它返回stream;若出错则返回NULL。freopen()函数一般用于改变与stdin、stdout和stderr相关联的文件。

其他的相关函数

int remove(const char* filename);
//删除filename指定的文件,后续试图打开该文件的操作都将失败,如果操作失败则返回一个非零值int rename(const char* oldname,const char* newname);
//修改文件的名字,操作失败返回非零值FILE* tmpfile(void);
//以w+b模式创建一个临时文件,该文件会在被关闭或程序正常结束时自动删除。创建成功返回一个流,创建失败返回NULL。char* tmpnam(char s[L_tmpnam]);
//传值NULL:创建一个与现有文件名不同的字符串,并返回一个指向一内部静态数组的指针。
//传值s:把创建的字符串保存至数组s中,并将它作为函数值返回,s中至少要有L_tmpnam个字符空间,每次调用是生成不同的名字
//在程序执行的过程中,最多只能确保生成TMP_MAX个不同的名字。在vs2015中的定义为:
#define TMP_MAX    _CRT_INT_MAX
#define _CRT_INT_MAX 2147483647
//tmpname()函数只是用于创建一个名字,而不是创建一个文件void setbuf(FILE* stream,char* buf);
//如果buf的值为NULL,则关闭流stream的缓冲;否则setbuf函数等价于(void)setvbuf(stream,buf,_IOFBF,BUFSIZ);int setvbuf(FILE* stream,char* buf,int mode,size_t size);
//该函数作用是控制流stream的缓冲,在调用读写或其他操作之前必须调用此函数。
//当mode的值为_IOFBF时,将进行完全缓冲。
//当mode的值为_IOLBF时,将对文本文进行缓冲。
//当mode的值为_IONBF时,表示不设置缓冲。
//如果buf的值不是NULL,则该函数将buf指向的区域作为流的缓冲区,否则将分配一个缓冲区,size决定缓冲区的长度。
//如果函数运行错误,返回一个非0值。

文件操作

字符输入输出函数

使用getc()函数和putc()函数,可以以单字符形式读取文件流中的内容。
函数原型:

int getc(FILE* fp);
int putc(int c, FILE* fp);

函数参数:文件流,int类型的字符c
函数返回值:getc()从文件中返回下一个字符,到达文件尾或出现错误,返回EOF
putc()将字符c写入到fp所指向的文件中,并返回写入的字符,到达文件尾或出现错误,返回EOF

注意:getc()和putc()是宏函数

文件格式化输入输出

格式化输出
函数原型:int fprintf(FILE* stream,const char* format,...);
函数说明:同printf()函数,按照format说明的格式对输出进行转换,并写道stream流中。返回值是实际写入的字符数,如出错返回一个负值
格式化输入
函数原型:int fscnaf(FILE* stream,const char* format,...);
函数说明:同scanf()函数,根据格式化串format从流stream中读取输入,并把转换后的值赋值给后续各个参数,每个参数都必须是一个指针。返回值是实际被转换并赋值输出项的数目,如出错返回EOF

直接输入输出

有时我们可以直接从流里面直接读写,就需要使用直接输入输出函数。
函数原型:

size_t fread(void* ptr,size_t size,size_t nobj,FILE* stream);//直接输入函数
size_t fwrite(const void* ptr,size_t size,size_t nobj,FILE* stream);//直接输出函数

函数说明:
fread()函数从流stream中读取最多nobj个长度为size的对象,并保存到ptr所指向的数组中,它返回读取的对象数目,此返回值可能小于nobj。必须通过函数feof()和ferror()获得结果执行状态。
fwrite()函数

行输入行输出

有些时候我们并不想一个一个字符的输入和输出,所以我们需要行输入和行输出。

标准库提供了一个行输入函数fgets()函数。
函数原型:char* fgets(char* line,int maxline,FILE* fp);
函数参数:字符指针指向字符串,整型表明字符串有多大,文件指针
函数返回值:运行正常的情况下会返回line,遇到错误返回NULL
说明:从fp指向的文件中读取下一个输入行(包括换行符),并将它存放在字符数组line中,最多可以读取maxline-1个字符,读取的行将以‘\0’结尾保存至数组中。

输出行函数fputs()
函数原型:int fputs(char* line,FILE* fp);
函数参数:待写入的字符串,文件指针
函数返回值:发生错误返回EOF,否则返回非负值
说明:将字符串line写入fp所指向的文件中。

文件定位函数

函数原型:int fseek(FILE* stream,long offset,int origin);
函数说明:设置流的文件位置,后续的读写操作将从新的位置开始。
对于二进制文件,此位置被设置为从origin开始的第offset字符处,origin的值可以是SEEK_SET(文件开始处)、SEEK_CUR(当前位置)、SEEK_END(文件结束处);
对于文本流,offset必须设置为0,或者是ftell()函数的返回值(此时的origin的值必须是SEEK_SET).
fseek()函数在出错时返回一个非0值。

函数原型:long ftell(FILE* stream);
函数说明:返回stream流的当前文件位置,出错时返回-1L。

函数原型:void rewind(FILE* stream);
函数说明:函数等价于fseek(fp,0l,SEEK_SET);clearerr(fp);的执行结果。即:将stream流指向文件开始处,并清除与流相关的错误提示符。

函数原型:int fgetpos(FILE* stream,fpos_t* ptr);
函数说明:把stream流的当前位置记录在ptr中,供fsetpos()函数调用。若出错返回一个非0值。

函数原型:int fsetpos(FILE* stream,const fpos_t* ptr);
函数说明:将流stream的当前位置设置为fgetpos()函数记录在ptr中的位置,若出错返回一个非0值。

错误处理

当发生错误或者到达文件尾时,大部分输入输出函数都会设置状态指示符。这些状态指示符可以被显示的设置和测试。在整型表达式errno中可以包含一个最近发生的错误的错误码,可以通过使用监视窗口输入“err,hr”,查看当前错误码,并且还可以看见其指示的意义。

void clearerr(FILE* stream);
//清除与流stream相关的文件结束符和错误指示符int feof(FILE* stream);
//如果设置了与流stream相关的文件结束指示符,feof函数将返回一个非0值。int ferror(FILE* stream);
//如果设置了与stream流相关的错误指示符,该函数将返回一个非0值。void perror(const char* s);
//函数打印字符串s以及与errno中整型至相应的错误信息,错误信息的具体内容与具体实现有关,函数功能相当于fprintf(stderr,"%s: %s\n",s,"error message");

格式化转换说明

字符 参数类型:输出形式
d,i int类型;十进制数
o int类型;无符号八进制数(没有前导0)
x,X int类型;无符号十六进制(没有前导0x或0X)
u int类型;无符号十进制
c int类型;单个字符
s char*类型;顺序打印字符串中的字符,直到遇到’\0’或已打印了由精度指定的字符数为止
f double类型;十进制小数[-]m.dddddd,其中d的个数由精度指定(默认值为6)
e,E double类型;[-]m.dddddd e ±xx或[-]m.dddddd E ±xx,其中d的个数由精度指定(默认值为6)
g,G double类型;如果指数小于-4或大于等于精度,则用%e或%E格式输出,否则用%f格式输出,尾部的0和小数点不打印
p void*类型;指针(取决于具体实现)
% 不转换参数;打印一个百分号%

Published by

风君子

独自遨游何稽首 揭天掀地慰生平