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

Почему С++ не поддерживает функции, возвращающие массивы?

Некоторые языки позволяют просто объявить функцию, возвращающую массив как обычную функцию, например Java:

public String[] funcarray() {
   String[] test = new String[]{"hi", "hello"};
   return test;
}

Почему С++ не поддерживает что-то вроде int[] funcarray(){}? Вы можете вернуть массив, но это настоящая хлопот, чтобы сделать такую ​​функцию. Кроме того, я слышал, что строки - это всего лишь массивы char. Итак, если вы можете вернуть строку в С++, почему бы не массив?

4b9b3361

Ответ 1

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

Сначала подумайте о C. На языке C существует четкое различие между "pass by reference" и "pass by value". Чтобы относиться к нему легко, имя массива в C действительно является указателем. Во всех смыслах и целях разница (обычно) сводится к распределению. Код

int array[n];

создаст 4 * n байт памяти (в 32-разрядной системе) в стеке, соответствующий масштабу того, какой блок кода делает объявление. В свою очередь,

int* array = (int*) malloc(sizeof(int)*n);

создаст такую ​​же сумму памяти, но в куче. В этом случае то, что в этой памяти не привязано к области видимости, ограничивается только ссылкой на память. Здесь, где проходят по значению и передаются по ссылке. Передача по значению, как вы, вероятно, знаете, означает, что когда что-то передается или возвращается из функции, передаваемая "вещь" является результатом оценки переменной. Другими словами,

int n = 4;
printf("%d", n);

будет печатать число 4, потому что конструкция n оценивается до 4 (извините, если это элементарно, я просто хочу покрыть все базы). Это 4 абсолютно не имеет отношения к пространству памяти вашей программы, это просто буквальное, и поэтому, когда вы покидаете область действия, в которой этот контекст имеет 4, вы теряете его. Как насчет прохождения по ссылке? Передача по ссылке ничем не отличается в контексте функции; вы просто оцениваете переданную конструкцию. Единственное отличие состоит в том, что после оценки пройденной "вещи" вы используете результат оценки в качестве адреса памяти. У меня когда-то был особый циничный преподаватель CS, который любил заявлять, что нет такой вещи, как передача по ссылке, просто способ передать умные ценности. Действительно, он прав. Итак, теперь мы рассматриваем сферу действия в терминах функции. Представьте, что вы можете иметь тип возвращаемого массива:

int[] foo(args){
    result[n];
    // Some code
    return result;
}

Проблема заключается в том, что результат оценивается по адресу 0-го элемента массива. Но когда вы пытаетесь получить доступ к этой памяти из-за пределов этой функции (через возвращаемое значение), у вас есть проблема, потому что вы пытаетесь получить доступ к памяти, которая не входит в область действия, с которой вы работаете (стек вызовов функций). Таким образом, мы обходим это со стандартным "переходом по ссылке" jiggery-pokery:

int* foo(args){
    int* result = (int*) malloc(sizeof(int)*n));
    // Some code
    return result;
}

Мы по-прежнему получаем адрес памяти, указывающий на 0-й элемент массива, но теперь у нас есть доступ к этой памяти.

Какая моя мысль? В Java принято утверждать, что "все передается по значению". Это правда. Тот же циничный инструктор сверху также имел это, чтобы сказать о Java и ООП в целом: все просто указатель. И он тоже прав. Хотя все в Java фактически передается по значению, почти все эти значения являются фактически адресами памяти. Таким образом, в Java язык позволяет вам возвращать массив или строку, но он делает это, превращая его в версию с указателями для вас. Он также управляет вашей памятью для вас. А автоматическое управление памятью, хотя и полезно, неэффективно.

Это приводит нас к С++. Вся причина, по которой С++ была изобретена, заключалась в том, что Бьярн Страуструп экспериментировал с Simula (в основном оригинальным OOPL) во время его работы в PhD, и считал, что это было фантастически концептуально, но он заметил, что он выполнялся довольно ужасно. И поэтому он начал работать над тем, что называлось C с классами, которое переименовано в С++. При этом его цель заключалась в том, чтобы сделать язык программирования, который принял НЕКОТОРЫЕ из лучших функций от Simula, но оставался сильным и быстрым. Он решил расширить C благодаря своей уже легендарной производительности, и одним из компромиссов было то, что он решил не реализовывать автоматическое управление памятью или сбор мусора в таких больших масштабах, как другие OOPL. Возвращение массива из одного из классов шаблонов работает, потому что вы используете класс. Но если вы хотите вернуть массив C, вы должны сделать это способом C. Другими словами, С++ поддерживает возврат массива ТОЧНО так же, как Java; он просто не выполняет всю работу за вас. Потому что датский чувак подумал, что это будет слишком медленно.

