如何在二维数组(多维数组)中使用 fgets()?
How to use fgets() in 2d-arrays (multiple dimension arrays)?
#include <stdio.h>
#include <stdlib.h>
void *salloc(int x){
char **pointer;
int i;
pointer = malloc(sizeof(char)*x);
if(pointer == NULL){
exit(-1);
}
for(i=0; i<x; i++){
pointer[i] = malloc(sizeof(char) * 20);
if(pointer[i] == NULL){
exit(-1);
}
}
return pointer;
}
void Input(int value, char **array){
for(i = 0; i < value; i++){
printf("%d ----\n", i);
fgets(array[i], 20, stdin);
printf("%d ----\n", i);
}
}
int main(int argc, char *argv[]){
char **array;
int value = 2;
array = salloc(value);
Input(value, array);
return 0;
}
大概的意思,可能是我漏掉了一些语法。
所以我想读入一个带空格的字符串。如果我 运行 这对于 value 2,它将打印:
0 ----
0 ----
1 ----
"some string"
按回车后它崩溃了。
如果我使用 value 1 执行此操作:
它立即崩溃。
但是,如果我将 fgets() 替换为:
scanf("%s", array[i]);
有效(空格除外)。
那么 fgets() 如何在二维数组中工作?
因为我让它在一维数组中工作。出于某种原因,当数组只有 2 行时,我可以从第 2 行打印一维数组,所以它应该只能从第 0 行和第 1 行打印,对吗?
这是一个演示程序,展示了如何将 fgets
用于二维数组。
#include <stdio.h>
#define N 5
#define M 10
int main( void )
{
char lines[N][M];
size_t n = 0;
while( n < N && fgets( lines[n], sizeof( *lines ), stdin ) != NULL ) ++n;
for ( size_t i = 0; i < n; i++ ) puts( lines[i] );
return 0;
}
如果输入例如
One
Two
Three
Four
Five
那么程序输出将是相同的
One
Two
Three
Four
Five
当你这样做时
pointer = malloc(sizeof(char)*x);
你只分配 x
个字符 (即字节),而不是指向字符的指针。改为
pointer = malloc(sizeof(char*)*x);
如果不进行更改,您可能会越界并出现 未定义的行为。这正是您的代码中发生的情况,您只分配 两个字节 来存储两个指针,而单个指针是四个或八个字节,因此您没有分配足够的内存甚至一个指针。
未定义的行为是导致崩溃的常见原因,但有时它也似乎起作用。
还请注意,当使用 fgets
(或任何 面向行的 输入函数)进行输入时,fgets
将最多读取,并且 include,读取每行末尾的'\n'
。您应该执行 2 个额外的 tests/operations。 (1) 您应该测试 fgets
读取的最后一个字符实际上是 '\n'
字符。如果不是,则表示您的输入被 fgets
截断为您在 fgets
的第二个参数中指定的长度,并且该行的其他字符仍未读。如果没有这个检查和一些处理超过指定宽度的行的方法,你下次调用 fgets
将读取 current 行的剩余字符作为你的 下一个行输入。
(2) 您应该删除 fgets
包含的换行符,以防止您的字符串在末尾包含嵌入的 '\n'
字符。 (如果您只是简单地解析行中的数字而不是将其存储为字符串,则可以通过几种不同的方式处理)。但是,对于一般情况,您使用 strlen
定位字符串的结尾,然后用 nul-terminating 字符覆盖 '\n'
。
除上述之外,仅在读取一行数据后才为 array
中的每个单独指针分配内存可能更有意义,以防止在您的 space 中过度分配代码。由于您指定每个字符串的分配大小为 20
,因此可以使用该大小的简单字符缓冲区来接收输入,并且在确认输入后,您可以在 array
中为该行分配存储空间].您的函数的一个简短示例包含检查和包含在 input
中的分配将是:
#define MAXC 20 /* max chars per read */
...
void Input (int value, char **array)
{
int i = 0;
size_t len = 0;
char buf[MAXC] = {0};
while (i < value && fgets (buf, MAXC, stdin)) {
printf ("%d ----\n", i);
len = strlen (buf); /* get length */
if (len + 1 == MAXC && buf[len-1] != '\n') /* validate read */
fprintf (stderr, "warning: chars exceed MAXC, line[%d]\n", i);
else
buf[--len] = 0; /* strip '\n' */
printf ("%d ----\n", i);
array[i++] = strdup (buf); /* allocate/copy */
}
}
最后,为什么选择void
作为功能类型?如果值成功读入数组,为什么不 return 读入 array
的值的数量,否则 0
。这将至少允许一些指示读取成功或失败,并提供一种方法 returning 分配回调用函数的行数。
您的代码包含调整后的分配、必要的检查和有用的 return 类型的示例是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 20
void *salloc (int x)
{
char **pointer;
pointer = malloc (sizeof *pointer * x);
if (pointer == NULL)
exit(-1);
return pointer;
}
int input (int value, char **array)
{
int i = 0;
size_t len = 0;
char buf[MAXC] = {0};
while (i < value && fgets (buf, MAXC, stdin)) {
printf ("%d ----\n", i);
len = strlen (buf); /* get length */
if (len + 1 == MAXC && buf[len-1] != '\n') /* validate read */
fprintf (stderr, "warning: chars exceed MAXC, line[%d]\n", i);
else
buf[--len] = 0; /* strip '\n' */
printf ("%d ----\n", i);
array[i++] = strdup (buf); /* allocate/copy */
}
return i;
}
int main (int argc, char *argv[]) {
int i, nlines, value = argc > 1 ? atoi (argv[1]) : 2;
char **array;
array = salloc (value);
if (!(nlines = input (value, array))) /* validate input */
return 1;
for (i = 0; i < nlines; i++) /* print input */
printf (" array[%2d] : %s\n", i, array[i]);
for (i = 0; i < nlines; i++) /* free memory */
free (array[i]);
free (array);
return 0;
}
测试输入文件
以下是第 1 行(第 2 行)超过 20 个字符的测试输入文件:
$ cat dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
示例输出
$ ./bin/readarray 10 <../dat/captnjack.txt
0 ----
0 ----
1 ----
warning: chars exceed MAXC, line[1]
1 ----
2 ----
2 ----
3 ----
3 ----
array[ 0] : This is a tale
array[ 1] : Of Captain Jack Spa
array[ 2] : rrow
array[ 3] : A Pirate So Brave
array[ 4] : On the Seven Seas.
不要忘记使用像 valgrind
这样的 内存错误检查 程序来验证您对您分配的内存的使用,并确保您在分配内存时已释放它不再需要。如果您还有其他问题,请告诉我。
#include <stdio.h>
#include <stdlib.h>
void *salloc(int x){
char **pointer;
int i;
pointer = malloc(sizeof(char)*x);
if(pointer == NULL){
exit(-1);
}
for(i=0; i<x; i++){
pointer[i] = malloc(sizeof(char) * 20);
if(pointer[i] == NULL){
exit(-1);
}
}
return pointer;
}
void Input(int value, char **array){
for(i = 0; i < value; i++){
printf("%d ----\n", i);
fgets(array[i], 20, stdin);
printf("%d ----\n", i);
}
}
int main(int argc, char *argv[]){
char **array;
int value = 2;
array = salloc(value);
Input(value, array);
return 0;
}
大概的意思,可能是我漏掉了一些语法。 所以我想读入一个带空格的字符串。如果我 运行 这对于 value 2,它将打印:
0 ----
0 ----
1 ----
"some string"
按回车后它崩溃了。 如果我使用 value 1 执行此操作: 它立即崩溃。 但是,如果我将 fgets() 替换为:
scanf("%s", array[i]);
有效(空格除外)。
那么 fgets() 如何在二维数组中工作?
因为我让它在一维数组中工作。出于某种原因,当数组只有 2 行时,我可以从第 2 行打印一维数组,所以它应该只能从第 0 行和第 1 行打印,对吗?
这是一个演示程序,展示了如何将 fgets
用于二维数组。
#include <stdio.h>
#define N 5
#define M 10
int main( void )
{
char lines[N][M];
size_t n = 0;
while( n < N && fgets( lines[n], sizeof( *lines ), stdin ) != NULL ) ++n;
for ( size_t i = 0; i < n; i++ ) puts( lines[i] );
return 0;
}
如果输入例如
One
Two
Three
Four
Five
那么程序输出将是相同的
One
Two
Three
Four
Five
当你这样做时
pointer = malloc(sizeof(char)*x);
你只分配 x
个字符 (即字节),而不是指向字符的指针。改为
pointer = malloc(sizeof(char*)*x);
如果不进行更改,您可能会越界并出现 未定义的行为。这正是您的代码中发生的情况,您只分配 两个字节 来存储两个指针,而单个指针是四个或八个字节,因此您没有分配足够的内存甚至一个指针。
未定义的行为是导致崩溃的常见原因,但有时它也似乎起作用。
还请注意,当使用 fgets
(或任何 面向行的 输入函数)进行输入时,fgets
将最多读取,并且 include,读取每行末尾的'\n'
。您应该执行 2 个额外的 tests/operations。 (1) 您应该测试 fgets
读取的最后一个字符实际上是 '\n'
字符。如果不是,则表示您的输入被 fgets
截断为您在 fgets
的第二个参数中指定的长度,并且该行的其他字符仍未读。如果没有这个检查和一些处理超过指定宽度的行的方法,你下次调用 fgets
将读取 current 行的剩余字符作为你的 下一个行输入。
(2) 您应该删除 fgets
包含的换行符,以防止您的字符串在末尾包含嵌入的 '\n'
字符。 (如果您只是简单地解析行中的数字而不是将其存储为字符串,则可以通过几种不同的方式处理)。但是,对于一般情况,您使用 strlen
定位字符串的结尾,然后用 nul-terminating 字符覆盖 '\n'
。
除上述之外,仅在读取一行数据后才为 array
中的每个单独指针分配内存可能更有意义,以防止在您的 space 中过度分配代码。由于您指定每个字符串的分配大小为 20
,因此可以使用该大小的简单字符缓冲区来接收输入,并且在确认输入后,您可以在 array
中为该行分配存储空间].您的函数的一个简短示例包含检查和包含在 input
中的分配将是:
#define MAXC 20 /* max chars per read */
...
void Input (int value, char **array)
{
int i = 0;
size_t len = 0;
char buf[MAXC] = {0};
while (i < value && fgets (buf, MAXC, stdin)) {
printf ("%d ----\n", i);
len = strlen (buf); /* get length */
if (len + 1 == MAXC && buf[len-1] != '\n') /* validate read */
fprintf (stderr, "warning: chars exceed MAXC, line[%d]\n", i);
else
buf[--len] = 0; /* strip '\n' */
printf ("%d ----\n", i);
array[i++] = strdup (buf); /* allocate/copy */
}
}
最后,为什么选择void
作为功能类型?如果值成功读入数组,为什么不 return 读入 array
的值的数量,否则 0
。这将至少允许一些指示读取成功或失败,并提供一种方法 returning 分配回调用函数的行数。
您的代码包含调整后的分配、必要的检查和有用的 return 类型的示例是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 20
void *salloc (int x)
{
char **pointer;
pointer = malloc (sizeof *pointer * x);
if (pointer == NULL)
exit(-1);
return pointer;
}
int input (int value, char **array)
{
int i = 0;
size_t len = 0;
char buf[MAXC] = {0};
while (i < value && fgets (buf, MAXC, stdin)) {
printf ("%d ----\n", i);
len = strlen (buf); /* get length */
if (len + 1 == MAXC && buf[len-1] != '\n') /* validate read */
fprintf (stderr, "warning: chars exceed MAXC, line[%d]\n", i);
else
buf[--len] = 0; /* strip '\n' */
printf ("%d ----\n", i);
array[i++] = strdup (buf); /* allocate/copy */
}
return i;
}
int main (int argc, char *argv[]) {
int i, nlines, value = argc > 1 ? atoi (argv[1]) : 2;
char **array;
array = salloc (value);
if (!(nlines = input (value, array))) /* validate input */
return 1;
for (i = 0; i < nlines; i++) /* print input */
printf (" array[%2d] : %s\n", i, array[i]);
for (i = 0; i < nlines; i++) /* free memory */
free (array[i]);
free (array);
return 0;
}
测试输入文件
以下是第 1 行(第 2 行)超过 20 个字符的测试输入文件:
$ cat dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
示例输出
$ ./bin/readarray 10 <../dat/captnjack.txt
0 ----
0 ----
1 ----
warning: chars exceed MAXC, line[1]
1 ----
2 ----
2 ----
3 ----
3 ----
array[ 0] : This is a tale
array[ 1] : Of Captain Jack Spa
array[ 2] : rrow
array[ 3] : A Pirate So Brave
array[ 4] : On the Seven Seas.
不要忘记使用像 valgrind
这样的 内存错误检查 程序来验证您对您分配的内存的使用,并确保您在分配内存时已释放它不再需要。如果您还有其他问题,请告诉我。