Hallo Zusammen,
folgendes Problemchen: In einer Liste von Objekten mit verschiedenem Typ
soll zyklisch die Methode "runObject" aufgerufen werden.
Jaja, ich weiss, C ist nicht objektorientiert. Deshalb möchte ich
versuchen, in C Strukturen zu schafen, mit denen eine "quasi"
Objektorientierung entsteht.
1
typedefstruct
2
{
3
intseitenlaenge;
4
}dreieck_t;
5
6
typedefstruct
7
{
8
intseiteA;
9
intseiteB;
10
}rechteck_t
11
12
....
13
14
dreieck_td;
15
recheck_tr;
16
17
runObject(d);
18
runObject(r);
Hat jemand von euch schon einmal so einen Ansatz verfolgt? Wie kann ma
es besser machen?
chris schrieb:
> Jaja, ich weiss, C ist nicht objektorientiert.
Manche Vorurteile sterben wohl nie aus :-/
> folgendes Problemchen: In einer Liste von Objekten mit verschiedenem Typ> soll zyklisch die Methode "runObject" aufgerufen werden.
Ich sehe in deinem Beispiel weder eine Liste, noch einen Zyklus. Mir
wird ueberhaupt nicht klar, was du nun eigentlich wirklich machen
moechtest.
Wenn du virtuelle Methoden haben moechtest, wirst du einen
Funktionszeiger in deine Struktur aufnehmen muessen.
Willst du ueberladene Methoden, wirst du darauf verzichten muessen -
aber das ist doch sowieso nur Namenskosmetik.
> Deshalb möchte ich> versuchen, in C Strukturen zu schafen, mit denen eine "quasi"> Objektorientierung entsteht.
Dann ist wahrscheinlich deine beste Option die, dass du den Mechanismus
von C++ virtuellen Funktionen nachbaust. Da du nur 1 virtuelle Funktion
hast, lohnt sich eine extra VTable nicht, anstelle des VTable Pointers
im Objekt kannst du auch gleich den Funktionszeiger nehmen
1
typedefvoid(*runFkt)(void*);
2
3
typedefstruct
4
{
5
void*obj;
6
runFktfnct;
7
}object_vt_t;
8
9
typedefstruct
10
{
11
intseitenlaenge;
12
}dreieck_t;
13
14
typedefstruct
15
{
16
intseiteA;
17
intseiteB;
18
}rechteck_t
19
20
voidrunDreieck(void*obj)
21
{
22
// obj muss ein Pointer auf ein Dreieck sein, sonst
23
// wäre diese Funktion nie aufgerufen worden
24
dreieck_t*d=(dreieck_t*)obj;
25
26
// mach was mit d->
27
...
28
}
29
30
voidrunRechteck(void*obj)
31
{
32
// obj muss ein Pointer auf ein Rechteck sein, sonst
33
// wäre diese Funktion nie aufgerufen worden
34
rechteck_t*r=(rechteck_t*)obj;
35
36
// mach was mit r->
37
...
38
}
39
40
41
....
42
// in die Liste baust du jetzt 'Objekte' vom Typ object_vt_t ein.
43
// In jedem object_vt_t steht ein Pointer auf das 'Objekt' und ein
44
// Pointer auf die Funktion, die dieses Objekt bearbeitet
45
....
46
// jetzt nur mal als Beispiel
47
48
dreieck_tdrei1;
49
object_vt_tobj1={drei1,runDreieck};
50
51
rechteck_trecht1;
52
object_vt_tobj2={recht1,runRechteck};
53
54
obj1.fnct(obj1.obj);// rufe runDreieck indirekt auf
55
obj2.fnct(obj2.obj);// rufe runRechteck indirekt auf
56
57
...
Alternativ könnte man auch mit diversen Typflags arbeiten, die in der
Liste mitgespeichert werden und an eine run Funktion mitgegeben werden.
In der Funktion gibt es dann einen grossen switch-case über diesen Typ,
der darüber entscheidet wie es mit dem übergebenen Pointer weitergeht.
Die Variante mit den Funktionspointern ist aber meistens besser
erweiterbar und auch schneller.
Hallo KH,
warum uebergibst du der Funktion "fnct" einen Pointer auf das Object
selbst?
Kann man es nicht bewerkstelligen, dass innerhalb des Objects das Object
selbst bekannt ist?
Sowas wie ein "this" Zeiger in C++.
MFG
RMP schrieb:
> Hallo KH,>> warum uebergibst du der Funktion "fnct" einen Pointer auf das Object> selbst?
Damit die Funktion auch auf dem 'Objekt' selbst arbeiten kann.
> Kann man es nicht bewerkstelligen, dass innerhalb des Objects das Object> selbst bekannt ist?
Innerhalb welchen Objektes?
Da ist kein Objekt. Da ist nur eine Funktion, die auf einer
Datenstruktur arbeiten soll und irgendwie mitkriegen muss, welche
konkrete Ausprägung dieser Datenstruktur es sein soll.
> Sowas wie ein "this" Zeiger in C++.
Genau so macht das auch der C++ Compiler. Nur macht er es so, dass du
das nicht siehst :-) Aber im Geheimen wird jeder Memberfunktion ein
zusätzlicher Paramater angehängt, den du in der Funktion mittels 'this'
ansprechen kannst.
So etwas
1
classA
2
{
3
public:
4
voidfoo(intb){member=b;}
5
6
protected:
7
intmember;
8
};
9
10
intmain()
11
{
12
Aa,b;
13
a.foo(5);
14
b.foo(7);
15
}
wird vom C++-Compiler so bearbeitet
1
structA
2
{
3
intmember;
4
};
5
6
inlinevoidA_foo_vi(structA*constthis,intb)
7
{
8
this->member=b;
9
}
10
11
intmain()
12
{
13
structAa,b;
14
A_foo_vi(&a,5);
15
A_foo_vi(&b,7);
16
}
und in der Tat haben die ersten C++ Compiler genau so gearbeitet und C++
zuerst nach C 'übersetzt'. Die heutigen Compiler machen diesen Schritt
nicht mehr explizit, aber konzeptionell ist es immer noch dasselbe.
Vielen Dank für eure Antworten.
Ich sehe ein, dass der Ansatz von KH im Vergleich zum Ansatz von 900ss
eine schneller Ausführungsgeschwindigkeit bringt. Allerdings bin ich mir
noch nicht sicher, ob die Objektunterscheidung in der runObject Function
nicht übersichtlicherwäre, wenn sie über eine case Anweisung
durchgeführt wird, wie von 900ss vorgeschlagen.
Nach einigem Nachdenken ist für mich ein neues Problem aufgetaucht: Wie
wird die Allokation des Speichers für die Objekte am günstigsten
bewerkstelligt, wenn ich die Objektliste erzeugen will?
1
object_vt_tObjektListe[10];
2
3
ObjektListe[0]={recht1,runRechteck};
4
ObjektListe[1]={drei1,runDreieck};
5
....
Die Objektliste soll zur Laufzeit veränderbar sein.
Bei C++ ist der Vorgang ja relativ einfach. Man kann ein Objekt mit
"new" erzeugen. Im obigen Fall scheint mir das schwierig.
chris schrieb:
> Bei C++ ist der Vorgang ja relativ einfach. Man kann ein Objekt mit> "new" erzeugen. Im obigen Fall scheint mir das schwierig.
malloc und Zuweisungen sind schon erfunden.
BTW: ein statisch allokiertes Array als 'Liste' zu bezeichnen, ist schon
etwas ... verwegen.
Hier gibts ein interessantes PDF zu genau dem Thema:
http://www.planetpdf.com/codecuts/pdfs/ooc.pdf
Nennt sich "Object-Oriented Programming With ANSI-C" und erklärt ganz
gut, wie man mit C objektorientiert Programmieren kann.
chris schrieb:
> Vielen Dank für eure Antworten.>> Ich sehe ein, dass der Ansatz von KH im Vergleich zum Ansatz von 900ss> eine schneller Ausführungsgeschwindigkeit bringt. Allerdings bin ich mir> noch nicht sicher, ob die Objektunterscheidung in der runObject Function> nicht übersichtlicherwäre, wenn sie über eine case Anweisung> durchgeführt wird, wie von 900ss vorgeschlagen.
Kann man auch machen.
Der Geschwindigkeitsgewinn ist eigentlich nur eine nette Zugabe.
Viel wichtiger in der Objektorientierung mittels 'virtuellen Funktionen'
ist es eigentlich, dass man im Nachhinein auch noch andere
Objekt-Klassen hinzufügen kann, ohne das halbe Programm absuchen zu
müssen, welcher switch-case wie erweitert werden muss. Wenn man die
Aufteilung in mehrere *.c Files richtig macht, dann braucht der
'Verteiler' noch nicht einmal neu kompiliert werden und verteilt
trotzdem die Run Aufrufe für die neu hinzugekommenen Kreise richtig :-)
was willst du überhaupt mit verschienden "objecten" in einer Liste?
Soetwas mach man doch auch in C++ nicht. Dort würden man eine Liste
Basis-Klasse machen und alle Objecte müssen zu diesen Basisklasse
konvertierbar sein. Aber eine Liste mit komplett verschienden Objekten
zu haben nur weil sie eine identische Methode macht doch wenig sinn
oder?
Peter schrieb:
> was willst du überhaupt mit verschienden "objecten" in einer Liste?> Soetwas mach man doch auch in C++ nicht.
Der Einwand ist berechtigt :-)
In C++ macht man eine Liste von Pointern auf Objekte. Weil man nur so
polymorphes Verhalten hinkriegt.
Aber das C-Pendant oben wurde stillschweigend in diese Richtung
verändert.
> Aber eine Liste mit komplett verschienden Objekten> zu haben nur weil sie eine identische Methode macht doch wenig sinn> oder?
Würde ich nicht sagen.
Man kann ja auch den Ansatz vertreten:
Es muss eine Basisklasse, ausgeführt als Interface, geben, welche genau
diese identische Methode enthält.
Alle Objekte, die in die Liste mit aufgenommen werden möchten, müssen
dieses Interface implementieren. Ob diese 'Objekte', ausser der
Unterstützung für dieses Interface auch sonst noch Dinge gemeinsam
haben, braucht ja die Liste, bzw denjenigen der etwas mit der Liste
macht, nicht zu interessieren.
Einem Verteiler, der Mausklicks verteilt, soll es doch völlig egal sein,
ob der Mausklick nun in einem Fenster, einem Objekt-Selektierer oder auf
einem Pin der parallelen Schnittstelle landet. Ein paar Objekte haben
sich bei ihm registriert, das entsprechende Interface implementiert und
kriegen daher Mausklicks zugestellt.
> was willst du überhaupt mit verschienden "objecten" in einer Liste?
Ich will die "RunFunktion"en der Objekte zyklisch aufrufen. In C++ wäre
es wohl folgendermaßen zu bewerkstelligen:
1
for(n=0;n<10;n++)
2
ObjektListe[n].RunFunktion();
Aber wie das mit dem Code von KH möglich ist, ist mir nicht ganz klar:
1
obj1.fnct(obj1.obj);// rufe runDreieck indirekt auf
2
obj2.fnct(obj2.obj);// rufe runRechteck indirekt auf
Karl heinz Buchegger schrieb:
> Viel wichtiger in der Objektorientierung mittels 'virtuellen Funktionen'> ist es eigentlich, dass man im Nachhinein auch noch andere> Objekt-Klassen hinzufügen kann, ohne das halbe Programm absuchen zu> müssen, welcher switch-case wie erweitert werden muss. Wenn man die> Aufteilung in mehrere *.c Files richtig macht, dann braucht der> 'Verteiler' noch nicht einmal neu kompiliert werden und verteilt> trotzdem die Run Aufrufe für die neu hinzugekommenen Kreise richtig :-)
Da stimme ich dir zu. Würde jetzt auch eher deine Lösung bevorzugen.
Gruß 900ss
chris schrieb:
>> was willst du überhaupt mit verschienden "objecten" in einer Liste?>> Ich will die "RunFunktion"en der Objekte zyklisch aufrufen. In C++ wäre> es wohl folgendermaßen zu bewerkstelligen:>>
1
>for(n=0;n<10;n++)
2
>ObjektListe[n].RunFunktion();
3
>
Das ganz sicher nicht :-)
Wohl schon eher
1
for(n=0;n<10;n++)
2
ObjektListe[n]->RunFunktion();
Für Polymorphismus braucht man in C++ Pointer oder Referenzen.
>> Aber wie das mit dem Code von KH möglich ist, ist mir nicht ganz klar:>
1
for(n=0;n<10;n++)
2
if(ObjektListe[n].fnct&&ObjektListe[n].obj)// nur zur Sicherheit
3
ObjektListe[n].fnct(ObjektListe[n].obj);
Bist du sicher, dass du nicht erst einmal ein paar Wochen
'konventionelles' C-Training machen solltest, ehe du daran gehst OOP
Strukturen in C nachzubilden?
Karl heinz Buchegger schrieb:
> Bist du sicher, dass du nicht erst einmal ein paar Wochen> 'konventionelles' C-Training machen solltest, ehe du daran gehst OOP> Strukturen in C nachzubilden?
Und bist du dir weiterhin ganz sicher, dass es partout C sein muss
und nicht doch besser in C++ zu schreiben wäre?
Jörg Wunsch schrieb:
> Karl heinz Buchegger schrieb:>>> Bist du sicher, dass du nicht erst einmal ein paar Wochen>> 'konventionelles' C-Training machen solltest, ehe du daran gehst OOP>> Strukturen in C nachzubilden?>> Und bist du dir weiterhin ganz sicher, dass es partout C sein muss> und nicht doch besser in C++ zu schreiben wäre?
LOL
So wie ich das sehe, spielt es für den TO keine allzugroße Rolle. Auch
in C++ kommt man ohne das entsprechende Wissen nicht weiter. Der Rest
ist dann einfach nur Programmierer-Handwerk.
Karl heinz Buchegger schrieb:
> Auch> in C++ kommt man ohne das entsprechende Wissen nicht weiter.
Das ist richtig, aber die Gefahr, dass man sich mit diesen OO-
Konzepten in C verheddert, finde ich noch größer. OO in C ist
nun nicht gerade extrem übersichtlich, auch wenn es machbar ist.
Jörg Wunsch schrieb:
> Karl heinz Buchegger schrieb:>>> Auch>> in C++ kommt man ohne das entsprechende Wissen nicht weiter.>> Das ist richtig, aber die Gefahr, dass man sich mit diesen OO-> Konzepten in C verheddert, finde ich noch größer. OO in C ist> nun nicht gerade extrem übersichtlich, auch wenn es machbar ist.
Wohl wahr, wohl wahr.
Zumindest mit Pointern und speziell Funktionspointern sollte man auf 'Du
und Du' stehen :-) Wenn das nicht sitzt, hat es wenig Sinn die OO
Schiene in C zu versuchen.
Aber, aber, wer wird denn gleich aufgeben? Wenn ich jetzt sage, dass ich
mindestens seit 15 Jahren ( erfolgreich ) Mikrocontroller in C
programmiere, werdet Ihr vielleicht lachen.
Allerdings kann man die Verwendung von Pointern auch in C weitestgehend
reduzieren. Ich versuche, die meine Programme in möglichst einfachem
Stil zu halten und keine speziellen C-Konstruktionen zu benutzen. Und ob
Ihr's glaubt oder nicht, damit kommt man ziemlich weit.
RPM schrieb:
> warum uebergibst du der Funktion "fnct" einen Pointer auf das Object> selbst?
Ich denke mal, dass sich die Frage von RPM auf dieses Konstrukt bezog:
1
ObjektListe[n].fnct(ObjektListe[n].obj);
sieht komisch aus, wenn man der Funktion eines Objektes noch mal den
Pointer auf das Objekt übergeben muss.
chris schrieb:
> Stil zu halten und keine speziellen C-Konstruktionen zu benutzen. Und ob> Ihr's glaubt oder nicht, damit kommt man ziemlich weit.
Doch das glaube ich dir.
Aber mit Pointern kommt man dann eben noch etwas weiter :-) bzw. kann
das Gewünschte effizienter und Laufzeiteffektiver ausdrücken. Man kann
schliesslich ja auch Arithmetik betreiben ohne je Multiplizieren zu
müssen. Addieren reicht dicke.
Im Ernst: Funktionspointer sind nur am Anfang gewöhnungsbedürftig. Das
wichtiste Hilfsmittel bei Funktionspointer ist m.M. ein typedef, mit dem
man den Datentyp kapselt. Ab dann ist das einfach nur ein Datentyp wie
ein int oder jeder andere Pointer auch. Wenn man den Pointer
'dereferenziert', kriegt man keine Daten zurück, sondern eine Funktion
wird aufgerufen (die natürlich dann auch wieder Daten zurückliefern
kann). Bei der Zuweisung einer Funktion schreibt man auf der rechten
Seite den Funktionsnamen ohne () [mit () würde ja die Funktion
aufgerufen]. Das wars dann auch schon - das sind Funktionspointer.
Und es ist egal, ob man schreibt
(*fnct)(i);
oder
fnct(i);
Beide mal wird die Funktion, deren Adresse in fnct abgelegt wurde
aufgerufen und ihr i mitgegeben. In der ersten Variante sieht man
vielleicht etwas besser, dass es sich um einen Funktionspointer handelt
und dass dieser derferenziert werden muss um an die Funktion zu kommen.
chris schrieb:
> sieht komisch aus, wenn man der Funktion eines Objektes
Tja. Nur gibt es in C so etwas wie 'die Funktion eines Objektes' nicht.
In C gibt es Funktionen und es gibt 'Objekte' (als Instantieerungen
einer struct oder union). Aber die beiden Dinge haben nichts miteinander
zu tun.
chris schrieb:
> Allerdings kann man die Verwendung von Pointern auch in C weitestgehend> reduzieren. Ich versuche, die meine Programme in möglichst einfachem> Stil zu halten und keine speziellen C-Konstruktionen zu benutzen.
Auch wenn sich da andere Sprachen feige zurueck halten, wuerde ich
Zeiger nicht als "spezielle C-Konstruktion" bezeichnen. Geruechteweise
soll das ja auf den heutigen Prozessorkonstruktionen ein ganz normales
Konstrukt sein...
Hallo KH,
Deiner Erklärungen sind sehr nüttzlich und helfen mir gut weiter.
Zum Verständnis habe ich den Funktionspointer mal in "methode"
umbenannt.
Aber so langsam kommen mir Selbstzweifel: Bei der Initialisierung eines
Objektes ergibt sich folgender Fehler:
1
typedefvoid(*methode)(void*);
2
3
// generelle Objektstruktur
4
typedefstruct
5
{
6
void*obj;
7
methodeRun;
8
}object_t;
9
10
.....
11
12
intmain(void)
13
{
14
dreieck_tdrei1;
15
object_tobj1={drei1,runDreieck};//../ObjTest.c:: error: incompatible types in initialization
16
17
rechteck_trecht1;
18
object_tobj2={recht1,runRechteck};//../ObjTest.c:: error: incompatible types in initialization
19
obj1.Run(obj1.obj);// rufe runDreieck indirekt auf
20
obj2.Run(obj2.obj);// rufe runRechteck indirekt auf
chris schrieb:
> // generelle Objektstruktur> typedef struct> {> void * obj;> methode Run;> }object_t;> object_t obj1 = { drei1, runDreieck }; //../ObjTest.c:: error:> incompatible types in initialization
Na ja.
Was ist denn das erste Member von object_t
void * obj;
also ein Pointer.
Und womit wird versucht, diesen Pointer zu initialisieren
..... = { drei1 ....
und drei1 ist was?
dreieck_t drei1;
Ein dreieck_t. Also eine komplette Struktur und kein Pointer.
object_t obj1 = { &drei1, runDreieck };
( Wie hast du es bloss 15 Jahre auf diesem Level geschafft? Irgendwann
muss es einem doch zu blöd sein immer nur mit Arrays und Indizes
rumzuwurschteln. Obwohl: Ich kenne auch jemanden der ein komplettes
3D-CAD geschrieben hat, indem er 10 Arrays mit double, int, char-Arrays,
... parallel hatte und ständig Indizes abgleichen musste, also wie wenn
es keine struct gäbe. Man war der auf diesen Unsinn stolz.
>Ein dreieck_t. Also eine komplette Struktur und kein Pointer.
Mist, habe zusehr auf den mir bis zu diesem Thread unbekannten
Funktionspointer geschielt ...
>Wie hast du es bloss 15 Jahre auf diesem Level geschafft?
Das kommt davon, wenn man einfach unbesehen code von Dir einsetzt ;-)
>
chris schrieb:
>>Ein dreieck_t. Also eine komplette Struktur und kein Pointer.> Mist, habe zusehr auf den mir bis zu diesem Thread unbekannten> Funktionspointer geschielt ...>>>Wie hast du es bloss 15 Jahre auf diesem Level geschafft?> Das kommt davon, wenn man einfach unbesehen code von Dir einsetzt ;-)
LOL
Ich schreibe auch direkt im hiesigen Foreneditor. Da kommt sowas schon
mal vor. Dafür gibt es dann ja auch einen Compiler, der sowas nicht
durchgehen lässt.
>Ich schreibe auch direkt im hiesigen Foreneditor
Alle Achtung, wenn Du das Freihand machst.
Ich habe mal das Programm zusammengefasst und einen Konstruktor und eine
Delete Funktion für die Objekte gebastelt. Der Konstruktor funktioniert,
allerdings meckert der Compiler noch ein paar Typkonvertierungen an.
Bei der Delete Funktion bin ich mir noch nicht sicher, ob sie
funktioniert.
Kann es sein, dass das Speicher freigeben nicht ganz so geht, wie es
wünschenswert wäre? Ich habe den AVR-GCC verwendet, git es in C sowas
wie "Garbage Collection"?
Vorsicht mit solchen Sachen!
Du hast keinen 'Copy-Konstruktor' der sich darum kümmert und der
'Destruktor' des temporären Objektes welches zurückgeliefert wird, wird
auch nicht aufgerufen, weil ist ihn schlicht und ergreifend nicht gibt:
Memory-Leak
chris schrieb:
> Kann es sein, dass das Speicher freigeben nicht ganz so geht, wie es> wünschenswert wäre?
Sollte schon.
Aber dazu musst du auch deine Delete Funktion für alle 'Objekte'
aufrufen (und solche Spässe wie im vorigen Posting tunlichst vermeiden).
> Ich habe den AVR-GCC verwendet, git es in C sowas> wie "Garbage Collection"?
Nein.