webentwicklung-frage-antwort-db.com.de

Warum können wir keine Arrays übergeben, um nach Wert zu funktionieren?

Anscheinend können wir komplexe Klasseninstanzen an Funktionen übergeben, aber warum können wir keine Arrays an Funktionen übergeben?

38
Alcott

Der Ursprung ist historisch. Das Problem ist, dass die Regel "Arrays in Zeiger zerfallen, wenn sie an eine Funktion übergeben werden" einfach ist.

Das Kopieren von Arrays wäre ziemlich kompliziert und nicht sehr klar, da sich das Verhalten für verschiedene Parameter und unterschiedliche Funktionsdeklarationen ändern würde.

Beachten Sie, dass Sie immer noch eine indirekte Wertübergabe durchführen können:

struct A { int arr[2]; };
void func(struct A);
52
Let_Me_Be

Hier ist eine andere Perspektive: Es gibt keinen einzigen Typ "Array" in C. Vielmehr ist T[N] ein anderer Typ für jede N. Also sind T[1], T[2] usw. alle verschiedene Typen .

In C gibt es keine Funktionsüberladung, und das einzig Vernünftige, was Sie hätten zulassen können, wäre eine Funktion, die einen einzigen Typ von Array übernimmt:

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

Vermutlich wurde dies als weitaus weniger nützlich angesehen als die eigentliche Entscheidung, alle Arrays in einen Zeiger auf das erste Element zerfallen zu lassen und den Benutzer auf andere Weise über die Größe zu informieren. Immerhin könnte das Obige umgeschrieben werden als:

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

Es gibt also keinen Verlust an Ausdruckskraft, aber einen großen Gewinn an Allgemeinheit.

Beachten Sie, dass dies in C++ nicht anders ist, aber die Erzeugung von vorlagengesteuertem Code ermöglicht es Ihnen, eine vorgefertigte Funktion foo(T (&a)[N]) zu schreiben, von der N für Sie abgeleitet wird. Dies bedeutet jedoch nur, dass Sie eine ganze Familie von different erstellen können. verschiedene Funktionen, eine für jeden Wert von N.

Stellen Sie sich als Extremfall vor, dass Sie zwei Funktionen print6(const char[6]) und print12(const char[12]) benötigen, um print6("Hello") und print12("Hello World") zu sagen, wenn Sie Arrays nicht in Zeiger zerlegen möchten, oder andernfalls eine explizite Konvertierung, print_p((const char*)"Hello World"), hinzufügen müssten.

25
Kerrek SB

Um eine sehr alte Frage zu beantworten, da Question is market gerade mit C++ zum Zwecke der Fertigstellung hinzugefügt wird, können wir std :: array verwenden und Arrays per Wert oder per Referenz an Funktionen übergeben, was Schutz vor dem Zugriff auf außerhalb gebundener Indizes bietet:

unten ist beispiel:

#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);
}
4
Ayub

Der Grund, aus dem Sie ein Array nicht nach Wert übergeben können, besteht darin, dass es keine bestimmte Möglichkeit gibt, die Größe eines Arrays so zu verfolgen, dass die Funktionsaufruflogik wissen würde, wie viel Speicher belegt und was kopiert werden soll. Sie können eine Klasseninstanz übergeben, da Klassen über Konstruktoren verfügen. Arrays nicht.

3
David Schwartz

Das Äquivalent dazu wäre, zuerst eine Kopie des Arrays zu erstellen und sie dann an die Funktion zu übergeben (was bei großen Arrays sehr ineffizient sein kann). 

Abgesehen davon würde ich sagen, es ist aus historischen Gründen, d. H. Man könnte Arrays nicht nach Wert in C übergeben.

Meine Vermutung ist, dass der Grund, warum KEINE übergebenen Arrays nach Wert in C++ einführte, die Vermutung, dass Objekte im Vergleich zu Arrays als mittelgroße Objekte angesehen wurden.

Wie von delnan erläutert, können Sie bei Verwendung von std::vector tatsächlich Array-ähnliche Objekte per Wert an Funktionen übergeben.

0
Andre Holzner

Sie sind are durch value: Der Wert des Zeigers auf das Array. Denken Sie daran, dass die Verwendung der eckigen Klammernotation in C einfach eine Abkürzung für die Aufhebung der Referenzierung eines Zeigers ist. ptr [2] bedeutet * (ptr + 2). 

Wenn Sie die Klammern weglassen, erhalten Sie einen Zeiger auf das Array, der per Wert an eine Funktion übergeben werden kann:

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

Siehe Liste der Typen in der ANSI C-Spezifikation. Arrays sind keine primitiven Typen, sondern aus einer Kombination von Zeigern und Operatoren aufgebaut. (Ich darf keinen anderen Link setzen, aber die Konstruktion wird unter "Ableitung des Array-Typs" beschrieben.)

0
KonradG

Auf diese Weise wurde die syntaktische und semantische Kompatibilität mit der B-Sprache beibehalten, in der Arrays als physikalische Zeiger implementiert wurden.

Eine direkte Antwort auf diese Frage gibt Dennis Ritchie's "Die Entwicklung der Sprache C" , siehe den Abschnitt "Kritik". Es sagt

Zum Beispiel die leeren eckigen Klammern in der Funktionsdeklaration

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

sind ein lebendes Fossil, ein Überbleibsel der Art und Weise, wie NB einen Zeiger deklariert; a wird nur in diesem speziellen Fall in C als Zeiger interpretiert. Die Notation blieb zum Teil aus Kompatibilitätsgründen erhalten, zum Teil unter der Rationalisierung, dass es Programmierern ermöglichen würde, ihren Lesern die Absicht zu vermitteln, f einen von einem Array erzeugten Zeiger zu übergeben, statt einen Verweis auf eine einzelne Ganzzahl. Unglücklicherweise dient es dem Lernenden ebenso zu verwirren wie dem Leser zu warnen.

Dies ist im Zusammenhang mit dem vorherigen Teil des Artikels zu verstehen, insbesondere "Embryonales C", in dem erklärt wird, wie die Einführung von struct-Typen in C zu einer Ablehnung des B- und BCPL-Ansatzes bei der Implementierung von Arrays geführt hat (dh als normale Zeiger) . C wechselte zu einer Nicht-Pointer-Array-Implementierung, wobei die alte Semantik im B-Stil nur in Funktionsparameterlisten beibehalten wurde.

Die aktuelle Variante des Verhaltens von Array-Parametern ist also das Ergebnis eines Kompromisses: Einerseits mussten wir in structs kopierbare Arrays haben, andererseits wollten wir die semantische Kompatibilität mit in B geschriebenen Funktionen, in denen Arrays sind immer "per Zeiger" übergeben.

0
AnT

Sommerlich:

  1. Übergeben der Adresse des ersten Elements des Arrays&a = a = &(a[0])
  2. Neuer Zeiger (neuer Zeiger, neue Adresse, 4 Bytes im Speicher)
  3. Zeigt auf den gleichen Speicherplatz, in anderem Typ.

Beispiel 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 !!! 
}

Adressen:

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

Beispiel 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);
}

Adressen

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

Bitte beachten Sie:

  1. & (required-lvalue): lvalue-to-> rvalue
  2. Array Decay: Neuer Zeiger (temporär) zeigt auf die Array-Adresse (nach Wert)

Weiterlesen:

Rvalue

Array Decay

0
Almog