使用类似 strndup 的语义从 char[] 创建 std::string

Posted

技术标签:

【中文标题】使用类似 strndup 的语义从 char[] 创建 std::string【英文标题】:Create a std::string from char[] with strndup like semantics 【发布时间】:2021-05-14 16:45:04 【问题描述】:

我有一个

char txt_msg[80];

数组最多可以包含 80 个字符,例如不能保证有一个终止的空值。但是,如果少于 80 个字符,则有一个终止空值。

现在我正在使用它从中获取 std::string:

std::string(txt_msg, txt_msg + ::strnlen(txt_msg, sizeof(txt_msg)));

创建一个 C++ 字符串,看起来有点冒犯。有没有更 C++y 的方式来做到这一点?

【问题讨论】:

填充数组的函数会“返回”它填充的字符数吗?或者如果添加了空终止符,则“返回”其他一些指示符? 您可以使用(char*, size) 构造函数而不是(begin, end) 构造函数来执行std::string(txt_msg, ::strnlen(txt_msg, sizeof txt_msg));。嗯,显然strnlen 不是 C 或 C++ 标准(它是 POSIX.1-2008),但写起来并不难。 @Someprogrammerdude 不,这是在一个由 C 库填充的结构中。我只得到填充结构。 which looks kind of offensive 我不觉得被冒犯,看起来不错。 @Remy 我同意它并不比使用strnlen 更有效......但它“更好”,因为它不依赖于可用的非标准函数。 【参考方案1】:

我可能会这样做:

char txt_msg[80];

auto s = std::string(std::begin(txt_msg), std::find(std::begin(txt_msg), std::end(txt_msg), '\0'));

std::find 将返回第一个 null 终止符 或数组末尾的位置。

【讨论】:

这假设 OP 可以访问实际的数组,并且它没有衰减为指针。 @Someprogrammerdude 如果这是真的,这段代码甚至无法编译。【参考方案2】:

有没有更 C++y 的方式来做到这一点?

std::string 的构造而言,并非如此。不过,至少,由于您已经知道 char[] 的最大长度,您可以使用 std::string(const char*, size_type) 构造函数而不是 std::string(InputIt, InputIt) 构造函数,因此构造函数可以避免计算长度:

std::string(txt_msg, ::strnlen(txt_msg, sizeof(txt_msg));

由于strnlen() 是一个非标准的 POSIX 扩展,如果需要,编写手动实现并不难:

#include <algorithm>

size_t strnlen(const char *s, size_t maxlen)

    const char *s_end = s + maxlen;
    const char *found = std::find(s, s_end, '\0');
    return (found != s_end) ? size_t(found - s) : maxlen;

话虽如此,解决您的问题的 C++ 解决方案是将 std::string 构造包装在辅助模板函数中,例如:

template<size_t N>
std::string to_string(const char (&arr)[N])

    return std::string(arr, strnlen(arr, N));

然后您可以在需要时执行此操作:

char txt_msg[80];
...
std::string s = to_string(txt_msg);

而不是这样做:

char txt_msg[80];
...
std::string s = std::string(txt_msg, txt_msg + strnlen(txt_msg, sizeof(txt_msg)));
//or
std::string s = std::string(txt_msg, strnlen(txt_msg, sizeof(txt_msg)));

【讨论】:

@einpoklum std::to_string() 仅支持数字类型,不支持字符数组。我的“最终建议”中没有sizeof()。您指的是什么 DRY 违规? 好的,所以 - 我会犹豫是否有人写一个 to_string() 来做其他事情。也许函数的名称不同? @einpoklum 随意命名。我更喜欢使用更符合标准的东西 @einpoklum 再次仔细阅读我的答案。您指的是我说不要使用的代码,它与OP的原始代码有关。 哦,是的,对不起,我跳了一条线。另一点是,你强迫to_string() 的用户猜测字符串长度是否总是数组大小,或者有时会小于这个值。这并不明显。名称的选择也并不明显。最后,您仍然在 to_string() 的实现中使用仅 POSIX 的函数,而您有足够好的标准库设施来代替使用。【参考方案3】:

也许您应该考虑使用std::string_view - 一种非拥有的类似字符串的引用类型,可以像std::string 一样使用;在您的情况下,它将由您的消息数组支持:

auto sv = std::string_viewtxt_msg, ::strnlen(txt_msg, std::extent_v<decltype(txt_msg)>;

但这仍然,确实,相当不确定,并且严重破坏了DRY principle:3 次重复。那么,我们写一个实用函数怎么样? :

inline std::string_view 
constrain_by_nul(std::string_view sv) 
    return sv.substr(0, sv.find('\0'));

有了这个,你可以写:

auto sv = constrain_by_nul(std::string_viewtxt_msg, std::size(txt_msg));

更好,但还不够:我们两次提到txt_msg。不幸的是,我们不能直接从容器(IIANM)构造字符串视图。那么也许是另一个实用功能?

template<typename CharT, std::size_t N> 
std::basic_string_view<CharT>
inline make_string_view(CharT (&arr)[N])  
    return arr, N;
;

现在你可以写了:

auto sv = constrain_by_nul(make_string_view(txt_msg));

这几乎就是您最初想做的事情。通过体面的编译器优化,它实际上可能会编译成相同的东西。而且 - 没有复制和堆分配,因为它不是 std::string


在这个 SO 问题中了解有关字符串视图的更多信息:What is string_view?

【讨论】:

std::extent_v&lt;decltype(txt_msg)&gt; 可以替换为 std::size(txt_msg)。鉴于您希望避免 DRY,make_string_view() 可以按原样使用arr 而不是std::begin(arr)N 而不是std::end(arr)return std::string_view&lt;CharT&gt;arr, N; 我不知道std::size(),谢谢!关于 string_view 构造的好点。我进一步改进了它......

以上是关于使用类似 strndup 的语义从 char[] 创建 std::string的主要内容,如果未能解决你的问题,请参考以下文章

Strtok_r 返回 NULL

如何抓取语义相似的句子

用于语义相似性的 BERT 嵌入

C语义char*显示中文---ASCIIDBCSUnicode三种编码---char* CString string区别

如何从 char 中获取 + - / *

如何在 C# 中编组数据类型 unsigned char**?