文档章节

C和指针---第十五章:输入/输出函数

fzyz_sb
 fzyz_sb
发布于 2013/09/21 13:56
字数 3166
阅读 86
收藏 0

15.1 错误报告

perror函数以一种简单,统一的方式报告错误。函数原型如下:

void perror( char const *message )
如果message不是NULL并且指向一个非空的字符串,perror函数就打印这个字符串,后面跟一个分号和一个空格,然后打印出一条用于解释errno当前错误代码的信息。
#include <stdio.h>

int main(void)
{
	perror("this have a error\n");

	return 0;
}
程序输出:

注意:只有当一个库函数失败时,errno才会被设置。当函数成功运行时,errno的值不会被修改。这意味着我们不能通过测试errno的值来判断是否有错误发生。反之,只有当被调用的函数提示有错误发生时检查errno的值才有意义。

15.2 终止执行

void exit( int status )
status参数返回给操作系统,用于提示程序是否正常完成。预定义符号有EXIT_SUCCESS和EXIT_FAILURE。

当程序发现错误情况使它无法继续执行下去时,这个函数尤其有用。我们经常会调用perror后调用exit终止程序。

15.4 ANSI I/O概念

15.4.1 流

所有的I/O操作只是简单的从程序移进或移出字节(字节流被称为流)。绝大多数流是完全缓冲,这意味着“读取(进来)”和“写入(出去)”实际上是从一块被称为缓冲区的内存区域来回复制数据。用于输出流的缓冲区只有当它写满时才会被刷新到设备或文件中。

只有当操作系统可以断定它们与交互设备并无联系时才会进行完全缓冲。否则,它们的缓冲状态将因编译器而异。一个常见的策略是把标准输出和标准输入联系在一起,就是当请求输入时同时刷新输出缓冲区。这样,在用户必须进行输入之前,提示用户进行输入的信息和以前写入到输出缓冲区中的内容将出现在屏幕上。

警告:在调试程序时候加入的大量printf函数,后面最好跟fflush:

printf( "something or other" );
fflush( stdout );
fflush迫使缓冲区的数据立即写入,不管它是否已满。

流分两种类型:文本流和二进制流

15.4.2 文件

FILE是一个数据结构,用于访问一个流。对于每个ANSI C程序, 运行时系统必须提供至少三个流---标准输入,标准输出和标准错误。这些流的名字分别是stdin, stdout和stderr,它们都是一个指向FILE结构的指针。

15.5 流I/O总览

文件I/O的一般概况:

1. 程序为必须同时处于活动状态的每个文件声明一个指针变量,其类型为FILE*。这个指针指向这个FILE结构,当它处于活动状态时由流使用。

2. 流通过调用fopen函数打开。为了打开一个流,你必须指定需要访问的文件或设备以及它们的访问方式。fopen和操作系统验证文件确实存在并初始化FILE结构。

3. 然后,根据需要对该文件进行读取或写入。

4. 最后,调用fclose函数关闭流。关闭一个流可以防止与它相关联的文件被再次访问,保证任何存储与缓冲区的数据被正确的写道文件中,并且释放FILE结构使它可以用于另外的文件。

15.6 打开流

fopen函数打开一个特定的文件,并把一个流和这个文件相关联。

FILE *fopen( char const *name, char const *mode );
模式有以下几种:

                     读取            写入            添加

文本                “r”               "w"            "a"

二进制            "rb"                "wb"           "ab"

而a+则表示该文件打开用于更新,并且流既允许读也允许写。

如果fopen函数执行成功,它返回一个指向FILE结构的指针,该结构代表这个新创建的流。如果函数执行失败,它就返回一个NULL指针,errno会提示问题的性质。

FILE *input;
input = fopen( "data3", "r" );
if ( NULL == input ){
	perror( "data3" );
	exit( EXIT_FAILURE );
}
freopen函数用于打开(或重新打开)一个特定的文件流。
FILE *freopen( char const *filename, char const *mode, FILE *stream );
最后一个参数就是需要打开的流,它可能是一个先前从fopen函数返回的流,也可能是标准流stdin,stdout或stderr。

