Dlaczego tablice nie mogą być przekazywane jako argumenty funkcji?

Dlaczego nie można przekazać tablic jako argumentów funkcji?

Czytałem tę książkę w języku C++, która mówi "nie można przekazywać tablic jako argumentów funkcji" , ale nigdy nie wyjaśnia dlaczego. Poza tym, kiedy sprawdzałem to w Internecie, znalazłem komentarze w stylu " dlaczego to robisz? Nie chodzi o to, że bym to zrobił, chcę tylko wiedzieć, dlaczego ty nie możesz.

Author: Peter Mortensen, 2011-08-16

6 answers

Dlaczego tablice nie mogą być przekazywane jako argumenty funkcji?

Mogą:

void foo(const int (&myArray)[5]) {
   // `myArray` is the original array of five integers
}

Pod względem technicznym, typem argumentu do foo jest "odniesienie do tablicy 5 const ints" ; za pomocą referencji możemy przekazać rzeczywisty obiekt wokół (disclaimer : terminologia różni się w zależności od poziomu abstrakcji) .

Nie można przekazać przez wartość, ponieważ ze względów historycznych nie będziemy kopiować tablic. Zamiast, próba przekazania tablicy przez wartość do funkcji (lub przekazanie kopii tablicy) prowadzi do rozpadu jej nazwy na wskaźnik. (niektóre zasoby źle to rozumieją!)


Nazwy tablic rozpadają się na wskaźniki dla wartości pass-by-

Oznacza to:

void foo(int* ptr);

int ar[10]; // an array
foo(ar);    // automatically passing ptr to first element of ar (i.e. &ar[0])

Jest też bardzo mylący "cukier składniowy", który wygląda tak, jakbyś mógł przekazać tablicę o dowolnej długości według wartości:

void foo(int ptr[]);

int ar[10]; // an array
foo(ar);

Ale, właściwie, wciąż jesteś tylko przekazywanie wskaźnika (do pierwszego elementu ar). foo jest taka sama jak powyżej!

Podczas gdy jesteśmy przy tym, następująca funkcja również nie ma tak naprawdę podpisu, jak się wydaje. Zobacz, co się stanie, gdy spróbujemy wywołać tę funkcję bez jej zdefiniowania:

void foo(int ar[5]);
int main() {
   int ar[5];
   foo(ar);
}

// error: undefined reference to `func(int*)'

Więc foo bierze int* W rzeczywistości Nie int[5]!

(demo na żywo.)


Ale możesz to obejść!

Możesz włamać się do tego przez zawijanie tablicy w struct lub class, ponieważ domyślny operator kopii skopiuje tablicę:

struct Array_by_val
{
  int my_array[10];
};

void func (Array_by_val x) {}

int main() {
   Array_by_val x;
   func(x);
}
Jest to nieco mylące zachowanie.
Nie jest to jednak żaden problem.]}

W C++, za pomocą magii szablonów, możemy stworzyć funkcję zarówno nadającą się do ponownego użycia, jak i zdolną do otrzymania tablicy:

template <typename T, size_t N>
void foo(const T (&myArray)[N]) {
   // `myArray` is the original array of N Ts
}

Ale nadal nie możemy przekazać jednego przez wartość. Coś do zapamiętania.


Przyszłość...

A ponieważ C++11 jest za horyzontem, a obsługa C++0x jest dobrze dostępna w głównych łańcuchach narzędzi, możesz użyć uroczego std::array odziedziczonego po Boost! Zostawię to jako ćwiczenie dla czytelnika.

 46
Author: Lightness Races in Orbit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:00:20

Widzę więc odpowiedzi wyjaśniające: "dlaczego kompilator nie pozwala mi na to?"Zamiast" co spowodowało, że standard określił takie zachowanie?"Odpowiedź leży w historii języka C. Jest to zaczerpnięte z" rozwoju języka C " (źródło) autorstwa Dennisa Ritchie.

W językach proto-C pamięć była podzielona na" komórki", z których każda zawierała słowo. Mogą one być dereferowane za pomocą ewentualnego operatora unary * - tak, były to zasadniczo języki bez typowych, takie jak niektóre z dzisiejszych języków zabawkowych jak Brainf_ck. Cukier składniowy pozwalał na udawanie, że wskaźnik jest tablicą:
a[5]; // equivalent to *(a + 5)

Następnie dodano automatyczną alokację:

auto a[10]; // allocate 10 cells, assign pointer to a
            // note that we are still typeless
a += 1;     // remember that a is a pointer

W pewnym momencie zachowanie specyfikacji przechowywania auto stało się domyślne - możesz się również zastanawiać, jaki był sens słowa kluczowego auto, to jest to. Wskaźniki i tablice zostały pozostawione do zachowania w nieco dziwaczny sposób w wyniku tych stopniowych zmian. Być może typy zachowywałyby się bardziej podobnie, gdyby język został zaprojektowany z lotu ptaka. W obecnej formie jest to jeszcze jeden C / C++.

 14
