使用类似 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)));
【讨论】:
@einpoklumstd::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<decltype(txt_msg)>
可以替换为 std::size(txt_msg)
。鉴于您希望避免 DRY,make_string_view()
可以按原样使用arr
而不是std::begin(arr)
和N
而不是std::end(arr)
:return std::string_view<CharT>arr, N;
我不知道std::size()
,谢谢!关于 string_view 构造的好点。我进一步改进了它......以上是关于使用类似 strndup 的语义从 char[] 创建 std::string的主要内容,如果未能解决你的问题,请参考以下文章
C语义char*显示中文---ASCIIDBCSUnicode三种编码---char* CString string区别