Почему мы не можем передать массивы в функцию по значению? - PullRequest
42 голосов
/ 17 сентября 2011

Очевидно, мы можем передавать сложные экземпляры классов в функции, но почему мы не можем передавать массивы в функции?

Ответы [ 9 ]

54 голосов
/ 17 сентября 2011

Происхождение историческое.Проблема в том, что правило «распадаются массивы на указатели при передаче в функцию» очень просто.

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

Обратите внимание, что вы все равно можете сделать косвенный проход по значению:

struct A { int arr[2]; };
void func(struct A);
26 голосов
/ 17 сентября 2011

Вот еще одна перспектива: в C. нет единственного типа «массив». Скорее, T[N] - это другой тип для каждого N. Так что T[1], T[2] и т. Д. - это все разных типов .

В C нет перегрузки функций, и поэтому единственно разумной вещью, которую вы могли бы разрешить, была бы функция, которая принимает (или возвращает) единственный тип массива :

void foo(int a[3]);  // hypothetical

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

void foo(int * a)
{
  static const unsigned int N = 3;
  /* ... */
}

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

Обратите внимание, что в C ++ это ничем не отличается, но генерация кода на основе шаблонов позволяет написать шаблонную функцию foo(T (&a)[N]), где для вас выводится N, но это просто означает, что вы можете создать целое семейство различных, различных функций, по одной на каждое значение N.

В крайнем случае представьте, что вам понадобятся две функции print6(const char[6]) и print12(const char[12]), чтобы сказать print6("Hello") и print12("Hello World"), если вы не хотите распадать массивы на указатели или иначе вы ' Я должен добавить явное преобразование, print_p((const char*)"Hello World").

6 голосов
/ 12 августа 2016

Отвечая на очень старый вопрос, так как Вопрос - это рынок, в котором С ++ просто добавляется для целей завершения, мы можем использовать std :: array и передавать массивы в функции по значению или по ссылке, что обеспечивает защиту от доступа из связанных индексов:

ниже образец:

#include <iostream>
#include <array>

//pass array by reference
template<size_t N>
void fill_array(std::array<int, N>& arr){
    for(int idx = 0; idx < arr.size(); ++idx)
        arr[idx] = idx*idx;
}

//pass array by value
template<size_t N>
void print_array(std::array<int, N> arr){
    for(int idx = 0; idx < arr.size(); ++idx)
        std::cout << arr[idx] << std::endl;
}

int main()
{
    std::array<int, 5> arr;
    fill_array(arr);
    print_array(arr);
    //use different size
    std::array<int, 10> arr2;
    fill_array(arr2);
    print_array(arr2);
}
5 голосов
/ 17 сентября 2011

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

1 голос
/ 04 января 2016

Summery:

  1. Передача адреса первого элемента массива &a = a = &(a[0])
  2. Новый указатель (новый указатель, новый адрес , 4 байта, в памяти)
  3. Указывает на ту же ячейку памяти , в другой тип .

Пример 1:

void by_value(bool* arr) // pointer_value passed by value
{
    arr[1] = true;
    arr = NULL; // temporary pointer that points to original array
}

int main()
{
    bool a[3] = {};
    cout << a[1] << endl; // 0
    by_value(a);
    cout << a[1] << endl; // 1 !!! 
}

Адреса:

[main] 
     a = 0046FB18 // **Original**
     &a = 0046FB18 // **Original**
[func]
     arr = 0046FB18 // **Original**
     &arr = 0046FA44 // TempPTR
[func]
     arr = NULL
     &arr = 0046FA44 // TempPTR

Пример 2:

void by_value(bool* arr) 
{
    cout << &arr << arr; // &arr != arr
}

int main()
{
    bool a[3] = {};
    cout << &a << a; // &a == a == &a[0]
    by_value(arr);
}

Адреса

Prints: 
[main] 0046FB18 = 0046FB18
[func] 0046FA44 != 0046FB18

Обратите внимание:

  1. & (обязательно-lvalue): lvalue -to-> rvalue
  2. Распад массива: новый указатель (временный) указывает на (по значению) адрес массива

readmore:

Rvalue

Распад массива

0 голосов
/ 25 октября 2018

Это было сделано таким образом, чтобы сохранить синтаксическую и семантическую совместимость с языком B, в котором массивы были реализованы как физические указатели.

Прямой ответ на этот вопрос дан в «1003 *» Денниса Ритчи ».Развитие языка Си ", см. Раздел" Критика ".В нем говорится

Например, пустые квадратные скобки в объявлении функции

int f(a) int a[]; { ... }

являются живым ископаемым, остатком способа объявления НБуказатель;a только в этом особом случае интерпретируется в C как указатель.Нотация сохранилась частично ради совместимости, частично благодаря рационализации, которая позволила бы программистам сообщать своим читателям намерение передать f указатель, сгенерированный из массива, а не ссылку на одно целое число.К сожалению, это не только сбивает с толку ученика, но и предупреждает читателя.

Это следует рассматривать в контексте предыдущей части статьи, особенно «Эмбриональный С», в которой объясняется, как введениеstruct типов в C привели к отказу от B- и BCPL-стиля подхода к реализации массивов (то есть как обычные указатели).C переключился на реализацию массива без указателей, сохранив эту унаследованную семантику в стиле B только в списках параметров функции.

Итак, текущий вариант поведения параметров массива является результатом компромисса: с одной стороны, мыдолжен был иметь копируемые массивы в struct с, с другой стороны, мы хотели сохранить семантическую совместимость с функциями, написанными на B, где массивы всегда передаются «по указателю».

0 голосов
/ 26 марта 2015

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

0 голосов
/ 18 сентября 2011

Вы передаете по значению: значение указателя на массив. Помните, что использование квадратной скобки в C - это просто сокращение для разыменования указателя. ptr [2] означает * (ptr + 2).

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

int x[2] = {1, 2};
int result;
result = DoSomething(x);

См. Список типов в спецификации ANSI C. Массивы не являются примитивными типами, но построены из комбинации указателей и операторов. (Это не позволит мне поставить другую ссылку, но конструкция описана в разделе «Вывод типа массива».)

0 голосов
/ 17 сентября 2011

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

Кроме этого, я бы сказал, что это по историческим причинам, то есть нельзя передавать массивы по значению в C.

Мое предположение состоит в том, что причина НЕ вводить проходящие массивы по значению в C ++ заключалась в том, что объекты считались умеренными по размеру по сравнению с массивами.

Как указал delnan, при использовании std::vector вы можете фактически передавать объекты в виде массивов функциям по значению.

...