Ответ 2

С++ поддерживает его - ну вроде:

vector< string> func()
{
   vector<string> res;
   res.push_back( "hello" );
   res.push_back( "world" );
   return res;
}

Даже C-тип поддерживает его:

struct somearray
{
  struct somestruct d[50];
};

struct somearray func()
{
   struct somearray res;
   for( int i = 0; i < 50; ++i )
   {
      res.d[i] = whatever;
   }
   // fill them all in
   return res;
}

A std::string - это класс, но когда вы говорите строку, вы, вероятно, подразумеваете литерал. Вы можете безопасно возвращать литерал из функции, но на самом деле вы можете статически создавать любой массив и возвращать его из функции. Это было бы поточно-безопасным, если бы это был массив const (только для чтения), который имеет место со строковыми литералами.

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

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

Помните, что в первые дни C все переменные должны были быть объявлены в верхней части функции, и вы не могли просто объявить при первом использовании. Таким образом, в то время это было неосуществимо.

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

Примечание. В таких языках, как Java, массив является классом. Вы создаете одно с новым. Вы можете переназначить их (это l-значения).

Ответ 3

Массивы в C (и в С++ для обратной совместимости) имеют специальную семантику, которая отличается от остальных типов. В частности, в то время как для остальных типов C имеет только семантику pass-by-value, в случае массивов эффект синтаксиса pass-by-value имитирует pass-by-reference странным образом:

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

Из-за этого конкретного обращения к массивам - они не могут быть переданы по значению - они также не могут быть возвращены по значению. В C вы можете вернуть указатель, а на С++ вы также можете вернуть ссылку, но сам массив не может быть выделен в стеке.

Если вы думаете об этом, это не отличается от языка, который вы используете в вопросе, поскольку массив динамически распределяется, и вы возвращаете только указатель/ссылку на него.

С другой стороны, язык С++ позволяет использовать различные решения для этой конкретной проблемы, например, использовать std::vector в текущем стандарте (содержимое динамически распределено) или std::array в предстоящем стандарте (содержимое может быть выделено в стек, но он может иметь большую стоимость, поскольку каждый элемент должен быть скопирован в тех случаях, когда копия не может быть удалена компилятором). Фактически, вы можете использовать тот же тип подхода с текущим стандартом, используя готовые библиотеки, такие как boost::array.

Ответ 4

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

Из обсуждения здесь:

http://forum.codecall.net/c-c/32457-function-return-array-c.html

Ответ 5

Другие сказали, что в С++ один использует vector < > вместо массивов, унаследованных от C.

Итак, почему С++ не позволяет возвращать C-массивы? Потому что C не делает.

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

Ответ 6

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

public std::string* funcarray() {
    std::string* test = new std::string[2];
    test[0] = "hi";
    test[1] = "hello";
    return test;
}

// somewhere else:
std::string* arr = funcarray();
std::cout << arr[0] << " MisterSir" << std::endl;
delete[] arr;

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

Ответ 7

"Почему С++ не поддерживает что-то вроде": потому что это не имеет никакого смысла. В языках на основе ссылок, таких как JAVA или PHP, управление памятью основано на сборке мусора. Части памяти, которые не имеют ссылок (никакая переменная в вашей программе не указывает на нее больше), автоматически освобождаются. В этом контексте вы можете выделить память и осторожно передать ссылку.

Код С++ будет переведен на машинный код, и в нем нет GC. Таким образом, в C и С++ существует сильное чувство владения блоков памяти. Вы должны знать, будет ли указатель, который вы отправляете, бесплатно в любое время (на самом деле вы освобождаете его после использования), или у вас есть указатель на общую часть памяти, что является абсолютным no-no, чтобы освободить.

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

Будет ли массив, возвращаемый функцией, всегда быть копией (ваш бесплатно), или вы должны сделать их копии? Вы выиграли бы Whet, получив массив с указателем на массив?

Ответ 8

Верните a std::vector<> вместо массива. В общем случае массивы не работают с С++, и их обычно следует избегать.

Кроме того, тип данных string - это не только массив символов, но и строка с цитированием. string управляет массивом символов, и вы можете получить доступ к нему с помощью .c_str(), но там больше string.