这个函数试图关闭这个流,然后用指定的文件和模式打开这个流。如果打开失败,函数返回一个NULL值。如果打开成功,函数就返回它的第三个参数值。

PS:我不知道这个函数到底可以用来干嘛。

15.7 关闭流

流是用fclose关闭的。

int fclose( FILE *f );
对于输出流,fclose函数在文件关闭之前刷新缓冲区。如果它执行成功,fclose返回零值,否则返回EOF。
#include <stdio.h>
#include <stdlib.h>

int main( int argc, char **argv )
{
	int		exit_status = EXIT_SUCCESS;
	FILE	*input;

	while ( NULL != *++argv ){
		input = fopen( *argv, "r" );
		if ( NULL == input ){
			perror( *argv );
			exit_status = EXIT_FAILURE;
			continue;
		}

		/*
		*这里对这个文件进行处理
		*/

		if ( 0 != fclose( input ) ){
			perror( "fclose" );
			exit( EXIT_FAILURE );
		}
	}

	return exit_status;
}
input变量可能因为fopen和fclose之间的一个程序BUG而发生修改。这个BUG无疑将导致程序失败。在那些并不检查fopen函数的返回值的程序中,input的值甚至可能是NULL。在任何一种情况下,fclose都将会失败,而且程序很可能在fclose被调用之前很早便已终止。

养成检查的习惯吧,很多情况下维护软件的成本永远高于编写软件的成本。

15.8 字符I/O

当一个流被打开之后,它可以用于输入和输出。

字符输入是由getchar函数家族执行的,原型如下:

int fgetc( FILE *stream );
int getc( FILE *stream );
int getchar( void );
需要操作的流作为参数传递给getc和fgetc,但getchar始终从标准输入读取。每个函数从流中读取下一个字符,并把它作为函数的返回值返回。如果流中不存在更多的字符,函数就返回常量值EOF。

为了把单个字符写入到流中,可以使用putchar函数家族:

int fputc( int character, FILE *stream );
int putc( int character, FILE *stream );
int putchar( int character );
第一个参数是要被打印的字符。在打印之前,函数把这个整型参数裁剪为一个无符号字符型值,所以:
putchar( 'abc' );
只打印一个字符(具体哪个视编译器)。

如果由于任何原因导致函数失败,它们就返回EOF。

15.8.2 撤销字符I/O

假设我们读取一系列的数字,结果读到一个非数字的时候停止,但是我们又不想丢弃这个非数字时,怎么办?

ungetc函数就是为了解决这种类型的问题:

int ungetc( int character, FILE *stream );
ungetc把一个先前读入的字符返回到流中,这样它可以在以后被重新读入。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

int read_int()
{
	int		value;
	int		ch;

	value = 0;

	while ( ( ch = getchar() ) != EOF && isdigit( ch ) ){
		value *= 10;
		value += ch - '0';
	}

	ungetc( ch, stdin );

	return value;
}
每个流都允许至少一个字符被退回。如果一个流允许退回多个字符,那么这些字符再次被读取的顺序就以退回时的反序进行。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

int main(void)
{
	FILE *file1 = fopen( "data", "w" );
	int		i;
	int		tempNum;

	if ( NULL == file1 ){
		perror( "data" );
		exit( EXIT_FAILURE );
	}

	for ( i = 0; i < 10; i++ ){
		putc( i + '0', file1 );
	}
	fclose( file1 );			//这里要注意:文件写入后要及时关闭,不要把写入的文件直接用于读取

	file1 = fopen("data", "r");

	for ( i = 0; i < 8; i++ ){
		tempNum = getc( file1 );
		printf("%c ", tempNum );
	}
	ungetc( tempNum, file1 );
	ungetc( '0', file1 );
	printf("\n");

	while ( ( tempNum = getc( file1 ) ) != EOF ){
		printf("%c ", tempNum );
	}

	printf("\n");
	return 0;
}
程序输出:

