Подтвердить что ты не робот

С++ правильный способ вернуть указатель на массив из функции

Я новичок в С++ и избегаю указателей. Из того, что я читал в Интернете, я не могу вернуть массив, но я могу вернуть указатель на него. Я сделал небольшой код, чтобы проверить его, и задавался вопросом, был ли это нормальный/правильный способ сделать это:

#include <iostream>
using namespace std;

int* test (int in[5]) {
    int* out = in;
    return out;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* pArr = test(arr);
    for (int i = 0; i < 5; i++) cout<<pArr[i]<<endl;
    cout<<endl;
    return 0;
}

Изменить. Кажется, это нехорошо. Как мне переписать его?

int* test (int a[5], int b[5]) {
    int c[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    int* out = c;
    return out;
}

Изменить (снова): Спасибо, здесь есть много полезных ответов:) Я закончил с этим как функцию:

int* test (int* a, int* b) {
    int* c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

и это можно назвать:

int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
int* m3 = test(m1, m2);
for (int i = 0; i < 5; i++) cout<<m3[i]<<endl;
cout<<endl;
delete[] m3;
4b9b3361

Ответ 1

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

  • Когда вы создаете массив с синтаксисом int arr[5];, он выделяется в стеке и является локальным для этой функции.
  • С++ позволяет вам возвращать указатель на этот массив, но это undefined поведение использовать память, на которую указывает этот указатель, за пределами его локальной области. Прочитайте этот отличный ответ, используя реальную мировую аналогию, чтобы получить ясное понимание, чем то, что я когда-либо мог бы объяснить.
  • Вы все равно можете использовать массив вне области видимости, если вы можете гарантировать, что память массива не будет очищена. В вашем случае это верно, когда вы передаете arr в test().
  • Если вы хотите передать указатели на динамически распределенный массив, не беспокоясь о утечке памяти, вы должны сделать некоторое чтение на std::unique_ptr/std::shared_ptr<>.

Изменить - ответить на прецедент матричного умножения

У вас есть два варианта. Наивный способ - использовать std::unique_ptr/std::shared_ptr<>. Современный С++ способ состоит в том, чтобы иметь класс Matrix, где вы перегружаете operator *, и вы абсолютно должны использовать новый rvalue references, если хотите избежать копирования результата умножения, чтобы вывести его из функции. Помимо наличия copy constructor, operator = и destructor, вам также необходимо иметь move constructor и move assignment operator. Просмотрите вопросы и ответы этого поиска, чтобы получить более полное представление о том, как достичь этого.

Изменить 2 - ответ на добавленный вопрос

int* test (int a[5], int b[5]) {
    int *c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

Если вы используете это как int *res = test(a,b);, то через некоторое время в коде вы должны вызвать delete []res, чтобы освободить память, выделенную в функции test(). Теперь вы видите, что очень сложно вручную отслеживать, когда звонить на delete. Отсюда и подходы к тому, как с этим бороться, если это указано в ответе.

Ответ 2

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

int* test (void)
{
    int out[5];
    return out;
}

Вышеизложенное никогда не будет работать, потому что out больше не существует, когда возвращается test(). Возвращаемый указатель больше не должен использоваться. Если вы его используете, вы будете читать/записывать в память, которую вы не должны.

В исходном коде массив arr выходит за пределы области видимости, когда возвращается main(). Очевидно, что никакой проблемы, поскольку возврат из main() также означает, что ваша программа завершается.

Если вы хотите что-то, что будет придерживаться и не может выйти из области видимости, вы должны выделить его с помощью new:

int* test (void)
{
    int* out = new int[5];
    return out;
}

Возвращаемый указатель всегда будет действительным. Помните, что удалите его снова, когда вы закончите с ним, используя delete[]:

int* array = test();
// ...
// Done with the array.
delete[] array;

Удаление это единственный способ вернуть память, которую он использует.

Ответ 3

Новый ответ на новый вопрос:

Вы не можете вернуть указатель на автоматическую переменную (int c[5]) из функции. Автоматическая переменная заканчивает свое время жизни блоком возврата (функция в этом случае) - поэтому вы возвращаете указатель на не существующий массив.

Либо сделайте свою переменную динамической:

int* test (int a[5], int b[5]) {
    int* c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

Или измените свою реализацию на использование std::array:

std::array<int,5> test (const std::array<int,5>& a, const std::array<int,5>& b) 
{
   std::array<int,5> c;
   for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
   return c;
}

Если ваш компилятор не предоставляет std::array, вы можете заменить его простой структурой, содержащей массив:

struct array_int_5 { 
   int data[5];
   int& operator [](int i) { return data[i]; } 
   int operator const [](int i) { return data[i]; } 
};

Старый ответ на старый вопрос:

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

int arr[5] = {1, 2, 3, 4, 5};
//int* pArr = test(arr);
int* pArr = arr;

Более эффективная подпись вашей функции:

int* test (int in[5])

Является эквивалентным:

int* test (int* in)

Итак, вы видите, что это не имеет смысла.

Однако эта подпись принимает массив, а не указатель:

int* test (int (&in)[5])

Ответ 4

Переменная, ссылающаяся на массив, в основном является указателем на ее первый элемент, поэтому да, вы можете законно вернуть указатель на массив, потому что thery're по существу то же самое. Проверьте это самостоятельно:

#include <assert.h>

int main() {
  int a[] = {1, 2, 3, 4, 5}; 

  int* pArr = a;
  int* pFirstElem = &(a[0]);

  assert(a == pArr);
  assert(a == pFirstElem);

  return 0;
}

Это также означает, что передача массива функции должна выполняться с помощью указателя (а не через int in[5]) и, возможно, вместе с длиной массива:

int* test(int* in, int len) {
    int* out = in;
    return out;
}

Тем не менее, вы правы, что использование указателей (без их полного понимания) довольно опасно. Например, ссылка на массив, который был выделен в стеке и вышел из области видимости, дает поведение undefined:

#include <iostream>

using namespace std;

int main() {
  int* pArr = 0;
  {
    int a[] = {1, 2, 3, 4, 5};
    pArr = a; // or test(a) if you wish
  }
  // a[] went out of scope here, but pArr holds a pointer to it

  // all bets are off, this can output "1", output 1st chapter
  // of "Romeo and Juliet", crash the program or destroy the
  // universe
  cout << pArr[0] << endl; // WRONG!

  return 0;
}

Итак, если вы недостаточно компетентны, просто используйте std::vector.

[ответ на обновленный вопрос]

Правильный способ записи вашей функции test заключается в следующем:

void test(int* a, int* b, int* c, int len) {
  for (int i = 0; i < len; ++i) c[i] = a[i] + b[i];
}
...
int main() {
   int a[5] = {...}, b[5] = {...}, c[5] = {};
   test(a, b, c, 5);
   // c now holds the result
}

Или это (используя std::vector):

#include <vector>

vector<int> test(const vector<int>& a, const vector<int>& b) {
  vector<int> result(a.size());
  for (int i = 0; i < a.size(); ++i) {
    result[i] = a[i] + b[i];
  }
  return result; // copy will be elided
}

Ответ 5

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

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

Ответ 6

Ваш код (который выглядит нормально) не возвращает указатель на массив. Он возвращает указатель на первый элемент массива.

На самом деле, обычно, что вы хотите делать. Большинство манипуляций с массивами выполняются с помощью указателей на отдельные элементы, а не с помощью указателей на массив в целом.

Вы можете определить указатель на массив, например:

double (*p)[42];

определяет p как указатель на 42-элементный массив double s. Большая проблема заключается в том, что вам нужно указать количество элементов в массиве как часть типа - и это число должно быть константой времени компиляции. Большинство программ, связанных с массивами, должны иметь дело с массивами разного размера; заданный размер массива не изменится после его создания, но его начальный размер не обязательно известен во время компиляции, а разные объекты массива могут иметь разные размеры.

Указатель на первый элемент массива позволяет использовать либо арифметику указателя, либо оператор индексирования [] для перемещения элементов массива. Но указатель не говорит вам, сколько элементов имеет массив; вы, как правило, должны сами отслеживать это.

Если функции необходимо создать массив и вернуть указатель на свой первый элемент, вам нужно управлять хранилищем для этого массива самостоятельно одним из нескольких способов. Вы можете передать вызывающему абоненту указатель на (первый элемент) объекта массива, возможно, вместе с другим аргументом, определяющим его размер, что означает, что вызывающий должен знать, насколько большой должен быть массив. Или функция может возвращать указатель на (первый элемент) статический массив, определенный внутри функции, что означает, что размер массива фиксирован, и тот же массив будет сбиваться вторым вызовом функции. Или функция может выделять массив в куче - что заставляет вызывающего абонента освобождать его позже.

Все, что я написал до сих пор, является общим для C и С++, и на самом деле это намного больше в стиле C, чем С++. В разделе 6 comp.lang.c FAQ обсуждается поведение массивов и указателей в C.

Но если вы пишете на С++, вам, вероятно, лучше использовать идиомы С++. Например, стандартная библиотека С++ предоставляет несколько заголовков, определяющих классы контейнеров, такие как <vector> и <array>, которые позаботятся об этом. Если у вас нет особых причин использовать необработанные массивы и указатели, вам, вероятно, лучше использовать только контейнеры на С++.

EDIT: Я думаю, вы отредактировали свой вопрос, когда я набирал этот ответ. Новый код в конце вашего вопроса, как вы, наблюдатель, не хорош; он возвращает указатель на объект, который перестает существовать, как только функция возвращается. Я думаю, что рассмотрел альтернативы.

Ответ 7

вы можете (сортировать) возвращать массив

вместо

int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
int* m3 = test(m1, m2);

записи

struct mystruct
{
  int arr[5];
};


int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
mystruct m3 = test(m1,m2);

где тест выглядит как

struct mystruct test(int m1[5], int m2[5])
{
  struct mystruct s;
  for (int i = 0; i < 5; ++i ) s.arr[i]=m1[i]+m2[i];
  return s;
}

не очень эффективен, так как он копирует, он предоставляет копию массива