在C中从文本文件中将字符串分配给链表时,使用fscanf跳过行的问题。

huangapple go评论90阅读模式
英文:

Problems with fscanf skipping lines when assigning strings to a linked list from a txt file in C

问题

以下是您提供的代码的翻译部分:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #define MAX_REG 8+1
  5. #define MAX_OSG 2+1
  6. #define MAX_DIO 5+1
  7. typedef struct automobili {
  8. char registracija[MAX_REG];
  9. char osiguran[MAX_OSG];
  10. char dio[MAX_DIO];
  11. double steta;
  12. struct automobili *sledeci;
  13. } AUTO;
  14. // 打开文本文件
  15. FILE *open(char *ime, char *tip) {
  16. FILE *fp = fopen(ime, tip);
  17. if (fp == NULL) {
  18. if (strcmp(tip, "r") == 0) {
  19. printf("Nije moguće otvoriti ulaznu datoteku %s!\n", ime);
  20. exit(1);
  21. } else if (strcmp(tip, "w") == 0) {
  22. printf("Nije moguće otvoriti izlaznu datoteku %s!\n", ime);
  23. exit(2);
  24. }
  25. }
  26. return fp;
  27. }
  28. // 创建新的链表元素
  29. AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta) {
  30. AUTO *novi = (AUTO *)malloc(sizeof(AUTO));
  31. if (novi == NULL) {
  32. printf("Greška prilikom zauzimanja memorije!\n");
  33. exit(EXIT_FAILURE);
  34. }
  35. strcpy(novi->registracija, registracija);
  36. strcpy(novi->osiguran, osiguran);
  37. strcpy(novi->dio, dio);
  38. novi->steta = steta;
  39. novi->sledeci = NULL;
  40. return novi;
  41. }
  42. // 添加元素到链表的末尾
  43. void dodaj_cvor(AUTO **glava, AUTO *novi) {
  44. if (*glava == NULL) {
  45. *glava = novi;
  46. return;
  47. } else {
  48. AUTO *tekuci = *glava;
  49. while (tekuci->sledeci != NULL) {
  50. tekuci = tekuci->sledeci;
  51. }
  52. tekuci->sledeci = novi;
  53. }
  54. }
  55. // 从输入文件读取并使用函数将新元素添加到链表中
  56. void unos(FILE *in, AUTO **glava) {
  57. char registracija[MAX_REG];
  58. char osiguran[MAX_OSG];
  59. char dio[MAX_DIO];
  60. double steta;
  61. while (fscanf(in, "%s %s %s %lf", registracija, osiguran, dio, &steta) != EOF) {
  62. AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
  63. dodaj_cvor(glava, novi);
  64. }
  65. }
  66. void inicijalizacija(AUTO **glava) {
  67. *glava = NULL;
  68. }
  69. // 删除链表
  70. void brisanje(AUTO **glava) {
  71. if (*glava != NULL) {
  72. brisanje(&((*glava)->sledeci));
  73. free(*glava);
  74. *glava = NULL;
  75. }
  76. }
  77. void provjera_osiguranja(AUTO *glava) {
  78. while (glava != NULL) {
  79. if (strcmp(glava->osiguran, "da") == 0) {
  80. glava->steta = glava->steta * 0.85;
  81. }
  82. glava = glava->sledeci;
  83. }
  84. }
  85. // 检查正确的元素并将它们写入输出文件
  86. void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]) {
  87. double sum = 0;
  88. while (glava != NULL) {
  89. if (strcmp(glava->registracija, br_reg) == 0) {
  90. fprintf(out, "%s %s %s %.2lf\n", glava->registracija, glava->osiguran, glava->dio, glava->steta);
  91. sum += glava->steta;
  92. }
  93. glava = glava->sledeci;
  94. }
  95. if (sum == 0) {
  96. printf("Registracija nije na spisku!\n");
  97. exit(11);
  98. }
  99. fprintf(out, "Ukupna vrijednost štete je %.2lf din.\n", sum);
  100. }
  101. int main(int argc, char **argv) {
  102. char br_reg[MAX_REG];
  103. // 期望的输入格式为 "./a.out automobili.txt <exit_filename>.txt"
  104. if (argc != 3) {
  105. printf("Loš unos! Pokušajte ponovo!\n");
  106. exit(EXIT_FAILURE);
  107. }
  108. FILE *in = open(argv[1], "r");
  109. FILE *out = open(argv[2], "w");
  110. AUTO *glava;
  111. inicijalizacija(&glava);
  112. unos(in, &glava);
  113. printf("Unesite registraciju: ");
  114. fgets(br_reg, MAX_REG, stdin);
  115. provjera_osiguranja(glava);
  116. provjera_registracije(out, glava, br_reg);
  117. brisanje(&glava);
  118. fclose(in);
  119. fclose(out);
  120. return 0;
  121. }