15.9 未格式化的行I/O

char *fgets( char *buffer, int buffer_size, FILE *stream );
char *gets( char *buffer );

int fputs( char const *buffer, FILE *stream );
int puts( char const *buffer );
fgets从指定的stream读取字符并把它们复制到buffer中。当它读取一个换行符并存储到缓冲区之后就不再读取。如果缓冲区内存储的字符数达到buffer_size - 1个时它也停止读取。在这种情况下,并不会出现数据丢失情况,因为下一次调用fgets将从流的下一个字符开始读取。在任何一种情况下,一个NUL字节将被添加到缓冲区所存储数据的末尾,使它成为一个字符串。 读取完毕时候返回NULL。

传递给fputs必须是一个字符串,而这个字符串是逐字写入的。

警告:gets函数并未定义缓冲区大小,所以写“玩具程序”的时候gets倒很方便。

从一个文件向另一个文件复制文本行:

#include <stdio.h>

#define MAX_LINE_LENGTH		1024

void copylines( FILE *input, FILE *output )
{
	char	buffer[MAX_LINE_LENGTH];

	while ( fgets( buffer, MAX_LINE_LENGTH, input ) ){
		fputs( buffer, output );
	}
}

15.10 格式化的行I/O

15.10.1 scanf家族

int fscanf( FILE *stream, char const *format,... );
int scanf( char const *format,... );
int sscanf( char const *string, char const *format,... );
以两个例子来解释这些貌似纯理论的东西:

1. 用scanf处理行定向的输入

#include <stdio.h>

#define BUFFER_SIZE 100

void function( FILE *input )
{
	int a, b, c, d, e;
	char	buffer[ BUFFER_SIZE ];

	while ( NULL != fgets( buffer, BUFFER_SIZE, input ) ){
		if ( sscanf( buffer, "%d %d %d %d %d",
			&a, &b, &c, &d, &e ) != 5 ){
				fprintf( stderr, "bad input skipped: %s",
					buffer );
				continue;
		}
	}
}
2. 使用sscanf处理可变格式的输入
#include <stdio.h>
#include <stdlib.h>

#define DEFAULT_A 1
#define DEFAULT_B 2

void function( char *buffer )
{
	int a,b,c;

	if ( sscanf( buffer, "%d %d %d", &a, &b, &c ) != 3 ){
		a = DEFAULT_A;
		if ( sscanf( buffer, "%d %d", &b, &c ) != 2 ){
			b = DEFAULT_B;
			if ( sscanf( buffer, "%d", &c ) != 1 ){
				fprintf( stderr, "bad input:%s", buffer );
				exit( EXIT_FAILURE );
			}
		}
	}
}
15.10.3 printf家族
int fprintf( FILE *stream, char const *format,...);
int printf( char const *format,...);
int sprintf( char *buffer, char const *format,...);
15.11 二进制I/O

fread函数用于读取二进制数据,fwrite函数用于写入二进制数据。

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
size_t fwrite( void *buffer, size_t size, size_t count, FILE *stream );
buffer是一个指向用于保存数据的内存位置的指针,size是缓冲区中每个元素的字节数,count是读取或写入的元素数,当然stream是数据读取或写入的流。函数返回值是实际读取或写入的元素数目。
struct VALUE{
	long	a;
	float	b;
	char	c[SIZE];
}values[ARRAY_SIZE];

n_value = fread( values, sizeof( struct VALUE ), ARRAY_SIZE, input_stream );
fwrite( values, sizeof( struct VALUE ), n_value, output_stream );
习题:

1.

#include <stdio.h>

int main(void)
{
	int		ch;
	while ( ( ch = getc( stdin ) ) != EOF ){
		putc( ch, stdout );
	}

	return 0;
}
程序输出:

