In der tatsächlichen Programmierung kommt es oft vor, dass der benötigte Speicherplatz von den tatsächlichen Eingabedaten abhängt und nicht im Voraus bestimmt werden kann. Für solche Probleme ist es schwierig, sie mit statischen Arrays zu lösen. Um die oben genannten Probleme zu lösen, bietet die C-Sprache einige Speicherverwaltungsfunktionen. Diese Speicherverwaltungsfunktionen können in Kombination mit Zeigern dynamisch Speicherplatz nach Bedarf zuweisen, um dynamische Arrays zu erstellen, und können auch ungenutzten Speicherplatz für die spätere Verwendung zurückfordern, was ein Mittel zur effektiven Nutzung von Speicherressourcen darstellt.
Ein dynamisches Array ist relativ zu einem statischen Array. Die Länge eines statischen Arrays ist vordefiniert, und sobald seine Größe im Programm angegeben wurde, kann sie nicht geändert werden. Ein dynamisches Array ist anders; es kann nach den Anforderungen des Programms in seiner Größe angepasst werden. Der Speicherplatz eines dynamischen Arrays wird vom Heap zugewiesen (dynamische Zuordnung). Speicherplatz wird ihm durch die Ausführung von Code zugewiesen. Er wird erst zugewiesen, wenn das Programm diese Anweisungen ausführt. Der Programmierer ist für die Freigabe des Speichers verantwortlich.
Warum dynamische Arrays verwenden?
In der tatsächlichen Programmierung kommt es oft vor, dass der benötigte Speicherplatz von den tatsächlichen Eingabedaten abhängt und nicht im Voraus bestimmt werden kann. Für solche Probleme ist es schwierig, sie mit statischen Arrays zu lösen. Um die oben genannten Probleme zu lösen, bietet die C-Sprache einige Speicherverwaltungsfunktionen. Diese Speicherverwaltungsfunktionen können in Kombination mit Zeigern dynamisch Speicherplatz nach Bedarf zuweisen, um dynamische Arrays zu erstellen, und können auch ungenutzten Speicherplatz für die spätere Verwendung zurückfordern, was ein Mittel zur effektiven Nutzung von Speicherressourcen darstellt.
Vergleich von dynamischen Arrays und statischen Arrays
Bei statischen Arrays ist ihre Erstellung sehr bequem, sie müssen nach der Verwendung nicht freigegeben werden, und sie zu referenzieren ist einfach. Ihre fatale Schwäche ist jedoch, dass ihre Größe nach der Erstellung nicht geändert werden kann!
Bei dynamischen Arrays ist ihre Erstellung mühsam, und nach der Verwendung müssen sie vom Programmierer selbst freigegeben werden; andernfalls kann dies ernsthaft Speicherlecks verursachen. Ihre Verwendung ist jedoch sehr flexibel, da sie die Größe dynamisch nach den Anforderungen des Programms allokieren können.
Wie man ein dynamisches Array konstruiert
Zu befolgende Prinzipien
Beim Allokieren wird Ebene für Ebene von der äußeren Ebene zur inneren Ebene allokiert.
Beim Freigeben wird Ebene für Ebene von der inneren Ebene zur äußeren Ebene freigegeben.
Für die Konstruktion erforderliche Zeiger
Um ein eindimensionales dynamisches Array zu konstruieren, wird ein eindimensionaler Zeiger benötigt.
Für zwei Dimensionen werden eindimensionale und zweidimensionale Zeiger benötigt.
Drei Dimensionen erfordern ein-, zwei- und dreidimensionale Zeiger.
Und so weiter.
Für den Aufbau erforderliche Funktionen
| Funktionsprototyp | Return | Funktionsbeschreibung |
| void *malloc(unsigned int size); | Erfolg: gibt die Startadresse des zugewiesenen Speicherbereichs zurück. Fehler: gibt einen Nullzeiger zurück. | Alloziert Heap-Speicher von size Bytes vom System. |
| void *calloc(unsigned int num, unsigned int size); | Erfolg: gibt die Startadresse des zugewiesenen Speicherbereichs zurück. Fehler: gibt einen Nullzeiger zurück. | Alloziert Heap-Speicher für num Elemente der Größe size Bytes pro Typ. |
| void free(void *p); | Kein Rückgabewert | Gibt den Heap-Speicher frei, auf den p zeigt. |
| void *realloc(void *p,unsigned int size); | Erfolg: gibt die Startadresse des neu zugewiesenen Speicherbereichs zurück. Fehler: gibt einen Nullzeiger zurück. | Ändert den Heap-Speicher, auf den p zeigt, auf size. |
Erklärung:
-
(1) Der Rückgabetyp ist als void * angegeben. Das bedeutet nicht, dass die Funktion nach dem Aufruf keinen Rückgabewert hat, sondern dass sie die Adresse eines Knotens zurückgibt. Der Typ dieser Adresse ist void (kein Typ oder unbestimmter Typ), also die Startadresse eines Speicherbereichs. Ihr konkreter Typ kann erst bestimmt werden, wenn sie anhand der Daten jedes Feldes verwendet wird. Sie kann mit einem Cast in andere Typen umgewandelt werden. Zum Beispiel: double *pd=NULL; pd=(double *)calloc(10,sizeof(double)); Das bedeutet, beim System 10 aufeinanderfolgende Speicherplätze vom Typ double anzufordern und den Zeiger pd auf die Startadresse dieses zusammenhängenden Bereichs zeigen zu lassen. Außerdem wird (double) verwendet, um den Rückgabetyp von calloc() umzuwandeln, damit die Adresse von double-Daten dem Zeiger pd zugewiesen werden kann.
-
(2) Der Zweck der Verwendung von sizeof besteht darin, die Anzahl der Bytes zu berechnen, die ein Typ belegt, um verschiedenen Compilern gerecht zu werden.
(3) Da dynamische Speicherzuweisung nicht immer erfolgreich sein muss, sollte eine Ausnahmebehandlungsroutine hinzugefügt werden, damit das Programm nicht anhält und den Benutzer ratlos zurücklässt. Normalerweise wird ein solches Ausnahmebehandlungssegment verwendet: if(p==NULL) /* orif(!p)*/ { printf("Dynamic申pleaseMemoryfailure!\n"); exit(1); //ExceptionLogout }
-
(4) Die Header-Dateien für diese vier Funktionen sind alle in
in. -
(5) Der zugewiesene Heap-Speicher hat keinen Namen; er kann nur über den zurückgegebenen Zeiger gefunden werden.
-
(6) Verwenden Sie niemals free für einen nicht dynamisch zugewiesenen Speicherblock. Sie können auch nicht denselben Speicherbereich zweimal mit free freigeben. Zum Beispiel: free(p);free(p);
(7) Wenn free() aufgerufen wird, wird der Speicher freigegeben, auf den der übergebene Zeiger zeigt, aber der Zeigerwert in der aufrufenden Funktion kann unverändert bleiben, weil p als formaler Parameter an die Funktion übergeben wird. Streng genommen ist der Wert des freigegebenen Zeigers ungültig, weil er nicht mehr auf den zugewiesenen Speicherbereich zeigt. Jede Verwendung zu diesem Zeitpunkt kann Probleme verursachen.
Der Unterschied zwischen malloc und calloc
Für den mit malloc zugewiesenen Speicherbereich kann jedes Bit darin 0 sein, wenn er zuvor noch nicht verwendet wurde; umgekehrt kann dieser Teil des Speichers, wenn er zuvor zugewiesen, freigegeben und erneut zugewiesen wurde, alle möglichen Restdaten enthalten. Mit anderen Worten: Ein Programm, das malloc() verwendet, kann anfangs normal laufen (wenn der Speicherplatz noch nicht erneut zugewiesen wurde), aber nach einer gewissen Zeit (nachdem der Speicherplatz erneut zugewiesen wurde) können Probleme auftreten. Daher muss er vor der Verwendung initialisiert werden (memset kann verwendet werden, um ihn auf 0 zu initialisieren). Der durch Aufrufen von calloc() zugewiesene Speicherplatz ist jedoch zum Zeitpunkt der Zuweisung bereits auf 0 initialisiert. Bei der Wahl zwischen calloc() und malloc() müssen Sie berücksichtigen, ob der zugewiesene Speicherplatz initialisiert werden soll, und dann die entsprechende Funktion auswählen.
Spezielle Konstruktionsmethode
Nehmen wir das dreidimensionale Integer-Array array[n1][n2][n3] als Beispiel.
Befolgen Sie zunächst das Prinzip, Ebene für Ebene von der äußeren Ebene zur inneren Ebene Speicher zu reservieren:
Der äußerste Zeiger ist array, ein dreidimensionaler Zeiger. Worauf er zeigt, ist array[], ein zweidimensionaler Zeiger. Also für array
sollte die Speicherreservierung so erfolgen:
array=(int***)calloc(n1,sizeof(int**));
Der Zeiger der nächsten Ebene ist array[], ein zweidimensionaler Zeiger. Worauf er zeigt, ist array[][], ein eindimensionaler Zeiger. Also für array[]
sollte die Speicherreservierung so erfolgen:
for(i=0;i<n1;i++)
{
array[i]=(int**)calloc(n2,sizeof(int*));
}
Der innerste Zeiger ist array[][], ein eindimensionaler Zeiger. Worauf er zeigt, ist array[][][], eine Integer-Konstante. Um also Speicher für array[][] zu reservieren, sollte man:
for(i=0;i<n1;i++)
{
for(j=0;j<n2;j++)
{
array[i][j]=(int*)calloc(n3,sizeof(int));
}
}
Natürlich kann man sie auch folgendermaßen zusammenfassen:
int i,j,k;
int n1,n2,n3;
int ***array;
scanf("%d%d%d",&n1,&n2,&n3);
array=(int***)calloc(n1,sizeof(int**));
for(i=0;i<n1;i++)
{
array[i]=(int**)calloc(n2,sizeof(int*));
for(j=0;j<n2;j++)
{
array[i][j]=(int*)calloc(n3,sizeof(int));
for(k=0;k<n3;k++)
{
array[i][j][k]=i+j+k+1;
}
}
}
Vergessen Sie schließlich nicht, diesen Speicher freizugeben. Dabei sollte das Prinzip befolgt werden, Ebene für Ebene von der inneren Ebene zur äußeren Ebene freizugeben.
Für den Analyseprozess verweisen Sie auf die obige Lösung; ich gehe hier nicht ins Detail. Ich gebe nur den Code an:
for(i=0;i<n1;i++)
{
for(j=0;j<n2;j++)
{
free(array[i][j]);//释放第三维指针
}
}
for(i=0;i<n1;i++)
{
free(array[i]);//释放第二维指针
}
free(array);//释放第一维指针
Der Erstellungsprozess für andere Dimensionen, wie etwa vierdimensionale, ist weitgehend derselbe, daher gehe ich hier nicht ins Detail.