在C++中将数组作为函数参数传递

24

在C++中,数组不能简单地作为参数传递。这意味着如果我创建了以下函数:

void doSomething(char charArray[])
{
    // if I want the array size
    int size = sizeof(charArray);
    // NO GOOD, will always get 4 (as in 4 bytes in the pointer)
}
我无法知道数组有多大,因为我只有指向该数组的指针。
在不更改方法签名的情况下,我有哪些方法可以获取数组的大小并遍历其数据?
编辑: 关于解决方案的一个附加说明。如果字符数组是这样初始化的:
char charArray[] = "i am a string";

那么 \0 已经附加到数组的末尾。在这种情况下,答案(标记为已接受)可以直接使用。


2
实际上这里存在一个相当大的误解。您示例中的语法没有意义:函数在堆栈上接收未知数量的数据。编译器实际上忽略了您原始的语法,并为函数提供了签名“void doSomething(char* charArray)”。如果您使用“char array[10]; doSomething(array);”调用它,则不会保留任何数组状态。请参阅http://c-faq.com/aryptr/aryptrparam.html - kfsone
12个回答

47

使用模板。严格来说,这不符合您的要求,因为它会更改签名,但是调用代码无需进行修改。

void doSomething(char charArray[], size_t size)
{
   // do stuff here
}

template<size_t N>
inline void doSomething(char (&charArray)[N])
{
    doSomething(charArray, N);
}

这种技术被微软的Secure CRT函数和STLSoft的array_proxy类模板所使用。


27

不改变签名怎么办?加一个哨兵元素。对于char数组,可以使用用于标准C字符串的空终止字符'\0'

void doSomething(char charArray[])
{
    char* p = charArray;
    for (; *p != '\0'; ++p)
    {
         // if '\0' happens to be valid data for your app, 
         // then you can (maybe) use some other value as
         // sentinel
    }
    int arraySize = p - charArray;

    // now we know the array size, so we can do some thing
}
当然,你的数组本身不能包含哨兵元素作为内容。对于其他类型的(即非字符)数组,则可以是任何不合法的数据值。如果不存在这样的值,则此方法无效。
此外,这需要调用方的配合。您必须确保调用者预留了一个arraySize + 1元素的数组,并始终设置哨兵元素。
但是,如果你真的不能改变签名,你的选择很有限。

哨兵元素技术即使在没有无效元素的数据中也可以工作,但是在这种情况下,当数据进入时需要对其进行转义,当数据输出时需要对其进行反转义。但此时可能是使用类的时候了。 - Brian

6

实际上,将长度传递到数组的第一个元素中曾经是一种常见的解决方案。这种结构通常被称为BSTR(“BASIC字符串”),尽管它也表示不同(但类似)的类型。

与接受的解决方案相比的优点是,对于大型字符串,使用哨兵确定长度会很慢。缺点显然是这是一种相当低级的黑客方式,既不尊重类型也不尊重结构。

在下面给出的形式中,它仅适用于长度<= 255的字符串。但是,可以通过在多个字节中存储长度来轻松扩展此功能。

void doSomething(char* charArray)
{
    // Cast unnecessary but I prefer explicit type conversions.
    std::size_t length = static_cast<std::size_t>(static_cast<unsigned char>(charArray[0]));
    // … do something.
}

3
由于C++和C对字符的处理方式有些奇怪,它存在一个轻微的问题。如果字符是带符号的,那么当转换为size_t时,大于127的值可能会导致巨大的size_t值(回绕)。你可以通过先转换为无符号字符来解决这个问题:static_caststd::size_t((unsigned char) charArray[0]); - Johannes Schaub - litb

6

通常在使用C或低级C++时,您可能需要重新训练自己的大脑,不要考虑将数组参数写入函数,因为C编译器总是将它们视为指针。实际上,通过键入那些方括号,您会误以为正在传递一个真正的数组,包括大小信息。但事实上,在C中,您只能传递指针。该函数

void foo(char a[])
{
    // Do something...
}

从C编译器的角度来看,is与以下内容完全等同:

void foo(char * a)
{
    // Do something
}

显然,裸指针中不包含任何长度信息。

如果您被困在一个角落里,无法更改函数签名,请考虑使用上面建议的长度前缀。一个非便携但兼容的技巧是在数组之前的 size_t 字段中指定数组长度,类似于这样:

void foo(char * a)
{
    int cplusplus_len = reinterpret_cast<std::size_t *>(a)[-1];
    int c_len = ((size_t *)a)[-1];
}

显然,调用方在将数组传递给foo之前需要以适当的方式创建它们。

毫无疑问,这是一种可怕的hack。但是在紧急情况下,这个技巧可以帮助解决问题。


这不是未定义行为吗? - José D.

4
如果它是以空字符结尾的,那么strlen()函数就可以工作。

1
是的,对于问题中的char数组来说,这是完美的答案,但对于其他数据类型并不适用。 - Reunanen
除了写doSomething(NULL);是完全合法的事实之外,编译器会将OPs指纹更改为char* charArray :) http://c-faq.com/aryptr/aryptrparam.html - kfsone

2
你不能仅仅通过 charArray 来确定大小,这个信息不会自动传递给函数。
当然,如果它是以 null 结尾的字符串,你可以使用 strlen(),但你可能已经考虑过了!
考虑传递一个 std::vector<char> & 参数,或一对指针,或一个指针加一个大小参数。

我认为Yuval在函数签名上受到了限制。 - Calyth
@finnw这是因为它根本没有传递数组,而是只传递了一个指向它的指针。 - kfsone

2
这实际上更像是C语言而不是C++,在C++中,您可能更愿意使用std::vector。然而,在C中,没有办法知道数组的大小。编译器只会允许您对当前范围内显式声明大小的数组进行sizeof操作(编辑:“显式声明大小”是指它要么用整数大小声明,要么在声明时初始化,而不是作为参数传递,感谢下投票者)。
在C中常见的解决方案是传递第二个描述数组元素数量的参数。 编辑:
抱歉,错过了不想改变方法签名的部分。那么除非有一些数据不允许在数组中出现,否则没有其他解决方案,可以将其用作终止符(在C字符串中为0,-1也相当常见,但这取决于您的实际数据类型,假设字符数组是虚构的)。

1
-1. (a) 声明没有指定大小的数组可以从其初始化器中推断出大小,并且可以使用sizeof()访问该大小。 (b) 使用函数模板(根据Josh Kelley的答案)确实允许您推断传递给函数的数组的大小。 - j_random_hacker

1
为了让一个函数知道传递给它的数组中有多少项,你必须做以下两件事之一:
  1. 传递一个大小参数
  2. 以某种方式将大小信息放入数组中。

你可以用以下几种方法来实现后者:

  • 用 NULL 或其他在正常数据中不会出现的标记终止它。
  • 如果数组保存数字,则将项目计数存储在第一个条目中。
  • 如果数组包含指针,则存储指向最后一个条目的指针。

即使编译器也认为他需要更改指纹... http://c-faq.com/aryptr/aryptrparam.html - kfsone

0
尝试使用strlen(charArray);使用cstring头文件。这将产生包括空格在内的字符数,直到达到闭合“。”

-1

你保证在32位PC中会收到4,这是正确的答案。原因可以在这里这里找到解释。 简短的回答是你实际上正在测试指针的sizeof,而不是数组,因为“数组隐式转换或衰减为指针。指针遗憾地不存储数组的维度;它甚至不告诉你所涉及的变量是一个数组。”

现在你正在使用C++,boost::array比原始数组更好的选择。因为它是一个对象,你不会失去数组的维度信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接