请注意,此代码翻译部分仅包括代码本身的注释和字符串,不包括任何其他内容。希望这可以帮助您解决代码问题。如果您有其他问题,请随时提出。

英文:

The following code reads the input from a file called "automobili.txt" and outputs them into a different file, however I cannot figure out why the code skips over some strings when scanning the input

  1. #include &lt;stdio.h&gt;
  2. #include &lt;stdlib.h&gt;
  3. #include &lt;string.h&gt;
  4. #define MAX_REG 8+1
  5. #define MAX_OSG 2+1
  6. #define MAX_DIO 5+1
  7. typedef struct automobili {
  8. char registracija[MAX_REG];
  9. char osiguran[MAX_OSG];
  10. char dio[MAX_DIO];
  11. double steta;
  12. struct automobili *sledeci;
  13. } AUTO;
  14. //opens the txt files
  15. FILE *open(char *ime, char *tip){
  16. FILE *fp = fopen(ime, tip);
  17. if (fp == NULL) {
  18. if (strcmp(tip, &quot;r&quot;) == 0) {
  19. printf(&quot;Nije moguće otvoriti ulaznu datoteku %s!\n&quot;, ime);
  20. exit(1);
  21. } else if (strcmp(tip, &quot;w&quot;) == 0) {
  22. printf(&quot;Nije moguće otvoriti izlaznu datoteku %s!\n&quot;, ime);
  23. exit(2);
  24. }
  25. }
  26. return fp;
  27. }
  28. //creates new list element
  29. AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta){
  30. AUTO *novi = (AUTO*)malloc(sizeof(AUTO));
  31. if (novi == NULL) {
  32. printf(&quot;Greška prilikom zauzimanja memorije!\n&quot;);
  33. exit(EXIT_FAILURE);
  34. }
  35. strcpy(novi-&gt;registracija, registracija);
  36. strcpy(novi-&gt;osiguran, osiguran);
  37. strcpy(novi-&gt;dio, dio);
  38. novi-&gt;steta = steta;
  39. novi-&gt;sledeci = NULL;
  40. return novi;
  41. }
  42. //adds element to the end of the linked list
  43. void dodaj_cvor(AUTO **glava, AUTO *novi){
  44. if (*glava == NULL) {
  45. *glava = novi;
  46. return;
  47. } else {
  48. AUTO *tekuci = *glava;
  49. while (tekuci-&gt;sledeci != NULL) {
  50. tekuci = tekuci-&gt;sledeci;
  51. }
  52. tekuci-&gt;sledeci = novi;
  53. }
  54. }
  55. //reads from the input file and adds new elements to the list using functions
  56. void unos(FILE *in, AUTO **glava){
  57. char registracija[MAX_REG];
  58. char osiguran[MAX_OSG];
  59. char dio[MAX_DIO];
  60. double steta;
  61. while (fscanf(in, &quot;%s %s %s %lf&quot;, registracija, osiguran, dio, &amp;steta) != EOF) {
  62. AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
  63. dodaj_cvor(glava, novi);
  64. }
  65. }
  66. void inicijalizacija(AUTO **glava){
  67. *glava = NULL;
  68. }
  69. //deletes the list
  70. void brisanje(AUTO **glava){
  71. if (*glava != NULL) {
  72. brisanje(&amp;((*glava)-&gt;sledeci));
  73. free(*glava);
  74. *glava = NULL;
  75. }
  76. }
  77. void provjera_osiguranja(AUTO *glava) {
  78. while (glava != NULL) {
  79. if (strcmp(glava-&gt;osiguran, &quot;da&quot;) == 0) {
  80. glava-&gt;steta = glava-&gt;steta * 0.85;
  81. }
  82. glava = glava-&gt;sledeci;
  83. }
  84. }
  85. //checks for the right elemets and writres them down in the output file
  86. void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]){
  87. double sum=0;
  88. while (glava != NULL) {
  89. if (strcmp(glava-&gt;registracija, br_reg) == 0) {
  90. fprintf(out, &quot;%s %s %s %.2lf\n&quot;, glava-&gt;registracija, glava-&gt;osiguran, glava-&gt;dio, glava-&gt;steta);
  91. sum += glava-&gt;steta;
  92. }
  93. glava = glava-&gt;sledeci;
  94. }
  95. if (sum == 0) {
  96. printf(&quot;Registracija nije na spisku!\n&quot;);
  97. exit(11);
  98. }
  99. fprintf(out, &quot;Ukupna vrijednost štete je %.2lf din.\n&quot;, sum);
  100. }
  101. int main(int argc, char **argv){
  102. char br_reg[MAX_REG];
  103. //expected input is &quot;./a.out automobili.txt &lt;exit_filename&gt;.txt&quot;
  104. if (argc != 3) {
  105. printf(&quot;Loš unos! Pokušajte ponovo!\n&quot;);
  106. exit(EXIT_FAILURE);
  107. }
  108. FILE *in = open(argv[1], &quot;r&quot;);
  109. FILE *out = open(argv[2], &quot;w&quot;);
  110. AUTO *glava;
  111. inicijalizacija(&amp;glava);
  112. unos(in, &amp;glava);
  113. printf(&quot;Unesite registraciju: &quot;);
  114. fgets(br_reg, MAX_REG, stdin);
  115. provjera_osiguranja(glava);
  116. provjera_registracije(out, glava, br_reg);
  117. brisanje(&amp;glava);
  118. fclose(in);
  119. fclose(out);
  120. return 0;
  121. }

