C++ c++;strtok跳过第二个标记或连续分隔符
我正在尝试读取CSV逗号分隔文件,文件内容如下C++ c++;strtok跳过第二个标记或连续分隔符,c++,c,C++,C,我正在尝试读取CSV逗号分隔文件,文件内容如下 one,,three 读取文件的代码是 inFile.getline(line, 500); token1 = strtok(line, ","); token2 = strtok(NULL, ","); token3 = strtok(NULL, ","); if(token1 != NULL){
one,,three
读取文件的代码是
inFile.getline(line, 500);
token1 = strtok(line, ",");
token2 = strtok(NULL, ",");
token3 = strtok(NULL, ",");
if(token1 != NULL){
cout << "token1 = " << token1 << "\n";
}else{
cout << "token1 = null\n" ;
}
if(token2 != NULL){
cout << "token2 = " << token2 << "\n";
}else{
cout << "token2 = null\n" ;
}
if(token3 != NULL){
cout << "token3 = " << token3 << "\n";
}else{
cout << "token3 = null\n";
}
而我的期望是输出应该是这样的
token1 = one
token2 = null
token3 = three
我确实改变了如果来自
if(token1 != NULL)
到
但它也不起作用
在检查了这个示例之后,我更新了
token2 = strtok(NULL, ",");
到
此外,它也不适用于标准(C99,从C++11中引用以获取兼容性功能):
序列中的第一个调用在s1指向的字符串中搜索s2指向的当前分隔符字符串中未包含的第一个字符
随后的每个调用(以空指针作为第一个参数的值)都会从保存的指针开始搜索,其行为如上所述
这意味着,在查找第二个标记时,它首先跳过与分隔符字符串中的任何字符匹配的所有字符。因此,,
被视为输入字符串中的单个分隔符
如果您希望令牌服务器的工作方式与标准令牌服务器的工作方式不同,那么很遗憾,您必须查看其他地方,例如下面的代码:
#include <string.h>
char *paxtok (char *str, char *seps) {
static char *tpos, *tkn, *pos = NULL;
static char savech;
// Specific actions for first and subsequent calls.
if (str != NULL) {
// First call, set pointer.
pos = str;
savech = 'x';
} else {
// Subsequent calls, check we've done first.
if (pos == NULL)
return NULL;
// Then put character back and advance.
while (*pos != '\0')
pos++;
*pos++ = savech;
}
// Detect previous end of string.
if (savech == '\0')
return NULL;
// Now we have pos pointing to first character.
// Find first separator or nul.
tpos = pos;
while (*tpos != '\0') {
tkn = strchr (seps, *tpos);
if (tkn != NULL)
break;
tpos++;
}
savech = *tpos;
*tpos = '\0';
return pos;
}
这将使用第二个字符串中的两个分隔符(下划线和逗号)标记第一个字符串。它的输出显示了它的工作方式,您可以看到它拾取空令牌,包括在开始和结束处:
Initial string is ',_start,,middle_,end,'
Token is ''
Token is ''
Token is 'start'
Token is ''
Token is 'middle'
Token is ''
Token is 'end'
Token is ''
Final string is ',_start,,middle_,end,'
请记住,使用statics时,它受到与strtok相同的限制-不能同时运行两个标记化操作。您可以制作一个paxtok\u r
来镜像strtok\u r
,但我将把它留给读者作为练习。说:
要确定标记的开头和结尾,函数首先从起始位置扫描分隔符中包含的第一个字符而不是(它将成为标记的开头)。然后从标记的这个开头开始扫描分隔符中包含的第一个字符,它将成为标记的结尾。如果找到终止的空字符,扫描也会停止
因此,当函数“扫描(…)分隔符中包含的第一个字符而不是”时,它将跳过任何分隔符字符序列。这使得它无法检测到连续分隔符之间的“空令牌”。您必须自己逐个字符扫描输入字符串。一旦我在读取CSV逗号分隔文件时遇到这个问题。但是,对于分隔符连续出现的问题,我们不能使用
strtok()
作为解决方案。因为按照标准,
序列中的第一个调用搜索字符串
由s1
指向第一个非
包含在由s2
指向的当前分隔符字符串中。
如果找不到这样的字符,则表示中没有标记
s1
指向的字符串和strtok
函数返回
空指针。如果找到这样一个字符,它就是
第一个令牌的开始。C11§7.24.5.8 3
因此,对于我的例子,我使用strpbrk()
函数定义了另一个解决方案,它对您也很有用
#include<iostream.h>
char *strtok_new(char * string, char const * delimiter){
static char *source = NULL;
char *p, *riturn = 0;
if(string != NULL) source = string;
if(source == NULL) return NULL;
if((p = strpbrk (source, delimiter)) != NULL) {
*p = 0;
riturn = source;
source = ++p;
}
return riturn;
}
int main(){
char string[] = "one,,three,";
char delimiter[] = ",";
char * p = strtok_new(string, delimiter);
while(p){
if(*p) cout << p << endl;
else cout << "No data" << endl;
p = strtok_new(NULL, delimiter);
}
system("pause");
return 0;
}
希望这是您想要的输出。您可以使用strep()
而不是strok()
,前者将多个分隔符视为空标记并返回所有分隔符
与strtok()
不同,您不必使用NULL
第一个参数调用strep()
。你可以这样称呼它:
#包括
#包括
内部主(空){
char string[]=“this,is,the,string,,,,you,want,to,parse”;
char*strprtr=字符串;
字符*令牌;
while(标记=strep(&strprtr,“,”)){
printf(“正在处理“%s”\n”,令牌);
}
返回0;
}
该程序生成以下输出:
Processing 'this'
Processing 'is'
Processing 'the'
Processing 'string'
Processing ''
Processing ''
Processing ''
Processing 'you'
Processing 'want'
Processing 'to'
Processing 'parse'
如果该状态使您感到紧张或引发编译器警告,您可以始终明确检查NULL
:
while((令牌=strep(&strprtr,”,“”!=NULL))
请记住,一些旧的编译器库没有strsep()
,从技术上讲,它不是ISO标准的一部分,但在大多数实现中应该可以使用。这是一个改进的可重入版本:
char *strtok_new_r(char * string, char const * delimiter, char **saveptr) {
char *ptr, *riturn = 0;
if (string != NULL) {
*saveptr = string;
}
if (*saveptr == NULL) {
return NULL;
}
if ((ptr = strpbrk(*saveptr, delimiter)) != NULL) {
*ptr = 0;
riturn = *saveptr;
*saveptr = ++ptr;
}
if (!ptr) {
if (*saveptr) {
riturn = *saveptr;
*saveptr = NULL;
}
}
return riturn;
}
这就是strtok的工作方式。我也偶然发现了这一点。如果你想处理空令牌,你必须编写自己的令牌化函数。cplusplus.com是一个众所周知的坏资源,不要相信你在那里读到的。而是搜索。另外,使用
strchr()
查找字符串中的字符。@TheParamagneticCroissant:虽然该网站可能有一些(不应有的)恶名,但链接页面看起来很准确,并回答了这个问题。“函数首先从起始位置扫描未包含在分隔符中的第一个字符”,这意味着连续分隔符被视为单个分隔符。分隔符之间的空字符串实际上不是“标记”。BTW考虑空白分隔符(我想,它是<代码> Stotok())/代码>被发明了……CiAPANS答案是正确的。Strtok不适合解析CSV类型的文件,因为相邻的分隔符是静默允许的。如果你必须做C和C++,你需要找到一个好的库,或者自己的解决方案。哇,很明显,strtok
不是再进入者。想到不相关软件组件中的两个线程可能同时使用strtok
执行字符串操作,而那些static
变量将导致这两个线程都失败,这实际上很可怕。为什么有人在生产软件中使用不可重入版本?!现在整件事看起来真是个坏主意。谢谢你睁开我的眼睛:)@Andon,我
testprog ,_start,,middle_,end, _,
Initial string is ',_start,,middle_,end,'
Token is ''
Token is ''
Token is 'start'
Token is ''
Token is 'middle'
Token is ''
Token is 'end'
Token is ''
Final string is ',_start,,middle_,end,'
#include<iostream.h>
char *strtok_new(char * string, char const * delimiter){
static char *source = NULL;
char *p, *riturn = 0;
if(string != NULL) source = string;
if(source == NULL) return NULL;
if((p = strpbrk (source, delimiter)) != NULL) {
*p = 0;
riturn = source;
source = ++p;
}
return riturn;
}
int main(){
char string[] = "one,,three,";
char delimiter[] = ",";
char * p = strtok_new(string, delimiter);
while(p){
if(*p) cout << p << endl;
else cout << "No data" << endl;
p = strtok_new(NULL, delimiter);
}
system("pause");
return 0;
}
one
No data
three
Processing 'this'
Processing 'is'
Processing 'the'
Processing 'string'
Processing ''
Processing ''
Processing ''
Processing 'you'
Processing 'want'
Processing 'to'
Processing 'parse'
char *strtok_new_r(char * string, char const * delimiter, char **saveptr) {
char *ptr, *riturn = 0;
if (string != NULL) {
*saveptr = string;
}
if (*saveptr == NULL) {
return NULL;
}
if ((ptr = strpbrk(*saveptr, delimiter)) != NULL) {
*ptr = 0;
riturn = *saveptr;
*saveptr = ++ptr;
}
if (!ptr) {
if (*saveptr) {
riturn = *saveptr;
*saveptr = NULL;
}
}
return riturn;
}