如何在不复制的情况下标记文件中的c样式字符串

How can I tokenize a c-style string from a file without making a copy?

本文关键字:文件 字符串 样式 下标 情况下 复制 情况      更新时间:2023-10-16

假设我有一个常量c样式字符串,比如

const char* msg = "fred,jim,345,7665";

我想对此进行标记并读出各个字段,但出于性能原因,我不想复制。我该怎么做?

显然,strtok采用了一个非常量指针,boost::tokenizer是一个选项,但我不确定幕后在做什么。

不可避免地,您将需要字符串的一些副本,即使它是正在复制的子字符串。

如果你有一个strtok_r函数,你可以使用它,但它仍然需要一个可变的字符串来完成它的工作。然而,请注意,并不是所有系统都提供该功能(例如Windows),这就是我在这里提供实现的原因。它需要一个额外的参数:一个指向C字符串的指针来保存下一个匹配的地址。这使得它在理论上更具可重入性(线程安全)。但是,您仍将更改该值。如果愿意,您可以修改它以满足您的需要,也许可以将N个字节复制到目标缓冲区中,并以null终止该缓冲区以避免修改源字符串。

/*
   Usage:
     char *tok;
     char *savep;
     tok = mystrtok_r (somestr, ",", &savep);
     while (NULL != tok)
       {
         /* Do something with `tok'.  */
         tok = mystrtok_r (NULL, ",", &savep);
       }
*/
char *
mystrtok_r (char *str, const char *delims, char **nextp)
{
  if (str == NULL)
    str = *nextp;
  str += strspn (str, delims);
  *nextp = str + strcspn (str, delims);
  **nextp = 0;
  if (*str == 0)
    return NULL;
  ++*nextp;
  return str;
}

这取决于您将如何使用它。如果你想获得下一个令牌,然后是下一个(就像对字符串的迭代一样),那么你只需要将当前令牌复制到内存中

long strtok2( char *strDest, const char *strSrc, const char cTok, long lOffset, long lMax)
{
  if(lMax > 0)
  {
    strSrc += lOffset;
    char * start = strDest;
    while(--lMax && *strSrc != cTok && (*strDest++ = * strSrc++) );
    *strDest = 0; //for when the token was found, not the null.
    return strDest - start - 1; //the length of the token
  }
  return 0;
}

我从http://vijayinterviewquestions.blogspot.com.au/2007/07/implement-strcpy-function.html

const char* msg = "fred,jim,345,7665";
char * buffer[20];
long offset = 0;
while(length = strtok2(buffer, msg, ',', offset, 20))
{
  cout << buffer;
  offset += (length+1);
}

好吧,如果没有更多的细节,很难确切地知道你想要什么。我猜您正在解析分隔符项,其中连续的分隔符应被视为零长度标记(这通常适用于逗号分隔的元素)。我还假设一个空行算作一个零长度的标记。这就是我的做法:

const char *token_begin = msg;
int length;
for(;;)
{
   length = 0;
   while(!isDelimiter(token_begin[length]))    //< must include  as delimiter
      ++length;
   //..do something here with token.  token is at: token_begin[0..length)
   if ( token_begin[length] != 0 )
      token_begin = &token_begin[length+1];    //skip beyond non-null delimiter
   else
      break;                                   //token null terminated.  exit
}

如果您要将令牌存储在某个地方,那么在任何情况下都需要一个副本,strtok通过使用字符串(其中放置null终止字符)很好地做到了这一点。

我看到的唯一一个避免复制它的选项是lexer,它读取字符串,并通过状态机通过扫描字符串并将部分结果存储在缓冲区中来生成令牌,但在任何情况下,每个令牌都应该至少存储在以null结尾的字符串中,因为你并没有真正保存任何东西。

这是我的建议,我的代码是结构化的,并使用全局变量pos(我知道全局变量是一种糟糕的做法,但这只是给你一个想法),如果你需要OOP,你可以用数据成员来替换它。

int position, messageLength;
char token[MAX]; // MAX = Value greater than the maximum length
                 // of the tokens(e.g. 1,000);

bool hasNext()
{
  return position < messageLength;
}
char* next(const char* message)
{
  int i = 0;
  while (position < messageLength && message[position] != ',') {
    token[i++] = message[position];
    position++;
  }
  position++; // ',' found
  token[i] = '';
  return token;
}
int main(int argc, char **argv)
{
  const char* msg = "fred,jim,345,7665";
  position = 0;
  messageLength = strlen(msg);
  while (hasNext())
    cout << next(msg) << endl;
  return EXIT_SUCCESS;
}