编写一个函数的原型,该函数采用正好16个整数的数组

其中一个面试问题让我“编写一个C函数的原型,它采用了16个整数的数组”,我想知道它可能是什么? 也许像这样的函数声明:

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

或者是其他东西?

如果语言是C ++而不是呢?

在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中的警告更好,但实质内容相同。

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

 void foo(int values[16]); 

接受任何指向int指针,但array-size用作文档

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

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

 void foo(int values[static 16]); 

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

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

接受一个装有16个元素的数组的结构,按值传递它们。

&在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

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

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

您已经获得了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  const size_t N = 16; void bar(std::array arr) { // Do something with arr. } 

作为容器,std :: array允许与普通C风格数组大致相同的function,同时还添加了其他function。

 std::array 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. } 

但是,根据我的知识(当时肯定是有限的),除非使用成员函数data()首先获取实际数组的地址,否则指针运算对于std :: array是不安全的。 这既是为了防止将来对std :: array类的修改破坏你的代码,也是因为一些STL实现除了实际的数组之外还可以存储其他数据。


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

 #include  #include  const size_t N = 16; std::array cArrayConverter(int (&arr)[N]) { std::array 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  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 arr1 = { 1, 2, 3, 4, 5 }; std::array 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 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  auto begin (Container& cont) -> decltype (cont.begin()); // Examples: std::array arr; std::vector 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  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()作为示例,但在标准库中使用范围和迭代器非常常见。 设计用于操作容器的大多数(如果不是全部)函数(或者更具体地说, 容器概念的任何实现,例如std::arraystd::vectorstd::string )使用范围,使它们与任何容器兼容当前和未来的容器,以及C风格的数组。 但是,我不了解这种广泛兼容性的例外情况。

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

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

3)所有这些示例都假设代码中的所有数组都具有相同的大小,由常量N指定。 为了使您的代码更多地与实现无关,您可以自己使用范围和迭代器,或者如果您希望将代码集中在数组上,请使用模板化函数。 [基于对另一个问题的回答 。]

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

如果这样做,你甚至可以模拟数组的成员类型,只要你的代码可以在int以外的类型上运行。

 template void foo(std::array& arr); ... std::array arr1; std::array arr2; foo(arr1); // Calls foo(arr1). foo(arr2); // Calls foo(arr2). 

有关此操作的示例,请参见此处 。


如果有人发现我可能错过的任何错误,请随意指出我修复,或自己修复它们。 我想我抓住了他们所有人,但我不是百分百肯定的。