Ich war schon immer neugierig auf Computer und dachte immer: „Okay, ich weiß, wie man sie benutzt, aber wie funktioniert das wirklich?“ Dabei mache ich oft ein Gedankenexperiment: Wenn ich gebeten würde, damit anzufangen Was würde ich von vorne anfangen? In diesem Artikel werden wir untersuchen, wie Schnittstellen in der objektorientierten Programmierung (mit Java) funktionieren, und dann eine bescheidene Version der Schnittstelle in C implementieren.
Unser Beispiel ist einfach: Berechnen Sie den Preis eines Fahrzeugs. Handelt es sich um ein Auto, richtet sich der Preis nach der Höchstgeschwindigkeit, bei einem Motorrad nach dem Hubraum. Wir definieren zunächst das Verhalten des Fahrzeugs über eine Schnittstelle:
<code class="language-java">public class Main { public interface Vehicle { Integer price(); } }</code>
Hier gibt es nichts Besonderes, nur eine Methode, die eine Ganzzahl zurückgibt. Jetzt implementieren wir die Autoklasse:
<code class="language-java">public class Main { // ... public static class Car implements Vehicle { private final Integer speed; public Car(Integer speed) { this.speed = speed; } @Override public Integer price() { return speed * 60; } } }</code>
Ganz klassisch: eine Implementierung einer Konstruktor- und Preismethode, die die Geschwindigkeit mit 60 multipliziert. Nun implementieren wir die Motorradklasse:
<code class="language-java">public class Main { // ... public static class Motorcycle implements Vehicle { private final Integer cc; public Motorcycle(Integer cc) { this.cc = cc; } @Override public Integer price() { return cc * 10; } } }</code>
Fast das Gleiche, der einzige Unterschied besteht darin, dass wir jetzt die Verschiebung mit 10 multiplizieren. Anschließend implementieren wir eine Methode zum Drucken des Fahrzeugpreises:
<code class="language-java">public class Main { // ... public static void printVehiclePrice(Vehicle vehicle) { System.out.println("$" + vehicle.price() + ".00"); } }</code>
Keine Geheimnisse. Zum Schluss unsere Hauptmethode:
<code class="language-java">public class Main { // ... public static void main(String[] args) { Car car = new Car(120); Motorcycle motorcycle = new Motorcycle(1000); printVehiclePrice(car); printVehiclePrice(motorcycle); } }</code>
<code>$ java Main.java 00.00 000.00</code>
Dies ist das Modell, das wir erreichen wollen, aber jetzt von Grund auf in C implementiert.
Wenn ich an Objekte denke, fallen mir als Erstes eine Reihe von Daten ein, die einen Zustand darstellen, sowie Methoden, um diesen Zustand zu betreiben und zu verwalten. Der direkteste Weg, eine Datensammlung in der Sprache C darzustellen, ist eine Struktur. Bei Methoden ist der nächste Ansatz eine Funktion, die einen Zustand als Parameter empfängt. Dieser Zustand wird diesem in der Klasse entsprechen, zum Beispiel:
<code class="language-c">typedef struct { int height_in_cm; int weight_in_kg; } Person; float person_bmi(Person *person) { float height_in_meters = (float)person->height_in_cm / 100; float bmi = (float)person->weight_in_kg / (height_in_meters * height_in_meters); return bmi; }</code>
Hier definieren wir die Daten einer Person in der Personenstruktur und verwenden diese Daten, um einfache Berechnungen durchzuführen. Dies ist die Struktur, die einer Klasse am nächsten kommt, die wir in C haben können. Vielleicht ist die Verwendung von Funktionszeigern in Strukturen auch eine gute Idee? Nun, das belasse ich für den nächsten Artikel.
Okay, wir haben eine klassenähnliche Struktur. Wie definieren wir nun die Schnittstelle in der C-Sprache? Wenn Sie darüber nachdenken, kann der Compiler/Interpreter nicht zaubern, um zu erraten, welche Klassen die Schnittstelle implementieren. Es ermittelt dies zur Kompilierungszeit und ersetzt alle Teile, in denen wir die Schnittstelle verwenden, durch konkrete Typen. Im kompilierten Programm existiert die Schnittstelle nicht einmal.
Da der C-Sprachcompiler diese Möglichkeit nicht bietet, müssen wir diese Lösung selbst implementieren. Wir müssen alle Typen kennen, die unsere Schnittstelle implementieren, und herausfinden, wie wir die Funktionen dieser Implementierungen verwenden.
Lassen Sie uns zunächst das Grundgerüst unserer bescheidenen Benutzeroberfläche definieren. Wir erstellen eine Enumeration, die die verschiedenen Implementierungen und Signaturen unserer Funktionen enthält.
<code class="language-java">public class Main { public interface Vehicle { Integer price(); } }</code>
Hier definieren wir unsere Enumeration, die die Implementierung enthält, die wir später implementieren werden. Das scheint vielleicht nicht so, aber dieser Teil ist sehr wichtig. Als nächstes deklarieren wir die Funktion „vehikel_frei“ (wird später erklärt) und die Funktion „fahrzeug_preis“, die wir in unserer „Klasse“ implementieren möchten. Schauen wir uns nun die Auto-Implementierung an:
<code class="language-java">public class Main { // ... public static class Car implements Vehicle { private final Integer speed; public Car(Integer speed) { this.speed = speed; } @Override public Integer price() { return speed * 60; } } }</code>
Die Funktion car_init initialisiert ein neues „Objekt“ Auto im Speicher. In Java erfolgt dies automatisch über new. Hier müssen wir es manuell tun. Die Funktion „vehikel_free“ wird verwendet, um den Speicher freizugeben, der von einem zuvor initialisierten „Objekt“ zugewiesen wurde, das mit „car_free“ usw. implementiert wurde. Die Umsetzung für Motorräder ist sehr ähnlich:
<code class="language-java">public class Main { // ... public static class Motorcycle implements Vehicle { private final Integer cc; public Motorcycle(Integer cc) { this.cc = cc; } @Override public Integer price() { return cc * 10; } } }</code>
Fast das Gleiche, außer dass wir jetzt mit VEHICLE_MOTORCYCLE initialisieren und mit 10 multiplizieren. Schauen wir uns nun die Funktion an, die den Fahrzeugpreis ausdruckt:
<code class="language-java">public class Main { // ... public static void printVehiclePrice(Vehicle vehicle) { System.out.println("$" + vehicle.price() + ".00"); } }</code>
So einfach...es sieht nicht so aus, als würden wir viel arbeiten. Der letzte und wichtigste Punkt ist, dass wir die Funktionen implementieren müssen, die wir in der Schnittstellendefinition oben deklariert haben, erinnern Sie sich? Glücklicherweise müssen wir über diese Implementierung nicht einmal nachdenken. Wir haben immer einen einfachen, umfassenden Schalter/Koffer und das war's.
<code class="language-java">public class Main { // ... public static void main(String[] args) { Car car = new Car(120); Motorcycle motorcycle = new Motorcycle(1000); printVehiclePrice(car); printVehiclePrice(motorcycle); } }</code>
Jetzt können wir alles nutzen, was wir getan haben:
<code>$ java Main.java 00.00 000.00</code>
<code class="language-c">typedef struct { int height_in_cm; int weight_in_kg; } Person; float person_bmi(Person *person) { float height_in_meters = (float)person->height_in_cm / 100; float bmi = (float)person->weight_in_kg / (height_in_meters * height_in_meters); return bmi; }</code>
Erfolg! Aber Sie denken vielleicht: „Was nützt das?“
Eine meiner Lieblingsprojektarten sind Parser, vom Parser bis zum einfachen Parser für mathematische Ausdrücke. Wenn Sie diese Parser implementieren, stoßen Sie normalerweise auf etwas, das als AST (Abstract Syntax Tree) bezeichnet wird. Wie der Name schon sagt, handelt es sich um einen Baum, der die Syntax darstellt, mit der Sie es zu tun haben. Beispielsweise ist die Variablendeklaration int foo = 10 ein Knoten des AST, der drei weitere Knoten enthält, einen Typknoten, für int einen Bezeichnerknoten , für foo, und einen Ausdrucksknoten, für 10, der einen weiteren Ganzzahlknoten mit dem Wert 10 enthält. Sehen Sie, wie kompliziert es ist?
Wenn wir dies in C tun, müssen wir zwischen einer großen Struktur mit mehreren Feldern zur Darstellung jedes möglichen AST-Knotens oder einer abstrakten Definition mit mehreren kleinen Strukturen wählen, wobei jede Struktur unterschiedliche Knoten darstellt, genau wie wir es hier mit unserem tun „Schnittstellen“. Wenn Sie ein einfaches Beispiel sehen möchten: In diesem Parser für mathematische Ausdrücke habe ich den zweiten Ansatz implementiert.
Nichts, was ein Compiler oder Interpreter tut, ist Magie. Es macht immer Spaß, zu versuchen, etwas selbst umzusetzen. Ich hoffe, das war eine hilfreiche Lektüre. Danke!
Das obige ist der detaillierte Inhalt vonObjektorientierung in C? Implementierung einer Schnittstelle von Grund auf.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!