2. 

#include <stdio.h>

#define BUFFER_SIZE 80

int main(void)
{
	char	buffer[BUFFER_SIZE];

	while ( NULL != fgets( buffer, BUFFER_SIZE, stdin ) ){
		fputs( buffer, stdout );
	}

	return 0;
}
程序输出:

3.习题2的答案就是习题3的答案。

4. 

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 1024

int main(void)
{
	FILE *inputFile;
	FILE *outputFile;
	char	fileName[100];
	char	buffer[BUFFER_SIZE];

	printf("please enter the filename:\n");
	scanf("%s", &fileName);
	inputFile = fopen( fileName, "r");
	if ( NULL == inputFile ){
		perror(fileName);
		exit( EXIT_FAILURE );
	}
	printf("please enter the output filename:\n");
	scanf("%s", &fileName );
	outputFile = fopen( fileName, "w");

	while ( NULL != fgets( buffer, BUFFER_SIZE, inputFile ) ){
		fputs( buffer, outputFile );
	}

	fclose( inputFile );
	fclose( outputFile );

	return 0;
}

5.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define BUFFER_SIZE 1024

int getNum( char buffer[] )
{
	char *temp = buffer;
	int		value = 0;
	while ( ( *temp >= '0' ) && ( *temp <= '9' ) ){
		value *= 10;
		value += *temp - '0';
		temp++;
	}

	return value;
}

int main(void)
{
	FILE *inputFile;
	FILE *outputFile;
	char	fileName[100];
	char	buffer[BUFFER_SIZE];
	char	temp[10];
	int		sum = 0;

	printf("please enter the filename:\n");
	scanf("%s", &fileName);
	inputFile = fopen( fileName, "r");
	if ( NULL == inputFile ){
		perror(fileName);
		exit( EXIT_FAILURE );
	}
	printf("please enter the output filename:\n");
	scanf("%s", &fileName );
	outputFile = fopen( fileName, "w");

	while ( NULL != fgets( buffer, BUFFER_SIZE, inputFile ) ){
		sum += getNum( buffer );
		fputs( buffer, outputFile );
	}

	itoa( sum, temp, 10 );			//fputs只能处理字符串,fputc只能处理单个字符,所以必须把sum转换为字符串来进行存储
	fputs( temp, outputFile );

	fclose( inputFile );
	fclose( outputFile );

	return 0;
}

程序输入:

input.txt:

12hello world i love this world
23and i love c, python too
i miss you
45really
程序输出:
12hello world i love this world
23and i love c, python too
i miss you
45really80
6.
#include <stdio.h>
#include <stdlib.h>

int numeric_palindrome( int value )
{
	int oldValue = value;
	int resultValue = 0;
	while ( value % 10 ){
		resultValue *= 10;
		resultValue += value % 10;
		value /= 10;
	}

	return resultValue == oldValue;
}

int main(void)
{
	printf("%d\n", numeric_palindrome( 245 ) );
	printf("%d\n", numeric_palindrome( 14741 ) );
	printf("%d\n", numeric_palindrome( 147741 ) );

	return 0;
}

程序输出:

7.

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 100

float averageFunc( char age[] )
{
	int		totalage = 0;
	char	temp[BUFFER_SIZE];
	int		i = 0;
	int		count = 0;
	while ( *age != '\0' ){
		if ( *age == ' ' && i ){
			temp[i] = '\0';
			i = 0;
			totalage += atoi( temp);
			count++;
		}
		else{
			temp[i++] = *age;
		}
		age++;
	}
	totalage += atoi( temp );		//最后一个年龄并不以空格结束---这种编程方法可移植性非常差。
	count++;

	return totalage * 1.0 / count;
}

