:::tips 本章习题全部使用 fgets 进行输入,必要的时候会结合 Linux 的重定向输入/出。 :::

1、什么是文件型指针?通过文件指针访问文件有什么好处?

2、对文件的打开和关闭的含义是什么?为什么要打开文件和关闭文件?

3、从键盘输入一个字符串,将其中的小写字母全部转换为大写字母,然后输出到一个磁盘文件 test 中保存,输入的字符串以“!”结束。

解题思路

  1. 明确本本题需要输出文件和用于从终端(以文本形式的读入内存)中的数组。每次从终端得到的字符放入数组中暂存,然后再写入文件中。(注意,不要以为可以直接从终端到输出文件,并且库函数也必须要buffer)
  2. 程序读入逻辑应该是一直判断当前用户是否输入字符是 ! ,如果是就结束循环,最后关闭文件。 ```c

    include

    include

    include

    define MAXSIZE 100

int main() { FILE *fp; char str[MAXSIZE]; fp = fopen(“uppercase.txt”, “w”); if (NULL == fp) { printf(“Can’t open file\n”); exit(0); } printf(“Input sting:\n”); gets(str); // can get all sting excpet newline for (int i = 0; str[i] != ‘!’; i++) { // since it may [^a-z], you can’t use str[i] ^ 0x20 to swapcase if (‘a’ <= str[i] && str[i] <= ‘z’) { str[i] = str[i] - 32; fputc(str[i], fp); } } fclose(fp); // 2.open lowercase.txt fp = fopen(“uppercase.txt”, “r”); if (NULL == fp) { printf(“Can’t open file\n”); exit(0); } fgets(str, strlen(str) + 1, fp); // +1:\0 printf(“%s\n”, str); fclose(fp); return 0; }

  1. `GCC` 编译:由于 `gets` 函数很危险,因为要求程序员自己确定 `buffer` 大小,因此可能会溢出,在 `GCC` 中建议使用 `fgets` 替换,因其会自动检查最大长度而保护 `buffer` 不会溢出。
  2. :::danger
  3. b12@PC:~/chapter10$ gcc -Wall -o ./bin/3 ./src/3.c<br />./src/3.c: In function main’:<br />./src/3.c:15:9: warning: implicit declaration of function gets’; did you mean fget ’? [-Wimplicit-function-declaration]<br /> 15 | gets(str); // can get all sting excpet newline<br /> | ^~~~<br /> | fgets<br />/usr/bin/ld: /tmp/ccQCU657.o: in function `main':<br />3.c:(.text+0x68): warning: the `gets' function is dangerous and should not be used.
  4. :::
  5. **运行结果**:
  6. :::success
  7. b12@PC:~/chapter10$ ./bin/3<br />Input sting:<br />i love china!<br />I LOVE CHINA
  8. :::
  9. **问题**:书上只是简单示例下,很多问题没解决,比如输入字符串中不含有 `!` 直接因为 `for` 循环非法访问!甚至可能因为超过`100`字符而 `str` 溢出。
  10. :::warning
  11. b12@PC:~/chapter10$ ./bin/3<br />Input sting:<br />afhia adiahsfiuahfia adasufaicb;[.',dasd,A.2804fafk21<br />AFHIA ADIAHSFIUAHFIA ADASUFAICB;[.',DASD,A.2804FAFK21<br />Segmentation fault (core dumped)
  12. b12@PC:~/chapter10$ ./bin/3<br />Input sting:<br />afhia adiahsfiuahfia adasufaicb;[.',dasd,A.2804fafk21 afhia adiahsfiuahfia adasufaicb;[.',dasd,A.2804fafk21!<br />AFHIA ADIAHSFIUAHFIA ADASUFAICB;[.',DASD,A.2804FAFK21 AFHIA ADIAHSFIUAHFIA ADASUFAICB;[.',DASD,A.2804FAFK21<br />*** stack smashing detected ***: terminated<br />Aborted (core dumped)
  13. :::
  14. 最后根据所给编译提示,我们需要进行修改防止 `str` 溢出风险。
  15. ```c
  16. #include <stdio.h>
  17. #include <string.h>
  18. #include <stdlib.h>
  19. #define MAXSIZE 100
  20. int main() {
  21. FILE *fp;
  22. char str[MAXSIZE];
  23. fp = fopen("uppercase.txt", "w");
  24. if (NULL == fp) {
  25. printf("Can't open file\n");
  26. exit(0);
  27. }
  28. printf("Input sting:\n");
  29. // can get all sting(<=sizeof(str)-1) from stdin and avoid stackoverflow
  30. fgets(str, sizeof(str), stdin);
  31. for (int i = 0; str[i] != '!' && i < MAXSIZE; i++) { // modify!
  32. if ('a' <= str[i] && str[i] <= 'z') {
  33. str[i] = str[i] - 32;
  34. fputc(str[i], fp);
  35. }
  36. }
  37. fclose(fp);
  38. // 2.open lowercase.txt
  39. fp = fopen("uppercase.txt", "r");
  40. if (NULL == fp) {
  41. printf("Can't open file\n");
  42. exit(0);
  43. }
  44. fgets(str, strlen(str) + 1, fp); // +1:\0
  45. printf("%s\n", str);
  46. fclose(fp);
  47. return 0;
  48. }

输入超过 100 字符 :::success b12@PC:~/chapter10$ gcc -Wall -o ./bin/3 ./src/3.c
b12@PC:~/chapter10$ ./bin/3
Input sting:
afhia adiahsfiuahfia adasufaicb;[.’,dasd,A.2804fafk21 afhia adiahsfiuahfia adasufaicb;[.’,dasd,A.2804fafk21!
AFHIAADIAHSFIUAHFIAADASUFAICBDASDFAFKAFHIAADIAHSFIUAHFIAADASUFAICBDASD
b12@PC:~/chapter10$ cat ./uppercase.txt
AFHIAADIAHSFIUAHFIAADASUFAICBDASDFAFKAFHIAADIAHSFIUAHFIAADASUFAICBDASD

::: 输入超过 100 字符且不以 ! 结束。
:::success b12@PC:~/chapter10$ bin/3
Input sting:
dsafasfaoowu323oahdoansofnoi12oi1joiKFnkaskfn.’,’asdbafjhaiusryiweyiahfkafkjanfkaj.[[.,dsafasfaoowu323oahdoansofnoi12oi1joiKFnkaskfn.’,’asdbafjhaiusryiweyiahfkafkjanfkaj
DSAFASFAOOWUOAHDOANSOFNOIOIJOINKASKFNASDBAFJHAIUSRYIWEYIAHFKAFKJANFKAJDSAFASFAOOWU
b12@PC:~/chapter10$ cat ./uppercase.txt
DSAFASFAOOWUOAHDOANSOFNOIOIJOINKASKFNASDBAFJHAIUSRYIWEYIAHFKAFKJANFKAJDSAFASFAOOWU :::

4、有两个磁盘文件 A 和 B,各存放一行字母,今要求把这两个文件中的信息合并(按字母顺序合并),输出到一个新的文件 C 去。

解题思路

  1. 两文件在磁盘中,首先需要读入内存,那么并要开数组放字符。
  2. 按字母「字典序」排序合并,肯定是字符数组的排序
  3. 将字符输出以ASCII形式输出到文件 C 中。 ```c

    include

    include

    define MAXSZIE 100

void swap(char a, char b) { char tmp = a; a = b; b = tmp; }

void quickSort(char nums[], int left, int right) { /*

  1. * 随机选取基元+三路排序
  2. * (left,lt] < pivot
  3. * [lt+1, i) = pivot
  4. * [gt, right] > pivot
  5. */
  6. if (right > left) {
  7. int idx = rand() % (right - left + 1) + left; // 获取随机下标
  8. swap(nums + idx, nums + left); // 将其交换为nums[left]
  9. char pivot = nums[left];
  10. int lt = left, gt = right + 1, i = left + 1;
  11. while (i < gt) {
  12. if (nums[i] < pivot) {
  13. lt++; // 先移动 lt 到之前看过位置
  14. swap(nums + i, nums + lt);
  15. i++; // 交换过来的肯定比pivot小,所以移动
  16. } else if (nums[i] == pivot) {
  17. i++; // 相等那就当看过呗
  18. } else {
  19. gt--; // 先减是因为初始化
  20. swap(nums + i, nums + gt);
  21. // 特别注意:从后面交换过来的不知道和pivot大小,因此不能i++
  22. }
  23. }
  24. swap(nums + left, nums + lt); // 将最左侧坑放到 lt,此时有 nums[left]>=arr[lt]
  25. quickSort(nums, left, lt - 1); // 左边继续排序
  26. quickSort(nums, gt, right); // 右边继续排序
  27. }

}

int main() { FILE *fp; // 定义文件类型指针 char buffer[MAXSZIE], ch; int idx = 0; // 1.打开当前路径下文件 a 并读取内容 fp = fopen(“a.txt”, “r”); if (NULL == fp) { printf(“Can not open file a\n”); exit(0); } printf(“file A:\n”); ch = fgetc(fp); while (ch != EOF) { buffer[idx++] = ch; putchar(ch); ch = fgetc(fp); } fclose(fp);

  1. // 2.打开当前路径下文件 b 并读取内容
  2. fp = fopen("b.txt", "r");
  3. if (NULL == fp) {
  4. printf("Can not open file\n");
  5. exit(0);
  6. }
  7. printf("file B:\n");
  8. ch = fgetc(fp);
  9. while (ch != EOF) {
  10. buffer[idx++] = ch;
  11. putchar(ch);
  12. ch = fgetc(fp);
  13. }
  14. fclose(fp);
  15. // 3.对数组 buffer 排序
  16. buffer[idx] = '\0';
  17. quickSort(buffer, 0, idx - 1);
  18. // 4.打印输出到终端并保存到 c 文件中
  19. fp = fopen("c.txt", "w");
  20. if (NULL == fp) {
  21. printf("Can not open file\n");
  22. exit(0);
  23. }
  24. printf("Write follow text to file C:\n");
  25. for (int i = 0; i < idx; i++) {
  26. putc(buffer[i], fp);
  27. putchar(buffer[i]);
  28. }
  29. fclose(fp);
  30. putchar('\n');
  31. return 0;

}

  1. :::success
  2. b12@PC:~/chapter10$ gcc -Wall ./src/sort2file.c -o ./bin/sort2file<br />b12@PC:~/chapter10$ cd ./bin<br />b12@PC:~/chapter10/bin$ echo "abc"<br />abc
  3. # 创建两个文件,字符串都不换行,使用 echo -n 创建<br />b12@PC:~/chapter10/bin$ echo -n "I LOVE CHINA" > ./a.txt<br />b12@PC:~/chapter10/bin$ cat ./a.txt<br />I LOVE CHINA**b12@PC:~/chapter10/bin$ # 注意没换行!**<br />b12@PC:~/chapter10/bin$ echo -n "I LOVE BEIJING" > ./b.txt<br />b12@PC:~/chapter10/bin$ cat ./b.txt<br />I LOVE BEIJING**b12@PC:~/chapter10/bin$ # 注意没换行!**
  4. I LOVE CHINAb12@PC:~/chapter10/bin$ ./sort2file<br />file A:<br />I LOVE CHINAfile B:<br />I LOVE BEIJINGWrite follow text to file C:<br /> ABCEEEGHIIIIIJLLNNOOVV<br />b12@PC:~/chapter10/bin$ ls<br />a.txt b.txt c.txt sort2file<br />b12@PC:~/chapter10/bin$ cat ./c.txt<br /> ABCEEEGHIIIIIJLLNNOOVV**b12@PC:~/chapter10/bin$ # 注意没换行!**
  5. :::
  6. <a name="J5J2s"></a>
  7. # 5、有 `5` 个学生,每个学生有 `3` 门成绩,从键盘上输入学生数据(学号,姓名,`3` 门课的成绩),计算出平均成绩,将原有数据和计算出的平均分存放在磁盘文件 `stud` 中。
  8. **解题思路**:
  9. 1. 学生含有学号(int)、姓名(char [])、成绩(float)三种属性,因此定义结构体数组,大小为 5 名学生
  10. 1. `stdin` 获取信息保存到结构体变量中,因此注意 `scanf` 格式化书写与输入技巧
  11. 1. 新建 `stud` 文件将结构体数组元素依次输出到该文件中。
  12. :::tips
  13. 到底选择文件输出方式为二进制还是文本呢?
  14. - 二进制方便下次程序读入
  15. - 文本方式可以直接用 `fprintf` 输出,但是下次读入 `fscanf` 就可能出现很多 **BUG**。
  16. 具体建议还是选择以二进制写入文件,因为后者实现虽然简单,但是从文件中再次格式化读取却不容易!
  17. :::
  18. ```c
  19. #include <stdio.h>
  20. #define N 5
  21. struct Student {
  22. int num;
  23. char name[8];
  24. float score[3];
  25. float ave;
  26. } stu[N];
  27. int main() {
  28. for (int i = 0; i < N; i++) {
  29. printf("Please input score of student %d\n", i + 1);
  30. printf("Id & Name:");
  31. scanf("%d %s", &stu[i].num, stu[i].name);
  32. int sum = 0;
  33. for (int j = 0; j < 3; j++) {
  34. printf("score %d:", j + 1);
  35. scanf("%f", stu[i].score + j);
  36. sum += stu[i].score[j];
  37. }
  38. stu[i].ave = sum / 3.0;
  39. }
  40. // 2.将数据以 文本 方式写入文件
  41. FILE *fp = fopen("stud.txt", "w");
  42. if (NULL == fp) {
  43. printf("Can not open file\n");
  44. return 0;
  45. }
  46. for (int i = 0; i < N; i++) {
  47. if (1 != fwrite(stu + i, sizeof(stu[0]), 1, fp)) {
  48. printf("Can not write file\n");
  49. return 0;
  50. }
  51. }
  52. fclose(fp);
  53. // 3. 将 stud.txt 文件又读出来覆盖到 stu 数组内(多此一举),并打印到stdout
  54. fp = fopen("stud.txt", "r");
  55. if (NULL == fp) {
  56. printf("Can not open file\n");
  57. return 0;
  58. }
  59. for (int i = 0; i < N; i++) {
  60. if (1 != fread(stu + i, sizeof(stu[0]), 1, fp)) {
  61. printf("Can not write file\n");
  62. return 0;
  63. }
  64. printf("%d,%s,%.2f,%.2f,%.2f,%.2f\n",
  65. stu[i].num, stu[i].name, stu[i].score[0],
  66. stu[i].score[1], stu[i].score[2], stu[i].ave);
  67. }
  68. fclose(fp);
  69. return 0;
  70. }

编译运行: :::success b12@PC:~/chapter10$ gcc -Wall ./src/stuInfo.c -o ./bin/stuInfo
b12@PC:~/chapter10$ ./bin/stuInfo # 注意运行路径是~/chapter10而不是~/chapter10/bin
Please input score of student 1
Id & Name:101 Li
score 1:90
score 2:89
score 3:88.5
Please input score of student 2
Id & Name:120 Wang
score 1:80
score 2:97.5
score 3:85.5
Please input score of student 3
Id & Name:130
Chen
score 1:46
score 2:98
score 3:47.5
Please input score of student 4
Id & Name:140 Ma
score 1:100
score 2:99
score 3:98
Please input score of student 5
Id & Name:150 Wei
score 1:60
score 2:87
score 3:55.5
101,Li,90.00,89.00,88.50,89.00
120,Wang,80.00,97.50,85.50,87.33
130,Chen,46.00,98.00,47.50,63.67
140,Ma,100.00,99.00,98.00,99.00
150,Wei,60.00,87.00,55.50,67.33 ::: 请注意上面运行路径,这关系到最后的 stud.txt 文件生成! :::success b12@PC:~/chapter10$ ls
bin include obj src stud.txt
b12@PC:~/chapter10$ cat ./stud.txt
e Li B B B Bx Wang B B BB Chen 8B B >B~B Ma B B B B Wei pB B ^BB ::: 如果用 cat 查看会出现乱码,原因就是 fread 函数写入的东西是乱七八糟的。为什么呢?不适用 w 格式而不是二进制处理,怎么就出现数字错误,而字符类型的没有出错呢???

何况再次用 fread 读取后任然没错啊! fread 实际上写入的二进制流,字符类型在内存中是以一个字节存储,但是写入文件时,虽然 r 是以文本方式打开(打开方式不决定写入方式,而是决定写入函数,如 fprintf 先将内存中的二进制转化为文件中的 ASCII 码形式, fwrite 则是直接把内存中二进制写入文件)写入的依然是二进制形式。

当用记事本或者 cat 命令查看时,都是以 ASCII 码去翻译,即让你人能看得懂的东西。实际上在翻译过程中,由于字符本身就是一个字节,因此翻译的出字符串,但是其它就不能,所以是乱码状态。

我们以二进制形式写入文件和读入文件尝试验证上面的说法:正常情况下应该是字符会显示,其余不会。

  1. #include <stdio.h>
  2. #define N 5
  3. struct Student {
  4. int num;
  5. char name[8];
  6. float score[3];
  7. float ave;
  8. } stu[N];
  9. int main() {
  10. for (int i = 0; i < N; i++) {
  11. printf("Please input score of student %d\n", i + 1);
  12. printf("Id & Name:");
  13. scanf("%d %s", &stu[i].num, stu[i].name);
  14. int sum = 0;
  15. for (int j = 0; j < 3; j++) {
  16. printf("score %d:", j + 1);
  17. scanf("%f", stu[i].score + j);
  18. sum += stu[i].score[j];
  19. }
  20. stu[i].ave = sum / 3.0;
  21. }
  22. // 2.将数据以 二进制 方式写入文件
  23. FILE *fp = fopen("stud.txt", "wb");
  24. if (NULL == fp) {
  25. printf("Can not open file\n");
  26. return 0;
  27. }
  28. for (int i = 0; i < N; i++) {
  29. if (1 != fwrite(stu + i, sizeof(stu[0]), 1, fp)) {
  30. printf("Can not write file\n");
  31. return 0;
  32. }
  33. }
  34. fclose(fp);
  35. // 3. 将 stud.txt 文件又读出来覆盖到 stu 数组内(多此一举),并打印到stdout
  36. fp = fopen("stud.txt", "rb");
  37. if (NULL == fp) {
  38. printf("Can not open file\n");
  39. return 0;
  40. }
  41. for (int i = 0; i < N; i++) {
  42. if (1 != fread(stu + i, sizeof(stu[0]), 1, fp)) {
  43. printf("Can not write file\n");
  44. return 0;
  45. }
  46. printf("%d,%s,%.2f,%.2f,%.2f,%.2f\n",
  47. stu[i].num, stu[i].name, stu[i].score[0],
  48. stu[i].score[1], stu[i].score[2], stu[i].ave);
  49. }
  50. fclose(fp);
  51. return 0;
  52. }

编译运行: :::success b12@PC:~/chapter10$ gcc -Wall ./src/stuInfo.c -o ./bin/stuInfo
b12@PC:~/chapter10$ ./bin/stuInfo
Please input score of student 1
Id & Name:101 Li
score 1:56
score 2:78
score 3:65.5
Please input score of student 2
Id & Name:120 Wang
score 1:80
score 2:77.5
score 3:55.5
Please input score of student 3
Id & Name:130 Chen
score 1:46
score 2:88
score 3:65.5
Please input score of student 4
Id & Name:140 Ma
score 1:100
score 2:99
score 3:98
Please input score of student 5
Id & Name:150 Wei
score 1:87
score 2:64
score 3:49
101,Li,56.00,78.00,65.50,66.33
120,Wang,80.00,77.50,55.50,70.67
130,Chen,46.00,88.00,65.50,66.33
140,Ma,100.00,99.00,98.00,99.00
150,Wei,87.00,64.00,49.00,66.67 ::: 请注意上面运行路径,这关系到最后的 stud.txt 文件生成! :::success b12@PC:~/chapter10$ cat ./stud.txt
e Li `B B BBx Wang B B ^BUUB Chen 8 B BB Ma B B B B Wei B B DBUUB ::: 可以看到此处依然能看懂字符,但是其余东西与上面文本模式打开有点区别。

6、将第五题 stud 文件中的学生数据,按平均分进行排序处理,将已排序的学生数据存入一个新的 stu_sort 文件中。

解题思路

  1. 同第五题类似,将磁盘文件读入到内存中,用结构体数组存储,因此就涉及到上面所讲的如何读取“格式化”文件,采用二进制?还是文本 fscanf 函数?以下将会一一实现并对比。
  2. 结构体数组依据平均分进行排序,最后写入输出文件中。 ```c

    include

    define N 5

    struct Student { int num; char name[8]; float score[3]; float ave; } stu[N];

int main() { // 1.将数据以 二进制 方式读入内存中 FILE *fp = fopen(“stud.txt”, “rb”); if (NULL == fp) { printf(“Can not open file\n”); return 0; } printf(“stud.txt:\n”); int size = 0; for (int i = 0; 1 == fread(stu + i, sizeof(stu[0]), 1, fp); i++) { printf(“%8d%8s”, stu[i].num, stu[i].name); for (int j = 0; j < 3; j++) { printf(“%8.1f”, stu[i].score[j]); } printf(“%10.1f\n”, stu[i].ave); size++; // 读入学生信息个数 } fclose(fp); // 2. 将 stud 数组排序 for (int i = 0; i < size - 1; i++) { int minIdx = i; for (int j = i + 1; j < size; j++) { if (stu[j].ave < stu[minIdx].ave) { minIdx = j; } } if (minIdx != i) { struct Student tmp = stu[i]; stu[i] = stu[minIdx]; stu[minIdx] = tmp; } } // 3.将排序数组以 二进制方式 写入 stu_sort.txt 文件中 printf(“Sorted:\n”); fp = fopen(“stu_sort.txt”, “wb”); if (NULL == fp) { printf(“Can not open file\n”); return 0; } for (int i = 0; i < size; i++) { fprintf(fp, “%d %s %f %f %f %f\n”, stu[i].num, stu[i].name, stu[i].score[0], stu[i].score[1], stu[i].score[2], stu[i].ave); printf(“%8d%8s”, stu[i].num, stu[i].name); for (int j = 0; j < 3; j++) { printf(“%8.1f”, stu[i].score[j]); } printf(“%10.1f\n”, stu[i].ave); } fclose(fp); return 0; }

  1. **编译运行**:代码中使用 `fprintf` 格式化输出,格式化输入将在下一题中进行
  2. :::success
  3. b12@PC:~/chapter10$ gcc -Wall ./src/stuInfoSort.c -o ./bin/stuInfoSort<br />b12@PC:~/chapter10$ ./bin/stuInfoSort<br />stud.txt:<br /> 101 Li 56.0 78.0 65.5 66.3<br /> 120 Wang 80.0 77.5 55.5 70.7<br /> 130 Chen 46.0 88.0 65.5 66.3<br /> 140 Ma 100.0 99.0 98.0 99.0<br /> 150 Wei 87.0 64.0 49.0 66.7<br />Sorted:<br /> 101 Li 56.0 78.0 65.5 66.3<br /> 130 Chen 46.0 88.0 65.5 66.3<br /> 150 Wei 87.0 64.0 49.0 66.7<br /> 120 Wang 80.0 77.5 55.5 70.7<br /> 140 Ma 100.0 99.0 98.0 99.0<br />b12@PC:~/chapter10$ cat ./stu_sort.txt<br />101 Li 56.000000 78.000000 65.500000 66.333336<br />130 Chen 46.000000 88.000000 65.500000 66.333336<br />150 Wei 87.000000 64.000000 49.000000 66.666664<br />120 Wang 80.000000 77.500000 55.500000 70.666664<br />140 Ma 100.000000 99.000000 98.000000 99.000000
  4. :::
  5. 由此可见,文本打开方式并不是并不是读取文件的根本,而是创建该文件时,使用哪种写入文件函数所决定!如上使用 `wb` 创建文件,但是使用 `fprintf` 进行格式化写入文件,最后使用 `cat` 查看依然正确。
  6. :::warning
  7. 在使用 `fprintf` 进行格式化写入文件后,尽量不要弄一些宽度控制符,这样会导致 `fscanf` 无法再次格式读取出,因为不支持 `%8.1f` 的格式!!
  8. :::
  9. <a name="0w0iW"></a>
  10. # 7、将第 `6` 题已排序的学生成绩进行插入处理。插入学生的 3 门课成绩,程序先计算插入学生的平均成绩,然后将它按成绩高低顺序插入,插入后建立一个新文件。
  11. **解题思路**:本题有两种形式的插入,不管哪种都需要读入 stu_sort 文件入内存。
  12. - 读入内存中与已有数据的数组再度排序。
  13. - 找到该名学生应该插入到数组 ![](https://cdn.nlark.com/yuque/__latex/865c0c0b4ab0e063e5caa3387c1a8741.svg#card=math&code=i&height=16&width=5) 位置,待写文件先写前 ![](https://cdn.nlark.com/yuque/__latex/800ba394e806c049c98cf4b570d04c72.svg#card=math&code=i-1&height=16&width=34),然后写入该名学生成绩,再写入剩余学生成绩。
  14. 相比而言,第一种做法同第 5 题,只不过需要把数组开大一点,方便增加学生;第二种实现**简单插入一个学生并关闭**是非常推荐的!大型数据库的增删查改可没这么简单,一次性把所有数据读入内存是不可能的,更何况再排序??
  15. | 输入待插入学生的数据 |
  16. | :---: |
  17. | 计算其平均分 |
  18. | 打开 `stu_sort` 文件 |
  19. | 从该文件读入数据并显示出来 |
  20. | 确定插入位置 i |
  21. | 向文件输出前面 `i-1` 个学生数据并显示 |
  22. | 向文件输出待输入的学生数据并显示 |
  23. | 向文件输出前面 `i` 之后的学生并显示 |
  24. | 关闭文件 |
  25. 最重要的就是实现 `insort` 对有序数组的插入元素操作。
  26. ```c
  27. #include <stdio.h>
  28. #define N 10
  29. struct Student {
  30. int num;
  31. char name[8];
  32. float score[3];
  33. float ave;
  34. } stu[N];
  35. void insort(struct Student stu[], int stuSize, struct Student element) {
  36. int left = 0, right = stuSize;
  37. while (left < right) {
  38. int mid = (right - left) / 2 + left;
  39. if (stu[mid].ave < element.ave) {
  40. left = mid + 1;
  41. } else {
  42. right = mid;
  43. }
  44. }
  45. // 2.把 stu[left:] 向右移动一个单位,前提是数组够用
  46. for (int i = stuSize; i > left; i--) {
  47. stu[i] = stu[i-1]; // left->leff+1
  48. }
  49. stu[left] = element;
  50. }
  51. int main() {
  52. // 1.将数据以 二进制 方式读入内存中
  53. FILE *fp = fopen("stu_sort.txt", "r");
  54. if (NULL == fp) {
  55. printf("Can not open file\n");
  56. return 0;
  57. }
  58. printf("stu_sort.txt:\n");
  59. int size = 0;
  60. for (int i = 0; 6 == fscanf(fp, "%d %s %f %f %f %f\n",
  61. &stu[i].num, stu[i].name, stu[i].score + 0,
  62. stu[i].score + 1, stu[i].score + 2, &stu[i].ave); i++) {
  63. printf("%8d%8s", stu[i].num, stu[i].name);
  64. for (int j = 0; j < 3; j++) {
  65. printf("%8.1f", stu[i].score[j]);
  66. }
  67. printf("%10.1f\n", stu[i].ave);
  68. size++;
  69. }
  70. fclose(fp);
  71. // 2. IO 用户插入数据
  72. fp = fopen("stu_sort2.txt", "wb");
  73. if (NULL == fp) {
  74. printf("Can not open file\n");
  75. return 0;
  76. }
  77. while (size < N) { // 保证数组还有插入空间
  78. printf("You can only insert %d, continue or not(Y/N):", N - size);
  79. if ('N' == getchar()) break;
  80. struct Student tmp;
  81. printf("Id & Name:");
  82. scanf("%d %s", &tmp.num, tmp.name);
  83. int sum = 0;
  84. for (int j = 0; j < 3; j++) {
  85. printf("score %d:", j + 1);
  86. scanf("%f", tmp.score + j);
  87. sum += tmp.score[j];
  88. }
  89. tmp.ave = sum / 3.0;
  90. // (2) 插入后打印出插入结果
  91. insort(stu, size, tmp);
  92. size++;
  93. printf("Now:\n");
  94. for (int i = 0; i < size; i++) {
  95. printf("%8d%8s", stu[i].num, stu[i].name);
  96. for (int j = 0; j < 3; j++) {
  97. printf("%8.1f", stu[i].score[j]);
  98. }
  99. printf("%10.1f\n", stu[i].ave);
  100. }
  101. getchar(); // 这里换行吸收
  102. }
  103. // 3.将数组以 二进制 保存到文件中
  104. for (int i = 0; i < size; i++) {
  105. if (1 != fwrite(stu + i, sizeof(stu[0]), 1, fp)) {
  106. printf("Can not write file\n");
  107. return 0;
  108. }
  109. }
  110. fclose(fp);
  111. return 0;
  112. }

编译运行: :::success b12@PC:~/chapter10$ gcc -Wall ./src/stuInfoInsert.c -o ./bin/stuInfoInsert
b12@PC:~/chapter10$ ./bin/stuInfoInsert
stu_sort.txt:
101 Li 56.0 78.0 65.5 66.3
130 Chen 46.0 88.0 65.5 66.3
150 Wei 87.0 64.0 49.0 66.7
120 Wang 80.0 77.5 55.5 70.7
140 Ma 100.0 99.0 98.0 99.0
You can only insert 5, continue or not(Y/N):110 KGG
Id & Name:score 1:87
score 2:69
score 3:88.5
Now:
101 Li 56.0 78.0 65.5 66.3
130 Chen 46.0 88.0 65.5 66.3
150 Wei 87.0 64.0 49.0 66.7
120 Wang 80.0 77.5 55.5 70.7
10 KGG 87.0 69.0 88.5 81.3
140 Ma 100.0 99.0 98.0 99.0
You can only insert 4, continue or not(Y/N):Y
Id & Name:5000 LiMing
score 1:88.5
score 2:84
score 3:89
Now:
101 Li 56.0 78.0 65.5 66.3
130 Chen 46.0 88.0 65.5 66.3
150 Wei 87.0 64.0 49.0 66.7
120 Wang 80.0 77.5 55.5 70.7
10 KGG 87.0 69.0 88.5 81.3
5000 LiMing 88.5 84.0 89.0 87.0
140 Ma 100.0 99.0 98.0 99.0
You can only insert 3, continue or not(Y/N):N ::: 由于选择以 fread 函数写入 stu_sort2.txt 文件,因此使用 cat 就会乱码! :::success b12@PC:~/chapter10$ cat ./stu_sort2.txt
e Li `B B BB Chen 8B B BB Wei B B DBUUBx Wang B B ^BUUB
KGG = B B BB LiMing B B B B Ma B B B B :::

8、将第 7 题结果任存入原有的 stu_sort 文件而不另建立新文件。

解题思路:书本上对本题没有做到对原文件的“读+写”,而是另外创建一个文件操作! :::tips 本题实现方式有两种:

  1. 对文件打开方式为 rw ,即读写模式,但是注意文件指针的位置!!(不建议这样做,原因如下)
  2. 另外创建“临时”文件,然后将输出内容写入该文件,最后删除原来 stu_sort 文件,然后再把“临时”文件重命名为 stu_sort

image.png :::

  1. 使用 r+ 模式打开文件, 全部 读取到内存后,再用 rewind 将文件指针放到开头重新写入。 ```c

    include

    define N 10

    struct Student { int num; char name[8]; float score[3]; float ave; } stu[N];

void insort(struct Student stu[], int stuSize, struct Student element) { int left = 0, right = stuSize; while (left < right) { int mid = (right - left) / 2 + left; if (stu[mid].ave < element.ave) { left = mid + 1; } else { right = mid; } } // 2.把 stu[left:] 向右移动一个单位,前提是数组够用 for (int i = stuSize; i > left; i—) { stu[i] = stu[i-1]; // left->leff+1 } stu[left] = element; }

int main() { // 1.将数据以 二进制 方式读入内存中 FILE *fp = fopen(“stu_sort.txt”, “r+”); if (NULL == fp) { printf(“Can not open file\n”); return 0; } printf(“stu_sort.txt:\n”); int size = 0; for (int i = 0; 6 == fscanf(fp, “%d %s %f %f %f %f\n”, &stu[i].num, stu[i].name, stu[i].score + 0, stu[i].score + 1, stu[i].score + 2, &stu[i].ave); i++) { printf(“%8d%8s”, stu[i].num, stu[i].name); for (int j = 0; j < 3; j++) { printf(“%8.1f”, stu[i].score[j]); } printf(“%10.1f\n”, stu[i].ave); size++; } // 2. IO 用户插入数据 rewind(fp); // 文件指针回到开头进行覆盖 if (NULL == fp) { printf(“Can not open file\n”); return 0; } while (size < N) { // 保证数组还有插入空间 printf(“You can only insert %d, continue or not(Y/N):”, N - size); if (‘N’ == getchar()) break; struct Student tmp; printf(“Id & Name:”); scanf(“%d %s”, &tmp.num, tmp.name); int sum = 0; for (int j = 0; j < 3; j++) { printf(“score %d:”, j + 1); scanf(“%f”, tmp.score + j); sum += tmp.score[j]; } tmp.ave = sum / 3.0; // (2) 插入后打印出插入结果 insort(stu, size, tmp); size++; printf(“Now:\n”); for (int i = 0; i < size; i++) { printf(“%8d%8s”, stu[i].num, stu[i].name); for (int j = 0; j < 3; j++) { printf(“%8.1f”, stu[i].score[j]); } printf(“%10.1f\n”, stu[i].ave); } getchar(); // 这里换行吸收 } // 3.将数组以 ASCII方式 保存到文件中 for (int i = 0; i < size; i++) { fprintf(fp, “%d %s %f %f %f %f\n”, stu[i].num, stu[i].name, stu[i].score[0], stu[i].score[1], stu[i].score[2], stu[i].ave); } fclose(fp); return 0; }

  1. **编译运行**:将当前路径下的 `stu_sort.txt` 进行修改
  2. :::success
  3. b12@PC:~/chapter10$ gcc -Wall ./src/stuInfoInplaceInsert.c -o ./bin/stuInfoInplaceInsert<br />b12@PC:~/chapter10$ ./bin/stuInfoInplaceInsert<br />stu_sort.txt:<br /> 101 Li 56.0 78.0 65.5 66.3<br /> 130 Chen 46.0 88.0 65.5 66.3<br /> 150 Wei 87.0 64.0 49.0 66.7<br /> 120 Wang 80.0 77.5 55.5 70.7<br /> 140 Ma 100.0 99.0 98.0 99.0<br />You can only insert 5, continue or not(Y/N):Y<br />Id & Name:998 HeHe<br />score 1:45<br />score 2:33<br />score 3:100<br />Now:<br /> 998 HeHe 45.0 33.0 100.0 59.3<br /> 101 Li 56.0 78.0 65.5 66.3<br /> 130 Chen 46.0 88.0 65.5 66.3<br /> 150 Wei 87.0 64.0 49.0 66.7<br /> 120 Wang 80.0 77.5 55.5 70.7<br /> 140 Ma 100.0 99.0 98.0 99.0<br />You can only insert 4, continue or not(Y/N):N<br />b12@PC:~/chapter10$ cat ./stu_sort.txt<br />998 HeHe 45.000000 33.000000 100.000000 59.333332<br />101 Li 56.000000 78.000000 65.500000 66.333336<br />130 Chen 46.000000 88.000000 65.500000 66.333336<br />150 Wei 87.000000 64.000000 49.000000 66.666664<br />120 Wang 80.000000 77.500000 55.500000 70.666664<br />140 Ma 100.000000 99.000000 98.000000 99.000000
  4. :::
  5. 可以看到对文件打开方式为 `r+` 可以原地修改!不过需要注意文件指针的位置。如果在读取数据后,没有进行 `rewind()` ,会出现 `a` 模式效果。
  6. :::success
  7. b12@PC:~/chapter10$ ./bin/stuInfoInplaceInsert<br />stu_sort.txt:<br /> 998 HeHe 45.0 33.0 100.0 59.3<br /> 101 Li 56.0 78.0 65.5 66.3<br /> 130 Chen 46.0 88.0 65.5 66.3<br /> 150 Wei 87.0 64.0 49.0 66.7<br /> 120 Wang 80.0 77.5 55.5 70.7<br /> 140 Ma 100.0 99.0 98.0 99.0<br />You can only insert 4, continue or not(Y/N):Y<br />Id & Name:103 Uokia<br />score 1:88<br />score 2:87<br />score 3:95<br />Now:<br /> 998 HeHe 45.0 33.0 100.0 59.3<br /> 101 Li 56.0 78.0 65.5 66.3<br /> 130 Chen 46.0 88.0 65.5 66.3<br /> 150 Wei 87.0 64.0 49.0 66.7<br /> 120 Wang 80.0 77.5 55.5 70.7<br /> 103 Uokia 88.0 87.0 95.0 90.0<br /> 140 Ma 100.0 99.0 98.0 99.0<br />You can only insert 3, continue or not(Y/N):N<br />b12@PC:~/chapter10$ cat ./stu_sort.txt<br />998 HeHe 45.000000 33.000000 100.000000 59.333332<br />101 Li 56.000000 78.000000 65.500000 66.333336<br />130 Chen 46.000000 88.000000 65.500000 66.333336<br />150 Wei 87.000000 64.000000 49.000000 66.666664<br />120 Wang 80.000000 77.500000 55.500000 70.666664<br />140 Ma 100.000000 99.000000 98.000000 99.000000<br />_**(说明:以下重复部分因为没有 ****`rewind(fp)`**** 导致的)**_<br />998 HeHe 45.000000 33.000000 100.000000 59.333332<br />101 Li 56.000000 78.000000 65.500000 66.333336<br />130 Chen 46.000000 88.000000 65.500000 66.333336<br />150 Wei 87.000000 64.000000 49.000000 66.666664<br />120 Wang 80.000000 77.500000 55.500000 70.666664<br />103 Uokia 88.000000 87.000000 95.000000 90.000000<br />140 Ma 100.000000 99.000000 98.000000 99.000000
  8. :::
  9. 2. 创建“临时”文件,然后写入后删除 `stu_sort.txt` 并将“临时”文件 `rename` `stu_sort.txt`
  10. ```c
  11. #include <stdio.h>
  12. #define N 10
  13. struct Student {
  14. int num;
  15. char name[8];
  16. float score[3];
  17. float ave;
  18. } stu[N];
  19. void insort(struct Student stu[], int stuSize, struct Student element) {
  20. int left = 0, right = stuSize;
  21. while (left < right) {
  22. int mid = (right - left) / 2 + left;
  23. if (stu[mid].ave < element.ave) {
  24. left = mid + 1;
  25. } else {
  26. right = mid;
  27. }
  28. }
  29. // 2.把 stu[left:] 向右移动一个单位,前提是数组够用
  30. for (int i = stuSize; i > left; i--) {
  31. stu[i] = stu[i-1]; // left->leff+1
  32. }
  33. stu[left] = element;
  34. }
  35. int main() {
  36. // 1.将数据以 二进制 方式读入内存中
  37. FILE *fp = fopen("stu_sort.txt", "r");
  38. if (NULL == fp) {
  39. printf("Can not open file\n");
  40. return 0;
  41. }
  42. printf("stu_sort.txt:\n");
  43. int size = 0;
  44. for (int i = 0; 6 == fscanf(fp, "%d %s %f %f %f %f\n",
  45. &stu[i].num, stu[i].name, stu[i].score + 0,
  46. stu[i].score + 1, stu[i].score + 2, &stu[i].ave); i++) {
  47. printf("%8d%8s", stu[i].num, stu[i].name);
  48. for (int j = 0; j < 3; j++) {
  49. printf("%8.1f", stu[i].score[j]);
  50. }
  51. printf("%10.1f\n", stu[i].ave);
  52. size++;
  53. }
  54. fclose(fp);
  55. printf("remove:%d\n", remove("stu_sort.txt")); // 删除文件
  56. // 2. IO 用户插入数据
  57. fp = fopen("tmp.txt", "w");
  58. if (NULL == fp) {
  59. printf("Can not open file\n");
  60. return 0;
  61. }
  62. while (size < N) { // 保证数组还有插入空间
  63. printf("You can only insert %d, continue or not(Y/N):", N - size);
  64. if ('N' == getchar()) break;
  65. struct Student tmp;
  66. printf("Id & Name:");
  67. scanf("%d %s", &tmp.num, tmp.name);
  68. int sum = 0;
  69. for (int j = 0; j < 3; j++) {
  70. printf("score %d:", j + 1);
  71. scanf("%f", tmp.score + j);
  72. sum += tmp.score[j];
  73. }
  74. tmp.ave = sum / 3.0;
  75. // (2) 插入后打印出插入结果
  76. insort(stu, size, tmp);
  77. size++;
  78. printf("Now:\n");
  79. for (int i = 0; i < size; i++) {
  80. printf("%8d%8s", stu[i].num, stu[i].name);
  81. for (int j = 0; j < 3; j++) {
  82. printf("%8.1f", stu[i].score[j]);
  83. }
  84. printf("%10.1f\n", stu[i].ave);
  85. }
  86. getchar(); // 这里换行吸收
  87. }
  88. // 3.将数组以 ASCII方式 保存到文件中
  89. for (int i = 0; i < size; i++) {
  90. fprintf(fp, "%d %s %f %f %f %f\n",
  91. stu[i].num, stu[i].name, stu[i].score[0],
  92. stu[i].score[1], stu[i].score[2], stu[i].ave);
  93. }
  94. fclose(fp);
  95. printf("rename:%d\n", rename("tmp.txt", "stu_sort.txt")); // 删除文件
  96. return 0;
  97. }

编译运行: :::success b12@PC:~/chapter10$ gcc -Wall ./src/stuInfoInplaceInsert.c -o ./bin/stuInfoInplaceInsert
b12@PC:~/chapter10$ ./bin/stuInfoInplaceInsert
stu_sort.txt:
101 Li 56.0 78.0 65.5 66.3
130 Chen 46.0 88.0 65.5 66.3
150 Wei 87.0 64.0 49.0 66.7
120 Wang 80.0 77.5 55.5 70.7
140 Ma 100.0 99.0 98.0 99.0
remove:0 # 删除原有文件成功
You can only insert 5, continue or not(Y/N):Y
Id & Name:2550 Ak
score 1:48
score 2:98
score 3:67.5
Now:
101 Li 56.0 78.0 65.5 66.3
130 Chen 46.0 88.0 65.5 66.3
150 Wei 87.0 64.0 49.0 66.7
120 Wang 80.0 77.5 55.5 70.7
2550 Ak 48.0 98.0 67.5 71.0
140 Ma 100.0 99.0 98.0 99.0
You can only insert 4, continue or not(Y/N):N
rename:0 # 重命名成功
b12@PC:~/chapter10$ cat ./stu_sort.txt
101 Li 56.000000 78.000000 65.500000 66.333336
130 Chen 46.000000 88.000000 65.500000 66.333336
150 Wei 87.000000 64.000000 49.000000 66.666664
120 Wang 80.000000 77.500000 55.500000 70.666664
2550 Ak 48.000000 98.000000 67.500000 71.000000
140 Ma 100.000000 99.000000 98.000000 99.000000 :::

9、有一磁盘文件 employee,内存放职工的数据。每个职工的数据包括职工姓名、职工号、性别、年龄、住址、工资、健康状况、文化程度。今要求将职工名、工资的信息单独抽出来另建一个简明的职工工资文件。

解题思路:常规数据筛选,没有特别的,根据需求进行对该结构体成员的筛选。

  1. 根据题意创建 employee 结构体,成员有姓名(name:str)、职工号(id:int)、性别(sex:char)、年龄(age:int)、住址(address:str)、工资(salary:float)、健康状况(health:str)、文化程度(education:str)
  2. 筛选成员姓名(name:str)、工资(salary:float)输出到文件中。

第一步:创建工资表(可以直接写入文件,而不是一次性用内存中的数组存储后再处理

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. struct Employee {
  4. int num;
  5. char name[10];
  6. char sex;
  7. int age;
  8. char address[20];
  9. int salary;
  10. char health[10];
  11. char class[10];
  12. };
  13. int main () {
  14. struct Employee em;
  15. FILE *fp = fopen("employee.txt", "w");
  16. if (NULL == fp) {
  17. printf("Can not open file\n");
  18. exit(0);
  19. }
  20. printf("Please input NO. name sex age address salary health class\n");
  21. for (int i = 0; i < 4; i++) {
  22. scanf("%d %s %c %d %s %d %s %s", &em.num, em.name, &em.sex,
  23. &em.age, em.address, &em.salary, em.health, em.class);
  24. if (1 != fwrite(&em, sizeof(struct Employee), 1, fp)) {
  25. printf("Can not write file\n");
  26. exit(0);
  27. }
  28. }
  29. fclose(fp);
  30. return 0;
  31. }

编译运行: :::success b12@PC:~/chapter10$ gcc -Wall ./src/buildSheet.c -o ./bin/buildSheet
b12@PC:~/chapter10$ ./bin/buildSheet
Please input NO. name sex age address salary health class
101 Li m 23 Beijing 670 good P.H.D
102 Wang f 45 Shanghai 780 bad master
103 Ma m 32 Taijin 650 good univ.
104 Liu f 56 Xian 540 pass college
b12@PC:~/chapter10$ cat ./employee.txt
e Li wm  Beijing J /J  good P.H.D J f Wang f - Shanghai  /J  bad master  g Ma g m Taijin i  /J  good univ.  h Liu f 8 Xian n i  /J  pass college ::: 因为以 fwrite 写入二进制数据,因此 cat 是乱码。
第二步:读取工资表,然后进行筛选输出操作。(如果本例不要求在控制台输出原表内容,那么可以边对工资表读,边写入结果表内,这样节约内存开销。但是控制台程序要输出原表内容就必须存在数据中)

  1. 不在控制台打印原工资表内容,而打印筛选后的工资表内容。(优点:无序一次性读入内存再处理,边读文件边写入结果) ```c

    include

    include

struct Employee { int num; char name[10]; char sex; int age; char address[20]; int salary; char health[10]; char class[10]; };

FILE open(char fileName, char mode) { FILE fp = fopen(fileName, mode); if (NULL == fp) { printf(“Can not open %s\n”, fileName); exit(0); } return fp; }

int main () { struct Employee em; FILE fp1 = open(“employee.txt”, “r”); FILE fp2 = open(“employeeSalary.txt”, “w”); for (int i = 0; i < 4; i++) { if (1 != fread(&em, sizeof(struct Employee), 1, fp1)) { printf(“Can not read file\n”); exit(0); } printf(“%s %d\n”, em.name, em.salary); fprintf(fp2, “%s %d\n”, em.name, em.salary); } fclose(fp1); fclose(fp2); return 0; }

  1. **编译运行**:
  2. :::success
  3. b12@PC:~/chapter10$ gcc -Wall ./src/filterEmployee.c -o ./bin/filterEmployee<br />b12@PC:~/chapter10$ ./bin/filterEmployee<br />NO. name sex age address salary health class<br />Li 670<br />Wang 780<br />Ma 650<br />Liu 540<br />b12@PC:~/chapter10$ cat ./employeeSalary.txt<br />Li 670<br />Wang 780<br />Ma 650<br />Liu 540
  4. :::
  5. 2. 将原表信息打印并输出过滤表的信息(用数组存起来)
  6. ```c
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #define N 10
  11. struct Employee {
  12. int num;
  13. char name[10];
  14. char sex;
  15. int age;
  16. char address[20];
  17. int salary;
  18. char health[10];
  19. char class[10];
  20. } sheet[N];
  21. struct EmployeeSalary {
  22. char name[10];
  23. int salary;
  24. } em[N];
  25. FILE *open(char *fileName, char *mode) {
  26. FILE *fp = fopen(fileName, mode);
  27. if (NULL == fp) {
  28. printf("Can not open %s\n", fileName);
  29. exit(0);
  30. }
  31. return fp;
  32. }
  33. int main () {
  34. FILE *fp1 = open("employee.txt", "r");
  35. FILE *fp2 = open("employeeSalary.txt", "w");
  36. printf("NO. name sex age address salary health class\n");
  37. int idx = 0;
  38. for (; 1 == fread(sheet + idx, sizeof(struct Employee), 1, fp1); idx++) {
  39. printf("%d %s %c %d %s %d %s %s\n", sheet[idx].num, sheet[idx].name, sheet[idx].sex,
  40. sheet[idx].age, sheet[idx].address, sheet[idx].salary,
  41. sheet[idx].health, sheet[idx].class);
  42. strcpy(em[idx].name, sheet[idx].name);
  43. em[idx].salary = sheet[idx].salary;
  44. }
  45. fclose(fp1);
  46. printf("*******************************************\n");
  47. for (int i = 0; i < idx; i++) {
  48. printf("%s %d\n", em[i].name, em[i].salary);
  49. fprintf(fp2, "%s %d\n", em[i].name, em[i].salary);
  50. }
  51. printf("*******************************************\n");
  52. fclose(fp2);
  53. return 0;
  54. }

编译运行: :::success b12@PC:~/chapter10$ gcc -Wall ./src/filterEmployee.c -o ./bin/filterEmployee
b12@PC:~/chapter10$ ./bin/filterEmployee
NO. name sex age address salary health class
101 Li m 23 Beijing 670 good P.H.D
102 Wang f 45 Shanghai 780 bad master
103 Ma m 32 Taijin 650 good univ.
104 Liu f 56 Xian 540 pass college
*
Li 670
Wang 780
Ma 650
Liu 540
*
b12@PC:~/chapter10$ cat ./employeeSalary.txt
Li 670
Wang 780
Ma 650
Liu 540 :::

10、从第 9 题的“职工工资文件”中删除一个职工的数据,再存回到原文件。

解题思路:同第 8 题插入数据类似,有两种方式,不管哪种都需要读入“职工工资文件”,把数据放入内存中比较。与其不同就是,先查找,再删除,如果找不到就不可删除!

查找输入查询员工是否在“职工工资文件”内,并以下标 课后习题 - 图2 进行标记。

  • 如果 课后习题 - 图3 不合法,即不存在,程序退出。
  • 如果 课后习题 - 图4 合法,有两种选择方式处理:
    1. 课后习题 - 图5 之后的数据往前搬动一个位置,然后输出数组元素。(数组“大规模”搬迁)
    2. 先写前 课后习题 - 图6 个员工信息,然后跳过第 课后习题 - 图7 个员工信息,接着写入剩余员工信息。(pass 这个元素)

同时本题处理方式也是两种:一次性读入内存;边读边写

  1. 将文件全部读入内存中,然后决定是否搬动 ```c

    include

    include

    include

    define N 10

struct EmployeeSalary { char name[10]; int salary; } em[N];

FILE open(char fileName, char mode) { FILE fp = fopen(fileName, mode); if (NULL == fp) { printf(“Can not open %s\n”, fileName); exit(0); } return fp; }

int main () { FILE fp = open(“employeeSalary.txt”, “r”); printf(“name salary\n”); int idx = 0; for (; 2 == fscanf(fp, “%s %d\n”, em[idx].name, &em[idx].salary); idx++) { printf(“%s %d\n”, em[idx].name, em[idx].salary); } fclose(fp); // 2.输入删除数据 printf(“Please input name to delete:”); char name[N]; scanf(“%s”, name); int cnt = 0; // 计数删除个数 for (int i = 0; i < idx; i++) { if (!strcmp(name, em[i].name)) { // 这里可以选择不搬动,用数组记录要跳过的元素 for (int j = i; j < idx - 1; j++) { strcpy(em[j].name, em[j+1].name); em[j].salary = em[j+1].salary; } cnt++, idx—; // 删除一个 } } printf(“Remove %d item from sheet\n”, cnt); // 3.将结果以 ASCII 形式写入同一文件 fp = open(“employeeSalary.txt”, “w”); printf(“**\n”); for (int i = 0; i < idx; i++) { printf(“%s %d\n”, em[i].name, em[i].salary); fprintf(fp, “%s %d\n”, em[i].name, em[i].salary); } printf(“*\n”); fclose(fp); return 0; }

  1. **编译运行**:
  2. :::success
  3. b12@PC:~/chapter10$ gcc -Wall ./src/removeEmployee.c -o ./bin/removeEmployee<br />b12@PC:~/chapter10$ ./bin/removeEmployee<br />name salary<br />Li 670<br />Wang 780<br />Li 880<br />Ma 100<br />He 8100<br />Wang 250<br />Please input name to delete:Li<br />Remove 2 item from sheet<br />*******************************************<br />Wang 780<br />Ma 100<br />He 8100<br />Wang 250<br />*******************************************<br />b12@PC:~/chapter10$ cat ./employeeSalary.txt<br />Wang 780<br />Ma 100<br />He 8100<br />Wang 250
  4. :::
  5. 2. 创建“临时”文件,将原工资表中所有满足删除条件的删除!(处理大文件时是最有效的实现方式)
  6. ```c
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #define N 10
  11. struct EmployeeSalary {
  12. char name[10];
  13. int salary;
  14. };
  15. FILE *open(char *fileName, char *mode) {
  16. FILE *fp = fopen(fileName, mode);
  17. if (NULL == fp) {
  18. printf("Can not open %s\n", fileName);
  19. exit(0);
  20. }
  21. return fp;
  22. }
  23. int main () {
  24. // 1. 默认你浏览过文件信息,想删除某人信息
  25. printf("Please input name to delete:");
  26. char name[N];
  27. scanf("%s", name);
  28. FILE *fp1 = open("employeeSalary.txt", "r");
  29. FILE *fp2 = open("tmp.txt", "w");
  30. struct EmployeeSalary employee;
  31. int cnt = 0;
  32. // 2.边读入 employeeSalary 边判断写入 tmp 文件
  33. while(2 == fscanf(fp1, "%s %d\n", employee.name, &employee.salary)) {
  34. if (strcmp(name, employee.name) != 0) {
  35. fprintf(fp2, "%s %d\n", employee.name, employee.salary);
  36. } else {
  37. printf("Remove: %s %d\n", employee.name, employee.salary);
  38. cnt++;
  39. }
  40. }
  41. printf("Total remove %d item from sheet\n", cnt);
  42. fclose(fp1);
  43. fclose(fp2);
  44. // 3.瞒天过海, 删除 employeeSalary 文件,重命名 tmp 文件
  45. printf("remove employeeSalary: %d\n", remove("employeeSalary.txt"));
  46. printf("rename tmp to employeeSalary: %d\n",
  47. rename("tmp.txt", "employeeSalary.txt"));
  48. return 0;
  49. }

编译运行: :::success b12@PC:~/chapter10$ cat ./employeeSalary.txt
Liu 78796
Liu 78796
Liu 78796
He 98746
Liu 78796
Qiu 23796
Wang 96
Qiu 23796
Wang 96
Jiang 9226
Liang 9226
Qiu 23796
b12@PC:~/chapter10$ vi ./src/removeEmployee.c
b12@PC:~/chapter10$ gcc -Wall ./src/removeEmployee.c -o ./bin/removeEmployee
b12@PC:~/chapter10$ ./bin/removeEmployee
Please input name to delete:Liu
Remove: Liu 78796
Remove: Liu 78796
Remove: Liu 78796
Remove: Liu 78796
Total remove 4 item from sheet
remove employeeSalary: 0
rename tmp to employeeSalary: 0
b12@PC:~/chapter10$ cat ./employeeSalary.txt
He 98746
Qiu 23796
Wang 96
Qiu 23796
Wang 96
Jiang 9226
Liang 9226
Qiu 23796 :::

11、从键盘输入若干行字符(每行长度不等),输入后把它们存储到一磁盘文件中。再从该文件中读入这些数据,将其中小写字母转换成大写字母后在屏幕上输出。

解题思路:书本上反抛出一个问题,当文件中出现字符串中含有空格时, fscanf 函数就没法处理!
书本源代码使用 gets() 函数可以接受含有空格输入的字符串,且将末尾的 \n 从缓冲区里面扔出去。同时对文件格式化输出 fprintf(fp,"%s ",str); 是留有每个字符串最后有一个空格作为下次 fscanf 函数处理的标志。

  1. 当输入字符串中不含有空格时,如 abandongood 等,在文件中存储为 abandon good ,因此下次fscanf 函数因为 %s 认为空格就代表输入结束,并将其抛出缓冲区。
  • 当输入字符串中含有空格时,如 I am a student, good 等,在文件中存储为 I am a student good ,因此下次fscanf 函数因为 %s 认为空格就代表输入结束,并将其抛出缓冲区。即出现 Iam 等原本属于一个单词却被拆分为两个单词!

综上所述:原因出现在 fscanf("%s") 函数无法识别带有空格分隔的字符串(老毛病);作者设置的分隔符和输入带有空格分隔的字符串的冲突(自己挖坑埋自己)

因此解决办法就是:对 fprintf 按行分隔写入,对 fscanf("%[^\n]s") 正则读入(或者 fget(fp) 一个个读取到 \n 为止; fgets(buffer, N, fp) 登场啦) :::tips 在下方代码中,对于字符的输入不用 gets 而改用 fgets 函数! ::: 代码1:使用 fgets(buffer, N, fp) 函数读取(推荐)

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #define N 10
  5. FILE *open(char *fileName, char *mode) {
  6. FILE *fp = fopen(fileName, mode);
  7. if (NULL == fp) {
  8. printf("Can not open %s\n", fileName);
  9. exit(0);
  10. }
  11. return fp;
  12. }
  13. int main() {
  14. char buffer[N];
  15. FILE *fp = open("text.txt", "w");
  16. while (1) {
  17. printf("Please input string:");
  18. fgets(buffer, N, stdin);
  19. int idx = strlen(buffer); // idx >= 1(\n)
  20. if (buffer[idx-1] != '\n') {
  21. fprintf(fp, "%s\n", buffer); // no newline
  22. } else {
  23. fprintf(fp, "%s", buffer); // endswith newline
  24. }
  25. // fflush(stdin); // 当输入超过 N 长度,清空缓冲区
  26. while (getchar() != '\n'); // gcc 不支持!
  27. printf("Continue(Y/N):");
  28. char ch = getchar();
  29. if ('N' == ch || 'n' == ch) break;
  30. getchar(); // 消除\n
  31. }
  32. fclose(fp);
  33. // 3. 将 text.txt 文件又读出来打印到 stdout
  34. fp = open("text.txt", "r");
  35. while (fgets(buffer, N, fp)) {
  36. for (int i = 0; buffer[i]; i++) {
  37. if ('a' <= buffer[i] && buffer[i] <= 'z') {
  38. buffer[i] -= 32;
  39. }
  40. }
  41. printf("%s", buffer);
  42. }
  43. fclose(fp);
  44. return 0;
  45. }

编译运行:(注意为什么要缓冲!为啥用 fgets 函数要判断是否有换行符) :::success b12@PC:~/chapter10$ gcc -Wall ./src/swapcase.c -o ./bin/swapcase
b12@PC:~/chapter10$ ./bin/swapcase
Please input string:I am a StudEnt.
Continue(Y/N):Y
Please input string:You are A Bad MaN.
Continue(Y/N):Y
Please input string:WHy You2d 23 Ka
Continue(Y/N):Y
Please input string:() SD2gdsa
Continue(Y/N):N
I AM A ST
YOU ARE A
WHY YOU2D
() SD2GDS
b12@PC:~/chapter10$ cat ./text.txt
I am a St
You are A
WHy You2d
() SD2gds ::: 代码2:使用 fscanf("%[^\n]s") 函数

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #define N 10
  5. FILE *open(char *fileName, char *mode) {
  6. FILE *fp = fopen(fileName, mode);
  7. if (NULL == fp) {
  8. printf("Can not open %s\n", fileName);
  9. exit(0);
  10. }
  11. return fp;
  12. }
  13. int main() {
  14. char buffer[N];
  15. FILE *fp = open("text.txt", "w");
  16. while (1) {
  17. printf("Please input string:");
  18. fgets(buffer, N, stdin);
  19. int idx = strlen(buffer); // idx >= 1(\n)
  20. if (buffer[idx-1] != '\n') {
  21. fprintf(fp, "%s\n", buffer); // no newline
  22. } else {
  23. fprintf(fp, "%s", buffer); // endswith newline
  24. }
  25. // fflush(stdin); // 当输入超过 N 长度,清空缓冲区
  26. while (getchar() != '\n'); // gcc 不支持!
  27. printf("Continue(Y/N):");
  28. char ch = getchar();
  29. if ('N' == ch || 'n' == ch) break;
  30. getchar(); // 消除\n
  31. }
  32. fclose(fp);
  33. // 3. 将 stud.txt 文件又读出来覆盖到 stu 数组内(多此一举),并打印到stdout
  34. fp = open("text.txt", "r");
  35. while (EOF != fscanf(fp, "%[^\n]s", buffer)) {
  36. for (int i = 0; buffer[i]; i++) {
  37. if ('a' <= buffer[i] && buffer[i] <= 'z') {
  38. buffer[i] -= 32;
  39. }
  40. }
  41. fgetc(fp); // 注意:因为 fp 指针停留在 \n,因此要移动到下个位置去
  42. printf("%s\n", buffer);
  43. }
  44. fclose(fp);
  45. return 0;
  46. }

编译运行: :::success b12@PC:~/chapter10$ gcc -Wall ./src/swapcase.c -o ./bin/swapcase
b12@PC:~/chapter10$ ./bin/swapcase
Please input string:123456789
Continue(Y/N):Y
Please input string:ADs we KGi
Continue(Y/N):Y
Please input string:7 SDAs kqe
Continue(Y/N):Y
Please input string:45 3&231 Ga
Continue(Y/N):N
123456789
ADS WE KG
7 SDAS KQ
45 3&231
b12@PC:~/chapter10$ cat ./text.txt
123456789
ADs we KG
7 SDAs kq
45 3&231 :::

总结

就本章内容来说,只是基础入门文件 IO 操作,但是实际上很多操作都需要注意具体机器,如不可能一次性读取 100G 的文件放到内存中(比如本章 7,8,9,10 题操作),关于这部分问题就是如何实现用“较小的内存”搞定“超大文件”增删查改操作。这部分知识可以深入学习数据库就明白。尤其是数据结构的引用。也就是说本章数据量都小,只供教学演示,实际处理上如果照搬教材代码务必遇到很多麻烦!