我有一个数组 int arr[5]
,它被传递给一个函数 fillarr(int arr[])
:
int fillarr(int arr[])
{
for(...);
return arr;
}
- 我如何返回这个数组?
- 如果我返回一个指针,我将如何访问它?
arr
实际上也可以被视为一个指向内存中数组块开头的指针,通过隐式转换。你使用的语法如下:
在这种情况下,你的数组变量 arr
实际上也可以被视为一个指向内存中数组块开头的指针,通过隐式转换。
int fillarr(int arr[])
这只是一种语法糖。如果用下面的代码替换,也能正常工作:
int fillarr(int* arr)
所以在同样的意义上,你想从函数返回的实际上是指向数组第一个元素的指针:
int* fillarr(int arr[])
而且您仍然可以像使用普通数组一样使用它:
int main()
{
int y[10];
int *a = fillarr(y);
cout << a[0] << endl;
}
C++函数无法按值返回C语言风格的数组。最接近的方法是返回一个指针。此外,参数列表中的数组类型会被简单转换为指针。
int *fillarr( int arr[] ) { // arr "decays" to type int *
return arr;
}
你可以通过对参数和返回值使用数组引用来改善它,这可以防止衰减:
int ( &fillarr( int (&arr)[5] ) )[5] { // no decay; argument must be size 5
return arr;
}
使用Boost或C++11时,按引用传递只是可选的,并且语法更加易懂:
array< int, 5 > &fillarr( array< int, 5 > &arr ) {
return arr; // "array" being boost::array or std::array
}
array
模板只是生成一个包含 C 风格数组的 struct
,因此您可以应用面向对象的语义并保留数组的原始简单性。
typedef int array[5]; array& foo();
但是,如果你愿意编写以下内容,则不需要typedef:int (&foo())[5] { static int a[5] = {}; return a; }
,问题中的示例将是:int (&foo( int (&a)[5] ))[5] { return a; }
。很简单,不是吗? - David Rodríguez - dribeastypedef int arri5[5]; arri5 &fillarr( arri5 & arr ) { return arr; }
相同。最好在这里使用typedef。 - Potatoswatter在C++11中,您可以返回std::array
。
#include <array>
using namespace std;
array<int, 5> fillarr(int arr[])
{
array<int, 5> arr2;
for(int i=0; i<5; ++i) {
arr2[i]=arr[i]*2;
}
return arr2;
}
$8.3.5/8 规定-
"函数的返回类型不能是数组或函数,但可以是指向这些类型的指针或引用。不允许定义函数的数组,但可以定义指向函数的指针的数组。"
int (&fn1(int (&arr)[5]))[5]{ // declare fn1 as returning refernce to array
return arr;
}
int *fn2(int arr[]){ // declare fn2 as returning pointer to array
return arr;
}
int main(){
int buf[5];
fn1(buf);
fn2(buf);
}
int
类型的指针,而不是一个数组。 - GManNickG答案可能会有所不同,具体取决于您计划如何使用该函数。为了给出最简单的答案,让我们假设您真正需要的是一个向量而不是数组。向量很好用,因为它们看起来就像普通的、无聊的值,可以存储在常规指针中。我们将在之后查看其他选项及其原因:
std::vector<int> fillarr( std::vector<int> arr ) {
// do something
return arr;
}
std::vector
确保一切处理得干净利落。坏处在于,如果您的数组很大,这将复制大量数据。实际上,它会两次复制每个数组元素。首先,它复制向量以便函数可以将其用作参数。然后它再次复制它以将其返回给调用方。如果您能够自己管理向量,那么您可以更轻松地完成某些操作。(如果调用方需要将其存储在某种变量中以进行更多计算,则可能需要第三次复制)void fillarr(std::vector<int> & arr) {
// modify arr
// don't return anything
}
std::auto_ptr<std::vector<int> > fillarr( const std::vector<int> & arr) {
std::auto_ptr<std::vector<int> > myArr(new std::vector<int>);
// do stuff with arr and *myArr
return myArr;
}
*myArr
与使用普通的vector相同。此示例还通过添加const
关键字来修改参数列表。现在,您可以获得一个引用而不是复制它,但您无法修改它,因此调用者知道它将与函数到达之前相同。template <class Iterator>
Iterator fillarr(Iterator arrStart, Iterator arrEnd) {
Iterator arrIter = arrStart;
for(;arrIter <= arrEnd; arrIter++)
;// do something
return arrStart;
}
如果你不习惯这种风格,使用它看起来有点奇怪。
vector<int> arr;
vector<int>::iterator foo = fillarr(arr.begin(), arr.end());
现在,foo指向修改后的arr
开头。
这个方法同样适用于vector和普通的C数组以及其他许多类型的集合。
int arr[100];
int *foo = fillarr(arr, arr+100);
现在它看起来非常像在这个问题的其他地方给出的普通指针示例。
&
符号必须出现在类型后面:void fillarr(std::vector<int> & arr)
。 - David Rodríguez - dribeas这个:
int fillarr(int arr[])
实际上被视为相同:
int fillarr(int *arr)
如果你真的想要返回一个数组,你可以将那一行改为:
int * fillarr(int arr[]){
// do something to arr
return arr;
}
它实际上并没有返回一个数组,而是返回指向数组地址开始的指针。
但请记住,当你传入数组时,你只是传入指针。所以当你修改数组数据时,你实际上是在修改指针指向的数据。因此,在传入数组之前,你必须意识到你已经在外部获得了修改后的结果。
例如:
int fillarr(int arr[]){
array[0] = 10;
array[1] = 5;
}
int main(int argc, char* argv[]){
int arr[] = { 1,2,3,4,5 };
// arr[0] == 1
// arr[1] == 2 etc
int result = fillarr(arr);
// arr[0] == 10
// arr[1] == 5
return 0;
}
我建议你考虑在 fillarr 函数中加入类似于这样的长度限制。
int * fillarr(int arr[], int length)
这样你可以使用 length 将数组填充到它的长度,无论它是多少。
要正确地使用它,可以像这样做:
int * fillarr(int arr[], int length){
for (int i = 0; i < length; ++i){
// arr[i] = ? // do what you want to do here
}
return arr;
}
// then where you want to use it.
int arr[5];
int *arr2;
arr2 = fillarr(arr, 5);
// at this point, arr & arr2 are basically the same, just slightly
// different types. You can cast arr to a (char*) and it'll be the same.
如果您只是想设置数组的默认值,请考虑使用内置的memset函数。
例如: memset((int*)&arr, 5, sizeof(int));
顺便说一下,您说您正在使用C ++。看一下使用STL向量的方法。您的代码可能更加健壮。
有很多教程。这里有一个可以让您了解如何使用它们。 http://www.yolinux.com/TUTORIALS/LinuxTutorialC++STL.html
std::copy
而不是memset
,它更安全、更容易。(而且如果不是更快的话,也同样快。) - GManNickGstd::array<int, 5>& fillarr(std::array<int, 5>& arr)
{
// (before c++11)
for(auto it = arr.begin(); it != arr.end(); ++it)
{ /* do stuff */ }
// Note the following are for c++11 and higher. They will work for all
// the other examples below except for the stuff after the Edit.
// (c++11 and up)
for(auto it = std::begin(arr); it != std::end(arr); ++it)
{ /* do stuff */ }
// range for loop (c++11 and up)
for(auto& element : arr)
{ /* do stuff */ }
return arr;
}
std::vector<int>& fillarr(std::vector<int>& arr)
{
for(auto it = arr.begin(); it != arr.end(); ++it)
{ /* do stuff */ }
return arr;
}
但是,如果您坚持使用C数组,请使用一个模板来保留数组中有多少项的信息。
template <size_t N>
int(&fillarr(int(&arr)[N]))[N]
{
// N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
for(int* it = arr; it != arr + N; ++it)
{ /* do stuff */ }
return arr;
}
template <typename T>
using type_t = T;
template <size_t N>
type_t<int(&)[N]> fillarr(type_t<int(&)[N]> arr)
{
// N is easier and cleaner than specifying sizeof(arr)/sizeof(arr[0])
for(int* it = arr; it != arr + N; ++it)
{ /* do stuff */ }
return arr;
}
这将把类型移动到人们期望的位置,使其更易读。当然,如果您只使用5个元素,则使用模板是多余的,因此您可以硬编码它:
type_t<int(&)[5]> fillarr(type_t<int(&)[5]> arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
type_t<>
技巧是行不通的。那时候你最好能做到的就是在结构体中使用一个类型:template<typename T>
struct type
{
typedef T type;
};
typename type<int(&)[5]>::type fillarr(typename type<int(&)[5]>::type arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
这看起来又有些难看了,但至少还是比较可读的,尽管在那个时候,依赖于编译器,typename
可能是可选的,导致以下结果:
type<int(&)[5]>::type fillarr(type<int(&)[5]>::type arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
typedef int(&array5)[5];
array5 fillarr(array5 arr)
{
// Prefer using the compiler to figure out how many elements there are
// as it reduces the number of locations where you have to change if needed.
for(int* it = arr; it != arr + sizeof(arr)/sizeof(arr[0]); ++it)
{ /* do stuff */ }
return arr;
}
过去,免费函数std::begin()
和std::end()
并不存在,尽管很容易实现。这将使得在C数组上迭代更加安全,但在指针上却不合适。
至于访问该数组,您可以将其传递给另一个接受相同参数类型的函数,或对其进行别名处理(这似乎没有太大意义,因为你已经在该作用域中有原始数组)。访问数组引用就像访问原始数组一样。
void other_function(type_t<int(&)[5]> x) { /* do something else */ }
void fn()
{
int array[5];
other_function(fillarr(array));
}
或者
void fn()
{
int array[5];
auto& array2 = fillarr(array); // alias. But why bother.
int forth_entry = array[4];
int forth_entry2 = array2[4]; // same value as forth_entry
}
// separate size value
int* fillarr(int* arr, size_t size)
{
for(int* it = arr; it != arr + size; ++it)
{ /* do stuff */ }
return arr;
}
不需要传递数组的大小,而是可以传递一个指向数组末尾后一位的指针。这样做有助于使代码更接近于标准算法中使用的begin和end指针,但返回的只是一个必须记住的内容。
// separate end pointer
int* fillarr(int* arr, int* end)
{
for(int* it = arr; it != end; ++it)
{ /* do stuff */ }
return arr;
}
另外一种方法是记录下该函数只能够使用5个元素,并希望调用该函数的用户不会做出任何愚蠢的操作。
// I document that this function will ONLY take 5 elements and
// return the same array of 5 elements. If you pass in anything
// else, may nazal demons exit thine nose!
int* fillarr(int* arr)
{
for(int* it = arr; it != arr + 5; ++it)
{ /* do stuff */ }
return arr;
}
std::pair<int*,int*>
,用于开始和结束,并在周围传递它,但这样看起来就不像一个数组了。std::pair<int*, int*> fillarr(std::pair<int*, int*> arr)
{
for(int* it = arr.first; it != arr.second; ++it)
{ /* do stuff */ }
return arr; // if you change arr, then return the original arr value.
}
void fn()
{
int array[5];
auto array2 = fillarr(std::make_pair(&array[0], &array[5]));
// Can be done, but you have the original array in scope, so why bother.
int fourth_element = array2.first[4];
}
或者
void other_function(std::pair<int*, int*> array)
{
// Can be done, but you have the original array in scope, so why bother.
int fourth_element = array2.first[4];
}
void fn()
{
int array[5];
other_function(fillarr(std::make_pair(&array[0], &array[5])));
}
template <size_t N> auto fillarr(int (&arr)[N]) -> int(&)[N]
。虽然你的技巧很巧妙,但在这种情况下是不必要的,而且在我看来,它再次降低了代码的可读性,尤其是在表达这些类型时。 - undefinedarr
放在参数类型规范的中间。这是一个个人偏好的问题。 - undefinedint (&arr)[N]
和最后的 type_t<int(&)[N]> arr
有所不同。 - undefinedstruct Marks{
int list[5];
}
typedef struct Marks marks;
marks marks_list;
void setMarks(int marks_array[]){
for(int i=0;i<sizeof(marks_array)/sizeof(int);i++)
marks_list.list[i]=marks_array[i];
}
我们也可以返回数组。为了返回数组,函数的返回类型应该是结构体类型,即marks。这是因为实际上我们传递的是包含数组的结构体。因此,最终代码可能如下所示。
marks getMarks(){
return marks_list;
}
最简单的方法是通过引用返回它,即使你不写'&'符号,它也会自动被引用返回。
void fillarr(int arr[5])
{
for(...);
}
fillarr
。它会起作用的。 - undefinedint *fillarr(int arr[])
int *returned_array = fillarr(some_other_array);
if(returned_array[0] == 3)
do_important_cool_stuff();
5
会被编译器丢弃。 - David Rodríguez - dribeasvoid foo( int (&array)[5] );
(将一个包含5个整数的数组作为引用传递)。当你通过引用传递时,在函数内部得到的是对实际类型的引用。另一方面,void foo(int array[5])
在函数定义期间被编译器转换为void foo(int*)
。调用foo(myarray)
会导致数组衰变为指向第一个元素的指针。 - David Rodríguez - dribeas