使用 strtok 的 C 标记化正在打印出意外的值并且阻碍了我的 strtol 验证

C tokenising using strtok is printing out unexpected values and is hindering my strtol validation

尝试使用 strtok 进行标记化,输入文件是

InputVector:0(0,3,4,2,40)

试图输入数字,但我遇到了一些我不理解的意外情况,我的标记化代码如下所示。

    #define INV_DELIM1 ":"
    #define INV_DELIM2 "("
    #define INV_DELIM3 ",)"

    checkBuff = fgets(buff, sizeof(buff), (FILE*)file);

    if(checkBuff == NULL)
    {
        printf("fgets failure\n");
        return FALSE;
    }
    else if(buff[strlen(buff) - 1] != '\n')
    {
        printf("InputVector String too big or didn't end with a new line\n");
        return FALSE;
    }
    else 
    {
        buff[strlen(buff) - 1] = '[=11=]';
    }

    token = strtok(buff, INV_DELIM1);
    printf("token %s", token);
    token = strtok(buff, INV_DELIM2);
    printf("token %s", token);

    while(token != NULL) {
            token = strtok(NULL, INV_DELIM3);
            printf("token %s\n", token);
            if(token != NULL) {
                number = strtol(token, &endptr, 10);
                if((token == endptr || *endptr != '[=11=]')) {
                    printf("A token is Not a number\n");
                    return FALSE;
                }
                else {
                    vector[i] = number;
                    i++;
                }
            }
        }

输出:

token InputVector
token 0
token 0
token 3
token 4
token 2
token 40
token

因此代码首先调用 fgets 并检查它是否不大于我的缓冲区的长度,如果不是,则将最后一个字符替换为“\0”。

然后我标记了第一个单词,以及括号外的数字。 while 循环标记括号内的数字并使用 strtol 更改它们并将其放入数组中。我正在尝试使用 strtol 来检测括号内的数据类型是否为数字,但它始终会检测到错误,因为 strtok 会读取不在输入中的最后一个标记。我如何摆脱被读取的最后一个标记,以便我的 strtol 不接收它?或者有没有更好的方法来标记和检查括号内的值?

输入文件稍后将包含多个输入向量,我必须能够检查它们是否有效。

当您首先使用函数 strtok() 时,您将在分隔符“:”中拆分字符串,在“(”之后。例如句子

 InputVector:0(0,3,4,2,40)

当您应用 strtok(buffer,":") 时,您会得到唯一的第一个结果 InputVector。您必须再次申请 strtok(NULL,":") 才能获得剩余的拆分 0(0,3,4,2,40)。您不能对同一个缓冲区应用不同的定界符,也不能在同一个缓冲区中再次应用 strtok,因为 C split 在每个标记的末尾放置了一个 NULL 并且您将或丢失引用,或者只是应用 strtok int 字符串的第一部分。拆分这句话的最佳方法是使用所有分隔符 :(),,这将像这样拆分所有句子:

InputVector
0
0
3
4
2
40

您需要做的更改是

#define INV_DELIM1 ":(),\n"
token = strtok(buff,INV_DELIM1); //for the first call of strtok
token = strtok(NULL,INV_DELIM1); //for the rest of strtok call

最可能的解释是您的输入行以 Windows 换行序列 \r\n 结尾。如果您的程序在 unix(或 linux)上运行,并且您在 Windows 上键入输入,Windows 将发送两个字符的换行序列,但 Unix 程序不会知道它需要做行尾 t运行slation。 (如果您直接在 Windows 系统上 运行 程序,标准 I/O 库会为您处理换行符序列,通过 t运行 将其转换为单个 \n, 只要你不以二进制模式打开文件即可。)

由于 \r 不在您的分隔符列表中,strtok 会将其视为普通字符,因此您的最后一个字段将由 \r 组成。将其打印出来并不是什么空操作,但它是不可见的,因此很容易误以为正在打印一个空字段。 (如果该字段仅由空格组成,也会发生同样的情况。)

您可以将 \r 添加到分隔符列表中。实际上,您可以将 \n\r 添加到 strtok 调用的定界符列表中,这样您就不必担心修剪输入行。这将起作用,因为 strtok 将任何定界字符序列视为单个定界符。

但是,这可能不是您真正想要的,因为这会隐藏某些输入错误。例如,如果输入有两个连续的逗号,strtok 会将它们视为单个逗号,您永远不会知道该字段已被跳过。您可以使用 strspn 而不是 strtok 来解决该特定问题,但我个人认为更好的解决方案是根本不使用 strtok,因为 strtol 会告诉您在哪里行结束。

例如。 (为简单起见,我省略了错误消息的打印。没有必要在这段代码之前检查该行是否以换行符结尾;如果您觉得有必要进行该检查,则可以在找到右括号后进行检查循环结束。):

#include <ctype.h>     /* For 'isspace' */
#include <stdbool.h>   /* For 'false'   */
#include <stdlib.h>    /* For 'strtol'  */
#include <string.h>    /* For 'strchr'  */

// ...

char* token = strchr(buff, ':');          /* Find the colon */
if (token == NULL) return false;          /* No colon */
++token;                                  /* Character after the token */
char* endptr;
(void)strtol(token, &endptr, 10);         /* Read and toss away a number */
if (endptr == token) return false;        /* No number */
token = endptr;                           /* Character following number */
while (isspace(*token)) ++token;          /* Skip spaces (maybe not necessary) */
if (*token != '(') return false;          /* Wrong delimiter */
for (i = 0; i < n_vector; ++i) {          /* Loop until vector is full or ')' is found */
  ++token;
  vector[i] = strtol(token, &endptr, 10); /* Get another number */
  if (endptr == token) return false;      /* No number */
  token = endptr;                         /* Character following number */
  while (isspace(*token)) ++token;        /* Skip spaces */
  if (*token == ')') break;               /* Found the close parenthesis */
  if (*token != ',') return false;        /* Not the right delimiter */
}                                         /* Loop */
/* At this point, either we found the ')' or we read too many numbers */
if (*token != ')') return false;          /* Too many numbers */
/* Could check to make sure the following characters are a newline sequence */
/* ... */

调用strtol获取数字然后检查分隔符是什么的代码应该重构,但为了简单起见我就这样写了。我通常会使用一个读取数字和 returns 定界符(与 getchar() 一样)或 EOF(如果遇到缓冲区末尾)的函数。但这取决于您的具体需求。