The input file:

  1. PA454-TS da menjac 5500
  2. BG777-OS da volan 11500
  3. BG777-OS ne branik 7000
  4. BG777-OS ne menjac 23500
  5. PA454-TS da gume 16000
  6. BG777-OS da kvacilo 33000

P.S. I apologize for non English code, the important functions have been commented on what each of them does.

The expected output when not checking for a specific registration number should have been:

  1. PA454-TS da menjac 4675.00
  2. BG777-OS da volan 9775.00
  3. BG777-OS ne branik 7000.00
  4. BG777-OS ne menjac 23500.00
  5. PA454-TS da gume 13600.00
  6. BG777-OS da kvacilo 28050.00
  7. Ukupna vrijednost štete je 86600.00 din.

However the program outputs:

  1. da menjac 4675.00
  2. BG777-OS da volan 9775.00
  3. ne branik 7000.00
  4. ne menjac 23500.00
  5. PA454-TS da gume 13600.00
  6. o da kvacilo 28050.00
  7. Ukupna vrijednost štete je 86600.00 din.

I have tried rewriting the code however nothing changed and I cannot find a difference in how I executed the input here as compared to my previous code which works fine. I suspect the issue comes from fscanf() and not assigning the string to the element itself.

答案1

得分: 1

以下是已翻译的代码部分:

  1. 有多个问题在代码中:
  2. `while (fscanf(in, "%s %s %s %lf", registracija, osiguran, dio, &steta) != EOF)` 很容易出错且不安全:
  3. * 如果任何字段在 `%s` 转换中的长度超过了相应目标数组的长度,将导致缓冲区溢出和未定义行为。
  4. * 事实上,数组 `dio` 的长度为 `5+1` 字节,而输入文件中的每一行的这个字段都有更长的字符串,这将导致未定义行为。你应该在格式字符串中指定最大长度,如 `%8s %2s %5s %lf`,并可能增加这些长度以适应实际的文件内容。
  5. * 比较 `fscanf()` 的返回值与 `EOF` 只会检测在任何字段可以转换之前出现的意外文件结束。相反,你应该测试返回值是否为 `4`,表示所有 4 个转换已成功。
  6. * 直接调用 `fscanf()` 使得在输入文件中检测无效行变得困难。建议使用 `fgets()` 从文件中读取一行,并使用 `sscanf()` 尝试转换,在失败时生成有信息的错误消息。
  7. * `open` 作为函数或变量名应该避免使用,因为这个名称在 POSIX 系统上引用了一个系统调用。使用 `open_file` 既更安全又更可读。
  8. * 还要注意,宏定义,如 `#define MAX_REG 8+1` 是有风险的:如果你在代码中写 `MAX_REG*2`,它将展开为 `8+1*2`,其值为 `10`,而不是预期的 `18`。始终要给宏表达式以及宏表达式中使用的宏参数加上括号。
  9. 以下是修改后的版本:
  10. ```c
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #define MAX_REG (8+1)
  15. #define MAX_OSG (2+1)
  16. #define MAX_DIO (8+1)
  17. typedef struct automobili {
  18. char registracija[MAX_REG];
  19. char osiguran[MAX_OSG];
  20. char dio[MAX_DIO];
  21. double steta;
  22. struct automobili *sledeci;
  23. } AUTO;
  24. // 打开文本文件
  25. FILE *open_file(const char *ime, const char *tip) {
  26. FILE *fp = fopen(ime, tip);
  27. if (fp == NULL) {
  28. if (strcmp(tip, "r") == 0) {
  29. printf("无法打开输入文件 %s!\n", ime);
  30. exit(1);
  31. } else if (strcmp(tip, "w") == 0) {
  32. printf("无法打开输出文件 %s!\n", ime);
  33. exit(2);
  34. } else {
  35. printf("无法以模式 %s 打开文件 %s!\n", tip, ime);
  36. exit(3);
  37. }
  38. }
  39. return fp;
  40. }
  41. // 如果没有截断,则返回字符串的长度。
  42. // 否则截断字符串并返回目标大小。
  43. // 如果 size > 0,则目标始终以 null 结尾
  44. size_t pstrcpy(char *dest, size_t size, const char *src) {
  45. size_t len;
  46. for (len = 0; len < size && src[len]; len++)
  47. continue;
  48. if (len < size)
  49. size = len + 1;
  50. if (size > 0) {
  51. memmove(dest, src, size - 1);
  52. dest[size - 1] = '\0';
  53. }
  54. return len;
  55. }
  56. // 创建新的列表元素
  57. AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta) {
  58. AUTO *novi = (AUTO *)malloc(sizeof(AUTO));
  59. if (novi == NULL) {
  60. printf("内存分配失败!\n");
  61. exit(EXIT_FAILURE);
  62. }
  63. pstrcpy(novi->registracija, sizeof novi->registracija, registracija);
  64. pstrcpy(novi->osiguran, sizeof novi->osiguran, osiguran);
  65. pstrcpy(novi->dio, sizeof novi->dio, dio);
  66. novi->steta = steta;
  67. novi->sledeci = NULL;
  68. return novi;
  69. }
  70. // 在链表末尾添加元素
  71. void dodaj_cvor(AUTO **glava, AUTO *novi) {
  72. if (*glava == NULL) {
  73. *glava = novi;
  74. return;
  75. } else {
  76. AUTO *tekuci = *glava;
  77. while (tekuci->sledeci != NULL) {
  78. tekuci = tekuci->sledeci;
  79. }
  80. tekuci->sledeci = novi;
  81. }
  82. }
  83. // 从输入文件读取并使用函数将新元素添加到列表中
  84. void unos(FILE *in, AUTO **glava) {
  85. char registracija[MAX_REG];
  86. char osiguran[MAX_OSG];
  87. char dio[MAX_DIO];
  88. double steta;
  89. char buf[100];
  90. char c;
  91. while (fgets(buf, sizeof buf, in)) {
  92. if (sscanf(buf, "%8s %2s %8s %lf %c", registracija, osiguran, dio, &steta, &c) == 4) {
  93. AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
  94. dodaj_cvor(glava, novi);
  95. } else {
  96. printf("无效的输入行:%s\n", buf);
  97. }
  98. }
  99. }
  100. void inicijalizacija(AUTO **glava) {
  101. *glava = NULL;
  102. }
  103. // 删除列表
  104. void brisanje(AUTO **glava) {
  105. while (*glava != NULL) {
  106. AUTO *p = *glava;
  107. *glava = p->sledeci;
  108. free(p);
  109. }
  110. }
  111. void provjera_osiguranja(AUTO *glava) {
  112. while (glava != NULL) {
  113. if (strcmp(glava->osiguran, "da") == 0) {
  114. glava->steta = glava->steta * 0.85;
  115. }
  116. glava = glava->sledeci;
  117. }
  118. }
  119. // 检查正确的元素并将其写入输出文件
  120. void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]) {
  121. double sum = 0;
  122. <details>
  123. <summary>英文:</summary>
  124. There are multiple problems in the code:
  125. `while (fscanf(in, &quot;%s %s %s %lf&quot;, registracija, osiguran, dio, &amp;steta) != EOF)` is very brittle and unsafe:
  126. * if any of the fields parsed for the `%s` conversions exceeds the length of the corresponding target array, this will cause a buffer overflow with undefined behavior.
  127. * as a matter of fact, the array `dio` has a length of `5+1` bytes and every other line in the input file has a longer string for this field, causing undefined behavior. You should specify the maximum length in the format string as `%8s %2s %5s %lf` and should probably increase these lengths to accommodate the actual file contents.
  128. * comparing the return value of `fscanf()` to `EOF` will only detect an unexpected end of file occurring before any field can be converted. You should instead test that the return value is `4`, signifying all 4 conversions have succeeded.
  129. * calling `fscanf()` directly makes it difficult to detect invalid lines in the input file. It is recommended to use `fgets()` to read a line from the file and `sscanf()` to attempt the conversion, producing an informative error message upon failure.
  130. * `open` as a function or variable name should be avoided as this name refers to a system call on POSIX systems. Using `open_file` is both safer and more readable.
  131. * also note that macro definitions such as `#define MAX_REG 8+1` are risky: if you write `MAX_REG*2` in the code, it will expand to `8+1*2` which has the value `10` instead of the expected `18`. Always parenthesize macro expressions as well as macro arguments used in the macro expression.
  132. Here is a modified version:

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

