C++如何将字符串转换为char*

4

我需要将一个字符串转换为char *以便在strtok_s中使用,但一直无法弄清楚。c_str()将其转换为const char *,不兼容。

此外,如果有人能解释一下为什么需要第二个strtok_s函数(循环内部),那将是非常有帮助的。为什么我需要显式地推进标记,而不是例如它所在的while循环,连续获取文件的每一行。

while( getline(myFile, line) ) { // Only one line anyway. . . is there a better way?
    char * con = line.c_str();
    token = strtok_s( con, "#", &next_token);
    while ((token != NULL))
    {
        printf( " %s\n", token );
        token = strtok_s( NULL, "#", &next_token);
    }
}

related question.


为什么要在C++字符串上使用strtok()?C++有更好的工具来处理这种情况。 - Avdi
1
因为我不知道更好的方法。你有什么建议吗? - Nona Urbiz
1
请参考以下链接中使用Boost的示例代码,了解如何在C++中对字符串进行分词:https://dev59.com/cHVD5IYBdhLWcg3wNIvc#55680 - Bill
但是为什么这样做更好?使用库不会产生额外开销吗? - Nona Urbiz
Boost 中的几乎所有内容都是作为模板实现的,因此只有实际使用到的代码会被包含进来。 - Martin B
3
顺便提一下,C++ 字符串可以在中间包含 NUL 字符,因为 C++ 中的字符串是通过若干字节和一个长度来定义的,而不是像 C 一样用“以 NUL 结尾的字节序列”来定义。因此,如果你只知道输入是一个 C++ 字符串,那么 C 函数如 strtok 实际上不起作用,因为它们可能会错误地检测到字符串的结尾。 - Steve Jessop
10个回答

8

使用 strdup() 将由 c_str() 返回的 const char * 复制到一个 char * 中(记得在之后使用 free() 释放它)。

请注意,strdup()free() 是 C 语言中的函数,建议您使用 std::string 类中的方法来代替。

需要第二个 strtok_s() 是因为如果没有它,您的循环将不会终止(token 的值不会改变)。


但是为什么我需要显式地推进标记,而不是例如它所在的 while 循环隐式地连续获取文件的每一行呢? - Nona Urbiz
再看一下代码: 第一次调用strtok()从文件中的行中获取第一个标记。然后while()的条件检查标记是否为NULL。如果不是,则执行printf(),并提取下一个标记。 让你困惑的可能是变量next_token实际上并没有存储下一个标记,而是存储了该行的剩余部分。 这就是strtok_s()的工作方式。 - Wernsey
strdup 不是标准的 C 语言函数。它只是一个常见的扩展,但并不保证一定存在。 - Evan Teran

5

正如Daniel所说,你可以选择

strdup(line.c_str());

这比我最初提出的strcpy更好,因为它会分配所需的空间。


strcpy会产生弃用警告吗? - Nona Urbiz
3
strdup(line.c_str())可能会更好。 - Daniel Pryden
2
请注意:这个程序不能直接运行。你需要为“con”分配内存。 - Martin B
在VC++中,它可能会给出弃用警告。由于安全原因(这使得他们的操作系统看起来很糟糕),Microsoft已经将其弃用,但它并未从ISO标准库中弃用。警告消息还告诉您如何修复它(它提供了两种方法)。 - Clifford

5
您不能将其转换为char*,因为这样会允许您写入std::string的内部缓冲区。为了避免使std::string的实现可见,不允许这样做。
尝试一种更“C++”方式的字符串分词方法,而不是使用strtok。请参考此问题: 如何在C++中对字符串进行分词?

这似乎难以置信,这种强制类型转换根本不可能。 - Nona Urbiz
1
强制类型转换本身可以使用 const_cast,但不建议这样做。 - MP24
这是不可能的。原因在于,在面向对象编程中,对象不喜欢外部客户端直接访问它们的内部表示。请参见http://en.wikipedia.org/wiki/Information_hiding。 - Martin B
4
你只需要相信,将const指针转换为非const指针是可能的,但试图修改所指向的数据会导致未定义的行为。c_str()并不保证返回字符串的内部缓冲区 - 它可能会将字符串复制到一个新位置并显示给你。显然,修改任何此类原始字符串的克隆都不起作用。在C++0x中,string的实现更加严格控制,如我记得的那样,你将能够使用&line[0]作为指向字符串数据的char*。但这可能没有以NUL结尾。 - Steve Jessop

2

