Bisher habe ich nur objektorientierte Programmiersprachen (C ++, Ruby, Python, PHP) verwendet und lerne jetzt C. Ich finde es schwierig, die richtige Art und Weise zu finden, Dinge in einer Sprache zu tun, für die es kein Konzept gibt 'Objekt'. Mir ist klar, dass es möglich ist, OOP-Paradigmen in C zu verwenden, aber ich würde gerne die C-Idiomatik lernen.
Wenn ich ein Programmierproblem löse, stelle ich mir zuerst ein Objekt vor, das das Problem löst. Durch welche Schritte ersetze ich dies, wenn ich ein Paradigma für die imperative Programmierung ohne OOP verwende?
object-oriented
c
Mas Bagol
quelle
quelle
qux = foo.bar(baz)
zuqux = Foo_bar(foo, baz)
.Antworten:
struct
.Das ist es.
Wie hast du eine Klasse geschrieben? So schreiben Sie eine .C-Datei. Zugegeben, Sie erhalten keine Methodenpolymorphie und Vererbung, aber Sie können solche mit unterschiedlichen Funktionsnamen und -zusammensetzungen trotzdem simulieren .
Studieren Sie die Funktionale Programmierung, um den Weg zu ebnen . Es ist wirklich erstaunlich, was Sie ohne Unterricht tun können , und einige Dinge funktionieren tatsächlich besser, ohne den Aufwand für Unterricht.
Weitere Lektüre
Objektorientierung in ANSI C
quelle
typedef
dasstruct
und machen etwas klassen wie . undtypedef
-ed-Typen können in andere Typen eingeschlossen werdenstruct
, die selbsttypedef
-ed sein können. Was Sie mit C nicht bekommen, ist das Überladen von Operatoren und die oberflächlich einfache Vererbung von Klassen und den darin enthaltenen Elementen in C ++. und Sie bekommen nicht viel seltsame und unnatürliche Syntax, die Sie mit C ++ bekommen. Ich liebe das Konzept von OOP wirklich, aber ich denke, C ++ ist eine hässliche Realisierung von OOP. i wie C , weil es ist eine kleinere Sprache und Blätter aus Syntax der Sprache, die am besten zu Funktionen verlassen wird.a lot
of things actually work better without the overhead of classes
a lot of things actually work better with addition of class-based OOP
. Quelle: TypeScript, Dart, CoffeeScript und alle anderen Methoden, mit denen die Industrie versucht, sich von einer funktionalen / prototypischen OOP-Sprache zu lösen.Lesen Sie SICP und lernen Sie Schema und die praktische Idee der abstrakten Datentypen . Dann ist das Codieren in C einfach (da mit SICP, ein bisschen C und ein bisschen PHP, Ruby usw. Ihre Überlegungen erweitert würden und Sie verstehen würden, dass objektorientierte Programmierung möglicherweise nicht der beste Stil in C ist in allen Fällen, aber nur für bestimmte Programme). Seien Sie vorsichtig mit der dynamischen Speicherzuweisung in C , was wahrscheinlich der schwierigste Teil ist. Der Programmiersprachenstandard C99 oder C11 und seine C-Standardbibliothek sind eigentlich ziemlich schlecht (TCP oder Verzeichnisse sind ihm nicht bekannt!), Und Sie benötigen häufig einige externe Bibliotheken oder Schnittstellen (z. B.POSIX , libcurl für die HTTP-Client-Bibliothek, libonion für die HTTP-Server-Bibliothek, GMPlib für Bignums, einige Bibliotheken wie libunistring für UTF-8 usw.).
Ihre "Objekte" sind häufig in C verwandte
struct
-s, und Sie definieren die Menge der Funktionen, die auf sie angewendet werden. Für kurze oder sehr einfache Funktionen sollten Sie sie mit der entsprechenden Definitionstruct
wiestatic inline
in einer Header-Datei definierenfoo.h
, die an#include
anderer Stelle eingefügt werden soll.Beachten Sie, dass objektorientierte Programmierung nicht das einzige Programmierparadigma ist . In einigen Fällen lohnen sich andere Paradigmen ( funktionale Programmierung à la Ocaml oder Haskell oder sogar Schema oder Commmon Lisp, logische Programmierung à la Prolog usw. usw.). Lesen Sie auch J.Pitrats Blog über deklarative künstliche Intelligenz. Siehe Scotts Buch: Programming Language Pragmatics
Tatsächlich möchte ein Programmierer in C oder Ocaml normalerweise nicht in einem objektorientierten Programmierstil programmieren. Es gibt keinen Grund, sich dazu zu zwingen, an Gegenstände zu denken, wenn dies nicht nützlich ist.
Sie definieren einige
struct
und die Funktionen, die auf ihnen ausgeführt werden (häufig durch Zeiger). Möglicherweise benötigen Sie einige getaggte Gewerkschaften (häufig einestruct
mit einem Tag-Mitglied, häufig einigeenum
und einige imunion
Inneren), und möglicherweise ist es hilfreich, am Ende einiger Ihrer -s ein flexibles Array-Mitglied zu habenstruct
.Schauen Sie sich den Quellcode einiger freier Software in C an (siehe github & sourceforge , um einige zu finden). Wahrscheinlich wäre die Installation und Verwendung einer Linux-Distribution nützlich: Sie besteht fast nur aus freier Software, enthält großartige C-Compiler für freie Software ( GCC , Clang / LLVM ) und Entwicklungstools. Siehe auch Erweiterte Linux-Programmierung, wenn Sie für Linux entwickeln möchten.
Vergessen Sie nicht , mit allen Warnungen und Debug - Informationen, zum Beispiel zu kompilieren
gcc -Wall -Wextra -g
-notably bei der Entwicklung und Debugging phases- und lernen einige Werkzeuge zu verwenden, zB valgrind zu jagen Speicherlecks , dergdb
Debugger usw. Achten Sie auf gut zu verstehen , was nicht definiert Verhalten und stark vermeiden Sie es (denken Sie daran, dass ein Programm einige UB haben könnte und manchmal zu "arbeiten" scheint).Wenn Sie wirklich objektorientierte Konstrukte (insbesondere Vererbung ) benötigen , können Sie Zeiger auf verwandte Strukturen und auf Funktionen verwenden. Sie könnten Ihre eigene vtable- Maschinerie haben, wobei jedes "Objekt" mit einem Zeiger auf einen
struct
enthaltenden Funktionszeiger beginnt . Sie profitieren von der Möglichkeit, einen Zeigertyp in einen anderen Zeigertyp umzuwandeln (und von der Tatsache, dass Sie einenstruct super_st
Zeigertyp umwandeln können , der dieselben Feldtypen enthält wie diejenigen, die mit a beginnenstruct sub_st
, um die Vererbung zu emulieren). Beachten Sie, dass C ausreicht, um ziemlich ausgefeilte Objektsysteme zu implementieren - insbesondere unter Einhaltung einiger Konventionen -, wie GObject (von GTK / Gnome) demonstriert.Wenn Sie wirklich brauchen , Verschlüsse , werden Sie oft emulieren sie mit Rückrufen , mit der Konvention , dass jede Funktion eines Rückruf mit beiden Zeigern einer Funktion übergeben wird und einige Client - Daten (von dem Funktionszeiger verbraucht wird, wenn es das nennt). Sie könnten auch (konventionell) Ihre eigenen schließungsähnlichen
struct
-s haben (die einen Funktionszeiger und die geschlossenen Werte enthalten).Da C eine sehr einfache Sprache ist, ist es wichtig, eigene Konventionen zu definieren und zu dokumentieren (inspiriert von der Praxis in anderen C-Programmen), insbesondere in Bezug auf die Speicherverwaltung und möglicherweise auch einige Namenskonventionen. Es ist nützlich, eine Vorstellung von der Befehlssatzarchitektur zu haben . Vergessen Sie nicht, dass ein C- Compiler möglicherweise viele Optimierungen an Ihrem Code vornimmt (wenn Sie ihn dazu auffordern). Überlassen Sie die Mikrooptimierung also Ihrem Compiler (
gcc -Wall -O2
für eine optimierte Kompilierung der veröffentlichten Versionen) Software). Wenn Sie sich für Benchmarking und Raw-Leistung interessieren, sollten Sie Optimierungen aktivieren (sobald Ihr Programm debuggt wurde).Vergessen Sie nicht, dass Metaprogrammierung manchmal nützlich ist . In C geschriebene große Software enthält häufig Skripte oder Ad-hoc-Programme, um C-Code zu generieren, der an anderer Stelle verwendet wird (und möglicherweise spielen Sie auch einige schmutzige C-Präprozessor- Tricks ab, z. B. X-Makros ). Es gibt einige nützliche C-Programmgeneratoren (z. B. yacc oder gnu bison zum Generieren von Parsern, gperf zum Generieren perfekter Hash-Funktionen usw.). Auf einigen Systemen (insbesondere Linux und POSIX) können Sie sogar zur Laufzeit C-Code in einer
generated-001.c
Datei generieren , ihn zu einem gemeinsam genutzten Objekt kompilieren, indem Sie zur Laufzeit einen Befehl (z. B.gcc -O -Wall -shared -fPIC generated-001.c -o generated-001.so
) ausführen und dieses gemeinsam genutzte Objekt dynamisch mit dlopen laden& Mit dlsym einen Funktionszeiger von einem Namen abrufen . Ich mache solche Tricks in MELT (eine Lisp-ähnliche domänenspezifische Sprache, die für Sie nützlich sein könnte, da sie die Anpassung des GCC- Compilers ermöglicht).Seien Sie sich bewusst von der Garbage Collection Konzepte und Techniken ( Referenzzählung ist oft eine Technik zu verwalten Speicher in C, und es ist meiner Meinung nach eine schlechte Form der Garbage Collection , die nicht gut behandeln zirkuläre Referenzen , Sie haben könnten schwache Zeiger zu helfen darüber, aber es könnte schwierig sein). In einigen Fällen könnten Sie in Betracht ziehen, den konservativen Müllmann von Boehm zu verwenden .
quelle
Die Art und Weise, wie das Programm erstellt wird, definiert im Wesentlichen, welche Aktionen (Funktionen) ausgeführt werden müssen, um das Problem zu lösen (daher wird es als prozedurale Sprache bezeichnet). Jede Aktion entspricht einer Funktion. Anschließend müssen Sie definieren, welche Art von Informationen die einzelnen Funktionen erhalten und welche Informationen sie zurückgeben müssen.
Das Programm ist normalerweise in Dateien (Module) unterteilt. Jede Datei hat normalerweise eine Gruppe von Funktionen, die miteinander verbunden sind. Am Anfang jeder Datei deklarieren Sie (außerhalb einer Funktion) Variablen, die von allen Funktionen in dieser Datei verwendet werden. Wenn Sie das Qualifikationsmerkmal "static" verwenden, werden diese Variablen nur in dieser Datei angezeigt (jedoch nicht in anderen Dateien). Wenn Sie das Qualifikationsmerkmal "static" nicht für Variablen verwenden, die außerhalb von Funktionen definiert wurden, können Sie auch über andere Dateien darauf zugreifen. Diese anderen Dateien sollten die Variable als "extern" deklarieren (aber nicht definieren), damit der Compiler nach ihnen sucht in anderen Dateien.
Kurz gesagt, Sie denken zuerst über die Prozeduren (Funktionen) nach und stellen dann sicher, dass alle Funktionen Zugriff auf die benötigten Informationen haben.
quelle
C-APIs haben oft - vielleicht sogar normalerweise - eine im Wesentlichen objektorientierte Schnittstelle, wenn Sie sie richtig betrachten.
In C ++:
In C:
Wie Sie vielleicht wissen, verwendet in C ++ und verschiedenen anderen formalen OO-Sprachen eine Objektmethode unter der Haube ein erstes Argument, das ein Zeiger auf das Objekt ist, ähnlich wie die C-Version von
bar()
oben. Ein Beispiel dafür, wo dies in C ++ auftaucht, ist, wiestd::bind
Objektmethoden an Funktionssignaturen angepasst werden können:Wie andere betont haben, besteht der wirkliche Unterschied darin, dass formale OO-Sprachen Polymorphismus, Zugriffskontrolle und verschiedene andere nützliche Funktionen implementieren können. Das Wesen der objektorientierten Programmierung, die Erzeugung und Manipulation diskreter, komplexer Datenstrukturen, ist jedoch bereits eine grundlegende Praxis in C.
quelle
Einer der Hauptgründe, warum Menschen ermutigt werden, C zu lernen, ist, dass es eine der niedrigsten Programmiersprachen auf hoher Ebene ist. OOP-Sprachen erleichtern das Nachdenken über Datenmodelle und das Übermitteln von Code- und Nachrichtenvorlagen. Am Ende des Tages führt ein Mikroprozessor Code schrittweise aus, springt in Codeblöcke hinein und aus ihnen heraus (Funktionen in C) und bewegt sich Verweise auf Variablen (Zeiger in C), damit verschiedene Teile eines Programms Daten gemeinsam nutzen können. Stellen Sie sich C als Assemblersprache in Englisch vor - und geben Sie Schritt für Schritt Anweisungen für den Mikroprozessor Ihres Computers - und Sie werden nicht viel falsch machen. Als Bonus funktionieren die meisten Betriebssystemschnittstellen eher wie C-Funktionsaufrufe als wie OOP-Paradigmen.
quelle
uint16_t blah(uint16_t x) {return x*x;}
auf Computern mitunsigned int
16 Bit oder 33 Bit oder mehr identisch . Einige Compiler für Computer mitunsigned int
17 bis 32 Bit können jedoch einen Aufruf dieser Methode inuint16_t
9 den Wert ergibt, schreibt der Standard kein solches Verhalten vor, wenn Werte vom Typuint16_t
auf 17- bis 32-Bit-Plattformen multipliziert werden .Ich bin auch ein OO-Eingeborener (C ++ allgemein), der manchmal in einer Welt von C überleben muss. Für mich ist die grundlegend größte Hürde die Fehlerbehandlung und das Ressourcenmanagement.
In C ++ müssen wir einen Fehler von dort, wo er auftritt, an die oberste Ebene zurückgeben, wo wir damit umgehen können, und wir haben Destruktoren, um unseren Speicher und andere Ressourcen automatisch freizugeben.
Möglicherweise stellen Sie fest, dass viele C-APIs eine Init-Funktion enthalten, mit der Sie eine typisierte Leere * erhalten, die wirklich ein Zeiger auf eine Struktur ist. Dann übergeben Sie dies als erstes Argument für jeden API-Aufruf. Im Wesentlichen wird dies Ihr "dieser" Zeiger aus C ++. Es wird für alle internen Datenstrukturen verwendet, die versteckt sind (ein sehr OO-Konzept). Sie können es auch zum Verwalten des Speichers verwenden, z. B. eine Funktion namens myapiMalloc, die Ihren Speicher malloc und den malloc in Ihrer C-Version dieses Zeigers aufzeichnet, damit Sie sicherstellen können, dass er freigegeben wird, wenn Ihre API zurückkehrt. Wie ich kürzlich herausgefunden habe, können Sie damit Fehlercodes speichern und setjmp und longjmp verwenden, um ein Verhalten zu erzielen, das dem von throw catch sehr ähnlich ist. Durch die Kombination beider Konzepte erhalten Sie einen Großteil der Funktionalität eines C ++ - Programms.
Jetzt hast du gesagt, dass du nicht lernen willst, C in C ++ zu zwingen. Das ist nicht wirklich das, was ich beschreibe (zumindest nicht absichtlich). Dies ist einfach eine (hoffentlich) gut konzipierte Methode, um die C-Funktionalität zu nutzen. Es hat sich herausgestellt, dass es einige OO-Varianten gibt - vielleicht haben OO-Sprachen deswegen entwickelt. Sie waren eine Möglichkeit, Konzepte zu formalisieren, durchzusetzen und zu erleichtern, die sich bei manchen als Best Practice herausgestellt haben.
Wenn Sie der Meinung sind, dass dies für Sie ein OO-Gefühl ist, besteht die Alternative darin, dass praktisch jede Funktion einen Fehlercode zurückgibt, den Sie nach jedem Funktionsaufruf überprüfen und den Aufrufstapel weitergeben müssen. Sie müssen sicherstellen, dass alle Ressourcen nicht nur am Ende jeder Funktion, sondern auch an jedem Rückkehrpunkt freigegeben werden (möglicherweise nach jedem Funktionsaufruf, der einen Fehler zurückgeben kann, der darauf hinweist, dass Sie nicht fortfahren können). Es kann sehr mühsam werden und führt dazu, dass Sie denken, dass ich mich wahrscheinlich nicht mit diesem potenziellen Speicherzuweisungsfehler (oder dem Lesen von Dateien oder dem Herstellen einer Portverbindung ...) befassen muss. Ich gehe einfach davon aus, dass es funktionieren wird, oder ich Schreibe jetzt den "interessanten" Code und kehre zurück und kümmere dich um die Fehlerbehandlung - was niemals passiert.
quelle