#define MAX_REG (8+1)
#define MAX_OSG (2+1)
#define MAX_DIO (8+1)

typedef struct automobili {
char registracija[MAX_REG];
char osiguran[MAX_OSG];
char dio[MAX_DIO];
double steta;
struct automobili *sledeci;
} AUTO;

//opens the txt files
FILE *open_file(const char *ime, const char *tip) {
FILE *fp = fopen(ime, tip);
if (fp == NULL) {
if (strcmp(tip, "r") == 0) {
printf("Nije moguće otvoriti ulaznu datoteku %s!\n", ime);
exit(1);
} else
if (strcmp(tip, "w") == 0) {
printf("Nije moguće otvoriti izlaznu datoteku %s!\n", ime);
exit(2);
} else {
printf("Nije moguće otvoriti datoteku %s za mod %s!\n", ime, tip);
exit(3);
}
}
return fp;
}

// returns the length of the string if no truncation.
// otherwise truncate the string and return the destination size.
// destination is always null terminated if size > 0
size_t pstrcpy(char *dest, size_t size, const char *src) {
//size_t len = strnlen(src, size);
size_t len;
for (len = 0; len < size && src[len]; len++)
continue;
if (len < size)
size = len + 1;
if (size > 0) {
memmove(dest, src, size - 1);
dest[size - 1] = '\0';
}
return len;
}

