具有模板参数大小与字符指针的 const 字符数组

Const char array with template argument size vs. char pointer

本文关键字:字符 指针 const 数组 参数      更新时间:2023-10-16

>我今天在一些代码中看到以下类型的结构:

template<unsigned int N> unsigned int f(const char (&a)[N]);
拥有

这个是否有任何合理的意义:

unsigned int f(const char *a);

我隐约理解后者的指针共享含义,但它真的如此糟糕,需要用两倍大小的晦涩代码替换吗?

(不幸的是,我不能问代码的作者,否则我会(

将任何原始指针传递给函数的目的是调用对以下内容有一些想法:

  • 它指向什么。
  • 它指向多少。

作为输入参数的 C 样式字符串推断出后者,因为假设"多少"是通过到达空字符终止符来视为的。

但是,如果您传递 C 样式字符串怎么办?如果它只是零个或多个char值的序列怎么办?好吧,如果是这样的话,那么:

void f(const char *s)
{
    // how many char does s refer to?
}

合乎逻辑的推论是这样做的:

void f(const char *s, std::size_t N)
{
    // Now the caller is telling us there are N chars at s
}

这并不少见,尽管如果调用者通过错误的长度(永远不要说永远(,这是一个潜在的错误点。

但是,如果有一种方法可以通过非类型模板参数使用演绎来传递到函数的实际变量类型中的数据呢?如果调用方使用固定数组调用我们怎么办?

template<std::size_t N>
void f(const char(&ar)[N])
{
    // we know the caller is passing a const-reference to a
    // char array declared of size N. The value N can be used
    // in this function.
}

现在我们知道列表中的两个项目:"什么"和"多少"。此外,我们现在可以同时提供模板函数和重载,并拥有两个世界:

// length specified implementation
void f(const char *s, std::size_t N)
{
    // caller passed N
}
// fixed buffer template wrapper
template<std::size_t N>
void f(const char(&ar)[N])
{
    f(ar,N); // invokes length-specified implementation from above.
}

以下两种方法都将起作用:

int main()
{
    char buff[3];
    f(buff,3);
    f(buff);
}

那么这怎么好呢?因为以下内容将标记编译器错误,因为找不到匹配的实现:

int main()
{
    char buff[3];
    const char *ptr = buff;
    f(ptr); // ERROR: no matching function f(const char *)
}

总之,这是一种常见的技术,可以帮助将项目符号列表中的两个项目提供给被调用方:"什么"和"多少",而不必每次使用固定长度的本机数组作为输入参数时都长手sizeof(ar)/sizeof(*ar)

祝你好运。

指针

不保留信息,无论它们指向数组的单个对象还是第一个对象。因此,例如,如果您在函数体内编写

unsigned int f(const char *a);

表达

sizeof( a ) / sizeof( *a )

您将只获得指针本身的大小。而如果您在函数体内使用相同的表达式

template<unsigned int N> unsigned int f(const char (&a)[N]);

您将获得数组的大小(当然,您可以简单地使用值 N(。

因此,当使用第二种方法时,通常使用两个参数声明此类函数,其中第二个参数指定数组的大小

unsigned int f(const char *a, size_t n);

考虑一种情况,即您需要将作为参数传递的字符数组附加到其他字符串中(假设限定符 const 不存在(。当您使用第二个声明时,即使应用于指针的函数strlen也无法帮助您确定原始字符串是否足够大,可以附加。在第一种方法中,您可以使用表达式N - strlen( a )来确定数组中是否有足够的空间。

template<unsigned int N> unsigned int f(const char (&a)[N]);

此函数模板通过引用接收数组,这意味着当直接与静态分配的 C 数组一起使用时,函数在编译时知道数组大小。

unsigned int f(const char *a);

在这里,函数所知道的只是它被赋予了一些指向常量字符的指针。因此,如果你想使用它来操作数组,它必须将数组大小作为附加参数,因为无法单独从指针检索此信息。

模板版本采用特定大小的数组。指针版本仅采用一个指针,没有任何指针指向的数据大小的概念。

templateN的每个不同值实例化(因此生成了额外的机器代码(,但在f内部,它可以自由地使用它的N知识来做任何有用的事情(例如,从a处理正确数量的条目,相应地调整它可能需要的另一个字符数组的大小(。 你必须查看内部f才能知道N是否被实际使用,以及它是否有用。 对于非模板版本,调用方不提供数组/数据大小,因此没有提供等效的信息(如果a指向的数据长度是可变的,那么f将不得不依靠其他方式来了解有多少数据,例如 strlen()花时间搜索终止 NUL(。

使用它有两个

原因:

  • 除了 C 样式数组之外,不可能向该模板函数传递任何东西。因此,使用指针调用该方法将是一个编译错误。
  • 在模板函数中,您可以获得数组的大小,在编译期间进行评估

所以,这个:

const char* p="abc";
f(p);

导致编译失败:

garbage.cpp:39:5: error: no matching function for call to ‘f(const char*&)’
  f(p);
     ^
garbage.cpp:39:5: note: candidate is:
garbage.cpp:21:39: note: template<unsigned int N> unsigned int f(const char (&)[N])
 template<unsigned int N> unsigned int f(const char (&a)[N])

鉴于,这:

f("abc");

很好,你可以得到这个数组的大小确定为编译常量