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 45really806.
#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语句。
后面几道习题,没什么兴趣写.