【问题标题】:Passing an array as a function parameter in C++在 C++ 中将数组作为函数参数传递
【发布时间】:2010-10-01 11:30:12
【问题描述】:

在 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 数组是这样初始化的:

char charArray[] = "i am a string";

然后\0 已经附加到数组的末尾。在这种情况下,答案(标记为已接受)开箱即用,可以这么说。

【问题讨论】:

  • 其实这里有个比较大的误区。您示例中的语法没有意义:函数在堆栈上获取未知数量的数据。编译器实际上会忽略您的原始语法并为函数提供签名void doSomething(char* charArray)。如果您使用char array[10]; doSomething(array); 调用它,则不会保留任何数组。见c-faq.com/aryptr/aryptrparam.html

标签: c++ arrays pointers parameters arguments


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    伙计,你可以有一个全局变量来存储数组的大小,该数组在整个程序中都可以访问。至少您可以将数组的大小从 main() 函数传递给全局变量,并且您甚至不必更改方法签名,因为该大小将在全局范围内可用。

    请看例子:

    #include<...>
    using namespace std;
    
    int size; //global variable
    
    //your code
    
    void doSomething(char charArray[])
    {
        //size available
    
    }
    

    【讨论】:

      【解决方案3】:

      我认为你可以这样做:

      size_t size = sizeof(array)/sizeof(array[0]);
      

      PS:我觉得这个题目的标题也不对。

      【讨论】:

      • 不,OP中的所有陈述都是正确的。最大的问题是在定义void doSomething(char charArray[]) {/*...*/} 中,参数charArray 实际上并不是一个数组。
      【解决方案4】:

      保证您在 32 位 PC 中收到 4,这是正确答案。由于herehere 解释的原因。 简短的回答是,您实际上是在测试指针而不是数组的大小,因为“数组被隐式转换或衰减为指针。唉,指针不存储数组的维度;它不甚至告诉你有问题的变量是一个数组。”

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

      【讨论】:

        【解决方案5】:

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

        与已接受的解决方案相比,优势在于使用哨兵确定长度对于大字符串来说很慢。缺点显然是这是一个相当低级的 hack,既不考虑类型也不考虑结构。

        在下面给出的形式中,它也仅适用于长度

        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.
        }
        

        【讨论】:

        • 它有一个小问题,因为奇怪的 C++ 和 C 对 char 的处理。如果它是有符号的,当转换为 size_t 时,值 >127 可能会导致巨大的 size_t 值(环绕)。您可以通过先转换为 unsigned char 来修复它: static_cast<:size_t>((unsigned char) charArray[0]);
        【解决方案6】:

        这实际上是 C 而非 C++,在 C++ 中您可能更愿意使用 std::vector。但是,在 C 中无法知道数组的大小。如果数组是在当前范围内声明的,编译将允许您执行 sizeof,并且仅当它使用大小显式声明时(EDIT: 和“具有大小”,我的意思是它要么用整数大小声明,要么在声明时初始化,而不是作为参数传递,谢谢你的反对)。

        C 中常见的解决方案是传递第二个参数来描述数组中元素的数量。

        编辑:
        抱歉,错过了不想更改方法签名的部分。然后除了其他人描述的那样没有解决方案,如果数组中有一些不允许的数据,它可以用作终止符(C字符串中的0,-1也很常见,但这取决于你的实际数据类型,假设 char 数组是假设的)

        【讨论】:

        • -1。 (a) 声明没有大小的数组从它们的初始化器中推断出它们的大小,并且可以使用 sizeof() 访问这个大小。 (b) 改用函数模板(根据 Josh Kelley 的回答)确实 允许您推断传递给函数的数组的大小。
        【解决方案7】:

        一般来说,在使用 C 或低级 C++ 时,您可能会考虑重新训练您的大脑,不要考虑将数组参数写入函数,因为 C 编译器无论如何都会将它们视为指针。从本质上讲,通过键入这些方括号,您是在自欺欺人地认为正在传递一个真正的数组,其中包含大小信息。实际上,在 C 中您只能传递指针。功能

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

        从 C 编译器的角度来看,完全等同于:

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

        显然 nekkid char 指针不包含长度信息。

        如果您陷入困境并且无法更改函数签名,请考虑使用上述建议的长度前缀。一个不可移植但兼容的技巧是在位于数组之前的 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,但这个技巧可以在紧要关头摆脱困境。

        【讨论】:

        • 这不是未定义的行为吗?
        【解决方案8】:

        不改变签名?附加一个哨兵元素。特别是对于 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 元素的数组,并且始终设置哨兵元素。

        但是,如果您确实无法更改签名,那么您的选择就相当有限。

        【讨论】:

        • 哨兵元素技术甚至适用于没有无效元素的数据,但在这种情况下,您需要在数据进入时对其进行转义,在数据出现时对其进行转义。但到那时,可能是时候改用类了。
        【解决方案9】:

        使用模板。这在技术上不符合您的标准,因为它会更改签名,但不需要修改调用代码。

        void doSomething(char charArray[], size_t size)
        {
           // do stuff here
        }
        
        template<size_t N>
        inline void doSomething(char (&charArray)[N])
        {
            doSomething(charArray, N);
        }
        

        Microsoft 的 Secure CRT functions 和 STLSoft 的 array_proxy 类模板使用了这种技术。

        【讨论】:

          【解决方案10】:

          为了让函数知道传递给它的数组中的项目数,您必须做以下两件事之一:

          1. 传入一个大小参数
          2. 以某种方式将大小信息放入数组中。

          您可以通过以下几种方式完成后者:

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

          【讨论】:

          【解决方案11】:

          您无法单独从charArray 确定大小。该信息不会自动传递给函数。

          当然,如果它是一个以 null 结尾的字符串,您可以使用 strlen(),但您可能已经考虑过!

          考虑传递一个std::vector&lt;char&gt; & 参数,或者一对指针,或者一个指针加上一个大小参数。

          【讨论】:

          • 我认为 Yuval 对函数签名的限制。
          • @finnw 那是因为它根本不传递数组,它只是传递一个指向它的指针。
          【解决方案12】:

          如果它是空终止的,strlen() 会起作用。

          【讨论】:

          • 是的,这是问题中 char 数组的完美答案,但不适用于其他数据类型。
          • 除了写doSomething(NULL); 是完全合法的事实之外。 编译器将 OP 指纹更改为 char* charArray :) c-faq.com/aryptr/aryptrparam.html
          猜你喜欢
          • 2016-06-12
          • 1970-01-01
          • 2015-09-26
          • 1970-01-01
          • 2010-09-05
          • 2023-03-03
          相关资源
          最近更新 更多