|
lite-C Pointer |
Top Previous Next |
|
Der "Plug and Play" Artikel in diesem Monat ist eigentlich ein Workshop, der sich um eines am häufigsten missverstandenen Aspekte der Programmierung dreht: lite-C Pointer. Es gibt viele Anfänger, die nicht wissen wann und wie sie diese verwenden müssen und um diese Probleme wollen wir uns heute (hoffentlich) ein für alle Mal kümmern. Natürlich wird es mehrere "Plug and Play" Beispiele geben. Und vergessen Sie nicht den Workshop aus AUM70, in dem es um Standard C Pointer geht.
Jede Variable muss irgendwo im Speicher des Computers sitzen, der in verschiedene "Zellen" aufgeteilt ist, in denen Werte stehen können. Nehmen wir mal an, ich habe wie hier einige Variablen definiert.
int shotgun_bullets = 12; var players_health = 100; double seconds_passed = 1000000;
Dies alles sind gültige Deklarationen. Falls wir sie in ein Skript schreiben und dieses starten, dann wissen wir, dass es eine ganzzahlige Variable shotgun_bullets gibt, die den Wert 12 hat und irgendwo im Speicher steht. Der Speicher unseres PCs könnte so aussehen:
Niemand weiß genau, wo diese Variablen im Speicher stehen, sicher ist nur, dass sie ein oder mehrere Speicherzellen belegen. Wäre es nicht gut zu wissen, wie viele dieser Zellen (also wie viele Bytes) jeweils benötigt werden?
Die lite-C Sprache besitzt eine wichtige Funktion sizeof(), die genau dies leistet: sie liefert die Größe (in Bytes) zurück, die eine Variable oder Struktur im Speicher einnimmt. Starten Sie SED, öffnen Sie pointers1.c und starten Sie as Skript. Sie werden folgendes Bild sehen:
Sehen wir uns den Code an, der dies ermöglicht.
#include <acknex.h> #include <default.c>
int shotgun_bullets = 12; var players_health = 100; double seconds_passed = 1000000; var int_size, var_size, double_size;
FONT* arial_font = "Arial#20";
PANEL* time_pan = { layer = 15; digits (150, 130, "The size of shotgun_bullets is %.f bytes", arial_font, 1, int_size); digits (150, 160, "The size of players_health is %.f bytes", arial_font, 1, var_size); digits (150, 190, "The size of seconds_passed is %.f bytes", arial_font, 1, double_size); flags = visible; }
function main() { video_mode = 6; // create a program window of 640x480 pixels vec_set(screen_color, vector(0, 0, 0)); // make the background color black int_size = sizeof(shotgun_bullets); var_size = sizeof(players_health); double_size = sizeof(seconds_passed); }
Nichts Kompliziertes geschieht hier, die main Funktion erledigt alles für uns. Wir haben drei Variablen namens int_size, var_size und double_size und wir verwenden die sizeof() Funktion, um die Werte mit Hilfe von "digits" Elementen auf einem Panel anzuzeigen.
Nun wissen wir, wie viele Bytes eine Variable reserviert. Diese Daten stehen auch in einer Tabelle im Handbuch. Aber wäre es nicht schön, wenn wir direkt auf diese Zellen zugreifen könnten? Dies würde es uns erlauben, auf komplexe Datenstrukturen (Arrays, Matrizen, etc.) zuzugreifen, uns selbst Speicherbereiche zu reservieren etc.
Schauen wir uns pointers2.c einmal an.
#include <acknex.h> #include <default.c>
int shotgun_bullets = 12; var cell_value;
FONT* arial_font = "Arial#20";
PANEL* time_pan = { layer = 15; digits (100, 130, "The value stored in the memory for shotgun_bullets is %.f", arial_font, 1, cell_value); flags = visible; }
function main() { video_mode = 6; // create a program window of 640x480 pixels vec_set(screen_color, vector(0, 0, 0)); // make the background color black int *p; p = &shotgun_bullets; cell_value = *p; }
Die main Funktion enthält drei Codezeilen, die im Grunde die wichtigsten Dinge für das Arbeiten mit Pointern beinhalten. Wenn Sie diese verstehen, haben Sie zu 90% gewonnen. Und los gehts:
1) int *p; deklariert einen Pointer, eine Variable, in der eine Speicheradresse steht. Wir verwenden Zeiger nicht, um Werte von Variablen zu speichern, sondern die Adressen der Speicherzellen, wo diese Werte stehen.
Wir können alle möglichen Pointer deklarieren. All die folgenden Möglichkeiten sind gültig:
int *my_pointer; // pointer to an integer variable float *my_new_pointer; // pointer to a float variable char *my_character_pointer; // pointer to a character
Wir können auch mehrere Pointer des gleichen Typs mit einer einzigen Zeile deklarieren, so wie hier:
long *first_long_pointer, *second_long_pointer, *third_long_pointer;
Ein Pointer ist in vielen Dingen vergleichbar mit der Adresse eines Hauses in der Stadt. Wenn Sie eine Pizza in ein Haus liefern wollen (oder von dort etwas ausleihen wollen), dann müssen Sie die Adresse kennen. Ähnlich ist es, wenn Sie einen Wert in eine bestimmte Speicherzelle schreiben oder den Wert dort auslesen wollen - dann brauchen Sie einen Pointer auf diese Adresse.
2) p = &shotgun_bullets; weist die Adresse (&) unserer Variable shotgun_bullets dem Pointer p zu (wichtig ist, dass wir hier p und nicht etwa *p schreiben!). Von nun an ist p wie der Pizzalieferant, der die Adresse von shotgun_bullets kennt und dort jederzeit etwas abliefern oder etwas holen kann.
Also, diese Zeile stellt eine Verbindung her, zwischen dem Pointer p und der Speicherzelle, die davon betrachtet wird. Schneller Test: Wenn wir den Wert von p anzeigten, as würden wir sehen? Denken Sie kurz darüber nach. In der Zwischenzeit platziere ich hier schnell ein Bild eines unserer Künstler, damit Sie nicht "versehentlich" die Antwort lesen...
Die Antwort ist einfach: p enthält die Adresse von shotgun_bullets, die Speicheradresse, in der die Variable steht. Falls Sie gedacht haben, dass p den Wert 12 hat, erinnern Sie sich daran, dass Pointer nicht die Werte, sondern nur die Adressen speichern. Wir können die Adresse der Speicherzelle, auf die p zeigt, nicht vorhersehen, sie ändert sich von PC zu PC und kann sich auch auf dem gleichen PC ändern, wenn das Programm ein zweites Mal gestartet wird. Es ist aber beruhigend zu wissen, dass wir gleich was geschieht mit Hilfe von Pointern nach wie vor auf unsere wertvollen Daten zugreifen können, egal wie diese konkret auf dem jeweiligen PC aussehen.
3) Die dritte Codezeile ist die folgende: cell_value = *p; Diese Zeile ermittelt den Wert an der Speicherzelle, auf die p zeigt und speichert diesen in cell_value. Wir wissen, dass p die Adresse von shotgun_bullets (&shotgun_bullets) beinhaltet. Der * Operator liefert uns den Wert an dieser Adresse (den Wert von shotgun_bullets), welcher 12 ist. Diesen zeigen wir dann mit einem "digits" Element an.
Schauen wir uns den Prozess noch einmal an.
- Zunächst brauchen wir einen Pointer. Dieser braucht einen Typ und einen Namen. Falls er auf einen int Wert verweisen soll, brauchen wir einen int Pointer und so weiter. Der Name des Pointers beginnt mit einem *, damit die Engine weiß, dass wir keine gewöhnliche Variable, sondern einen Pointer deklariert haben. Zum Beispiel: float *pointer_to_float; - Falls wir einen Pointer definiert haben, können wir diesem die Adresse einer Variable mit Hilfe des & Operators zuweisen. Beispiel: pointer_to_float = &my_float_variable; - Wenn wir schließlich auf den Wert der Speicherzelle zugreifen wollen, auf die der Pointer zeigt, verwenden wir den * Operator. Beispiel: test = *pointer_to_float; Dieser Prozess nennt sich Dereferenzierung. Damit greifen wir wieder auf den Wert zu, auf den der Pointer zeigt. Sie können die Begriffe einfach ignorieren, das ist Tech Talk. Ich weiß nicht wie es Ihnen geht, aber ich hoffe, der Nebel lichtet sich ein wenig. Lassen Sie uns nun das, was wir über Standard C Pointer gelernt haben, auf lite-C anwenden.
Regel 1: Alle Engine Objekte sind Pointer. Schauen Sie sich diese Definitionen an.
BMAP* picture1_pcx = "picture1.pcx";
SOUND* effect2_wav = "effect2.wav";
STRING* hello_str = "Hello World!";
FONT* arial_font = "Arial#20";
PANEL* gameover_pan = { layer = 15; pos_x = 300; pos_y = 200; bmap = picture1_pcx; }
TEXT* message_txt = { pos_x = 200; pos_y = 20; string(hello_str); flags = VISIBLE; }
All dies sind Pointer Deklarationen udn so müssen Sie auch definiert werden. Versuchen Sie nicht so etwas wie das Folgende, das funktioniert nicht:
STRING hello_str = "Hello World!"; // this is not a pointer definition, so it won't work!
Was ist mit Vektoren? Wann und wie soll ein Vektor Pointer - VECTOR* - verwendet werden und wann eine einfache VECTOR Definition? Öffnen Sie pointers3.c und starten Sie das Programm, dann sehen Sie einen einfachen schwarzen Bildschirm.
Schauen Sie sich den Code an.
#include <acknex.h> #include <default.c>
VECTOR* my_vector;
function main() { video_mode = 6; // create a program window of 640x480 pixels vec_set(screen_color, vector(0, 0, 0)); // make the background color black }
Dies ist wohl einer der kürzesten Code Schnipsel überhaupt! Die einzige wichtige Codezeile ist die, in der der Vektor Pointer definiert wird. Ein Vektor ist einfach ein Name für 3 gruppierte Variablen; Sie sind mit Vektoren vertraut, wenn Sie schonmal Konstrukte wie player.x, player.y und player.z verwendet haben.
Also weiter, fügen wir noch eine Zeile hinzu. Wir werden die erste Komponente von my_vector initialisieren. Die folgende Zeile muss in die Funktion main:
my_vector.x = 1;
Diese sollte nun so aussehen:
function main() { video_mode = 6; // create a program window of 640x480 pixels vec_set(screen_color, vector(0, 0, 0)); // make the background color black my_vector.x = 1; }
Speichern Sie das Skript und starten Sie es. Obwohl unsere neue Codezeile nicht so gefährlich aussieht, begrüßt uns gleich eine häßliche Fehlermeldung.
Sicher ist hier etwas schief gelaufen... schauen wir uns die Definition nochmal genau an (nicht nur lesen, genau hinschauen!):
VECTOR* my_vector;
Und nun betrachten wir die lite-C Pointer Definitionen oben:
BMAP* picture1_pcx = "picture1.pcx";
SOUND* effect2_wav = "effect2.wav";
STRING* hello_str = "Hello World!";
Was ist der Unterschied? Versuchen Sie, selbst daraus zu kommen!
Die Antwort ist ganz einfach: Die Pointer, die funktionieren, werden etwas zugewiesen. picture1_pcx zeigt auf picture1.pcx, effect2_wav zeigt auf effect2.wav und so weiter. Unser Vektor Pointer wurde zwar definiert, aber zeigte auf keine Struktur, also führte der Versuch, auf seine x Komponente zuzugreifen zu einem Absturz.
Wir können unseren Vektor Pointer auf einen existierenden Vektor zeigen lassen, oder ihn direkt initialisieren, so wie die anderen Pointer:
VECTOR* my_vector = {x = 1; y = 2; z = 3;} // this vector pointer definition works fine!
Auf diese Weise können wir mit dem Vektor arbeiten, auf seine Komponenten zugreifen, etc. Nichtsdestotrotz sollte man Vektor Pointer vermeiden wenn es geht. Es gibt nämlich nur 64 Slots für sie, es könnte also etwas verloren gehen, wenn wir zu viele verwenden. Vektor Pointer sind wirkich dafür gedacht, wenn ein oder mehrere Vektoren an eine Funktion übergeben werden sollen, so wie hier.
function do_something(VECTOR* some_vector) { // do something with the values of the vector here // ....................... // don't forget to return the result of the operation! }
In diesem Fall müssen wir die Werte von some_vector nicht speichern. Wir übergeben sie einfach der Funktion und brauchen uns nicht mehr darum kümmern. Wenn ich mir die Funktion so ansehe, stelle ich fest, dass ein praktisches Beispiel nicht schlecht wäre, schauen wir uns also pointers4.c an.
VECTOR secret_code; VECTOR inv_result;
PANEL* time_pan = { layer = 15; digits (250, 200, 6, arial_font, 1, secret_code.x); digits (250, 220, 6, arial_font, 1, secret_code.y); digits (250, 240, 6, arial_font, 1, secret_code.z); digits (350, 200, 6, arial_font, 1, inv_result.x); digits (350, 220, 6, arial_font, 1, inv_result.y); digits (350, 240, 6, arial_font, 1, inv_result.z); flags = visible; }
function vec_invert(VECTOR* my_vector) { inv_result.x = -my_vector.x; inv_result.y = -my_vector.y; inv_result.z = -my_vector.z; return &inv_result; // we can't return more than a variable, so we return the address of inv_result }
function main() { video_mode = 6; // create a program window of 640x480 pixels vec_set(screen_color, vector(0, 0, 0)); // make the background color black vec_set(secret_code.x, vector(1, 2, 3)); // let's set our secret code vec_invert(secret_code); // and now let's invert it, so that nobody will be able to decipher it }
Wie Sie sehen werden zwei Vektoren (keine Pointer!) definiert. Einer von diesen enthält unseren geheimen Code und der andere die verschlüsselte Variante (die negativen Werte des anderen). Wir verwenden ein Panel mit 6 digits Elementen, welche die Komponenten unserer Vektoren anzeigen. Die Funktion main setzt unseren geheimen Code auf (1, 2, 3) und ruft dann vec_invert auf.
function vec_invert(VECTOR* my_vector) { inv_result.x = -my_vector.x; inv_result.y = -my_vector.y; inv_result.z = -my_vector.z; return &inv_result; // we can't return more than a variable, so we return the address of inv_result }
Diese Funktion erwartet einen Vektor Pointer als Parameter. Der Code kehrt das Vorzeichen jeder Komponente um und liefert das Ergebnis zurück. Die schlechte Nachricht ist, dass eine Funktion nur einen Wert zurückliefern kann, also können nicht alle Komponenten zurückgeliefert werden. Keine Angst, es gibt einen Ausweg: Wir können einfach die Adresse des Vektors zurückliefern und so auf alle Komponenten zugreifen. Dies ist, was die "return &inv_result;" Anweisung leistet.
Zusammenfassend gilt: Falls Sie einen permanenten Vektor benötigen, verwenden Sie keinen Pointer, sondern eine RICHTIGE Vektor Definition so wie hier.
VECTOR my_position;
Sie können globale oder lokale Vektoren nutzen, ohne sich Gedanken über potentielle Fehler machen zu müssen. Es sind einfach Variablen, müssen also nicht initialisiert werden oder auf etwas zeigen. Verwenden Sie einen Vektor Pointer nur dann, wenn Sie die Werte eines Vektors an eine Funktion übergeben wollen.
Zurück zu unserer Pointer Diskussion: Wir können natürlich Pointer definieren und ihnen kein Objekt zuweisen, sofern wir sie erst dann verwenden, wenn sie ein Objekt haben.
ENTITY* my_robot; // valid pointer definition
PANEL* winner_pan; // valid pointer definition
Es wäre allerdings ein großer Fehler, den VECTOR* Fehler zu wiederholen und zu versuchen, auf my_robot.x oder winner_pan.alpha zuzugreifen, ehe der Engine gesagt wurde, auf welche Entity bzw. welches Panel jeweils gezeigt werden soll. Öffnen Sie pointers5.c, dann sollten Sie eine weitere Fehlermeldung sehen.
Schauen wir uns den Code an.
#include <acknex.h> #include <default.c>
ENTITY* r2_robot;
action robot1() { r2_robot = my; // now the r2_robot pointer is assigned to the entity that has this action attached to it }
function main() { video_mode = 6; // create a program window of 640x480 pixels level_load("test.wmb"); r2_robot.z += 100; }
Ist es nicht erstaunlich, dass ein Fehler in so wenig Code stecken kann? Was ist hier schief gegangen? Zunächst wird ein Entity Pointer namens r2_robot definiert - kein Problem. Dann haben wir eine Action, die den Pointer dem Model zuweist, der die action robot1() in WED hat - unser Roboter! Schließlich lädt die Funktion main das Level und versucht, den Roboter 100 Quants nach oben zu bewegen. Es sieht alles korrekt aus, wo also ist der Fehler? Versuchen Sie selbst darauf zu kommen.
Die Pointer Definition und die Zuweisung sind Ok, aber es gibt einen Fehler mit dem Zeitablauf. Die "level_load" Anweisung braucht Zeit, um das Level von der Festplatte zu laden, also r2_robot.z += 100; liefert einen Fehler, weil das Model noch nicht existiert. Die Lösung ist einfach: Sie brauchen eine "wait" Anweisung, ehe Sie auf den Roboter zugreifen können.
function main() { video_mode = 6; // create a program window of 640x480 pixels level_load("test.wmb"); wait (3); // wait until the level and the entities from the level are loaded r2_robot.z += 100; }
Eine bessere Lösung ist es, sicherzustellen, dass der Pointer gültig ist, ehe Sie darauf zugreifen, so wie hier.
function main() { video_mode = 6; // create a program window of 640x480 pixels level_load("test.wmb"); while (!r2_robot) {wait (1);} // wait until the robot pointer becomes valid (the robot model is loaded in the level) r2_robot.z += 100; }
Dies war ein langer Workshop, aber ich hoffe, dass Sie sich mit lite-C Pointern nun etwas wohler fühlen. Im nächsten Monat besprechen wir lite-C Strukturen, spezielle Objekte, die Variablen, Pointer oder sogar andere Strukturen enthalten können.
|