[英]How to read columns from a text file and save to separate arrays in C?
為了使自己熟悉指針,在練習中,我用C語言編寫了一個簡短程序,能夠從文件中讀取文本。 我想堅持使用ANSIC。
該程序可以很好地完成其工作,但是我想繼續從文本文件中讀取列並將其保存到單獨的數組中。 有人問過類似的問題,使用strtok
或fgets
或sscanf
進行答復,但是我什么時候應該使用一個而不是另一個?
這是我的注釋代碼:
#include <stdio.h>
#include <stdlib.h>
char *read_file(char *FILE_INPUT); /*function to read file*/
int main(int argc, char **argv) {
char *string; // Pointer to a char
string = read_file("file.txt");
if (string) {
// Writes the string pointed to by string to the stream pointed to by stdout, and appends a new-line character to the output.
puts(string);
// Causes space pointed to by string to be deallocated
free(string);
}
return 0;
}
//Returns a pointer to a char,
char *read_file(char *FILE_INPUT) {
char *buffer = NULL;
int string_size, read_size;
FILE *input_stream = fopen(FILE_INPUT, "r");
//Check if file exists
if (input_stream == NULL) {
perror (FILE_INPUT);
}
else if (input_stream) {
// Seek the last byte of the file. Offset is 0 for a text file.
fseek(input_stream, 0, SEEK_END);
// Finds out the position of file pointer in the file with respect to starting of the file
// We get an idea of string_size since ftell returns the last value of the file pos
string_size = ftell(input_stream);
// sets the file position indicator for the stream to the start of the file
rewind(input_stream);
// Allocate a string that can hold it all
// malloc returns a pointer to a char, +1 to hold the NULL character
// (char*) is the cast return type, this is extra, used for humans
buffer = (char*)malloc(sizeof(char) * (string_size + 1));
// Read it all in one operation, returns the number of elements successfully read,
// Reads into buffer, up to string_size whose size is specified by sizeof(char), from the input_stream !
read_size = fgets(buffer, sizeof(char), string_size, input_stream);
// fread doesn't set it so put a \0 in the last position
// and buffer is now officially a string
buffer[string_size] = '\0';
//string_size determined by ftell should be equal to read_size from fread
if (string_size != read_size) {
// Something went wrong, throw away the memory and set
// the buffer to NULL
free(buffer);
buffer = NULL;
}
// Always remember to close the file.
fclose(input_stream);
}
return buffer;
}
如何將這種格式的文本文件中的所有列讀入數組? 列數是固定的,但是行數可以變化。
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
.
B 08902768 1060 800 Test3000
.
.
在進一步的研究中,我發現fread
用於允許程序在單個步驟中讀取和寫入大數據塊,因此單獨讀取列可能不是fread
打算執行的操作。 因此,我針對此類工作的程序實現是錯誤的。
我應該使用getc
, strtok
, sscanf
或getline
讀取此類文本文件嗎? 我試圖遵循良好的編程原則並動態分配內存。
編輯:
正確地說,我是指(但不限於)使用良好的c編程技術和動態內存分配。
我首先想到的是,以取代fread
與fgets
。 更新,感謝您的幫助,我得到了幫助。
// Allocate a string that can hold it all
// malloc returns a pointer to a char, +1 to hold the NULL character
// (char*) is the cast return type, this is extra, used for humans
buffer = (char*)malloc(sizeof(char) * (string_size + 1));
while (fgets(buffer, sizeof(char) * (string_size + 1), input_stream), input_stream)) {
printf("%s", buffer);
}
對於以上文本文件的打印:
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
B 08902768 1060 800 Test3000
我還設法使用以下命令從fgets()輸入中刪除了換行符:
strtok(buffer, "\n");
如何繼續將列保存到單獨的數組?
“最佳做法”在某種程度上是主觀的,但是“始終經過充分驗證,合乎邏輯且可讀性強”才是目標。
要讀取固定數量的字段(在您的情況下,將cols 1, 2, 5
作為未知長度的字符串值)和cols 3, 4
作為簡單的int
值,只需分配以下內容即可從文件中讀取未知數量的行:存儲一些合理預期的數據行數,跟蹤已填充的行數,然后在達到分配的存儲限制時根據需要重新分配存儲。
的處理重新分配的有效方式是通過額外存儲器塊的一些合理的數量,需要重新分配時重新分配(而不是使調用realloc
用於數據的每個附加的行)。 您可以添加固定數量的新塊,將現有塊數乘以3/2
或2
或滿足您需要的其他一些理智的方案。 通常,每次達到分配限制時,我只會將存儲空間增加一倍。
由於您有固定數量的未知大小的字段,因此可以通過簡單地用sscanf
分隔五個字段並通過檢查sscanf
返回值來驗證是否進行了5次轉換來簡化操作。 如果您正在讀取未知數量的字段,則只需使用相同的重新分配方案來處理上面討論的用於讀取未知數量行的按列擴展。
(在這種情況下,不要求任何行具有相同數量的字段,但是您可以通過設置一個變量來強制檢查,該變量包含從第一行讀取的字段數量,然后驗證所有后續行都具有相同的數量...)
正如評論中所討論的那樣,使用諸如fgets
或POSIX getline
類的面向行的輸入函數讀取一行數據,然后通過使用strtok
標記化或在這種情況下使用固定數量的字段來簡單地使用sscanf
解析來解析數據是一種扎實的方法。 它具有以下優點:允許獨立驗證(1)從文件中讀取數據; (2)將數據解析為所需的值。 (雖然靈活性較差,但對於某些數據集,您可以使用fscanf
一步完成此操作,但這還會為用戶輸入注入scanf
問題,這取決於所使用的轉換說明符 ,從而導致輸入緩沖區中尚未讀取的內容...)
存儲5字段的最簡單方法是聲明一個簡單的struct
。 由於每個字符字段的字符數是未知的,因此每個字段的結構成員將是字符指針,其余字段為int
,例如
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRSZ 2 /* use 8 or more, set to 2 here to force realloc */
#define MAXC 1024
typedef struct {
char *col1, *col2, *col5;
int col3, col4;
} mydata_t;
現在,您可以通過分配一些合理預期的數量來開始處理未知數量的分配(我一般會使用8
或16
的倍增方案,因為它將很快增長),但是我們在這里選擇了2
,使用#define ARRSZ 2
,以確保在處理3行數據文件時我們強制一次重新分配。 另請注意,我們為您的數據設置了每行 #define MAXC 1024
的最大字符數 ( 不要 #define MAXC 1024
緩沖區大小 )
首先,我們需要做的是聲明一個緩沖區來容納每一行,並聲明一些變量來跟蹤當前分配的結構數,一個行計數器(用於輸出准確的錯誤消息)和一個用於存儲行數的計數器。我們填寫的數據。 然后,當(rows_filled == allocated_array_size)
你realloc
,如
int main (int argc, char **argv) {
char buf[MAXC];
size_t arrsz = ARRSZ, line = 0, row = 0;
mydata_t *data = NULL;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* allocate an 'arrsz' initial number of struct */
if (!(data = malloc (arrsz * sizeof *data))) {
perror ("malloc-data");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line from file */
char c1[MAXC], c2[MAXC], c5[MAXC]; /* temp strings for c1,2,5 */
int c3, c4; /* temp ints for c3,4 */
size_t len = strlen (buf); /* length for validation */
line++; /* increment line count */
/* validate line fit in buffer */
if (len && buf[len-1] != '\n' && len == MAXC - 1) {
fprintf (stderr, "error: line %zu exceeds MAXC chars.\n", line);
return 1;
}
if (row == arrsz) { /* check if all pointers used */
void *tmp = realloc (data, arrsz * 2 * sizeof *data);
if (!tmp) { /* validate realloc succeeded */
perror ("realloc-data");
break; /* break, don't exit, data still valid */
}
data = tmp; /* assign realloc'ed block to data */
arrsz *= 2; /* update arrsz to reflect new allocation */
}
( 注:當調用realloc
,你永遠不realloc的指針本身 ,例如, data = realloc (data, new_size);
如果realloc
失敗(和它),它返回NULL
這將覆蓋原始的指針導致內存泄漏的始終。 realloc
用臨時指針,進行驗證,然后將新的內存塊分配給您的原始指針)
剩下的只是將行拆分為我們的值,處理行格式中的任何錯誤,將字段值添加到結構數組中,增加行/行數,然后重復進行直到我們用完要讀取的數據為止,例如
/* parse buf into fields, handle error on invalid format of line */
if (sscanf (buf, "%1023s %1023s %d %d %1023s",
c1, c2, &c3, &c4, c5) != 5) {
fprintf (stderr, "error: invalid format line %zu\n", line);
continue; /* get next line */
}
/* allocate copy strings, assign allocated blocks to pointers */
if (!(data[row].col1 = mystrdup (c1))) { /* validate copy of c1 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
if (!(data[row].col2 = mystrdup (c2))) { /* validate copy of c2 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
data[row].col3 = c3; /* assign integer values */
data[row].col4 = c4;
if (!(data[row].col5 = mystrdup (c5))) { /* validate copy of c5 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
row++; /* increment number of row pointers used */
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
puts ("values stored in struct\n");
for (size_t i = 0; i < row; i++)
printf ("%-4s %-10s %4d %4d %s\n", data[i].col1, data[i].col2,
data[i].col3, data[i].col4, data[i].col5);
freemydata (data, row);
return 0;
}
至此我們完成了(除了內存使用/錯誤檢查)
請注意,有兩個幫助器函數可為每個字符串分配並將每個字符串復制到其已分配的內存塊,並將該塊的起始地址分配給結構中的指針。 mystrdup()
如果有,可以使用strdup()
,我只是簡單地包含了向您展示如何手動處理malloc
和復制的malloc
。 注意:如何使用memcpy
而非strcpy
完成復制-為什么? 當您發現帶有strlen
的長度時,您已經向前掃描了字符串以查找'\\0'
strlen
無需strcpy
再次重復該過程-只需使用memcpy
。
/* simple implementation of strdup - in the event you don't have it */
char *mystrdup (const char *s)
{
if (!s) /* validate s not NULL */
return NULL;
size_t len = strlen (s); /* get length */
char *sdup = malloc (len + 1); /* allocate length + 1 */
if (!sdup) /* validate */
return NULL;
return memcpy (sdup, s, len + 1); /* pointer to copied string */
}
最后一個輔助函數是freemydata()
,它僅在每個分配的塊上調用free()
,以確保釋放所有已分配的內存。 它還使您的代碼保持整潔。 (您也可以對realloc
塊執行相同的操作,以將其移動到它自己的函數中)
/* simple function to free all data when done */
void freemydata (mydata_t *data, size_t n)
{
for (size_t i = 0; i < n; i++) { /* free allocated strings */
free (data[i].col1);
free (data[i].col2);
free (data[i].col5);
}
free (data); /* free structs */
}
將所有部分放在一起將為您提供:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRSZ 2 /* use 8 or more, set to 2 here to force realloc */
#define MAXC 1024
typedef struct {
char *col1, *col2, *col5;
int col3, col4;
} mydata_t;
/* simple implementation of strdup - in the event you don't have it */
char *mystrdup (const char *s)
{
if (!s) /* validate s not NULL */
return NULL;
size_t len = strlen (s); /* get length */
char *sdup = malloc (len + 1); /* allocate length + 1 */
if (!sdup) /* validate */
return NULL;
return memcpy (sdup, s, len + 1); /* pointer to copied string */
}
/* simple function to free all data when done */
void freemydata (mydata_t *data, size_t n)
{
for (size_t i = 0; i < n; i++) { /* free allocated strings */
free (data[i].col1);
free (data[i].col2);
free (data[i].col5);
}
free (data); /* free structs */
}
int main (int argc, char **argv) {
char buf[MAXC];
size_t arrsz = ARRSZ, line = 0, row = 0;
mydata_t *data = NULL;
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* allocate an 'arrsz' initial number of struct */
if (!(data = malloc (arrsz * sizeof *data))) {
perror ("malloc-data");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line from file */
char c1[MAXC], c2[MAXC], c5[MAXC]; /* temp strings for c1,2,5 */
int c3, c4; /* temp ints for c3,4 */
size_t len = strlen (buf); /* length for validation */
line++; /* increment line count */
/* validate line fit in buffer */
if (len && buf[len-1] != '\n' && len == MAXC - 1) {
fprintf (stderr, "error: line %zu exceeds MAXC chars.\n", line);
return 1;
}
if (row == arrsz) { /* check if all pointers used */
void *tmp = realloc (data, arrsz * 2 * sizeof *data);
if (!tmp) { /* validate realloc succeeded */
perror ("realloc-data");
break; /* break, don't exit, data still valid */
}
data = tmp; /* assign realloc'ed block to data */
arrsz *= 2; /* update arrsz to reflect new allocation */
}
/* parse buf into fields, handle error on invalid format of line */
if (sscanf (buf, "%1023s %1023s %d %d %1023s",
c1, c2, &c3, &c4, c5) != 5) {
fprintf (stderr, "error: invalid format line %zu\n", line);
continue; /* get next line */
}
/* allocate copy strings, assign allocated blocks to pointers */
if (!(data[row].col1 = mystrdup (c1))) { /* validate copy of c1 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
if (!(data[row].col2 = mystrdup (c2))) { /* validate copy of c2 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
data[row].col3 = c3; /* assign integer values */
data[row].col4 = c4;
if (!(data[row].col5 = mystrdup (c5))) { /* validate copy of c5 */
fprintf (stderr, "error: malloc-c1 line %zu\n", line);
break; /* same reason to break not exit */
}
row++; /* increment number of row pointers used */
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
puts ("values stored in struct\n");
for (size_t i = 0; i < row; i++)
printf ("%-4s %-10s %4d %4d %s\n", data[i].col1, data[i].col2,
data[i].col3, data[i].col4, data[i].col5);
freemydata (data, row);
return 0;
}
現在測試。
輸入文件示例
$ cat dat/fivefields.txt
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
使用/輸出示例
$ ./bin/fgets_fields <dat/fivefields.txt
values stored in struct
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
內存使用/錯誤檢查
在您編寫的任何可以動態分配內存的代碼中,對於任何分配的內存塊,您都有2個責任 :(1) 始終保留指向該內存塊起始地址的指針,因此,(2)在沒有內存塊時可以將其釋放需要更長的時間。
必須使用一個內存錯誤檢查程序來確保您不嘗試訪問內存或不要在已分配的塊的邊界之外/之外進行寫入,不要嘗試以未初始化的值讀取或基於條件跳轉,最后確定您可以釋放已分配的所有內存。
對於Linux, valgrind
是通常的選擇。 每個平台都有類似的內存檢查器。 它們都很容易使用,只需通過它運行程序即可。
$ valgrind ./bin/fgets_fields <dat/fivefields.txt
==1721== Memcheck, a memory error detector
==1721== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==1721== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==1721== Command: ./bin/fgets_fields
==1721==
values stored in struct
C 08902019 1020 50 Test1
A 08902666 1040 30 Test2
B 08902768 1060 80 Test3
==1721==
==1721== HEAP SUMMARY:
==1721== in use at exit: 0 bytes in 0 blocks
==1721== total heap usage: 11 allocs, 11 frees, 243 bytes allocated
==1721==
==1721== All heap blocks were freed -- no leaks are possible
==1721==
==1721== For counts of detected and suppressed errors, rerun with: -v
==1721== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始終確認已釋放已分配的所有內存,並且沒有內存錯誤。
仔細檢查一下,如果您還有其他問題,請告訴我。
我想繼續閱讀此文本文件的某些列。
您可以使用任何輸入函數來執行此操作: getc
, fgets
, sscanf
, getline
...,但是您必須首先准確定義特定列的含義。
,
, ;
或TAB,在這種情況下strtok()
絕對不是正確的選擇,因為它將分隔字符的所有序列都視為一個分隔符:因此, a,,b
將被視為只有2列。 strtok
, strpbrk
或strspn
/ strcspn
可以派上用場。 在任何情況下,您都可以使用fgets
逐行讀取文件,但是很長的行可能會出現問題。 getline
是一種解決方案,但可能並非在所有系統上都可用。
如果您知道什么是列分隔符以及有多少列,則可以將getline
與列分隔符一起使用,然后與行分隔符一起使用。
這是getline
:
http://man7.org/linux/man-pages/man3/getline.3.html
很好,因為它為您分配了空間,無需知道您的列或行有多少字節。
或者,您僅使用getline
作為鏈接中的代碼示例,以讀取整行,然后根據需要“解析”並提取列。
如果您確切地粘貼要如何使用輸入來運行程序,那么您將顯示出我可以嘗試編寫快速的C程序以獲得良好的答案。 現在,它只是評論樣式的答案,帶有太多要評論的詞:-(
還是無法使用圖書館?
盡管在等待更好的問題時,我會注意到您可以使用awk
從文本文件中讀取列,但這可能不是您想要的嗎? 因為您實際上想做什么?
根據數據和膽量,您可以使用scanf
或使用yacc / lex創建的解析器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.