定义不带终止符的 C++ 字符数组

Define a C++ char array without terminator

提问人:Damian Yerrick 提问时间:10/23/2015 最后编辑:CommunityDamian Yerrick 更新时间:1/11/2021 访问量:3194

问:

为什么这个程序在 C 中编译良好而在 C++ 中编译不行?的答案说明与 C 语言不同,C++ 语言不允许数组的初始值设定项字符串不够长,无法容纳终止 null 字符。有没有办法在 C++ 中指定一个未终止的数组,而不会在源代码中使字符串膨胀四倍?charchar

例如,在 C 和 C++ 中,以下内容是等效的:

const char s[] = "Hello from Stack Overflow";
const char s[] = {'H','e','l','l','o',' ','f','r','o','m',' ','S','t','a','c','k',' ','O','v','e','r','f','l','o','w','\0'};

由于字符串的长度为 25,因此它们会生成一个包含 26 个元素的数组,就好像编写了以下内容一样:"Hello from Stack Overflow"char

const char s[26] = "Hello from Stack Overflow";
const char s[26] = {'H','e','l','l','o',' ','f','r','o','m',' ','S','t','a','c','k',' ','O','v','e','r','f','l','o','w','\0'};

仅在 C 语言中,程序可以排除终止 null 字符,例如,如果字符串的长度是带外已知的。(在 C99 标准的第 6.7.9 章中查找“如果有空间,则包括终止 null 字符”。

const char s[25] = "Hello from Stack Overflow";
const char s[25] = {'H','e','l','l','o',' ','f','r','o','m',' ','S','t','a','c','k',' ','O','v','e','r','f','l','o','w'};

但在 C++ 中,只有第二个是有效的。如果我知道我将使用系列中的函数而不是系列中的函数来操作数据,那么 C++ 语言中是否有与 C 速记语法相对应的语法?std::strnstd::str

我的动机与 C++ 中关于未终止 char 数组的另一个问题不同。这样做的动机是游戏中的多个项目名称存储在二维数组中。例如:char

const char item_names[][16] = {
    // most items omitted for brevity
    "steel hammer",
    {'p','a','l','l','a','d','i','u','m',' ','h','a','m','m','e','r'}
};

如果没有速记来声明未终止的数组,则最大长度名称必须逐个字符编写,这使得它们比较短的名称更难读且更不容易维护。char

C++ 数组字符串

评论

3赞 edmz 10/23/2015
为什么不为你提供阵列?string
1赞 Damian Yerrick 10/23/2015
@black 问题的答案取决于以下问题的答案:在流行的 C++ 实现中,每个实例有多少恒定开销?std::string
1赞 edmz 10/23/2015
在“一般”中可以忽略不计。 通常(将)更轻。string_view
2赞 Alan Stokes 10/23/2015
何苦?你浪费了三个字节,因为零填充“钢锤”,而在钯上只得到一个。指向以 null 结尾的字符串的指针数组会占用更多还是更少的空间?您打算如何处理通过省略终止符而可能节省的少数字节?
0赞 Damian Yerrick 10/7/2020
@AlanStokes 这不仅仅是终结者。另外,如果字符串的长度不同,则需要存储指向每个字符串的指针。

答:

0赞 VladimirS 3/6/2016 #1

这是可能的,但我同意艾伦·斯托克斯(Alan Stokes)的“为什么”

例如,使用 C++:宏可以将“abc”扩展为“a”、“b”、“c”吗?可以调整父亲,以允许对任何提供的阵列进行操作,并通过长度 16 对其进行约束。

#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/comma_if.hpp>

template <unsigned int N>
constexpr char get_ch (char const (&s) [N], unsigned int i)
{
    return i >= N ? '\0' : s[i];
}

#define STRING_TO_CHARS_EXTRACT(z, n, data) \
    BOOST_PP_COMMA_IF(n) get_ch(data, n)

#define STRING_TO_CHARS(STRLEN, STR)  \
    BOOST_PP_REPEAT(STRLEN, STRING_TO_CHARS_EXTRACT, STR)


const char item_names[][16] = { // most items omitted for brevity
    "steel hammer",
    STRING_TO_CHARS(16, "palladium hammer"),
    //{'p','a','l','l','a','d','i','u','m',' ','h','a','m','m','e','r'},
};

但从长远来看,这可能会给你带来更多的麻烦......

0赞 jhcarl0814 1/11/2021 #2

C 样式数组的缺点是它不能传递给函数或从函数返回(传递或返回 char* 可以给你一个 char* 数组,但最终不能给你 char[] 数组),我们可以使用 std::array 来解决这个问题
C 样式数组的优点是可以使用小字符串文字来初始化 char[][] 的初始化列表中较大的 char[](如您的示例所示), 对于 std::array,我们使用 std::max 来模拟这一点

#include<array>
#include<utility>
#include<algorithm>
#include<iostream>

template<size_t string_length, typename T, T... ints, size_t string_literal_size>
constexpr std::array<char, string_length> generate_string_impl(std::integer_sequence<T, ints...> int_seq, char const(&s)[string_literal_size])
{
    return { {s[ints]...} };
}

template<size_t string_length, size_t string_literal_size>
constexpr std::array<char, string_length> generate_string(char const(&s)[string_literal_size])
{
    return generate_string_impl<string_length>(std::make_index_sequence<string_literal_size - 1>{}, s);
}

template<size_t ...string_literals_size>
constexpr std::array < std::array<char, std::max({ string_literals_size... }) - 1 > , sizeof...(string_literals_size) > generate_array_of_strings(char const(&...s)[string_literals_size])
{
    return { generate_string < std::max({ string_literals_size... }) - 1 > (s)... };
}

int main()
{
    constexpr auto array_of_string1 = generate_array_of_strings("1234", "12345", "123456");
    //std::array<std::array<char,6>,3>{{
    //  {{ '1', '2', '3', '4', '\0', '\0' }},
    //  {{ '1', '2', '3', '4', '5', '\0' }},
    //  {{ '1', '2', '3', '4', '5', '6' }}
    //}}

    //std::array<std::array<char,6>,3> satisfies StandardLayoutType requirement
    char const(&array_of_string2)[std::size(array_of_string1)][6] = reinterpret_cast<char const(&)[std::size(array_of_string1)][6]>(array_of_string1);
    char const(*p_array_of_string2)[std::size(array_of_string1)][6] = reinterpret_cast<char const(*)[std::size(array_of_string1)][6]>(&array_of_string1);

    for (int i = 0; i != 3; ++i)
    {
        for (int j = 0; j != 6; ++j)
        {
            std::cout << i << "," << j << " " << array_of_string2[i][j] << " " << (*p_array_of_string2)[i][j] << std::endl;
        }
    }
}