[英]C - cannot read and process a list of strings from a text file into an array
此代码逐行读取文本文件。 但是我需要将这些行放在一个数组中,但我无法做到。 现在我以某种方式得到了一组数字。 那么如何将文件读入列表。 我尝试使用二维列表,但这也不起作用。
我是 C 的新手。我主要使用 Python,但现在我想检查 C 是否更快完成任务。
#include <stdio.h>
#include <time.h>
#include <string.h>
void loadlist(char *ptext) {
char filename[] = "Z://list.txt";
char myline[200];
FILE * pfile;
pfile = fopen (filename, "r" );
char larray[100000];
int i = 0;
while (!feof(pfile)) {
fgets(myline,200,pfile);
larray[i]= myline;
//strcpy(larray[i],myline);
i++;
//printf(myline);
}
fclose(pfile);
printf("%s \n %d \n %d \n ","while doneqa",i,strlen(larray));
printf("First larray element is: %d \n",larray[0]);
/* for loop execution */
//for( i = 10; i < 20; i = i + 1 ){
// printf(larray[i]);
//}
}
int main ()
{
time_t stime, etime;
printf("Starting of the program...\n");
time(&stime);
char *ptext = "String";
loadlist(ptext);
time(&etime);
printf("time to load: %f \n", difftime(etime, stime));
return(0);
}
此代码逐行读取文本文件。 但是我需要将这些行放在一个数组中,但我无法做到。 现在我以某种方式得到了一组数字。
您看到数字是很自然的,因为您使用"%d"
说明符打印单个字符。 实际上,c 中的字符串几乎就是数字数组,这些数字就是相应字符的 ascii 值。 如果您改为使用"%c"
您将看到代表每个数字的字符。
您的代码还对打算作为字符串数组的东西调用strlen()
, strlen()
用于计算单个字符串的长度,字符串是具有非零值的char
项数组,以a 0. 因此, strlen()
肯定会导致未定义的行为。
此外,如果您想存储每个字符串,您需要像在注释行中尝试使用strcpy()
复制数据一样,因为您用于读取行的数组在每次迭代中都会被一遍又一遍地覆盖。
您的编译器必须抛出各种警告,如果不是,则是您的错,您应该让编译器知道您希望它进行一些诊断以帮助您找到常见问题,例如将指针分配给char
。
您应该修复代码中的多个问题,这是修复其中大部分问题的代码
void
loadlist(const char *const filename) {
char line[100];
FILE *file;
// We can only read 100 lines, of
// max 99 characters each
char array[100][100];
int size;
size = 0;
file = fopen (filename, "r" );
if (file == NULL)
return;
while ((fgets(line, sizeof(line), file) != NULL) && (size < 100)) {
strcpy(array[size++], line);
}
fclose(file);
for (int i = 0 ; i < size ; ++i) {
printf("array[%d] = %s", i + 1, array[i]);
}
}
int
main(void)
{
time_t stime, etime;
printf("Starting of the program...\n");
time(&stime);
loadlist("Z:\\list.txt");
time(&etime);
printf("Time to load: %f\n", difftime(etime, stime));
return 0;
}
只是为了证明它在 c 中有多复杂,看看这个
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
struct string_list {
char **items;
size_t size;
size_t count;
};
void
string_list_print(struct string_list *list)
{
// Simply iterate through the list and
// print every item
for (size_t i = 0 ; i < list->count ; ++i) {
fprintf(stdout, "item[%zu] = %s\n", i + 1, list->items[i]);
}
}
struct string_list *
string_list_create(size_t size)
{
struct string_list *list;
// Allocate space for the list object
list = malloc(sizeof *list);
if (list == NULL) // ALWAYS check this
return NULL;
// Allocate space for the items
// (starting with `size' items)
list->items = malloc(size * sizeof *list->items);
if (list->items != NULL) {
// Update the list size because the allocation
// succeeded
list->size = size;
} else {
// Be optimistic, maybe realloc will work next time
list->size = 0;
}
// Initialize the count to 0, because
// the list is initially empty
list->count = 0;
return list;
}
int
string_list_append(struct string_list *list, const char *const string)
{
// Check if there is room for the new item
if (list->count + 1 >= list->size) {
char **items;
// Resize the array, there is no more room
items = realloc(list->items, 2 * list->size * sizeof *list->items);
if (items == NULL)
return -1;
// Now update the list
list->items = items;
list->size += list->size;
}
// Copy the string into the array we simultaneously
// increase the `count' and copy the string
list->items[list->count++] = strdup(string);
return 0;
}
void
string_list_destroy(struct string_list *const list)
{
// `free()' does work with a `NULL' argument
// so perhaps as a principle we should too
if (list == NULL)
return;
// If the `list->items' was initialized, attempt
// to free every `strdup()'ed string
if (list->items != NULL) {
for (size_t i = 0 ; i < list->count ; ++i) {
free(list->items[i]);
}
free(list->items);
}
free(list);
}
struct string_list *
loadlist(const char *const filename) {
char line[100]; // A buffer for reading lines from the file
FILE *file;
struct string_list *list;
// Create a new list, initially it has
// room for 100 strings, but it grows
// automatically if needed
list = string_list_create(100);
if (list == NULL)
return NULL;
// Attempt to open the file
file = fopen (filename, "r");
// On failure, we now have the responsibility
// to cleanup the allocated space for the string
// list
if (file == NULL) {
string_list_destroy(list);
return NULL;
}
// Read lines from the file until there are no more
while (fgets(line, sizeof(line), file) != NULL) {
char *newline;
// Remove the trainling '\n'
newline = strchr(line, '\n');
if (newline != NULL)
*newline = '\0';
// Append the string to the list
string_list_append(list, line);
}
fclose(file);
return list;
}
int
main(void)
{
time_t stime, etime;
struct string_list *list;
printf("Starting of the program...\n");
time(&stime);
list = loadlist("Z:\\list.txt");
if (list != NULL) {
string_list_print(list);
string_list_destroy(list);
}
time(&etime);
printf("Time to load: %f\n", difftime(etime, stime));
return 0;
}
现在,这几乎就像你说你写的 python 代码一样,但它肯定会更快,这是毫无疑问的。
一个实验过的 Python 程序员可能会写出一个比没有实验过的 C 程序员运行得更快的 Python 程序,但是学习 C 真的很好,因为你了解了事情的真正运作方式,然后你可以推断出 Python 的特性可能已实现,因此了解这一点实际上非常有用。
虽然它肯定比在 python 中做同样的事情复杂得多,但请注意,我在近 10 分钟内写了这个。 所以如果你真的知道你在做什么并且你真的需要它是快速的,c当然是一个选择,但是你需要学习许多高级语言程序员不清楚的概念。
有很多方法可以正确地做到这一点。 首先,首先理清您实际需要/想要存储的内容,然后弄清楚该信息的来源,最后决定您将如何为信息提供存储空间。 在您的情况下, loadlist
显然打算加载一个行列表(最多10000
),以便可以通过您静态声明的指针数组访问它们。 (您也可以动态分配指针,但如果您知道不需要超过X
指针,静态声明它们就可以了(直到您导致StackOverflow... )
阅读loadlist
的行后,您需要提供足够的存储空间来保存该行(加上空终止字符)。 否则,您只是在计算行数。 在您的情况下,由于您声明了一个指针数组,您不能简单地复制您读取的行,因为数组中的每个指针尚未指向任何已分配的内存块。 (您不能使用fgets (buffer, size, FILE*)
分配您读入行的缓冲区的地址,因为 (1) 它是您的loadlist
函数的本地地址,并且当函数堆栈帧被销毁时它将消失函数返回;和 (2)显然它会在每次调用fgets
时被覆盖。
那么该怎么办? 这也很简单,只需使用@iharob 所说的每行的strlen
为每一行分配存储空间( nul-byte +1
),然后malloc
分配一个该大小的内存块。 然后,您可以简单地将读取缓冲区复制到创建的内存块并将指针分配给您的list
(例如,代码中的larray[x]
)。 现在,gnu 扩展提供了一个strdup
函数,既可以分配也可以复制,但要了解这不是 C99 标准的一部分,因此您可能会遇到可移植性问题。 (另请注意,如果内存重叠区域是一个问题,您可以使用memcpy
,但我们现在将忽略它,因为您正在从文件中读取行)
分配内存的规则是什么? 好吧,您使用malloc
、 calloc
或realloc
分配,然后在继续之前验证您对这些函数的调用是否成功,或者您刚刚通过写入实际上并未分配给您使用的内存区域而进入了未定义行为的领域。 那看起来像什么? 如果您有指针p
数组,并且想要在索引idx
处存储长度为len
读取缓冲区buf
中的字符串,则可以简单地执行以下操作:
if ((p[idx] = malloc (len + 1))) /* allocate storage */
strcpy (p[idx], buf); /* copy buf to storage */
else
return NULL; /* handle error condition */
现在您可以按如下方式在测试前自由分配,但将分配作为测试的一部分会很方便。 长格式是:
p[idx] = malloc (len + 1); /* allocate storage */
if (p[idx] == NULL) /* validate/handle error condition */
return NULL;
strcpy (p[idx], buf); /* copy buf to storage */
你想怎么做取决于你。
现在您还需要防止读取超出指针数组末尾的内容。 (因为您静态地声明了数组,所以您只有一个固定的数字)。 您可以非常轻松地将该检查作为读取循环的一部分。 如果您已经为您拥有的指针数量声明了一个常量(例如PTRMAX
),您可以执行以下操作:
int idx = 0; /* index */
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
...
idx++;
}
通过根据可用指针的数量检查索引,您可以确保您不能尝试将地址分配给比您拥有的更多的指针。
还有一个未解决的问题,即处理将包含在读取缓冲区末尾的'\\n'
。 回想一下, fgets
读取并包括'\\n'
。 您不希望换行符悬挂在您存储的字符串的末尾,因此您只需用空终止字符覆盖'\\n'
(例如,简单的十进制0
或等效的空字符'\\0'
- 您的选择)。 您可以在strlen
调用后进行简单的测试,例如
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
size_t len = strlen (buf); /* get length */
if (buf[len-1] == '\n') /* check for trailing '\n' */
buf[--len] = 0; /* overwrite '\n' with nul-byte */
/* else { handle read of line longer than 200 chars }
*/
...
(注意:这也带来了读取比您为读取缓冲区分配的200
字符长的行的问题。您可以通过检查fgets
末尾是否包含'\\n'
来检查是否已读取完整行,如果它没有,你知道你的下一个电话fgets
将再次从同一线上阅读,除非EOF
遇到在这种情况下,您只需要。 realloc
您的存储和附加任何额外的字符到这条线上-这是留待以后讨论)
如果您将所有部分放在一起并为可以指示成功/失败的loadlist
选择返回类型,您可以执行类似于以下操作:
/** read up to PTRMAX lines from 'fp', allocate/save in 'p'.
* storage is allocated for each line read and pointer
* to allocated block is stored at 'p[x]'. (you should
* add handling of lines greater than LNMAX chars)
*/
char **loadlist (char **p, FILE *fp)
{
int idx = 0; /* index */
char buf[LNMAX] = ""; /* read buf */
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
size_t len = strlen (buf); /* get length */
if (buf[len-1] == '\n') /* check for trailing '\n' */
buf[--len] = 0; /* overwrite '\n' with nul-byte */
/* else { handle read of line longer than 200 chars }
*/
if ((p[idx] = malloc (len + 1))) /* allocate storage */
strcpy (p[idx], buf); /* copy buf to storage */
else
return NULL; /* indicate error condition in return */
idx++;
}
return p; /* return pointer to list */
}
注意:您可以轻松地将返回类型更改为int
并返回读取的行数,或者将指向int
(或更好size_t
)的指针作为参数传递,以使存储的行数在调用函数中可用。
但是,在这种情况下,我们使用了指向NULL
的指针数组中所有指针的初始化,因此在调用函数中,我们只需要遍历指针数组,直到遇到第一个NULL
以遍历我们的行列表. 将一个简短的示例程序放在一起,该程序从作为程序的第一个参数给出的文件名中读取/存储所有行(最多PTRMAX
行)(如果没有给出文件名,则从stdin
中读取/存储),您可以执行类似于:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
enum { LNMAX = 200, PTRMAX = 10000 };
char **loadlist (char **p, FILE *fp);
int main (int argc, char **argv) {
time_t stime, etime;
char *list[PTRMAX] = { NULL }; /* array of ptrs initialized NULL */
size_t n = 0;
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
printf ("Starting of the program...\n");
time (&stime);
if (loadlist (list, fp)) { /* read lines from fp into list */
time (&etime);
printf("time to load: %f\n\n", difftime (etime, stime));
}
else {
fprintf (stderr, "error: loadlist failed.\n");
return 1;
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
while (list[n]) { /* output stored lines and free allocated mem */
printf ("line[%5zu]: %s\n", n, list[n]);
free (list[n++]);
}
return(0);
}
/** read up to PTRMAX lines from 'fp', allocate/save in 'p'.
* storage is allocated for each line read and pointer
* to allocated block is stored at 'p[x]'. (you should
* add handling of lines greater than LNMAX chars)
*/
char **loadlist (char **p, FILE *fp)
{
int idx = 0; /* index */
char buf[LNMAX] = ""; /* read buf */
while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
size_t len = strlen (buf); /* get length */
if (buf[len-1] == '\n') /* check for trailing '\n' */
buf[--len] = 0; /* overwrite '\n' with nul-byte */
/* else { handle read of line longer than 200 chars }
*/
if ((p[idx] = malloc (len + 1))) /* allocate storage */
strcpy (p[idx], buf); /* copy buf to storage */
else
return NULL; /* indicate error condition in return */
idx++;
}
return p; /* return pointer to list */
}
最后,在任何代码你写的是动态分配的内存,您有任何关于分配的内存任何块2个职责:(1)始终保持一个指针的起始地址的存储器中,以便块,(2),它可以被释放时,不再需要。
使用内存错误检查程序确保您没有在分配的内存块之外/之外写入,尝试读取或基于未初始化的值进行跳转,并最终确认您已释放所有分配的内存。
对于 Linux valgrind
是正常的选择。 每个平台都有类似的内存检查器。 它们都易于使用,只需通过它运行您的程序即可。
仔细看看,如果您有任何其他问题,请告诉我。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.