【问题标题】:How C++ compiler knows the length of an array [duplicate]C ++编译器如何知道数组的长度[重复]
【发布时间】:2015-01-26 00:19:39
【问题描述】:

比如在main函数中,我可以写int arr[42],我可以用range for loop而不指明它的长度for (auto i : arr)。它运作良好。编译器会知道它的长度。

如果我将此数组传递给另一个函数,例如void foo(int arr[]),我不能使用 range for 循环,因为编译器不知道它的长度。

毕竟,如果我有这个模板template<class T, size_t N> void arr_init(T (&foo)[N]),我可以通过arr_init(x) 调用它。显然,编译器调用arr_init(int (&)[42])。

我猜编译器通过声明int[42] 知道数组的长度。我对吗?当我使用 range for 循环和学习模板时,我遇到了这个问题。我以前用C的时候没有遇到过这样的问题。

【问题讨论】:

  • 如果您认为void foo(int arr[]) 与声明void foo(int* arr) 完全没有区别,这可能会有所帮助。
  • 为什么不让函数看起来像foo(int * arr, int arrSize)?
  • @peppe 基于范围的 for 不使用 std::begin/std::end。它被指定为与那些函数模板类似的行为方式。
  • @user657267,@Ubica。这只是显示编译器行为的示例。我知道实际上我在使用这个函数foo(int *arr) 时传递了一个指针。谢谢:)
  • @LeeRancher 您在 both 这两个函数中传递了一个指针,arr 是一个指针,无论您将其声明为 int* arr 还是 int arr[] .

标签: c++ c++11


【解决方案1】:

数组类型T[N] 与数组类型T[M] 不同,类型为T 和两个不同的大小N 和M。所以,数组的长度是内置在类型中的。

OTOH,数组自动decayed 指向函数调用参数列表中的指针。也就是说,这三个函数签名没有区别:

int func(int []);
int func(int[5]);
int func(int[10]);
int func(int*);

以上所有签名都只是简单地采用int*。如果要保留数组类型,则需要创建一个将数组引用作为参数的函数。这正是您在示例template<class T, size_t N> void arr_init(T (&foo)[N]) 中实现的。在arr_init 内部,foo 的行为类似于数组,因为它是对数组类型的引用。

对于T[N] 类型的数组arr,range-based for 分别使用表达式arr 和arr+N 作为起点和终点。

对于类类型,成员函数begin 和end(如果存在)用于确定端点,否则使用非成员重载begin 和end。

(begin和end,与std::begin和std::end相反,因为begin和end在标准库中的通用用法是这样的:

using std::begin;
begin(...);
using std::end;
end(...);

这允许 ADL 找到用户定义的 begin 和 end 重载。)

【讨论】:

    【解决方案2】:

    是的,你是对的。这是因为编译器知道在同一个翻译单元中定义的对象的大小。

    事实上,你不需要使用 range for 循环来查看类似的行为;你可以使用普通的sizeof。

    foo.c:

    #include <stdio.h>
    
    extern int a[];
    
    int main()
    {
        printf("%d\n", sizeof(a));
    }
    

    bar.c:

    int a[50];
    

    现在开始测试:

    gcc foo.c bar.c
    > foo.c: In function 'main':
    > foo.c:7:23: error: invalid application of 'sizeof' to incomplete type 'int[]' 
    

    【讨论】:

    • 谢谢,使用sizeof 真是个好主意。
    • 对不起,但我不知道这如何回答 整个 帖子,但由于 OP 已接受它,我假设 (s)/他可能只是复制粘贴出于好奇而从网站上收集的一些东西
    【解决方案3】:

    每当您通过引用传递数组时,您都可以推断出它的大小(请记住,数组不是指针)。所以基于范围的for使用的函数std::begin()和std::end()对于数组可能像这样重载

    namespace std
    {
        template<typename T, std::size_t N>
        T* std::begin(T (&arr)[N])
        {
            return arr;
        }
    
        template<typename T, std::size_t N>
        T* std::end(T (&arr)[N])
        {
            return arr + N;
        }
    }
    

    数组只有在按值传递时才会衰减为指针,例如

    void f(int arr[256]){...}
    

    这是语法糖

    void f(int* arr){...}
    

    PS:看起来基于范围的for 不使用std::begin 和std::end,因为编译器在编译时知道数组的大小(如@T.C. 所述)。所以上面的代码只是为了演示目的;)

    有关基于范围的for 的更多详细信息,请参阅标准的6.5.4:

    在每种情况下,基于范围的 for 语句都等价于

        {
           auto && __range = range-init;
           for ( auto __begin = begin-expr,
                      __end = end-expr;
                 __begin != __end;
                  ++__begin ) { 
               for-range-declaration = *__begin; 
               statement
           } 
        }
    

    6.4.3/1 如果 _RangeT 是数组类型,begin-expr 和 end-expr 是 __range em> 和 __range + __bound,其中 __bound 是数组边界。如果 _RangeT 是一个未知大小的数组或一个不完整类型的数组,则程序是非良构的;

    【讨论】:

    • 基于范围的for不使用std::begin和std::end。
    • @T.C.不知道这个,那它是如何工作的?我浏览了一些 S.O.如果通过基于范围的fors 使用std::begin/end,您可能会遇到一些ADL问题。
    • 我看到 6.5.4/1 描述了机制......不完全清楚,但我猜它对 begin-expr 和 end-expr
    • 编译器知道数组类型的大小。它为具有begin()/end() 成员的数组和类指定了这种方式,因此如果您只想迭代它,则不必包含&lt;iterator&gt;。
    猜你喜欢
    • 2017-06-20
    • 1970-01-01
    • 2015-01-15
    • 2018-08-11
    • 1970-01-01
    • 2020-10-10
    • 2021-10-21
    • 2012-11-30
    • 1970-01-01
    相关资源
    最近更新 更多