Возвращаем массив в функцию - PullRequest
178 голосов
/ 13 августа 2010

У меня есть массив int arr[5], который передается в функцию fillarr(int arr[]):

int fillarr(int arr[])
{
    for(...);
    return arr;
}
  1. Как я могу вернуть этот массив?
  2. Как я буду его использовать, скажем, я вернул указатель, как мне получить к нему доступ?

Ответы [ 15 ]

169 голосов
/ 13 августа 2010

В этом случае переменная массива 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;
}
99 голосов
/ 13 августа 2010

Функции 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 просто генерирует struct, содержащиймассив в стиле C, так что вы можете применять объектно-ориентированную семантику, сохраняя при этом первоначальную простоту массива.

19 голосов
/ 13 августа 2010

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);
}
16 голосов
/ 10 мая 2014

В 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;
}
14 голосов
/ 13 августа 2010

ответ может немного зависеть от того, как вы планируете использовать эту функцию. Для простейшего ответа давайте решим, что вместо массива вам действительно нужен вектор. Векторы хороши тем, что выглядят для всего мира как скучные, обычные значения, которые вы можете хранить в обычных указателях. Мы рассмотрим другие варианты и почему вы хотите их потом:

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 работает аналогично использованию простого ванильного вектора. В этом примере также изменяется список параметров путем добавления ключевого слова const. Теперь вы получаете ссылку, не копируя ее, но не можете ее изменить, поэтому вызывающая сторона знает, что она будет такой же, как и до того, как функция добралась до нее.

Все это прекрасно, но идиоматический c ++ редко работает с коллекциями в целом. Обычно вы будете использовать итераторы для этих коллекций. это будет выглядеть примерно так

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.

Что действительно хорошо в этом, так это то, что он одинаково хорошо работает с вектором, как с простыми массивами C и многими другими типами коллекций, например

int arr[100];
int *foo = fillarr(arr, arr+100);

Что сейчас выглядит очень похоже на примеры простых указателей, приведенные в других местах этого вопроса.

8 голосов
/ 13 августа 2010

Это:

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)

Таким образом, вы можете использовать длину, чтобы заполнить массив до его длины, независимо от того, какой он есть.

Для правильного использования. Сделайте что-то вроде этого:

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

5 голосов
/ 11 октября 2012

чтобы вернуть массив из функции, давайте определим этот массив в структуре;Так это выглядит примерно так:

struct 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 getMarks(){
 return marks_list;
}
4 голосов
/ 03 ноября 2017

Это довольно старый вопрос, но я собираюсь поставить свои 2 цента, так как есть много ответов, но ни один из них не показывает все возможные методы в четкой и сжатой форме (не уверен насчет краткого бита, так какэто немного вышло из-под контроля. TL; DR ?).

Я предполагаю, что OP хотел вернуть массив, который был передан без копирования, в качестве средства прямой передачи его вызывающей сторонепередается другой функции, чтобы код выглядел красивее.

Однако использовать такой массив - значит позволить ему распадаться на указатель и компилятор обрабатывать его как массив.Это может привести к незначительным ошибкам, если вы передадите массив, например, с функцией, ожидающей, что он будет иметь 5 элементов, но ваш вызывающий код фактически передает другое число.

Есть несколько способов, которыми вы можете справиться с этим лучше,Введите std::vector или std::array (не уверен, был ли std::array примерно в 2010 году, когда был задан вопрос).Затем вы можете передать объект в качестве ссылки без какого-либо копирования / перемещения объекта.

std::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;
}

За исключением того, что это выглядит уродливо и очень трудно читать.Теперь я использую кое-что, чтобы помочь с тем, чего не было в 2010 году, что я также использую для указателей на функции:

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
}

Подводя итог, лучше не допускать распада массива в указатель, если вы собираетесьперебрать его.Это просто плохая идея, так как она не дает компилятору защитить вас от удара ногой и затрудняет чтение кода.Всегда старайтесь помочь компилятору помочь вам, сохраняя типы как можно дольше, если только у вас нет веской причины не делать этого.

Редактировать

О, и для полноты вы можете разрешитьэто ухудшить до указателя, но это отделяет массив от числа элементов, которые он содержит.Это много делается в C / C ++ и обычно смягчается передачей количества элементов в массиве.Однако компилятор не может помочь вам, если вы допустили ошибку и передали неверное значение числу элементов.

// separate size value
int* fillarr(int* arr, size_t size)
{
    for(int* it = arr; it != arr + size; ++it)
    { /* do stuff */ }
    return arr;
}

Вместо передачи размера вы можете передать указатель конца, который будетукажите на один конец вашего массива.Это полезно, так как это делает что-то более похожее на алгоритмы std, которые принимают указатель начала и конца, но то, что вы возвращаете, теперь только то, что вы должны запомнить.

// 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])));
}

Достаточно забавно, это очень похоже на то, как std::initializer_list работает (c ++ 11), но они не работают в этом контексте.

3 голосов
/ 29 мая 2015

Самый простой способ сделать это - вернуть его по ссылке, даже если вы не пишете символ '&', он автоматически возвращается по ссылке

     void fillarr(int arr[5])
  {
       for(...);

  }
2 голосов
/ 13 августа 2010
int *fillarr(int arr[])

Вы все еще можете использовать результат как

int *returned_array = fillarr(some_other_array);
if(returned_array[0] == 3)
    do_important_cool_stuff();
...