使用 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(如果遇到缓冲区末尾)的函数。但这取决于您的具体需求。
尝试使用 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(如果遇到缓冲区末尾)的函数。但这取决于您的具体需求。