Author: Dietrich Epp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-16 03:32:59

Tablice są w pewnym sensie typami drugiej klasy, czymś, co C++ odziedziczył po C.

Cytując 6.3.2. 1p3 w standard C99 :

Z wyjątkiem, gdy jest operandem sizeof operatora lub unary & operator, OR jest ciągiem znaków używanym do inicjalizacji tablicy, wyrażenie, które ma typ "array of type " jest konwertowane na wyrażenie z typem " wskaźnik do typ ", który wskazuje na początkowy element tablicy obiekt i nie jest lvalue. Jeśli obiekt array posiada klasę register storage, zachowanie jest niezdefiniowane.

Ten sam akapit w C11 standard jest zasadniczo taki sam, z dodaniem nowego operatora _Alignof. (Oba linki są do szkiców, które są bardzo zbliżone do oficjalnych standardów. (UPDATE: w rzeczywistości był to błąd w projekcie n1570, poprawiony w opublikowanym standardzie C11. _Alignof nie można zastosować do wyrażenia, tylko do typu w nawiasie nazwa, więc C11 ma tylko te same 3 wyjątki co C99 i C90. (Ale dygresję.)))

Nie mam odpowiedniego C++ citation handy, ale wydaje mi się, że jest całkiem podobny.

Więc jeśli arr jest obiektem tablicy, a wywołujesz funkcję func(arr), to func otrzyma wskaźnik do pierwszego elementu arr.

Jak na razie jest to mniej więcej "to działa w ten sposób, ponieważ jest tak zdefiniowane", ale są ku temu powody historyczne i techniczne.

Parametry tablicy nie pozwoliłyby na dużą elastyczność (bez dalszych zmian w języku), ponieważ, na przykład, char[5] i char[6] są odrębnymi typami. Nawet przekazywanie tablic przez referencję nie pomaga w tym (chyba, że brakuje mi jakiejś funkcji C++, zawsze jest taka możliwość). Przechodząc pointers daje ogromną elastyczność (być może zbyt wiele!). Wskaźnik może wskazywać na pierwszy element tablicy o dowolnej wielkości . trzeba jednak obrócić własny mechanizm, aby powiedzieć funkcji, jak duży / align = "left"/

Zaprojektowanie języka tak, aby tablice o różnych długościach były w pewnym stopniu kompatybilne, a jednocześnie odrębne, jest w rzeczywistości dość trudne. Na przykład w Ada odpowiedniki char[5] i char[6] są tymi samymi typami , ale różnymi podtypami . Bardziej dynamiczne języki sprawiają, że długość jest częścią wartości obiektu array, a nie jego typu. C nadal dość dużo mętli wraz z wyraźnymi wskaźnikami i długościami lub wskaźnikami i terminatorami. C++ odziedziczył to wszystko bagaż z C. był głównie na całej tablicy i wprowadzał wektory, więc nie było potrzeby tworzenia tablic typu pierwszej klasy.

TL;DR: to jest C++, i tak powinieneś używać wektorów! (No, czasami.)

 5
Author: Keith Thompson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-28 01:29:35

Tablice nie są przekazywane przez wartość, ponieważ tablice są zasadniczo ciągłymi blokami memmory. Jeśli masz tablicę, którą chcesz przekazać wartości, możesz zadeklarować ją w strukturze, a następnie uzyskać do niej dostęp poprzez strukturę.

Ma to wpływ na wydajność, ponieważ oznacza to, że zamkniesz więcej miejsca na stosie. Przekazywanie wskaźnika jest szybsze, ponieważ Obwiednia danych do skopiowania na stos jest znacznie mniejsza.

 2
Author: ccozad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-16 03:10:41

Uważam, że powodem, dla którego C++ to zrobił, było to, że kiedy został stworzony, mógł zająć zbyt wiele zasobów, aby wysłać całą tablicę, a nie adres w pamięci. To tylko moje przemyślenia na ten temat i założenie.

 1
Author: nhutto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-16 17:59:41

To z przyczyn technicznych. Argumenty są przekazywane na stos; tablica może mieć ogromny rozmiar, megabajty i więcej. Kopiowanie tych danych na stos przy każdym wywołaniu będzie nie tylko wolniejsze, ale i dość szybko wyczerpie stos.

Możesz przezwyciężyć to ograniczenie, umieszczając tablicę w strukturze (lub używając metody Boost:: Array):

struct Array
{
    int data[512*1024];
    int& operator[](int i) { return data[i]; }
};
void foo(Array byValueArray) { .......... }

Spróbuj wykonać zagnieżdżone wywołania tej funkcji i zobacz, ile przepełnień stosu otrzymasz!

 1
Author: hamstergene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-16 18:01:14