使用strtok()将数值存储在2D数组中

时间:2017-11-15 21:43:32

标签: c strtok

我正在尝试编写一个程序,该程序从文件中获取数字列表,并使用strtok()将每行数字存储在2D数组中。我遇到的问题是,当我尝试将值存储在2D数组的行中时,while循环进入无限循环,但是当我使用行token = strtok时(NULL,"& #34;)停止循环,我只是将每行的第一个值存储到每列的第一个单元格中。

我做错了什么?继承人是我的代码

int main() {
  int myArray[240][30];
  char line[240];
  char *token;
  int i, n, j;
  FILE *fp;
  fp = fopen("array_list.txt", "rt");
  j = 0;
  i = 0;
  while (fgets(line, sizeof(line), fp)) {
    token = strtok(line, " ");

    while (token != NULL) {
      n = atoi(token);
      myArray[i][j] = n;
      token = strtok(line, " ");
      printf("%d ", myArray[i][j]);
      token = strtok(NULL, " ");
      j++;
    }

    printf("\n");
    i++;
  }
  return 0;
}

以下是我试图阅读的文件示例。

1 98 48 55 46 12 48 43 7 73 47 33
                                                                                                                                                                    97 26 67 25 67 47 46 74 67 43 78 98 90 58 6 5 53 19 37 42
81 84 36 70 59 65 99 56 94 23 79 73 27 47 98 78 8 2 3 43 84 19 59 98 51 72 2 13 55 3 20 61 25 84 28 70 56 23 52 69 86 70 60 90 72 51 71 92 91 55 6 72 67 73 64 78 43 65 30 49 99 82 40 72 5 44 20 21 22 72 99 74 27 59 25 1 26 26 22 66 63 27 54 46 46 91 65 77 45 94 94 95 11 77 46 64 14 69 70 75 48 68 22 55 78 1 4 31 76 55 12 93

3 个答案:

答案 0 :(得分:3)

这里有很多问题。

首先j=0必须在fgets之后完成,否则永远不会重置。

其次,最大的错误是当你执行strtok(line," ")时,你正在初始化strtok状态机,你必须使用NULL作为第一个参数循环,直到strtok返回NULL,但你没有这样做:你正在循环两个电话。作为改进,将多个选项作为分隔符传递:换行,制表和空格。

所以第二次字符串已经被标记化并且循环结束,但只有第一个项目。

此外,您必须为每一行存储j的值,否则您将忘记每行上有多少元素(因为它是变量)。在下面的代码中,我使用第一个位置来存储大小,然后是项目。

我的修正提案(经过测试并适用于您的数据):

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

int main(){
    const char *spaces = "\n\t ";
    int myArray[240][30];
    char line[240];
    char *token;
    int i, n, j;
    FILE *fp;
    fp=fopen("array_list.txt", "rt");
    i = 0;
    while(fgets(line, sizeof(line), fp))
    {
        j = 1;
        token = strtok(line, spaces);  // initialize line tokens
        while(token)  // won't enter if the line is empty, and stops when no more tokens
        {
            n = atoi(token);  // strtol + error checking would be better
            myArray[i][j] = n;
            j++;
            printf("%d ", myArray[i][j]);
            token = strtok(NULL, spaces);  // next token
        }
        myArray[i][0] = j;  // use first array position to store the size

        printf("\n");
        i++;
    }
    return 0;
}

该计划仍然缺少一些内容:

  • 防止“数组元素过多”。如果文本文件中包含太多元素(一行中有太多行/太多项),则可能会损坏您的内存。查看i&amp; j&amp;如果超过限制就会中止。
  • atoi没有错误检查,因此输入“whatever”会导致0值。 strtol允许错误检查。

答案 1 :(得分:2)

虽然您可以使用strtok拆分文件中的字词,然后使用atoi执行转换为int,但这种方法存在明显的弊端。首先,atoi(以及所有atoX函数)绝对无法验证您是否确实将atoi(word)转换为数字。 (真的是0吗? - 或转换是否失败?)

此外,strtok + atoistrtol为您所做的一部分。 long strtol (const char *nptr, char **endptr, int base)为您提供的其余内容通过errno提供广泛的错误检查,并在下溢LONG_MIN上返回LONG_MAX的范围检查< EM>溢出。它通过在转换期间未找到任何数字的情况下设置endptr = nptr来提供进一步检查。更好的是,当转换成功时,endptr被设置为转换的最后一个字符后面的下一个字符。 (它让你下次阅读!)

此外,您可以轻松地测试是否应该再次调用strtol之前跳过下一个字符,从而允许您解析文件中的数字,而不管文件中包含哪些内容。换句话说,您可以编写一次整数读取例程,然后使用它进行最少的修改,以便读取您给出的任何文件。

虽然您没有指定如何从您为文件显示的144值中填充myArray[240][30];,但以下示例显示了如何将这些值(以及几乎任何文件中的值)读取为阵列。您只需要添加一个额外的计数器或循环来在数组中按行添加x个值。

使用各种标题文件中提供的常量(例如errno.hlimits.h)来帮助您完成任务。以下示例从命令行上给出的文件名中读取(如果没有给出文件名,则默认来自stdin),并将文件中找到的任何整数读取到数组arr中,最大值为1024值,例如

#include <stdio.h>
#include <stdlib.h>     /* for strtol */
#include <string.h>     /* for strlen */
#include <limits.h>     /* for INT_MIN, INT_MAX */
#include <errno.h>      /* for errno */

#ifndef BUF_SIZ         /* set input buffer size */
#define BUF_SIZ 8192    /* gcc default, cl.exe (VS) uses 512 */
#endif

#define BASE 10         /* conversion base 10 */
#define AMAX 1024       /* max number of values to read */

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

    int arr[AMAX] = {0},            /* array to hold values */
        n = 0;                      /* number of values read */
    char buf[BUF_SIZ] = "";         /* line buffer */
    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;
    }

    while (fgets (buf, BUF_SIZ, fp)) {      /* read each line */
        char *p = buf,                      /* pointer to buf */
            *ep = p;                        /* end pointer for strtol */
        size_t len = strlen (buf);          /* length of buf */

        /* validate complete line read */
        if (len == BUF_SIZ - 1 && buf[len - 1] != '\n') {
            fprintf (stderr, "error: line too long.\n");
            break;
        }

        while (n < AMAX && *p) {    /* n < AMAX & each starting char */
            errno = 0;                          /* reset errno */
            long tmp = strtol (p, &ep, BASE);   /* convert to long */

            /* validate conversion */
            if (p != ep) {                  /* digits converted */
                /* if no error and in range - assign to array */
                if (!errno && INT_MIN <= tmp && tmp <= INT_MAX)
                    arr[n++] = (int)tmp;
                p = ep;     /* set p to 1-past converted string */
            }

            /* find beginning of next number in buf */
            for (; *p; p++) {
                if ('0' <= *p && *p <= '9')  /* positive value */
                    break;          /* explicitly signed value */
                if ((*p == '+' || *p == '-') && '0' <= *(p+1) && *(p+1) <= '9')
                    break;
            }
        }
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (int i = 0; i < n; i++)     /* output values read */
        printf (" arr[%3d] = %d\n", i, arr[i]);

    return 0;
}

