如何读入两个文本文件并统计关键字的数量?
How to read in two text files and count the amount of keywords?
我试过环顾四周,但对我来说,就我学习 C 而言,文件是最难理解的东西,尤其是文本文件,二进制文件要容易一些。基本上我必须读入两个文本文件,它们都包含格式如下的单词 "hard, working,smart, works well, etc.." 我想比较文本文件并计算关键字。我会展示一些代码,但老实说,我迷路了,除此之外,我唯一失望的就是胡说八道。
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#define SIZE 1000
void resumeRater();
int main()
{
int i;
int counter = 0;
char array[SIZE];
char keyword[SIZE];
FILE *fp1, *fp2;
int ch1, ch2;
errno_t result1 = fopen_s(&fp1, "c:\myFiles\resume.txt", "r");
errno_t result2 = fopen_s(&fp2, "c:\myFiles\ideal.txt", "r");
if (fp1 == NULL) {
printf("Failed to open");
}
else if (fp2 == NULL) {
printf("Failed to open");
}
else {
result1 = fread(array, sizeof(char), 1, fp1);
result2 = fread(keyword, sizeof(char), 1, fp2);
for (i = 0; i < SIZE; i++)
{
if (array[i] == keyword[i])
{
counter++;
}
}
fclose(fp1);
fclose(fp2);
printf("Character match: %d", counter);
}
system("pause");
}
当您遇到多项任务(例如阅读 2 个文件)时,提前计划很有意义。不要用读取 2 个文本文件所需的所有代码来混淆 main
的主体,而是创建一个函数来为您读取文本文件,并让它 return 一个包含文件行的数组。这确实可以帮助您专注于代码需要对行执行的操作的逻辑,而不是首先通过获取行来填充 space。现在把它全部塞进一个长main
并没有错,但是从可读性、维护和程序结构的角度来看,这让一切变得更加困难。
如果你很好地构造了读取函数,你可以将你的 main
减少到以下内容。这会将两个文本文件读入字符数组,并提供总共 4 行中读取的行数(加上检查以确保您提供了两个要读取的文件名):
int main (int argc, char **argv) {
if (argc < 3 ) {
fprintf (stderr, "error: insufficient input, usage: %s <filename1> <filename2>\n", argv[0]);
return 1;
}
size_t file1_size = 0; /* placeholders to be filled by readtxtfile */
size_t file2_size = 0; /* for general use, not needed to iterate */
/* read each file into an array of strings,
number of lines read, returned in file_size */
char **file1 = readtxtfile (argv[1], &file1_size);
char **file2 = readtxtfile (argv[2], &file2_size);
return 0;
}
到那时你就拥有了所有的数据,你可以处理你的关键字代码了。从文本文件中读取是一件非常简单的事情。您只需要熟悉可用的工具即可。读取 行 文本时,首选方法是使用 line-input 一次将整行读入缓冲区。然后你解析到缓冲区以获得你需要的东西。行输入工具是 fgets
和 getline
。阅读完该行后,您便可以使用 strtok
、strsep
或 sscanf
等工具将您想要的内容与该行分开。 fgets
和 getline
都读取每行末尾的 newline
作为其输入的一部分,因此您可能需要删除 newline
以满足您的需要。
存储读取的每一行通常是通过声明一个指向 char* 指针数组的指针来完成的。 (例如 char **file1;
)然后您为一些初始数量的指针分配内存。 (下面示例中的 NMAX
)当 n
是文件的行索引 0 - lastline
时,您可以将文件中的各个行作为 file1_array[n]
访问。如果你有一个大文件并且超过了你最初分配的指针数量,你只需使用 realloc
为你的数组重新分配额外的指针。 (您可以将 NMAX
设置为 1 以使每一行都发生这种情况)
您使用什么来分配内存以及如何重新分配内存会影响您在程序中使用数组的方式。仔细选择 calloc
来初始分配你的数组,然后在你重新分配时使用 memset
将所有未使用的指针设置为 0
(null),真的可以节省你的时间和头痛吗?为什么?因为,要遍历你的数组,你需要做的就是:
n = 0;
while (file1[n]) {
<do something with file1[n]>;
n++;
}
当您到达第一个未使用的指针(即第一个 file1[n]
即 0
)时,循环停止。
阅读文本文件时另一个非常有用的功能是strdup (char *line)
。 strdup
将使用 malloc
自动为 line
分配 space,将 line
复制到新分配的内存,并 return 指向新块的指针的记忆。这意味着你需要做的就是为每个指针分配 space 并将 getline
准备好的行复制到你的数组是:
file1[n] = strdup (line);
差不多就这些了。你已经阅读了你的文件并填充了你的数组,并且知道如何遍历数组中的每一行。剩下的就是清理并释放不再需要时分配的内存。通过确保您未使用的指针是 0
,这也很容易。您只需再次遍历 file1[n]
指针,边走边释放它们,然后在最后 free (file1)
。大功告成。
要吸收的东西很多,还有一些东西。在文件的初始读取中,如果您注意到,我们还声明了一个 file1_size = 0;
变量,并将其地址传递给读取函数:
char **file1 = readtxtfile (argv[1], &file1_size);
在readtxtfile
中,每读取一行,file1_size
地址处的值增加1
。当 readtxtfile
returns 时,file1_size
包含读取的行数。如图所示,不需要遍历 file1
数组,但您通常需要知道已阅读了多少行。
为了将所有这些放在一起,我创建了一个简短的函数示例来读取两个文本文件,打印两个文件中的行并释放与文件数组关联的内存。这个解释比我预期的要长。所以花点时间了解它是如何工作的,你将离轻松处理文本文件更近一步。下面的代码将使用 2 个文件名作为参数(例如 ./progname file1 file2
)使用类似于 gcc -Wall -Wextra -o progname srcfilename.c
:
的内容对其进行编译
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NMAX 256
char **readtxtfile (char *fn, size_t *idx);
char **realloc_char (char **p, size_t *n);
void prn_chararray (char **ca);
void free_chararray (char **ca);
int main (int argc, char **argv) {
if (argc < 3 ) {
fprintf (stderr, "error: insufficient input, usage: %s <filename1> <filename2>\n", argv[0]);
return 1;
}
size_t file1_size = 0; /* placeholders to be filled by readtxtfile */
size_t file2_size = 0; /* for general use, not needed to iterate */
/* read each file into an array of strings,
number of lines read, returned in file_size */
char **file1 = readtxtfile (argv[1], &file1_size);
char **file2 = readtxtfile (argv[2], &file2_size);
/* simple print function */
if (file1) prn_chararray (file1);
if (file2) prn_chararray (file2);
/* simple free memory function */
if (file1) free_chararray (file1);
if (file2) free_chararray (file2);
return 0;
}
char** readtxtfile (char *fn, size_t *idx)
{
if (!fn) return NULL; /* validate filename provided */
char *ln = NULL; /* NULL forces getline to allocate */
size_t n = 0; /* max chars to read (0 - no limit) */
ssize_t nchr = 0; /* number of chars actually read */
size_t nmax = NMAX; /* check for reallocation */
char **array = NULL; /* array to hold lines read */
FILE *fp = NULL; /* file pointer to open file fn */
/* open / validate file */
if (!(fp = fopen (fn, "r"))) {
fprintf (stderr, "%s() error: file open failed '%s'.", __func__, fn);
return NULL;
}
/* allocate NMAX pointers to char* */
if (!(array = calloc (NMAX, sizeof *array))) {
fprintf (stderr, "%s() error: memory allocation failed.", __func__);
return NULL;
}
/* read each line from fp - dynamicallly allocated */
while ((nchr = getline (&ln, &n, fp)) != -1)
{
/* strip newline or carriage rtn */
while (nchr > 0 && (ln[nchr-1] == '\n' || ln[nchr-1] == '\r'))
ln[--nchr] = 0;
array[*idx] = strdup (ln); /* allocate/copy ln to array */
(*idx)++; /* increment value at index */
if (*idx == nmax) /* if lines exceed nmax, reallocate */
array = realloc_char (array, &nmax);
}
if (ln) free (ln); /* free memory allocated by getline */
if (fp) fclose (fp); /* close open file descriptor */
return array;
}
/* print an array of character pointers. */
void prn_chararray (char **ca)
{
register size_t n = 0;
while (ca[n])
{
printf (" arr[%3zu] %s\n", n, ca[n]);
n++;
}
}
/* free array of char* */
void free_chararray (char **ca)
{
if (!ca) return;
register size_t n = 0;
while (ca[n])
free (ca[n++]);
free (ca);
}
/* realloc an array of pointers to strings setting memory to 0.
* reallocate an array of character arrays setting
* newly allocated memory to 0 to allow iteration
*/
char **realloc_char (char **p, size_t *n)
{
char **tmp = realloc (p, 2 * *n * sizeof *p);
if (!tmp) {
fprintf (stderr, "%s() error: reallocation failure.\n", __func__);
// return NULL;
exit (EXIT_FAILURE);
}
p = tmp;
memset (p + *n, 0, *n * sizeof *p); /* memset new ptrs 0 */
*n *= 2;
return p;
}
valgrind - 不要忘记检查泄漏
最后,无论何时在代码中分配内存,请确保使用 valgrind
之类的内存检查器来确认您没有内存错误并确认您没有内存泄漏(即您分配的块忘记释放,或者已经变得无法访问)。 valgrind
使用简单,就valgrind ./progname [any arguments]
。它可以提供丰富的信息。例如,在这个读取示例中:
$ valgrind ./bin/getline_readfile_fn voidstruct.c wii-u.txt
==14690== Memcheck, a memory error detector
==14690== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==14690== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==14690== Command: ./bin/getline_readfile_fn voidstruct.c wii-u.txt
==14690==
<snip - program output>
==14690==
==14690== HEAP SUMMARY:
==14690== in use at exit: 0 bytes in 0 blocks
==14690== total heap usage: 61 allocs, 61 frees, 6,450 bytes allocated
==14690==
==14690== All heap blocks were freed -- no leaks are possible
==14690==
==14690== For counts of detected and suppressed errors, rerun with: -v
==14690== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
特别注意以下几行:
==14690== All heap blocks were freed -- no leaks are possible
和
==14690== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
你可以忽略(suppressed: 2 from 2)
,这只是表明我没有为 libc 安装开发文件。
我试过环顾四周,但对我来说,就我学习 C 而言,文件是最难理解的东西,尤其是文本文件,二进制文件要容易一些。基本上我必须读入两个文本文件,它们都包含格式如下的单词 "hard, working,smart, works well, etc.." 我想比较文本文件并计算关键字。我会展示一些代码,但老实说,我迷路了,除此之外,我唯一失望的就是胡说八道。
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#define SIZE 1000
void resumeRater();
int main()
{
int i;
int counter = 0;
char array[SIZE];
char keyword[SIZE];
FILE *fp1, *fp2;
int ch1, ch2;
errno_t result1 = fopen_s(&fp1, "c:\myFiles\resume.txt", "r");
errno_t result2 = fopen_s(&fp2, "c:\myFiles\ideal.txt", "r");
if (fp1 == NULL) {
printf("Failed to open");
}
else if (fp2 == NULL) {
printf("Failed to open");
}
else {
result1 = fread(array, sizeof(char), 1, fp1);
result2 = fread(keyword, sizeof(char), 1, fp2);
for (i = 0; i < SIZE; i++)
{
if (array[i] == keyword[i])
{
counter++;
}
}
fclose(fp1);
fclose(fp2);
printf("Character match: %d", counter);
}
system("pause");
}
当您遇到多项任务(例如阅读 2 个文件)时,提前计划很有意义。不要用读取 2 个文本文件所需的所有代码来混淆 main
的主体,而是创建一个函数来为您读取文本文件,并让它 return 一个包含文件行的数组。这确实可以帮助您专注于代码需要对行执行的操作的逻辑,而不是首先通过获取行来填充 space。现在把它全部塞进一个长main
并没有错,但是从可读性、维护和程序结构的角度来看,这让一切变得更加困难。
如果你很好地构造了读取函数,你可以将你的 main
减少到以下内容。这会将两个文本文件读入字符数组,并提供总共 4 行中读取的行数(加上检查以确保您提供了两个要读取的文件名):
int main (int argc, char **argv) {
if (argc < 3 ) {
fprintf (stderr, "error: insufficient input, usage: %s <filename1> <filename2>\n", argv[0]);
return 1;
}
size_t file1_size = 0; /* placeholders to be filled by readtxtfile */
size_t file2_size = 0; /* for general use, not needed to iterate */
/* read each file into an array of strings,
number of lines read, returned in file_size */
char **file1 = readtxtfile (argv[1], &file1_size);
char **file2 = readtxtfile (argv[2], &file2_size);
return 0;
}
到那时你就拥有了所有的数据,你可以处理你的关键字代码了。从文本文件中读取是一件非常简单的事情。您只需要熟悉可用的工具即可。读取 行 文本时,首选方法是使用 line-input 一次将整行读入缓冲区。然后你解析到缓冲区以获得你需要的东西。行输入工具是 fgets
和 getline
。阅读完该行后,您便可以使用 strtok
、strsep
或 sscanf
等工具将您想要的内容与该行分开。 fgets
和 getline
都读取每行末尾的 newline
作为其输入的一部分,因此您可能需要删除 newline
以满足您的需要。
存储读取的每一行通常是通过声明一个指向 char* 指针数组的指针来完成的。 (例如 char **file1;
)然后您为一些初始数量的指针分配内存。 (下面示例中的 NMAX
)当 n
是文件的行索引 0 - lastline
时,您可以将文件中的各个行作为 file1_array[n]
访问。如果你有一个大文件并且超过了你最初分配的指针数量,你只需使用 realloc
为你的数组重新分配额外的指针。 (您可以将 NMAX
设置为 1 以使每一行都发生这种情况)
您使用什么来分配内存以及如何重新分配内存会影响您在程序中使用数组的方式。仔细选择 calloc
来初始分配你的数组,然后在你重新分配时使用 memset
将所有未使用的指针设置为 0
(null),真的可以节省你的时间和头痛吗?为什么?因为,要遍历你的数组,你需要做的就是:
n = 0;
while (file1[n]) {
<do something with file1[n]>;
n++;
}
当您到达第一个未使用的指针(即第一个 file1[n]
即 0
)时,循环停止。
阅读文本文件时另一个非常有用的功能是strdup (char *line)
。 strdup
将使用 malloc
自动为 line
分配 space,将 line
复制到新分配的内存,并 return 指向新块的指针的记忆。这意味着你需要做的就是为每个指针分配 space 并将 getline
准备好的行复制到你的数组是:
file1[n] = strdup (line);
差不多就这些了。你已经阅读了你的文件并填充了你的数组,并且知道如何遍历数组中的每一行。剩下的就是清理并释放不再需要时分配的内存。通过确保您未使用的指针是 0
,这也很容易。您只需再次遍历 file1[n]
指针,边走边释放它们,然后在最后 free (file1)
。大功告成。
要吸收的东西很多,还有一些东西。在文件的初始读取中,如果您注意到,我们还声明了一个 file1_size = 0;
变量,并将其地址传递给读取函数:
char **file1 = readtxtfile (argv[1], &file1_size);
在readtxtfile
中,每读取一行,file1_size
地址处的值增加1
。当 readtxtfile
returns 时,file1_size
包含读取的行数。如图所示,不需要遍历 file1
数组,但您通常需要知道已阅读了多少行。
为了将所有这些放在一起,我创建了一个简短的函数示例来读取两个文本文件,打印两个文件中的行并释放与文件数组关联的内存。这个解释比我预期的要长。所以花点时间了解它是如何工作的,你将离轻松处理文本文件更近一步。下面的代码将使用 2 个文件名作为参数(例如 ./progname file1 file2
)使用类似于 gcc -Wall -Wextra -o progname srcfilename.c
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NMAX 256
char **readtxtfile (char *fn, size_t *idx);
char **realloc_char (char **p, size_t *n);
void prn_chararray (char **ca);
void free_chararray (char **ca);
int main (int argc, char **argv) {
if (argc < 3 ) {
fprintf (stderr, "error: insufficient input, usage: %s <filename1> <filename2>\n", argv[0]);
return 1;
}
size_t file1_size = 0; /* placeholders to be filled by readtxtfile */
size_t file2_size = 0; /* for general use, not needed to iterate */
/* read each file into an array of strings,
number of lines read, returned in file_size */
char **file1 = readtxtfile (argv[1], &file1_size);
char **file2 = readtxtfile (argv[2], &file2_size);
/* simple print function */
if (file1) prn_chararray (file1);
if (file2) prn_chararray (file2);
/* simple free memory function */
if (file1) free_chararray (file1);
if (file2) free_chararray (file2);
return 0;
}
char** readtxtfile (char *fn, size_t *idx)
{
if (!fn) return NULL; /* validate filename provided */
char *ln = NULL; /* NULL forces getline to allocate */
size_t n = 0; /* max chars to read (0 - no limit) */
ssize_t nchr = 0; /* number of chars actually read */
size_t nmax = NMAX; /* check for reallocation */
char **array = NULL; /* array to hold lines read */
FILE *fp = NULL; /* file pointer to open file fn */
/* open / validate file */
if (!(fp = fopen (fn, "r"))) {
fprintf (stderr, "%s() error: file open failed '%s'.", __func__, fn);
return NULL;
}
/* allocate NMAX pointers to char* */
if (!(array = calloc (NMAX, sizeof *array))) {
fprintf (stderr, "%s() error: memory allocation failed.", __func__);
return NULL;
}
/* read each line from fp - dynamicallly allocated */
while ((nchr = getline (&ln, &n, fp)) != -1)
{
/* strip newline or carriage rtn */
while (nchr > 0 && (ln[nchr-1] == '\n' || ln[nchr-1] == '\r'))
ln[--nchr] = 0;
array[*idx] = strdup (ln); /* allocate/copy ln to array */
(*idx)++; /* increment value at index */
if (*idx == nmax) /* if lines exceed nmax, reallocate */
array = realloc_char (array, &nmax);
}
if (ln) free (ln); /* free memory allocated by getline */
if (fp) fclose (fp); /* close open file descriptor */
return array;
}
/* print an array of character pointers. */
void prn_chararray (char **ca)
{
register size_t n = 0;
while (ca[n])
{
printf (" arr[%3zu] %s\n", n, ca[n]);
n++;
}
}
/* free array of char* */
void free_chararray (char **ca)
{
if (!ca) return;
register size_t n = 0;
while (ca[n])
free (ca[n++]);
free (ca);
}
/* realloc an array of pointers to strings setting memory to 0.
* reallocate an array of character arrays setting
* newly allocated memory to 0 to allow iteration
*/
char **realloc_char (char **p, size_t *n)
{
char **tmp = realloc (p, 2 * *n * sizeof *p);
if (!tmp) {
fprintf (stderr, "%s() error: reallocation failure.\n", __func__);
// return NULL;
exit (EXIT_FAILURE);
}
p = tmp;
memset (p + *n, 0, *n * sizeof *p); /* memset new ptrs 0 */
*n *= 2;
return p;
}
valgrind - 不要忘记检查泄漏
最后,无论何时在代码中分配内存,请确保使用 valgrind
之类的内存检查器来确认您没有内存错误并确认您没有内存泄漏(即您分配的块忘记释放,或者已经变得无法访问)。 valgrind
使用简单,就valgrind ./progname [any arguments]
。它可以提供丰富的信息。例如,在这个读取示例中:
$ valgrind ./bin/getline_readfile_fn voidstruct.c wii-u.txt
==14690== Memcheck, a memory error detector
==14690== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==14690== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==14690== Command: ./bin/getline_readfile_fn voidstruct.c wii-u.txt
==14690==
<snip - program output>
==14690==
==14690== HEAP SUMMARY:
==14690== in use at exit: 0 bytes in 0 blocks
==14690== total heap usage: 61 allocs, 61 frees, 6,450 bytes allocated
==14690==
==14690== All heap blocks were freed -- no leaks are possible
==14690==
==14690== For counts of detected and suppressed errors, rerun with: -v
==14690== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
特别注意以下几行:
==14690== All heap blocks were freed -- no leaks are possible
和
==14690== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
你可以忽略(suppressed: 2 from 2)
,这只是表明我没有为 libc 安装开发文件。