//creates new list element
AUTO *novi_cvor(char registracija[], char osiguran[], char dio[], double steta) {
AUTO novi = (AUTO)malloc(sizeof(AUTO));

  1. if (novi == NULL) {
  2. printf(&quot;Greška prilikom zauzimanja memorije!\n&quot;);
  3. exit(EXIT_FAILURE);
  4. }
  5. pstrcpy(novi-&gt;registracija, sizeof novi-&gt;registracija, registracija);
  6. pstrcpy(novi-&gt;osiguran, sizeof novi-&gt;osiguran, osiguran);
  7. pstrcpy(novi-&gt;dio, sizeof novi-&gt;dio, dio);
  8. novi-&gt;steta = steta;
  9. novi-&gt;sledeci = NULL;
  10. return novi;

}

//adds element to the end of the linked list
void dodaj_cvor(AUTO **glava, AUTO *novi) {
if (*glava == NULL) {
*glava = novi;
return;
} else {
AUTO *tekuci = *glava;
while (tekuci->sledeci != NULL) {
tekuci = tekuci->sledeci;
}
tekuci->sledeci = novi;
}
}

//reads from the input file and adds new elements to the list using functions
void unos(FILE *in, AUTO **glava) {
char registracija[MAX_REG];
char osiguran[MAX_OSG];
char dio[MAX_DIO];
double steta;
char buf[100];
char c;

  1. while (fgets(buf, sizeof buf, in)) {
  2. if (sscanf(buf, &quot;%8s %2s %8s %lf %c&quot;, registracija, osiguran, dio, &amp;steta, &amp;c) == 4) {
  3. AUTO *novi = novi_cvor(registracija, osiguran, dio, steta);
  4. dodaj_cvor(glava, novi);
  5. } else {
  6. printf(&quot;invalid input line: %s\n&quot;, buf);
  7. }
  8. }

}

