第 15 章 输入输出函数
# 第 15 章 输入输出函数
书本内容
# 15.1 错误报告
书本内容
笔记
perror
# 15.2 终止执行
书本内容
笔记
exit
函数不会返回
# 15.3 标准 I/O 函数库
书本内容
# 15.4 ANSI I/O 函数库
书本内容
# 15.4.1 流
书本内容
提示
fflush(stdout)
强迫缓冲区的数据立即写入
# 15.4.2 文件
书本内容
笔记
FILE
结构- 声明在
stdio.h
中
- 声明在
# 15.4.3 标准 I/O 常量
书本内容
笔记
EOF
FOPEN_MAX
FILENAME_MAX
# 15.5 流 I/O 总览
书本内容
# 15.6 打开流
书本内容
笔记
fopen
freopen
# 15.7 关闭流
书本内容
笔记
fclose
# 15.8 字符 I/O
书本内容
笔记
fgetc
getc
getchar
fputc
putc
putchar
# 15.8.1 字符 I/O 宏
书本内容
笔记
- 只有
fgetc
和fputc
时真正的函数 getc
,putc
,getchar
和putchar
都是通过#define
指令定义的宏
# 15.8.2 撤消字符 I/O
书本内容
笔记
ungetc
函数可用于撤消字符 IO- 每个流都允许至少一个字符被退回
- 字符退回到流中与写入到流中并不相同
- 与一个流相关的外部储存不会受
ungetc
的影响
- 与一个流相关的外部储存不会受
# 15.9 未格式化的行 I/O
书本内容
笔记
fgets
与gets
gets
不会在缓冲区中存储结尾的换行符- 没有缓冲区长度的参数,不安全,其存在只是为了允许向后兼容
fputs
与puts
puts
会在字符串输出后再添加一个换行符
# 15.10 格式化的行 I/O
书本内容
# 15.10.1 scanf
家族
书本内容
笔记
fscanf
scanf
sscanf
- 从第一个参数所给出的字符串读取字符
# 15.10.2 scanf
格式代码
书本内容
提示
- 先用
fgets
读取一行的内容到buffer
中,再用sscanf
对buffer
进行处理- 对于输入的适应更好
- 宽度参数的使用
%4d
- 表示将整形值的宽度限制为 4 个数字或更少
%20s
- 表示将字符串中的字符个数限制为 20 个或更少
# 15.10.3 printf
家族
书本内容
笔记
fprintf
printf
sprintf
- 将结果以一个
NUL
结尾的字符串储存到指定的buffer
缓冲区
- 将结果以一个
# 15.10.4 printf
格式代码
书本内容
注意
printf
家族和scanf
家族的格式化码用法不同.
后应跟一个十进制数n
,翻译为精度有点奇怪- 对于浮点数而言,其是保留
n
位小数意思 - 对于字符串
%s
,其表示最多打印n
个字符
- 对于浮点数而言,其是保留
- 表示字段宽度和/或精度的十进制整数可以由一个星号
*
代替- 由此可以在
printf
的下一个参数中提供宽度和/或精度
- 由此可以在
# 15.11 二进制 I/O
书本内容
笔记
fread
fwrite
# 15.12 刷新和定位函数
书本内容
笔记
fflush
ftell
fseek
- 如果在
fseek
前使用ungetc
把一个字符返回到流中- 则这个被退回的字符会被丢弃
定位到文件尾之后并进行写入,将扩展这个文件
- 会在文件中填充相应的字节
- 如果在
rewind
fgetpos
fsetpos
# 15.13 改变缓冲方式
书本内容
笔记
setbuf
setvbuf
# 15.14 流错误函数
书本内容
笔记
feof
ferror
void clearerr
# 15.15 临时文件
书本内容
笔记
tmpfile
tmpnam
# 15.16 文件操纵函数
书本内容
笔记
remove
rename
# 15.17 总结
书本内容
# 15.20 问题
#
问题 1
答案
- 如何由于任何原因导致打开失败,
fopen
的返回值会是NULL
- 当这个值传递给后续的 I/O 操作时,该函数就会失败
- 至于程序是否失败,则取决于具体的编译器
#
问题 2
答案
- 程序会失败,因为试图使用的
FILE
结构没有适当的初始化- 某个不可预料的内存位置可能会被修改
#
问题 3
答案
- 当
fclose
失败时,意味着程序中的某个部分可能出现了问题- 不进行错误检查会导致这个错误被忽略,后续再出现问题,会很难调试出问题的原因
fclose
失败时,不会释放用于该流的FILE
结构,由于程序能够打开的文件流的数量是有限的,如果这种情况经常发生,则当文件流被用完时,程序将无法打开新的文件
#
问题 4
答案
- 不同操作系统提供不同的机制来检测这种重定向
- 但对于绝大多数应用程序来说,程序从标准输入读取字符的方式相同,不管输入实际来自何处
#
问题 5
答案
- 使用长度为 1 的缓冲区将永远被
NUL
字节填充,其无法从流中读入任何字符 - 使用长度为 2 的缓冲区,字符将会被一个一个的处理
#
问题 6
答案
%x
输出时不会输出0x
标志- 2 个字节 16 位二进制,对于
%d
来说- 加上一个负号需要 6 位
%c
只需要一个字符%x
对于 16 位二制制,最多需要 4 个字符表示%x
只会输出无符号数,因为没有不用考虑负号
- 加上两个空格和最后一个
NUL
字符,共需要个字符
#
问题 7
答案
- 无法确保字符串不溢出,除非在调用此条语句之前检查字符串
a
的大小
#
问题 8
答案
%f
的输出结果会四舍五入
#
问题 9
答案
使用下列程序,基本就能输出所有的错误信息
代码
#include <errno.h> #include <stdio.h> int main(void) { char buffer[100]; for (errno = 1; errno < 138; errno++) { sprintf(buffer, "errno = %4d", errno); perror(buffer); } }
1
2
3
4
5
6
7
8
9
10
11
12
但是在
Linux
系统中,更加优雅的用法是安装moreutils
包,然后使用下列命令即可查看所有的错误信息及其宏定义errno -l
1
#
问题 10
答案
- 传递的是指针,意味着函数可以对流进行修改
- 由于 C 的函数是传值调用的,如果直接传递的是 FILE 结构本身,则传递的只是一个副本
- 因此无法对原有流进行修改
#
问题 11
答案
- 使用
r+
模式,表示打开文件进行更新 - 参考 stackoverflow (opens new window)
#
问题 12
答案
freopen
允许将特定的流重新打开到新文件- 为了允许一个程序使用
printf
开始向一个不同的文件写入,程序需要重新打开stdout
freopen
是实现这种操作的可靠函数
#
问题 13
答案
- 不值得考虑
- 只有在程序极其在意运行速度或程序大小才需要考虑这种情况
#
问题 14
答案
- 浮点数默认会按 double 储存,如果浮点标准采用的是 IEE754,然后编译时使用
-m32
选项,则会打印出1374389535
,十六进制为51EB851F
,正好为3.14
用double
表示的低 32 位字节- 机器为小端字节序才会输出这种结果
- 可以通过
gcc -s
生成汇编代码,来查看3.14
的具体储存 - 具体可参见 csapp
- 但如果不加
-m32
选项,使用 64 位编译,结果运行时竟然是随机的,诚可怪也,需要搞明白为什么- 应该是与编译器的操作有关
#
问题 15
答案
- 字符会左对齐
- 至少 6 个字符,最多 10 个字符会被打印
- 如个字符数少于 6 个,则会在右边补空格
#
问题 16
答案
- 设原始值为
,则当 时,均会出现这种情况
# 15.21 编程练习
#
编程练习 1
答案
代码
#include <stdio.h> #include <stdlib.h> int main(void) { int ch; while ((ch = getchar()) != EOF) putchar(ch); return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
#
编程练习 2
答案
代码
#include <stdio.h> #include <stdlib.h> #define BUFFER_SIZE 82 int main(void) { char buffer[BUFFER_SIZE]; while (fgets(buffer, BUFFER_SIZE, stdin) != NULL) fputs(buffer, stdout); return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
#
编程练习 3
答案
与编程练习 2 的代码基本相同
代码
#include <stdio.h> #include <stdlib.h> #define BUFFER_SIZE 82 int main(void) { char buffer[BUFFER_SIZE]; while (fgets(buffer, BUFFER_SIZE, stdin) != NULL) fputs(buffer, stdout); return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
#
编程练习 4
答案
代码
#include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFSIZE 256 FILE *openfile(char *prompt, char *mode) { char buffer[BUFFSIZE]; FILE *file; printf("%s filename: ", prompt); if (fgets(buffer, BUFFSIZE, stdin) == NULL || strlen(buffer) == 1) { fprintf(stderr, "Missing %s file name\n", prompt); exit(EXIT_FAILURE); } // 去掉未尾可能存在的换行符 buffer[strcspn(buffer, "\n")] = '\0'; if ((file = fopen(buffer, mode)) == NULL) { perror(buffer); exit(EXIT_FAILURE); } return file; } int main(void) { FILE *input_file, *output_file; char buffer[BUFFSIZE]; input_file = openfile("input", "r"); output_file = openfile("output", "w"); while (fgets(buffer, BUFFSIZE, input_file) != NULL) fputs(buffer, output_file); fclose(input_file); fclose(output_file); return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
提示
- 注意
fgets
会读入换行符\n
,需要借助strcspn
函数将其替换为\0
#
编程练习 5
答案
代码
#include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFSIZE 256 FILE *openfile(char *prompt, char *mode) { char buffer[BUFFSIZE]; FILE *file; printf("%s filename: ", prompt); if (fgets(buffer, BUFFSIZE, stdin) == NULL || strlen(buffer) == 1) { fprintf(stderr, "Missing %s file name\n", prompt); exit(EXIT_FAILURE); } // 去掉未尾可能存在的换行符 buffer[strcspn(buffer, "\n")] = '\0'; if ((file = fopen(buffer, mode)) == NULL) { perror(buffer); exit(EXIT_FAILURE); } return file; } int main(void) { FILE *input_file, *output_file; char buffer[BUFFSIZE]; int value, total; input_file = openfile("input", "r"); output_file = openfile("output", "w"); total = 0; while (fgets(buffer, BUFFSIZE, input_file) != NULL) { if (!isblank(buffer[0]) && sscanf(buffer, "%d", &value) == 1) total += value; fputs(buffer, output_file); } fprintf(output_file, "%d\n", total); fclose(input_file); fclose(output_file); return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
提示
sscanf
会忽略行开头的空白,因此加入专门的逻辑判断行开头是否有空白
#
编程练习 6
答案
代码
#include <stdio.h> #include <string.h> #define BUFSIZE 50 #define TRUE 1 #define FALSE 0 int numberic_palindrome(int value) { char buf[BUFSIZ]; char *start, *end; sprintf(buf, "%+d", value); // print the sign start = buf + 1; // ignore the sign end = buf + strlen(buf) - 1; while (start < end) if (*start++ != *end--) return FALSE; return TRUE; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
提示
- 使用
%+d
打印出符号位,避免判断输入的正负值
#
编程练习 7
答案
代码
#include <stdio.h> #include <stdlib.h> #define BUFSIZE 50 int main(void) { char buf[BUFSIZE]; int age[10], members; float average_age; int i; while (fgets(buf, BUFSIZE, stdin) != NULL) { members = sscanf(buf, "%d %d %d %d %d %d %d %d %d %d", age, age + 1, age + 2, age + 3, age + 4, age + 5, age + 6, age + 7, age + 8, age + 9); if (members <= 0) continue; for (i = 0, average_age = 0; i < members; i++) average_age += age[i]; average_age = average_age / members; fprintf(stdout, "%5.2f: ", average_age); for (i = 0; i < members; i++) fprintf(stdout, "%d ", age[i]); fprintf(stdout, "\n"); } return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
提示
- 注意空行和
members
为 0 的情况
#
编程练习 8
答案
代码
#include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define LINE_SIZE 16 #define BUFFER_SIZE 64 void dump(FILE *stream) { char buffer[BUFFER_SIZE]; unsigned char data[LINE_SIZE]; int len; unsigned offset; memset(buffer, ' ', BUFFER_SIZE - 1); buffer[BUFFER_SIZE - 1] = '\0'; buffer[45] = '*'; buffer[62] = '*'; offset = 0; while ((len = fread(data, 1, LINE_SIZE, stream)) > 0) { char *hex_ptr; char *string_ptr; int i; sprintf(buffer, "%06X ", offset); offset += len; hex_ptr = buffer + 8; string_ptr = buffer + 46; // handle the statution that the last line doenot have 16 if (len < LINE_SIZE) { memset(hex_ptr, ' ', 35); memset(string_ptr, ' ', 16); } for (i = 0; i < len; i++) { sprintf(hex_ptr, "%02X", data[i]); hex_ptr += 2; *hex_ptr = ' '; if (i % 4 == 3) hex_ptr++; if (isprint(data[i])) *string_ptr++ = data[i]; else *string_ptr++ = '.'; } // sprintf will add '\0' to the end of the string puts(buffer); } } int main(int argc, char **argv) { if (argc <= 1) dump(stdin); else { FILE *stream; stream = fopen(argv[1], "rb"); if (stream == NULL) { perror(argv[1]); exit(EXIT_FAILURE); } dump(stream); fclose(stream); } return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
提示
- 用一个字符串当做输出缓冲区,借助
memset
进行初始化,可以避免一些复杂的逻辑判断- 比如将缓冲区全部初始化为空格
- 在指定的位置将缓冲区设置为符号
*
- 注意,最后一行需要将字符串数组十六进制和字符输出对应的位置全部设置为空格
- 对应于 35 ~38 行
sprintf
会向在字符串的末尾添加\0
,需要进行清除- 对应于第 45 行
#
编程练习 9
答案
代码
/* ** usage: ./fgrep string file [file ...] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFER_SIZE 512 void fgrep(FILE *stream, char *string, char *filename) { char buffer[BUFFER_SIZE]; while (fgets(buffer, BUFFER_SIZE, stream)) { if (strstr(buffer, string) != NULL) { if (filename != NULL) printf("%s: ", filename); puts(buffer); } } } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: ./fgrep string file [file..]\n"); exit(EXIT_FAILURE); } else { if (argc == 2) fgrep(stdin, argv[1], NULL); else { FILE *stream; char *string; string = *++argv; while (*++argv != NULL) { stream = fopen(*argv, "r"); if (stream == NULL) perror(*argv); else { fgrep(stream, string, *argv); fclose(stream); } } } } return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
提示
- 文件名不存在时,只是提示错误,但是程序继续处理下一个文件名
#
编程练习 10
答案
代码
/* ** Usage: sum [-f] [file ...] */ #include <stdio.h> #include <stdlib.h> #include <string.h> FILE *get_output_stream(int f_flag, char *filename) { FILE *output_stream; char *output_filename; if (!f_flag) output_stream = stdout; else { output_filename = malloc(strlen(filename) + 4 + 1); if (output_filename == NULL) { fprintf(stderr, "malloc: out of memory\n"); exit(EXIT_FAILURE); } strcpy(output_filename, filename); strcat(output_filename, ".cks"); output_stream = fopen(output_filename, "w"); if (output_stream == NULL) perror(output_filename); free(output_filename); } return output_stream; } void checksum(FILE *file_stream, FILE *output_stream) { int ch; unsigned short sum; sum = 0; while ((ch = getc(file_stream)) != EOF) sum += ch; fprintf(output_stream, "%hu\n", sum); } int main(int argc, char **argv) { if (argc == 1) checksum(stdin, stdout); else { int f_flag; f_flag = 0; if (strcmp(argv[1], "-f") == 0) { if (argc == 2) { fprintf(stderr, "-f illegal when reading standard input\n"); exit(EXIT_FAILURE); } f_flag = 1; argv++; } while (*++argv != NULL) { FILE *input_stream, *output_stream; input_stream = fopen(*argv, "r"); if (input_stream == NULL) { perror(*argv); continue; } output_stream = get_output_stream(f_flag, *argv); if (output_stream == NULL) { fclose(input_stream); exit(EXIT_FAILURE); } checksum(input_stream, output_stream); fclose(input_stream); if (f_flag) fclose(output_stream); } } return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
提示
- 有
-f
参数时,借助malloc
来生成输出文件的文件名
#
编程练习 11
答案
- 代码
个人实现,不值一看
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define BUFFER_SIZE 128 #define DESCRIP_POS 20 #define QUANTITY_POS 45 #define TOTAL_POS 62 #define PROFIT_POS 79 static FILE *Record; static long Start; static char Buffer[BUFFER_SIZE]; static char *Foramt = "| %-15d | %-22s | %-15d | %-15.2f | %-15.2f |\n"; static char Table_tile[] = "| Part Number | Description |" " Inventory | Total Value | Profit |\n"; void new_part() { char command[10]; char description[21]; int quantity; float cost_each; int part_number; long line_start; if (sscanf(Buffer, "%s %[^,], %d, %f %9s", command, description, &quantity, &cost_each, command) != 4 || quantity < 0) fprintf(stderr, "ERROR: command new, error argument!\n"); else { fseek(Record, Start, SEEK_SET); part_number = 1; line_start = Start; while (fgets(Buffer, BUFFER_SIZE, Record) != NULL) { if (Buffer[DESCRIP_POS] == ' ') break; line_start = ftell(Record); part_number++; } fseek(Record, line_start, SEEK_SET); fprintf(Record, Foramt, part_number, description, quantity, quantity * cost_each, 0); fflush(Record); } } void delete () { char command[10]; int quantity; int cost_each; int part_number; int line_number; long line_start; int i; if (sscanf(Buffer, "%s %d %9s", command, &part_number, command) != 2 || part_number <= 0) fprintf(stderr, "ERROR: command delete, error argument!\n"); else { fseek(Record, Start, SEEK_SET); line_number = 1; line_start = Start; while (fgets(Buffer, BUFFER_SIZE, Record) != NULL) { if (line_number == part_number) { if (Buffer[DESCRIP_POS] == ' ') break; Buffer[DESCRIP_POS] = ' '; fseek(Record, line_start, SEEK_SET); fputs(Buffer, Record); fflush(Record); return; } line_number++; line_start = ftell(Record); } fprintf(stderr, "ERROR: The part doesn't exist\n"); } } void buy_sell(char *type) { char command[10]; int quantity; float cost_each; int part_number; int line_number; long line_start; if (sscanf(Buffer, "%s %d, %d, %f %9s", command, &part_number, &quantity, &cost_each, command) != 4 || quantity <= 0 || part_number <= 0) fprintf(stderr, "ERROR: command %s, error argument!\n", type); else { fseek(Record, Start, SEEK_SET); line_start = Start; line_number = 1; while (fgets(Buffer, BUFFER_SIZE, Record) != NULL) { if (line_number == part_number) { int inventory; float total_value; if (Buffer[DESCRIP_POS] == ' ') { fprintf(stderr, "ERROR: The part has been deleted!\n"); return; } fseek(Record, line_start, SEEK_SET); if (strcmp(type, "buy") == 0) { sscanf(Buffer + QUANTITY_POS, "%d | %f", &inventory, &total_value); inventory += quantity; total_value += quantity * cost_each; sprintf(Buffer + QUANTITY_POS, "%-15d | %-15.2f |", inventory, total_value); Buffer[PROFIT_POS - 1] = ' '; fputs(Buffer, Record); fflush(Record); return; } else { float total_profit; float profit; sscanf(Buffer + QUANTITY_POS, "%d | %f | %f", &inventory, &total_value, &total_profit); if (quantity > inventory) { fprintf(stderr, "ERROR: The quantity is greater than the inventory.\n"); return; } profit = quantity * (cost_each - total_value / inventory); total_value -= quantity * (total_value / inventory); inventory -= quantity; total_profit += profit; sprintf(Buffer + QUANTITY_POS, "%-15d | %-15.2f | %-15.2f |\n", inventory, total_value, total_profit); fputs(Buffer, Record); fflush(Record); fprintf(stdout, "The profit of current transaction is %.2f.\n", profit); return; } } line_number++; line_start = ftell(Record); } fprintf(stderr, "Error: the part doesn't exist!\n"); // fprintf(Record, Foramt, part_number, // description, quantity, (float)quantity * cost_each, 0); // fflush(Record); // fseek(Record, Start, SEEK_SET); } } void print() { char command[10]; char argument[10]; int print_all_flag; int part_num; int line_number; if (sscanf(Buffer, "%s %9s %9s", command, argument, command) == 2 && strcmp(argument, "all") == 0) print_all_flag = 1; else if (sscanf(Buffer, "%s %d %9s", command, &part_num, command) == 2 && part_num > 0) { line_number = 1; print_all_flag = 0; } else { fprintf(stderr, "ERROR: command print, error argument!\n"); return; } fseek(Record, Start, SEEK_SET); if (print_all_flag) fputs(Table_tile, stdout); while (fgets(Buffer, BUFFER_SIZE, Record) != NULL) { if (print_all_flag) { if (Buffer[DESCRIP_POS] != ' ') fputs(Buffer, stdout); } else { if (line_number++ == part_num) { if (Buffer[DESCRIP_POS] != ' ') { fputs(Table_tile, stdout); fputs(Buffer, stdout); } else fputs("Error: the part has been deleted!\n", stderr); return; } } } if (!print_all_flag) fputs("Error: the part doesn't exist!\n", stderr); } void total() { char command[10]; float total; if (sscanf(Buffer, "%s %9s", command, command) != 1) fprintf(stderr, "ERROR: command total has no argument!\n"); fseek(Record, Start, SEEK_SET); total = 0; while (fgets(Buffer, BUFFER_SIZE, Record) != NULL) { if (Buffer[DESCRIP_POS] != ' ') { float value; sscanf(Buffer + TOTAL_POS, "%f", &value); total += value; } } printf("The total value of all parts is %.2f\n", total); } void exit_program(int exit_code) { fclose(Record); exit(exit_code); } void parse_command() { // TODO 考虑输入参数过长时的情况 // TODO 考虑输入错误的情况 char command[10]; sscanf(Buffer, "%s", command); if (!strcmp(command, "new")) new_part(); else if (!strcmp(command, "buy")) buy_sell(command); else if (!strcmp(command, "sell")) buy_sell(command); else if (!strcmp(command, "delete")) delete (); else if (!strcmp(command, "print")) print(); else if (!strcmp(command, "total")) total(); else if (!strcmp(command, "end")) exit_program(EXIT_SUCCESS); else fprintf(stderr, "Wrong command!\n"); } int main(int argc, char **argv) { if (argc == 1) { char filename[64]; time_t t = time(NULL); struct tm tm = *localtime(&t); sprintf(filename, "%d-%02d-%02d-%02d-%02d-%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); Record = fopen(filename, "w+"); if (Record == NULL) { perror("part-record"); exit(EXIT_FAILURE); } fputs(Table_tile, Record); Start = ftell(Record); fflush(Record); } else if (argc == 2) { Record = fopen(*++argv, "r+"); if (Record == NULL) { perror(*argv); exit(EXIT_FAILURE); } fgets(Buffer, BUFFER_SIZE, Record); if (strcmp(Buffer, Table_tile) != 0) { fclose(Record); fprintf(stderr, "The format of file %s is not correct!", *argv); } Start = ftell(Record); } else { fprintf(stderr, "Argument Error!\n"); exit(EXIT_FAILURE); } while (fgets(Buffer, BUFFER_SIZE, stdin) != NULL) parse_command(); exit(EXIT_SUCCESS); return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314参考参考答案的实现
part.h
/* ** Declarations and basic data structure for the simple inventory system. */ #define MAX_DESCRIPTION 20 typedef int Part_number; typedef int Quantity; typedef double Value; typedef struct { char description[MAX_DESCRIPTION + 1]; Value total_value; Value profit; Quantity quantity; } Part; #define TRUE 1 #define FALSE 0 #define STRCMP(a, R, b) (strcmp(a, b) R 0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21io.h
/* ** Declarations and protypes for I/O functions */ #ifndef Part_number #include "part.h" #endif /* ** Open the inventory file ** Return True if successful, else False */ int open_file(char const *filename); /* ** Close the inventory file */ void close_file(void); /* ** Return the number of the last part on file. */ Part_number last_part_number(void); /* ** Return the next available part number. */ Part_number next_part_number(void); /* ** Read an inventory record. ** Returns TRUE if the part exists and FALSE otherwise. */ int read_part(Part_number part_number, Part *part); /* ** Write an inventory record */ void write_part(Part_number part_number, Part *part);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40process.h
#define MAX_REQUEST_LINE_LENGTH 100 /* ** Process one transaction. ** Return FALSE if the request was "end" and TRUE otherwise. */ int process_request(void);
1
2
3
4
5
6main.c
#include "io.h" #include "process.h" #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: inventory inv-filename!\n"); return EXIT_FAILURE; } if (open_file(argv[1])) { while (process_request()) ; close_file(); } else { perror(argv[1]); return EXIT_FAILURE; } return EXIT_SUCCESS; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25process.c
#include "process.h" #include "io.h" #include <stdio.h> #include <stdlib.h> #include <string.h> static void total(void) { Part_number part_number; Part part; Value total_value; for (part_number = last_part_number(), total_value = 0; part_number > 0; part_number--) if (read_part(part_number, &part)) total_value += part.total_value; printf("Total value of inventory = %.2f\n", total_value); } static void new_part(char *description, Quantity quantity, Value price_each) { Part part; Part_number part_number; /* ** Copy the arguments into the Part structure */ strcpy(part.description, description); part.quantity = quantity; part.total_value = quantity * price_each; part.profit = 0; part_number = next_part_number(); write_part(part_number, &part); printf("%s is part number %u\n", description, part_number); } static void print_all(void) { Part_number part_number, last; Part part; static char tab_line[] = "|++++++++++++++|+++++++++++++++++++++++|++++++++++++++|" "++++++++++++++|++++++++++++++|"; printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++" "++++++++++++++++++++++++++++++\n"); printf("|%-14s|%-23s|%-14s|%-14s|%-14s|\n", " Part number", " Description", " Quantity", " Total Value", " Profit"); for (part_number = 1, last = last_part_number(); part_number <= last; part_number++) if (read_part(part_number, &part)) { printf("%s\n", tab_line); printf("|%11u |%-*.*s |%11u |%11.2f |%11.2f |\n", part_number, MAX_DESCRIPTION, MAX_DESCRIPTION, part.description, part.quantity, part.total_value, part.profit); } printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++" "++++++++++++++++++++++++++++++\n"); } static void buy(Part_number part_number, Quantity quantity, Value price_each) { Part part; if (read_part(part_number, &part)) { part.total_value += price_each * quantity; part.quantity += quantity; write_part(part_number, &part); } else fprintf(stderr, "The part doesn't exist!\n"); } static void sell(Part_number part_number, Quantity quantity, Value price_each) { Part part; if (read_part(part_number, &part)) { if (quantity > part.quantity) fprintf(stdout, "Sorry, only %d in stock.\n", part.quantity); else { Value profit; Value cost_each; cost_each = part.total_value / part.quantity; profit = (price_each - cost_each) * quantity; part.total_value -= cost_each * quantity; part.profit += profit; part.quantity -= quantity; write_part(part_number, &part); printf("The profit of current transaction is %.2f\n", profit); printf("The total profit of this part is %.2f\n", part.profit); } } else fprintf(stderr, "The part doesn't exist!\n"); } static void delete_part(Part_number part_number) { Part part; if (read_part(part_number, &part)) { part.description[0] = '\0'; write_part(part_number, &part); } else fprintf(stderr, "The part doesn't exist!\n"); } static void print_part(Part_number part_number) { Part part; if (read_part(part_number, &part)) { printf("\nPart number: %d\n", part_number); printf("Description: %s\n", part.description); printf("Quantity on hand: %d\n", part.quantity); printf("Total value: %.2f\n", part.total_value); printf("Profit: %.2f\n", part.profit); } else fprintf(stderr, "The part doesn't exist!\n"); } /* ** Process one transaction. ** Return FALSE if the request was "end" and TRUE otherwise. */ int process_request(void) { char request[MAX_REQUEST_LINE_LENGTH]; char request_type[11]; char description[MAX_DESCRIPTION + 1]; Part_number part_number; Quantity quantity; Value price_each; char left_over[2]; fputs("\nNext request? ", stdout); if (fgets(request, MAX_REQUEST_LINE_LENGTH, stdin) == NULL) return FALSE; if (sscanf(request, "%10s %1s", request_type, left_over) == 1 && (STRCMP(request_type, ==, "end") || STRCMP(request_type, ==, "total"))) { if (request_type[0] == 't') total(); else return FALSE; } else if (sscanf(request, "new %20[^,],%d,%lf %1s", description, &quantity, &price_each, left_over) == 3 && quantity >= 0) { new_part(description, quantity, price_each); } else if (sscanf(request, "%10s %d, %d, %lf %1s", request_type, &part_number, &quantity, &price_each, left_over) && quantity > 0 && (STRCMP(request_type, ==, "buy") || STRCMP(request_type, ==, "sell"))) { if (request_type[0] == 'b') buy(part_number, quantity, price_each); else sell(part_number, quantity, price_each); } else if (sscanf(request, "%10s %d %1s", request_type, &part_number, left_over) == 2 && part_number > 0 && (STRCMP(request_type, ==, "delete") || STRCMP(request_type, ==, "print"))) { if (request_type[0] == 'd') delete_part(part_number); else print_part(part_number); } else if (sscanf(request, "print %10s %1s", request_type, left_over) == 1 && STRCMP(request_type, ==, "all")) { print_all(); } else { fprintf(stderr, "Invalid Command: %s\n", request); } return TRUE; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194io.c
#include "io.h" #include <errno.h> #include <stdio.h> #include <stdlib.h> static FILE *inventory_file; static Part_number part_numbers[2]; enum { NEXT, LAST }; /* ** check the return value of fwrite */ static void Fwrite(const void *ptr, size_t size, size_t n, FILE *file) { if (fwrite(ptr, size, n, file) != n) { fprintf(stderr, "Error: can't write part!"); exit(EXIT_FAILURE); } } /* ** check the return value of fread */ static void Fread(void *ptr, size_t size, size_t n, FILE *file) { if (fread(ptr, size, n, file) != n) { fprintf(stderr, "Error: can't read part!"); exit(EXIT_FAILURE); } } /* ** Write the last and next part numbers to the file */ static void write_part_numbers(void) { static int offset = sizeof(Part) - 2 * sizeof(Part_number); fseek(inventory_file, 0, SEEK_SET); Fwrite(part_numbers, sizeof(Part_number), 2, inventory_file); /* ** Align to the part line */ // Fwrite(, 1, offset, inventory_file); } /* ** Open the inventory file ** Return True if successful, else False */ int open_file(char const *filename) { // Try opening the file inventory_file = fopen(filename, "rb+"); if (inventory_file == NULL) { /* ** It failed. If it is because the file did not exist, try to create it. */ if (errno == ENOENT) { inventory_file = fopen(filename, "wb+"); if (inventory_file != NULL) { part_numbers[NEXT] = 1; part_numbers[LAST] = 0; write_part_numbers(); } } } else Fread(part_numbers, sizeof(Part_number), 2, inventory_file); return inventory_file != NULL; } void close_file(void) { fclose(inventory_file); } /* ** Return the number of the last part on file. */ Part_number last_part_number(void) { return part_numbers[LAST]; } /* ** Return the next available part number. ** Update the part_number if necessary. */ Part_number next_part_number(void) { Part part; Part_number i, j; for (i = part_numbers[NEXT], j = part_numbers[LAST]; i <= j; i++) if (!read_part(i, &part)) break; if (part_numbers[NEXT] != i) write_part_numbers(); return i; } /* ** Read an inventory record. ** Returns TRUE if the part exists and FALSE otherwise. */ int read_part(Part_number part_number, Part *part) { if (part_number > 0 && part_number <= part_numbers[LAST]) { fseek(inventory_file, sizeof(Part) * part_number, SEEK_SET); Fread(part, sizeof(Part), 1, inventory_file); return part->description[0] != '\0'; } return FALSE; } /* ** Write an inventory record */ void write_part(Part_number part_number, Part *part) { if (part_number > 0 && part_number <= part_numbers[LAST] + 1) { fseek(inventory_file, sizeof(Part) * part_number, SEEK_SET); Fwrite(part, sizeof(Part), 1, inventory_file); /* ** Update the LAST and NEXT part number if necessary. */ if (part_number > part_numbers[LAST]) { part_numbers[LAST] = part_number; write_part_numbers(); } if (part->description[0] == '\0' && part_number < part_numbers[NEXT]) { part_numbers[NEXT] = part_number; write_part_numbers(); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
提示
- 按照 增 删 改 查的顺序来编写更加合理和方便调试
- 根据命令参数的类型来分类,从而对输入的命令进行处理
- 代码结构上更加清晰
- 文件第 0 行可以用来储存当前所用的最大零件号和被删除的最小零件号(用于下次新增零件)
- 这两个参数可以简化后续的读写逻辑
- 以二进制的格式读写文件,可以避免字符串读写复杂的逻辑判断,也可以提高读写效率
注意
fseek
定位到文件尾后再写入,其将会扩展这个文件The fseek() function shall allow the file-position indicator to be set beyond the end of existing data in the file. If data is later written at this point, subsequent reads of data in the gap shall return bytes with the value 0 until data is actually written into the gap.
- 参考 (opens new window)
编辑 (opens new window)
上次更新: 2022/03/07, 15:03:00