示例输入文件

这样,您几乎可以读取包含整数值的任何文件。以下文件都包含相同的10个整数值。读取不需要POSIX EOF来读取文件中的最后一个值成功(例如,在最后一行之后的'\n')。下面的每个文件都被上面的代码读取并正确解析 - 无需修改。

$ cat dat/10int_comma.txt
8572,-2213,6434,16330,3034,12346,4855,16985,11250,1495

$ cat dat/10int.csv
8572, -2213, 6434, 16330, 3034
12346, 4855, 16985, 11250, 1495

$ cat dat/10int_5x2.txt
[[  8572  -2213  ]
 [  6434  16330  ]
 [  3034  12346  ]
 [  4855  16985  ]
 [ 11250   1495  ]]

$ cat dat/10intmess.txt
8572,;a -2213,;--a 6434,;
a- 16330,;a

- The Quick
Brown%3034 Fox
12346Jumps Over
A
4855,;*;Lazy 16985/,;a
Dog.
11250
1495

示例使用/输出

通过读取上述每个文件产生的输出是相同的。

$ ./bin/fgets_strtol <dat/10intmess.txt
 arr[  0] = 8572
 arr[  1] = -2213
 arr[  2] = 6434
 arr[  3] = 16330
 arr[  4] = 3034
 arr[  5] = 12346
 arr[  6] = 4855
 arr[  7] = 16985
 arr[  8] = 11250
 arr[  9] = 1495

虽然您可以自由使用strtokatoi,但C库在strtol中为该作业提供了一个工具(以及其余{{1}中的所有不同数字类型}} 功能)。仔细看看,如果您有其他问题,请告诉我。

答案 2 :(得分:0)

我认为OP已经给出了一些巧妙的技巧。

看起来像6行输入可能是其他东西。虽然我编辑了OP的帖子,但我在编辑器中远离"array_list.txt"数据部分,出现,在最后一个数字后面有空行,空行,也许是一行超过30个数字。

换句话说,"array_list.txt"的格式可能不如首次考虑,代码需要采取防御措施以避免过度运行等。

设置常量

魔法数字240,30,240几乎没有意义,因为2个数字是相同的值,代码中的角色可能会变得混乱 - 使用常量。

//int myArray[240][30];
//char line[240];
#define LINE_N 240
#define INT_PER_LINE_N 30

// I think this should be more like (INT_PER_LINE_N * 13)
#define LINE_SIZE 240

int myArray[LINE_N][INT_PER_LINE_N];
char line[LINE_SIZE];

首选strtol() }

strtok()很好寻找代币。代码提供了一个像strtok()这样的分隔符,生活很美好。然而,如何处理非分隔符,非数字符号? " \n\t"可以很好地处理整数。

strtol()