如何构造带有嵌入式null的std :: string?


88

如果我想用以下行构造std :: string:

std::string my_string("a\0b");

我想在结果字符串中包含三个字符(a,null,b)时,我只能得到一个。正确的语法是什么?


4
您必须对此小心。如果将“ b”替换为任何数字字符,则会无提示地创建错误的字符串。请参阅:stackoverflow.com/questions/10220401/...
大卫·斯通

Answers:


128

从C ++ 14开始

我们已经能够创建文字 std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

在C ++ 14之前

问题是std::string构造函数const char*假设输入是C字符串。C字符串\0终止,因此在到达\0字符时解析停止。

为了弥补这一点,您需要使用从char数组(而非C-String)构建字符串的构造函数。这需要两个参数-指向数组的指针和一个长度:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

注:C ++std::string \0封端的(如在其他职位的建议)。但是,您可以使用方法提取指向包含C-String的内部缓冲区的指针c_str()

还要查看以下有关使用Doug T的答案vector<char>

另请参阅RiaD以获取C ++ 14解决方案。


6
更新:从c ++ 11开始,字符串以null终止。话虽如此,Loki的职位仍然有效。
matthewaveryusa 2014年

14
@mna:就存储而言,它们是空终止的,但不是以有意义的空终止(即具有字符串长度定义的语义)为空终止的意义,这是该术语的通常含义。
Lightness Races in Orbit 2015年

好解释。谢谢。
乔马,

22

如果您要像使用c样式字符串(字符数组)那样进行操作,请考虑使用

std::vector<char>

您拥有更多的自由,可以像对待c字符串一样将其像数组一样对待。您可以使用copy()复制到字符串中:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

您可以在许多可以使用c弦的地方使用它

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

但是,自然地,您会遭受与C弦相同的问题。您可能会忘记您的空终端或写超出分配的空间。


如果说要尝试将字节编码为字符串(grpc字节存储为字符串),请使用答案中指定的vector方法;不是通常的方法(请参见下文),它不会构成整个字符串 byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Alex Punnen

13

我不知道您为什么要这样做,但是请尝试以下操作:

std::string my_string("a\0b", 3);

1
您对此有何担忧?您是否质疑是否需要存储“ a \ 0b”?或质疑使用std :: string进行此类存储?如果是后者,您有什么建议呢?
安东尼·克兰普

3
@Constantin,那么如果您将二进制数据存储为字符串,则说明您做错了。那就是vector<unsigned char>或被unsigned char *发明的。
Mahmoud Al-Qudsi'1

2
我在尝试了解有关字符串安全性的更多信息时碰到了这一点。我想测试我的代码,以确保即使从文件/网络读取期望为文本数据的同时读取空字符,该代码仍然可以正常工作。我曾经std::string指出数据应该被视为纯文本,但是我正在做一些散列工作,并且我想确保所涉及的所有空字符仍然有效。这似乎是对带有嵌入的空字符的字符串文字的有效使用。
David Stone

3
@DuckMaestro不,那不是真的。\0UTF-8字符串中的一个字节只能为NUL。多字节编码字符绝不会包含-也不包含\0任何其他ASCII字符。
约翰·库格曼

1
我在尝试在测试用例中提出算法时碰到了这一点。因此有充分的理由;虽然很少。
namezero 2014年

12

用户定义的文字为C ++添加了哪些新功能?提出一个优雅的答案:定义

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

那么您可以通过以下方式创建您的字符串:

std::string my_string("a\0b"_s);

甚至是这样:

auto my_string = "a\0b"_s;

有一种“旧样式”方式:

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

然后你可以定义

std::string my_string(S("a\0b"));

8

以下将起作用...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');

您必须使用方括号内的括号。
jk。

5

您必须对此小心。如果将“ b”替换为任何数字字符,则将使用大多数方法静默创建错误的字符串。请参阅:C ++字符串文字的规则转义字符

例如,我在程序中间放了一个看起来很天真的片段

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

这是该程序为我输出的内容:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

那是我的第一个打印语句,两次是非打印字符,然后是换行符,然后是内部存储器中的内容,我刚刚重写了这些内容(然后打印,表明它已被覆盖)。最糟糕的是,即使使用彻底和冗长的gcc警告进行编译也没有给我任何错误的迹象,并且通过valgrind运行程序不会抱怨任何不当的内存访问模式。换句话说,现代工具完全无法检测到它。

您可以通过简单得多的方法获得相同的问题std::string("0", 100);,但是上面的示例有点棘手,因此更难发现问题所在。

幸运的是,C ++ 11使用初始化列表语法为我们提供了一个很好的解决方案。这使您不必指定字符数(如我上面显示的那样,您可能做错了),并且避免了组合转义的数字。std::string str({'a', '\0', 'b'})对于任何字符串内容都是安全的,这与采用数组char和大小的版本不同。


2
在准备这篇文章的过程中,我向gcc提交了一个错误报告,希望他们会添加警告以使其更安全一些:gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone

4

在C ++ 14中,您现在可以使用文字

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3

1
第二行也可以写成,更好的恕我直言,因为auto s{"a\0b"s};
underscore_d

不错的答案,谢谢。
乔马,


1

匿名者的答案非常好,但C ++ 98中也有一个非宏解决方案:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

使用此功能,RawString(/* literal */)将产生与以下相同的字符串S(/* literal */)

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

另外,宏存在一个问题:该表达式实际上不是std::string写的那样,因此不能用于例如简单的赋值初始化:

std::string s = S("a\0b"); // ERROR!

...因此,最好使用:

#define std::string(s, sizeof s - 1)

显然,您应该仅在项目中使用一种或另一种解决方案,并在您认为合适的地方调用它。


-5

我知道这个问题已经问了很长时间了。但是对于遇到类似问题的任何人,可能对以下代码感兴趣。

CComBSTR(20,"mystring1\0mystring2\0")

这个答案太特定于Microsoft平台,不能解决原始问题(询问std :: string的问题)。
6

-8

几乎所有std :: strings的实现都以null终止,因此您可能不应该这样做。请注意,由于自动空终止符(a,null,b,null),“ a \ 0b”实际上是四个字符长。如果您确实想这样做并破坏std :: string的合同,则可以执行以下操作:

std::string s("aab");
s.at(1) = '\0';

但是,如果您这样做,所有的朋友都会嘲笑您,您将永远找不到真正的幸福。


1
std :: string不需要以NULL终止。
马丁·约克

2
并不是必需的,但是在几乎所有的实现中,这可能都是因为c_str()访问器需要为您提供以null终止的等效项。
Jurney

2
为了提高效率,可以在数据缓冲区的背面保留一个空字符。但是对字符串的任何操作(即方法)都不会使用此知识,也不会受到包含NULL字符的字符串的影响。NULL字符将以与其他任何字符完全相同的方式进行操作。
马丁·约克

这就是为什么字符串为std如此有趣的原因:-在任何平台上都未定义其行为。

我希望user595447仍然在这里,以便我可以问他们他们认为他们在谈论什么。
underscore_d
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.