【问题标题】:Write the prototype for a function that takes an array of exactly 16 integers编写一个函数的原型,该函数需要一个正好包含 16 个整数的数组
【发布时间】:2011-06-10 06:35:23
【问题描述】:

其中一个面试问题要求我“编写一个 C 函数的原型,该函数需要一个恰好包含 16 个整数的数组”,我想知道它可能是什么?也许是这样的函数声明:

void foo(int a[], int len);

还是别的什么?

如果语言是 C++ 呢?

【问题讨论】:

    标签: c++ c arrays function-prototypes


    【解决方案1】:

    在 C 中,这需要一个指向 16 个整数数组的指针:

    void special_case(int (*array)[16]);
    

    它会被调用:

    int array[16];
    special_case(&array);
    

    在 C++ 中,您也可以使用对数组的引用,如 Nawaz 的答案所示。 (题主问的是C,原来标签里只提到了C++。)


    使用以下变体的任何版本:

    void alternative(int array[16]);
    

    最终等同于:

    void alternative(int *array);
    

    实际上可以接受任何大小的数组。


    问了这个问题 - special_case() 真的会阻止传递不同大小的数组吗?答案是“是”。

    void special_case(int (*array)[16]);
    
    void anon(void)
    {
    
        int array16[16];
        int array18[18];
        special_case(&array16);
        special_case(&array18);
    }
    

    编译器(MacOS X 10.6.6 上的 GCC 4.5.2,碰巧)抱怨(警告):

    $ gcc -c xx.c
    xx.c: In function ‘anon’:
    xx.c:9:5: warning: passing argument 1 of ‘special_case’ from incompatible pointer type
    xx.c:1:6: note: expected ‘int (*)[16]’ but argument is of type ‘int (*)[18]’
    $
    

    更改为 GCC 4.2.1 - 由 Apple 提供 - 警告是:

    $ /usr/bin/gcc -c xx.c
    xx.c: In function ‘anon’:
    xx.c:9: warning: passing argument 1 of ‘special_case’ from incompatible pointer type
    $
    

    4.5.2中的警告更好,但实质是一样的。

    【讨论】:

    • 它真的会阻止用户执行 array[17] 或 array[15] 吗?当我认为 int [] 到底是什么时,在后台它是一个指向非动态数组的 int 指针。那么程序/编译器会自动拒绝不同的长度,还是会接受它并尽可能地处理它?
    • 这完全正确,我测试后删除了我的答案。我认为我可能会因为静态 (AUFS) 的另一种用途而变得棘手并使用 C99 的 void foo(int a[static 16]);,但我刚刚意识到保证 至少 16 个成员不是 完全 所希望的OP
    • 所以如果我传递一个大小不是 16 的数组,它不会编译?假设我有 int intArray[20];特殊情况(&intArray)。那会编译吗?
    • @armanali:这取决于您的编译器和编译器选项。您应该收到有关类型不匹配的警告;除非您将所有警告都变成错误,否则您可能不会收到错误。如问题所示,GCC会发出警告但会编译代码,除非您包含-Werror
    • 这种结构void f(int (*array)[16]) 是否适用于 C90,还是需要 C99 或更高版本?
    【解决方案2】:

    有几种方法可以声明固定大小的数组参数:

    void foo(int values[16]);
    

    接受任何指向int 的指针,但数组大小用作文档

    void foo(int (*values)[16]);
    

    接受一个指向正好有 16 个元素的数组的指针

    void foo(int values[static 16]);
    

    接受指向具有至少 16 个元素的数组的第一个元素的指针

    struct bar { int values[16]; };
    void foo(struct bar bar);
    

    接受一个结构,该结构将一个正好包含 16 个元素的数组装箱,并按值传递它们。

    【讨论】:

    • void foo(int values[static 16]); 的情况下,C 标准不要求编译器验证给定的参数是否正确。而且,AFAIK,甚至没有编译器尝试这样做。因此,虽然理论上它很好,但目前它实际上并没有给你带来任何安全——如果你用更小的数组调用它,就不会发出警告。希望编译器会变得更好。
    • 实际上,如果您传递的数组太短,clang 会发出警告。但是,如果您通过 &local_int,它不会发出任何警告,即使这意味着您实际上给出了一个长度为 1 的数组。
    【解决方案3】:

    & 在 C++ 中是必需的:

    void foo(int (&a)[16]); // & is necessary. (in C++)
    

    注意:& 是必须的,否则你可以传递任意大小的数组!


    对于 C:

    void foo(int (*a)[16]) //one way
    {
    }
    
    typedef int (*IntArr16)[16]; //other way
    void bar(IntArr16 a)
    {
    }
    
    int main(void) 
    {
            int a[16];
            foo(&a); //call like this - otherwise you'll get warning!
            bar(&a); //call like this - otherwise you'll get warning!
            return 0;
    }
    

    演示:http://www.ideone.com/fWva6

    【讨论】:

    • 引用不是 C 语言的一部分。
    • @Stephen:你是对的 - 但是虽然标题要求 C,但标签包括 C++。
    • @Jonathan:你说得对,但是当我们讨论这些 c 或 c++ 之类的概念时,它是一样的……不是吗……现在我在 Que 中添加了 c++
    • @Srikanth:不,对于这样的概念,C 和 C++ 并不相同,因为 C++ 有引用,而 C 没有引用。因此,使用引用的答案不适用于 C。它们是两种不同但相关的语言。
    • foo()bar() 中对a 执行操作并不像简单的表指针那样简单,这是由于额外的间接级别。不可能做到a[5]=1;,但等效的工作似乎是a[0][5]=1;。没什么大不了的,但阅读起来仍然不太清楚。更重要的是,我不确定如何强制执行 const 属性。似乎这样的事情行不通:` void foo(const int (*a)[16]) { (...) } void bar (int (*a)[16]) { foo(a); }` 指针被认为是不同的类型。
    【解决方案4】:

    我认为最简单的类型安全方法是声明一个包含数组的结构,然后传递它:

    struct Array16 {
      int elt[16];
    };
    
    
    void Foo(struct Array16* matrix);
    

    【讨论】:

      【解决方案5】:

      您已经得到了一些 C 的答案,以及 C++ 的答案,但是在 C++ 中还有另一种方法。

      正如 Nawaz 所说,要传递一个 N 大小的数组,您可以在 C++ 中执行此操作:

      const size_t N = 16; // For your question.
      
      void foo(int (&arr)[N]) {
          // Do something with arr.
      }
      

      但是,从 C++11 开始,您还可以使用 std::array 容器,它可以使用更自然的语法进行传递(假设您熟悉模板语法)。

      #include <array>
      
      const size_t N = 16;
      
      void bar(std::array<int, N> arr) {
          // Do something with arr.
      }
      

      作为一个容器,std::array 允许与普通 C 样式数组大致相同的功能,同时还添加了额外的功能。

      std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 };
      int arr2[5] = { 1, 2, 3, 4, 5 };
      
      // Operator[]:
      for (int i = 0; i < 5; i++) {
          assert(arr1[i] == arr2[i]);
      }
      
      // Fill:
      arr1.fill(0);
      for (int i = 0; i < 5; i++) {
          arr2[i] = 0;
      }
      
      // Check size:
      size_t arr1Size = arr1.size();
      size_t arr2Size = sizeof(arr2) / sizeof(arr2[0]);
      
      // Foreach (C++11 syntax):
      for (int &i : arr1) {
          // Use i.
      }
      for (int &i : arr2) {
          // Use i.
      }
      

      但是,据我所知(当时确实是有限的),指针算术对于 std::array 是不安全的,除非您首先使用成员函数 data() 来获取实际数组的地址。这既是为了防止将来对 std::array 类的修改破坏您的代码,也是因为某些 STL 实现可能会存储除实际数组之外的其他数据。


      请注意,这对于新代码最有用,或者如果您将预先存在的代码转换为使用 std::arrays 而不是 C 样式的数组。由于 std::arrays 是聚合类型,它们缺少自定义构造函数,因此您不能直接从 C 样式数组切换到 std::array (没有使用强制转换,但这很丑陋,并且可能在未来引起问题)。要转换它们,您需要使用类似这样的东西:

      #include <array>
      #include <algorithm>
      
      const size_t N = 16;
      
      std::array<int, N> cArrayConverter(int (&arr)[N]) {
          std::array<int, N> ret;
      
          std::copy(std::begin(arr), std::end(arr), std::begin(ret));
      
          return ret;
      }
      

      因此,如果您的代码使用 C 样式的数组,并且将其转换为使用 std::arrays 是不可行的,那么您最好还是坚持使用 C 样式的数组。

      (注意:我将大小指定为 N,以便您可以更轻松地在任何需要的地方重用代码。)


      编辑:有几件事我忘了提:

      1) 为在容器上操作而设计的大多数 C++ 标准库函数与实现无关;它们不是为特定容器设计的,而是使用迭代器在范围上操作。 (这也意味着它们适用于std::basic_string 及其实例化,例如std::string。)例如,std::copy 具有以下原型:

      template <class InputIterator, class OutputIterator>
      OutputIterator copy(InputIterator first, InputIterator last,
                          OutputIterator result);
      // first is the beginning of the first range.
      // last is the end of the first range.
      // result is the beginning of the second range.
      

      虽然这看起来很壮观,但您通常不需要指定模板参数,只需让编译器为您处理即可。

      std::array<int, 5> arr1 = { 1, 2, 3, 4, 5 };
      std::array<int, 5> arr2 = { 6, 7, 8, 9, 0 };
      std::string str1 = ".dlrow ,olleH";
      std::string str2 = "Overwrite me!";
      
      std::copy(arr1.begin(), arr1.end(), arr2.begin());
      // arr2 now stores { 1, 2, 3, 4, 5 }.
      
      std::copy(str1.begin(), str1.end(), str2.begin());
      // str2 now stores ".dlrow ,olleH".
      // Not really necessary for full string copying, due to std::string.operator=(), but possible nonetheless.
      

      由于依赖于迭代器,这些函数也与 C 风格的数组兼容(因为迭代器是指针的泛化,所有指针根据定义都是迭代器(但并非所有迭代器都必须是指针))。这在处理遗留代码时很有用,因为这意味着您可以完全访问标准库中的范围函数。

      int arr1[5] = { 4, 3, 2, 1, 0 };
      std::array<int, 5> arr2;
      
      std::copy(std::begin(arr1), std::end(arr1), std::begin(arr2));
      

      您可能已经从这个示例和最后一个示例中注意到std::array.begin()std::begin() 可以与std::array 互换使用。这是因为std::begin()std::end() 的实现使得对于任何容器,它们具有相同的返回类型,并返回相同的值,就像调用该容器实例的begin()end() 成员函数一样。

      // Prototype:
      template <class Container>
      auto begin (Container& cont) -> decltype (cont.begin());
      
      // Examples:
      std::array<int, 5> arr;
      std::vector<char> vec;
      
      std::begin(arr) == arr.begin();
      std::end(arr) == arr.end();
      
      std::begin(vec) == vec.begin();
      std::end(vec) == vec.end();
      
      // And so on...
      

      C 风格的数组没有成员函数,因此需要使用std::begin()std::end()。在这种情况下,这两个函数被重载以提供适用的指针,具体取决于数组的类型。

      // Prototype:
      template <class T, size_t N>
      T* begin (T(&arr)[N]);
      
      // Examples:
      int arr[5];
      
      std::begin(arr) == &arr[0];
      std::end(arr) == &arr[4];
      

      作为一般经验法则,如果您不确定任何特定代码段是否必须使用 C 样式数组,则使用 std::begin()std::end() 会更安全。

      [请注意,虽然我使用std::copy() 作为示例,但范围和迭代器的使用在标准库中非常常见。大多数(如果不是全部)设计为在容器上运行的函数(或者更具体地说,Container concept 的任何实现,例如 std::arraystd::vectorstd::vectorstd::string)使用范围,使它们与任何当前和未来的容器,以及 C 风格的数组。但是,我不知道这种广泛的兼容性可能存在例外情况。]

      2) 当按值传递 std::array 时,可能会有相当大的开销,具体取决于数组的大小。因此,通常最好通过引用传递它,或者使用迭代器(如标准库)。

      // Pass by reference.
      const size_t N = 16;
      
      void foo(std::array<int, N>& arr);
      

      3) 所有这些示例都假定代码中的所有数组都具有相同的大小,由常量N 指定。为了使您的代码更加独立于实现,您可以自己使用范围和迭代器,或者如果您想让代码专注于数组,请使用模板函数。 [基于this answer to another question。]

      template<size_t SZ> void foo(std::array<int, SZ>& arr);
      
      ...
      
      std::array<int, 5> arr1;
      std::array<int, 10> arr2;
      
      foo(arr1); // Calls foo<5>(arr1).
      foo(arr2); // Calls foo<10>(arr2).
      

      如果这样做,您甚至可以对数组的成员类型进行模板化,前提是您的代码可以对非 int 类型进行操作。

      template<typename T, size_t SZ>
      void foo(std::array<T, SZ>& arr);
      
      ...
      
      std::array<int, 5> arr1;
      std::array<float, 7> arr2;
      
      foo(arr1); // Calls foo<int, 5>(arr1).
      foo(arr2); // Calls foo<float, 7>(arr2).
      

      有关此操作的示例,请参阅here


      如果有人发现我可能遗漏的任何错误,请随时指出以供我修正,或自行修正。我想我都抓到了,但我不是 100% 确定。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-03-30
        • 1970-01-01
        • 2016-04-11
        • 1970-01-01
        • 2019-01-19
        • 2020-06-28
        • 2018-12-07
        相关资源
        最近更新 更多