int main(void)
{
	FILE	*inputFile = fopen("input.txt", "r");
	float	average = 0;
	char	age[BUFFER_SIZE];
	if ( NULL == inputFile ){
		perror("input.txt");
		exit( EXIT_FAILURE );
	}

	while ( NULL != fgets( age, BUFFER_SIZE, inputFile ) ){
		average = averageFunc( age );
		fputs( age, stdout );
		printf(":%5.2f\n", average );
	}

	return 0;
}
程序输出:

当然,我们可以不断的scanf来读取数据,但是如果一行就一个年龄,而一行有10个年龄的似乎,怎么办?代码量将非常的大,一堆的if语句。

后面几道习题,没什么兴趣写.

© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 408
博文 209
码字总数 447144
作品 0
武汉
程序员
C++快速入门

只学过Java的我最近笔试接触到各种C++的题目,于是稍微恶补了一下C++的基础部分,以下内容是基于《21天学通C++》的部分读书笔记,按照章节写的。 第二章 C++程序的组成部分 ①#include 预处理...

waffle930
2016/10/02
56
0
C语言-第七章、用指针实现程序的灵活设计

7_1 指针的基本概念 _1.1 地址与指针 C语言中通常把地址成为指针。 _1.2 内存单元的指针与内存单元的内容 _1.3 变量的指针 变量的指针就是变量的地址 _1.4 直接访问与间接访问 7_2 指向变量的...

南风末
2016/11/04
11
0
阅读《C陷阱与缺陷》的知识增量

看完《C陷阱与缺陷》,忍不住要重新翻一下,记录一下与自己的惯性思维不符合的地方。记录的是知识的增量,是这几天的流量,而不是存量。 这本书是在ASCI C/C89订制之前写的,有些地方有疏漏。...

modernizr
2014/08/11
368
2
阅读《C陷阱与缺陷》的知识增量

看完《C陷阱与缺陷》,忍不住要重新翻一下,记录一下与自己的惯性思维不符合的地方。记录的是知识的增量,是这几天的流量,而不是存量。 这本书是在ASCI C/C89订制之前写的,有些地方有疏漏。...

modernizr
2014/06/30
613
7
c++ primer 第五版学习笔记

第二章 函数体外定义的内置类型变量会初始化为0,函数体外的是未初始化的 用constexpr声明变量表示它是一个常量表达式(编译器可以确定的值),且只能应用于字面值 c++11中可以用 来定义一个...

David栗子
2017/12/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

OSChina 周三乱弹 —— 公司女同事约我

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子:分享水木年华的单曲《蝴蝶花(2002年大提琴版)》 《蝴蝶花(2002年大提琴版)》- 水木年华 手机党少年们想听歌,请使劲儿戳(这里) ...

小小编辑
12分钟前
28
7
Linux环境搭建 | VMware下共享文件夹的实现

在进行程序开发的过程中,我们经常要在主机与虚拟机之间传递文件,比如说,源代码位于虚拟机,而在主机下阅读或修改源代码,这里就需要使用到 「共享文件」 这个机制了。本文介绍了两种共享文...

良许Linux
今天
5
0
JUC锁框架——AQS源码分析

JUC锁介绍 Java的并发框架JUC(java.util.concurrent)中锁是最重要的一个工具。因为锁,才能实现正确的并发访问。而AbstractQueuedSynchronizer(AQS)是一个用来构建锁和同步器的框架,使用A...

长头发-dawn
今天
3
0
docker中安装了RabbitMQ后无法访问其Web管理页面

在官网找了"$ docker run -d --hostname my-rabbit --name some-rabbit -p 8080:15672 rabbitmq:3-management"这条安装命令,在docker上安装了RabbitMQ,,结果输入http://localhost:8080并不......

钟然千落
今天
4
1
spring-cloud | 分布式session共享

写在前面的话 各位小伙伴,你们有福了,这一节不仅教大家怎么实现分布式session的问题,还用kotlin开发,喜欢kotlin的小伙伴是不是很开心! 以前在写Android的时候,就对客户端请求有一定的认...

冯文议
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部