Folgendes habe ich während meiner Lernphase gefunden:
#include<iostream>
using namespace std;
int dis(char a[1])
{
int length = strlen(a);
char c = a[2];
return length;
}
int main()
{
char b[4] = "abc";
int c = dis(b);
cout << c;
return 0;
}
So in den Variablen int dis(char a[1])
, das [1]
scheint nichts zu tun und nicht funktioniert
alles, weil ich verwenden kann a[2]
. Genau wie int a[]
oder char *a
. Ich weiß, dass der Array-Name ein Zeiger ist und wie man ein Array vermittelt, daher geht es in meinem Puzzle nicht um diesen Teil.
Ich möchte wissen, warum Compiler dieses Verhalten zulassen ( int a[1]
). Oder hat es andere Bedeutungen, von denen ich nichts weiß?
typedef
with-Array-Typ ist. So den „Zerfall Zeiger“ in Argumenttypen ist nicht nur syntaktischer Zucker ersetzt[]
mit*
, es ist wirklich das Typsystem durchlaufen. Dies hat reale Konsequenzen für einige Standardtypen wieva_list
diesen, die mit Array- oder Nicht-Array-Typ definiert werden können.int dis(char (*a)[1])
. Dann übergeben Sie einen Zeiger auf ein Array :dis(&b)
. Wenn Sie bereit sind, C-Funktionen zu verwenden, die in C ++ nicht vorhanden sind, können Sie auch Dinge wievoid foo(int data[static 256])
und sagenint bar(double matrix[*][*])
, aber das ist eine ganz andere Dose Würmer.Antworten:
Es ist eine Eigenart der Syntax zum Übergeben von Arrays an Funktionen.
Tatsächlich ist es nicht möglich, ein Array in C zu übergeben. Wenn Sie eine Syntax schreiben, die so aussieht, als ob sie das Array übergeben sollte, passiert tatsächlich, dass stattdessen ein Zeiger auf das erste Element des Arrays übergeben wird.
Da der Zeiger keine Längeninformationen enthält, wird der Inhalt Ihrer
[]
Funktion in der Liste der formalen Funktionsparameter tatsächlich ignoriert.Die Entscheidung, diese Syntax zuzulassen, wurde in den 1970er Jahren getroffen und hat seitdem viel Verwirrung gestiftet ...
quelle
void foo(int (*somearray)[20])
Syntax zu übergeben. In diesem Fall wird 20 auf den Anruferseiten erzwungen.[]
werden in mehrdimensionalen Arrays nicht ignoriert, wie in der Antwort von pat gezeigt. Daher war die Einbeziehung der Array-Syntax erforderlich. Darüber hinaus hindert nichts den Compiler daran, Warnungen auch auf eindimensionalen Arrays auszugeben.void foo(int (*args)[20]);
C streng genommen auch keine mehrdimensionalen Arrays; aber es hat Arrays, deren Elemente andere Arrays sein können. Das ändert nichts.Die Länge der ersten Dimension wird ignoriert, aber die Länge der zusätzlichen Dimensionen ist erforderlich, damit der Compiler Offsets korrekt berechnen kann. Im folgenden Beispiel wird der
foo
Funktion ein Zeiger auf ein zweidimensionales Array übergeben.Die Größe der ersten Dimension
[10]
wird ignoriert. Der Compiler hindert Sie nicht daran, das Ende zu indizieren (beachten Sie, dass das Formal 10 Elemente benötigt, das tatsächliche jedoch nur 2). Die Größe der zweiten Dimension[20]
wird jedoch verwendet, um den Schritt jeder Zeile zu bestimmen, und hier muss das Formale mit dem tatsächlichen übereinstimmen. Auch hier hindert Sie der Compiler nicht daran, das Ende der zweiten Dimension zu indizieren.Der Byte-Offset von der Basis des Arrays zu einem Element
args[row][col]
wird bestimmt durch:Beachten Sie, dass Sie, wenn
col >= 20
, dann tatsächlich in eine nachfolgende Zeile (oder am Ende des gesamten Arrays) indizieren.sizeof(args[0])
, kehrt80
auf meiner Maschine wo zurücksizeof(int) == 4
. Wenn ich jedoch versuche zu nehmensizeof(args)
, erhalte ich die folgende Compiler-Warnung:Hier warnt der Compiler, dass er nur die Größe des Zeigers angeben wird, in den das Array zerfallen ist, anstatt die Größe des Arrays selbst.
quelle
args
. In diesem Fall ist das erste Element von args ein "Array von 20 Zoll". Zeiger enthalten Typinformationen; Was übergeben wird, ist "Zeiger auf ein Array von 20 Zoll".int (*)[20]
Typ; "Zeiger auf ein Array von 20 Zoll".Das Problem und wie man es in C ++ löst
Das Problem wurde ausführlich von pat und Matt erklärt . Der Compiler ignoriert grundsätzlich die erste Dimension der Array-Größe und ignoriert effektiv die Größe des übergebenen Arguments.
In C ++ hingegen können Sie diese Einschränkung auf zwei Arten leicht überwinden:
std::array
(seit C ++ 11)Verweise
Wenn Ihre Funktion nur versucht, ein vorhandenes Array zu lesen oder zu ändern (nicht zu kopieren), können Sie problemlos Referenzen verwenden.
Angenommen, Sie möchten eine Funktion, die ein Array von zehn Sekunden zurücksetzt
int
und jedes Element auf setzt0
. Sie können dies einfach mit der folgenden Funktionssignatur tun:Dies funktioniert nicht nur einwandfrei , sondern erzwingt auch die Dimension des Arrays .
Sie können auch Vorlagen verwenden , um den obigen Code generisch zu gestalten :
Und schließlich können Sie die
const
Korrektheit nutzen. Betrachten wir eine Funktion, die ein Array von 10 Elementen druckt:Durch Anwenden des
const
Qualifikators verhindern wir mögliche Änderungen .Die Standardbibliotheksklasse für Arrays
Wenn Sie die obige Syntax wie ich für hässlich und unnötig halten, können wir sie in die Dose werfen und
std::array
stattdessen verwenden (seit C ++ 11).Hier ist der überarbeitete Code:
Ist es nicht wunderbar? Ganz zu schweigen davon, dass der generische Code-Trick, den ich Ihnen zuvor beigebracht habe, immer noch funktioniert:
Darüber hinaus erhalten Sie kostenlos Kopier- und Verschiebungssemantiken. :) :)
Also, worauf wartest Du? Geh und benutze
std::array
.quelle
Es ist eine unterhaltsame Funktion von C , mit der Sie sich effektiv in den Fuß schießen können, wenn Sie dazu neigen.
Ich denke, der Grund ist, dass C nur einen Schritt über der Assemblersprache liegt. Die Größenprüfung und ähnliche Sicherheitsfunktionen wurden entfernt, um Spitzenleistungen zu ermöglichen. Dies ist keine schlechte Sache, wenn der Programmierer sehr fleißig ist.
Das Zuweisen einer Größe zum Funktionsargument hat außerdem den Vorteil, dass bei Verwendung der Funktion durch einen anderen Programmierer die Möglichkeit einer Größenbeschränkung besteht. Nur die Verwendung eines Zeigers überträgt diese Informationen nicht an den nächsten Programmierer.
quelle
Erstens überprüft C niemals Array-Grenzen. Es spielt keine Rolle, ob es sich um lokale, globale, statische Parameter handelt. Das Überprüfen von Array-Grenzen bedeutet mehr Verarbeitung, und C soll sehr effizient sein, sodass die Überprüfung der Array-Grenzen vom Programmierer bei Bedarf durchgeführt wird.
Zweitens gibt es einen Trick, der es ermöglicht, ein Array als Wert an eine Funktion zu übergeben. Es ist auch möglich, ein Array von einer Funktion nach Wert zurückzugeben. Sie müssen nur einen neuen Datentyp mit struct erstellen. Beispielsweise:
Sie müssen auf folgende Elemente zugreifen: foo.a [1]. Das zusätzliche ".a" mag seltsam aussehen, aber dieser Trick fügt der C-Sprache eine großartige Funktionalität hinzu.
quelle
So teilen Sie dem Compiler mit, dass myArray auf ein Array von mindestens 10 Zoll verweist:
Ein guter Compiler sollte Sie warnen, wenn Sie auf myArray [10] zugreifen. Ohne das Schlüsselwort "static" würde die 10 überhaupt nichts bedeuten.
quelle
[static]
Ermöglicht dem Compiler zu warnen, wenn Sie mit einem aufrufen . Es gibt nicht vor, auf was Sie innerhalb zugreifen dürfen . Die Verantwortung liegt ganz auf der Anruferseite.bar
int[5]
bar
error: expected primary-expression before 'static'
habe diese Syntax noch nie gesehen. Es ist unwahrscheinlich, dass dies Standard C oder C ++ ist.Dies ist eine bekannte "Funktion" von C, die an C ++ übergeben wird, da C ++ C-Code korrekt kompilieren soll.
Das Problem ergibt sich aus mehreren Aspekten:
Man könnte sagen, dass Arrays in C nicht wirklich unterstützt werden (dies ist nicht wirklich wahr, wie ich bereits sagte, aber es ist eine gute Annäherung); Ein Array wird wirklich als Zeiger auf einen Datenblock behandelt und mithilfe der Zeigerarithmetik aufgerufen. Da C KEINE Form von RTTI hat, müssen Sie die Größe des Array-Elements im Funktionsprototyp deklarieren (um Zeigerarithmetik zu unterstützen). Dies gilt umso mehr für mehrdimensionale Arrays.
Jedenfalls ist alles oben nicht mehr wirklich wahr: p
Die meisten modernen C / C ++ Compiler tun Unterstützung die Überprüfung der Grenzen, sondern Standards erfordern es standardmäßig aus sein (für die Abwärtskompatibilität). In einigermaßen neueren Versionen von gcc wird beispielsweise die Überprüfung des Kompilierungszeitbereichs mit "-O3 -Wall -Wextra" und die Überprüfung der vollständigen Laufzeitgrenzen mit "-fbounds-Checking" durchgeführt.
quelle
struct MyStruct s = { .field1 = 1, .field2 = 2 };
) zum Initialisieren von Strukturen, da dies eine viel klarere Möglichkeit zum Initialisieren einer Struktur darstellt. Infolgedessen wird der meiste aktuelle C-Code von Standard-C ++ - Compilern abgelehnt, da der meiste C-Code Strukturen initialisiert.C transformiert nicht nur einen Parameter vom Typ
int[5]
in*int
; Mit der Deklarationtypedef int intArray5[5];
wird auch ein Parameter vom TypintArray5
in transformiert*int
. Es gibt einige Situationen, in denen dieses Verhalten zwar seltsam, aber nützlich ist (insbesondere bei Dingen wie demva_list
instdargs.h
, die in einigen Implementierungen als Array definiert sind). Es wäre unlogisch, einen als Parameter definierten Typ zuzulassenint[5]
(die Dimension zu ignorieren), aber nichtint[5]
direkt angeben zu lassen.Ich finde Cs Umgang mit Parametern vom Array-Typ absurd, aber es ist eine Folge der Bemühungen, eine Ad-hoc-Sprache zu verwenden, von der große Teile nicht besonders gut definiert oder durchdacht waren, und zu versuchen, Verhalten zu entwickeln Spezifikationen, die mit den vorhandenen Implementierungen für vorhandene Programme übereinstimmen. Viele der Macken von C sind in diesem Licht sinnvoll, besonders wenn man bedenkt, dass große Teile der Sprache, die wir heute kennen, noch nicht existierten, als viele von ihnen erfunden wurden. Soweit ich weiß, haben Compiler im Vorgänger von C, BCPL genannt, die Variablentypen nicht wirklich gut verfolgt. Eine Erklärung
int arr[5];
war gleichbedeutend mitint anonymousAllocation[5],*arr = anonymousAllocation;
; sobald die Zuteilung aufgehoben wurde. Der Compiler wusste weder, noch kümmerte es ihn, obarr
war ein Zeiger oder ein Array. Beim Zugriff als entwederarr[x]
oder*arr
wird es als Zeiger betrachtet, unabhängig davon, wie es deklariert wurde.quelle
Eine Sache, die noch nicht beantwortet wurde, ist die eigentliche Frage.
Die bereits gegebenen Antworten erklären, dass Arrays weder in C noch in C ++ als Wert an eine Funktion übergeben werden können. Sie erklären auch, dass ein als deklarierter Parameter so
int[]
behandelt wird, als hätte er einen Typint *
, und dass eine Variable vom Typint[]
an eine solche Funktion übergeben werden kann.Sie erklären jedoch nicht, warum es nie zu einem Fehler gekommen ist, explizit eine Array-Länge anzugeben.
Warum ist der letzte nicht ein Fehler?
Ein Grund dafür ist, dass es Probleme mit typedefs verursacht.
Wenn es ein Fehler wäre, die Array-Länge in Funktionsparametern anzugeben, könnten Sie den
myarray
Namen nicht im Funktionsparameter verwenden. Und da einige Implementierungen Array-Typen für Standardbibliothekstypen verwenden, wie z. B.va_list
und alle Implementierungen erforderlich sind, umjmp_buf
einen Array-Typ zu erstellen, wäre es sehr problematisch, wenn es keine Standardmethode zum Deklarieren von Funktionsparametern unter Verwendung dieser Namen gäbe: Ohne diese Fähigkeit wäre dies möglich keine tragbare Implementierung von Funktionen wievprintf
.quelle
Compiler können überprüfen, ob die Größe des übergebenen Arrays der erwarteten entspricht. Compiler können ein Problem warnen, wenn dies nicht der Fall ist.
quelle