void inicijalizacija(AUTO **glava) {
*glava = NULL;
}

//deletes the list
void brisanje(AUTO **glava) {
while (*glava != NULL) {
AUTO *p = *glava;
*glava = p->sledeci;
free(p);
}
}

void provjera_osiguranja(AUTO *glava) {
while (glava != NULL) {
if (strcmp(glava->osiguran, "da") == 0) {
glava->steta = glava->steta * 0.85;
}
glava = glava->sledeci;
}
}

//checks for the right elemets and writres them down in the output file
void provjera_registracije(FILE *out, AUTO *glava, char br_reg[]) {
double sum = 0;
while (glava != NULL) {
if (strcmp(glava->registracija, br_reg) == 0) {
fprintf(out, "%s %s %s %.2lf\n", glava->registracija, glava->osiguran, glava->dio, glava->steta);
sum += glava->steta;
}
glava = glava->sledeci;
}

  1. if (sum == 0) {
  2. printf(&quot;Registracija nije na spisku!\n&quot;);
  3. exit(11);
  4. }
  5. fprintf(out, &quot;Ukupna vrijednost štete je %.2lf din.\n&quot;, sum);

}

int main(int argc, char **argv) {

  1. char br_reg[MAX_REG];
  2. //expected input is &quot;./a.out automobili.txt &lt;exit_filename&gt;.txt&quot;
  3. if (argc != 3) {
  4. printf(&quot;Loš unos! Pokušajte ponovo!\n&quot;);
  5. exit(EXIT_FAILURE);
  6. }
  7. FILE *in = open_file(argv[1], &quot;r&quot;);
  8. FILE *out = open_file(argv[2], &quot;w&quot;);
  9. AUTO *glava;
  10. inicijalizacija(&amp;glava);
  11. unos(in, &amp;glava);
  12. printf(&quot;Unesite registraciju: &quot;);
  13. fgets(br_reg, MAX_REG, stdin);
  14. provjera_osiguranja(glava);
  15. provjera_registracije(out, glava, br_reg);
  16. brisanje(&amp;glava);
  17. fclose(in);
  18. fclose(out);
  19. return 0;

}

  1. </details>

huangapple
  • 本文由 发表于 2023年6月9日 00:04:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76433777.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定