strtok()是一个设计不良的函数。请查看文档,看是否有更好的替代品。另外,在任何线程环境下都不要使用strtok(),除非您的文档明确说明它是安全的,因为它在调用之间存储状态并修改调用的字符串。我假设strtok_s()是一个更安全的版本,但它也不会是真正安全的。

要将std::string转换为char *,可以执行以下操作:

char * temp_line = new char[line.size() + 1];  // +1 char for '\0' terminator
strcpy(temp_line, line.c_str());

并使用temp_line。您的安装可能有一个strdup()函数,它将复制上面的内容。

你需要两次调用strtok_s()是因为它们执行不同的操作。第一次调用告诉strtok_s()它需要处理哪个字符串,而第二次继续处理相同的字符串。这就是NULL参数的原因;它告诉strtok_s()继续使用原始字符串。

因此,您需要一次调用以获取第一个标记,然后对于每个后续标记都需要一次调用。它们可以与以下内容合并:

char * temp_string_pointer = temp_line;
while ((token = strtok_s( con, "#", &next_token)) != NULL)
{
   temp_string_pointer = NULL;

等等,因为这会使用 strtok_s()一次调用字符串指针,之后再使用 NULL。不要为此使用temp_line,因为您希望在处理后 delete [] temp_line;

您可能认为这是很多琐碎的工作,但这通常是 strtok()和相关函数所需的。


我会给你点赞,因为你说“strtok()本来就是一个设计不良好的函数”,但是你又建议使用裸字符缓冲区而不是一些资源管理对象。 :( - sbi

1

strtok 的工作方式如下:

第一次调用返回从开头到分隔符或整个字符串(如果没有找到分隔符)的字符串:

token = strtok_s(con, "#", &next_token);

使用 NULL 进行第二次调用,可以继续解析相同的字符串以查找下一个分隔符:

token = strtok_s(NULL, "#", &next_token);

如果你到达字符串的末尾,下一次调用将返回NULL;


但是为什么我需要显式地推进标记,而不是例如它所在的 while 循环隐式地连续获取文件的每一行呢? - Nona Urbiz
你在这里具体是在问什么?你必须重复调用strtok,直到消耗掉给它的数据中的所有标记,在这种情况下是文件的一行。while循环检查strtok的结果以确保发生这种情况。 - Kylotan

1
无论何时您拥有一个 std::string 并且您需要的是一个(可修改的)字符数组,那么 std::vector<char> 就是您所需要的:
void f(char* buffer, std::size_t buffer_size);

void g(std::string& str)
{
  std::vector<char> buffer(str.begin(),str.end());
  // buffer.push_back('\0');    // use this if you need a zero-terminated string
  f(&buffer[0], buffer.size()); // if you added zero-termination, consider it for the size
  str.assign(buffer.begin(), buffer.end());
}

0
你可以轻松编写一个转换程序,将字符串分词并返回子字符串向量:
std::vector<std::string> parse(const std::string& str, const char delimiter)
{
    std::vector<std::string> r;

    if(str.empty())
        return r;

    size_t prev = 0, curr = 0;

    do
    {
        if(std::string::npos == (curr = str.find(delimiter, prev)))
            curr = str.length();

        r.push_back(str.substr(prev, curr - prev));
        prev = curr + 1;
    }
    while(prev < (int)str.length());
    return r;
}

0

第二个strtok调用在循环内部。它会使您的令牌指针前进,以便逐个打印出令牌,直到您打印出所有令牌,指针变为null并退出循环。

回答您问题的第一部分,正如其他人建议的那样,c_str()只给您内部缓冲区指针-您无法修改该指针,这就是为什么它是const。如果您想要修改它,您需要分配自己的缓冲区并将字符串内容复制到其中。


0
如果你真的需要访问字符串的内部缓冲区,可以这样做:&*string.begin()。 直接访问字符串缓冲区在某些情况下非常有用,在这里你可以看到这样一个例子。

1
我不想这样做。通常来说,玩弄数据结构的内部是很危险的。 - David Thornley
理论上来说,在这种情况下是这样的,因为 std::string 甚至不能保证它将字符存储在连续的内存块中(例如,自 C++03 以来 std::vector 做到了这一点)。实际上,没有人见过这个类的一个不将其字符连续存储的实现。正如 onebyone 所说,在 C++1x 中这将得到保证。 - sbi
这些在我上面的评论是否得出结论,认为这种方法是安全可靠的?它似乎可以轻松解决我的问题...但我不想冒失败的风险... - bazz

-1

我认为你可以先将字符串转换为const char*,然后将const char*复制到char*缓冲区以供进一步使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接