Unterschied zwischen einem Objekt und einer Klasse??
-
Ne das Teil heisst dann MyDraw. Und es ist auf Deutsch, ich empfehle doch in einem Deutschen C++ Forum kein englisches Buch
Grüssli
-
Schade, wenn das nicht Scribble ist, dann isses nicht mehr sooo interessant.
-
Aber es ist gut! Für dich vielleicht nicht interessant, da du schon alles weisst und kennst und nur nach einem Buch für deine Kindheitsträume suchst *lol* ... Aber sag mal, wie hiess das Buch denn in Englisch? Kann man doch sicher eine deutsche Übersetzung in Google finden. Sicherlich auch zum kaufen, bzw. offiziell übersetzt.
Grüssli
-
Hey, ich weiß auch nicht alles, gerade mit CDC und den Drumherum stehe ich sehr auf Kriegsfuß.
Deswegen ja die kurzzeitige Begeisterung. Wenn es mir günstig über den Weg läuft, kommt es sicherlich trotzdem in die Sammlung.Das ist kein Buch, was ich meinte. Das ist in der MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vctutor98/HTML/_gs_scribble_tutorial_steps.asp
-
Ich glaub ich wiederhol mich
Aber nochmal...
Klasse Danke
gruß mercer
-
Achso, das meinst du ^^ ...
Najo, diese Dinge sind meistens nicht übersetzt in der deutschen MSDN. Und im Google habe ich jetzt so auf die schnelle nichts gefunden. Und nachdem nun die deutsche Online-MSDN 5 mal abgestürzt ist (Es lebe Microsoft!), verschwende ich meine Zeit nun für was anderes...
Was du aber machen kannst, ist die Seit von Google übersetzen lassen.
Google Übersetzung
Allerdings ist es nicht so empfehlenswert, da es ein wenig sehr schlechtes Deutsch ist XDAnsonsten CDC Tutorial, bzw. GDI Tutorial, wenn mit dem Zeugs auf Kriegsfuss stehst ^^ ... Aber ich glaube das ist dann doch wieder zu starke Grundlagentheorie ...
GDI TutorialNajo, aber was red ich da lange rum, du hast sicher schon selber gesucht, gelernt und gemacht ... ich habe ja eigentlich wirklich besseres zu tun ^^
Grüssli
-
Ich höre trotzdem immer wieder gerne auf Empfehlungen anderer.
Danke
-
Ich habe vor einiger Zeit mal etwas verfasst zum Thema Objektorientierte Programmierung (OOP):
Ein Softwareentwickler sollte sich auf jeden Fall mit der objektorientierten Programmierung (OOP) auseinander setzen. In der Windows-Programmierung hat dieser Programmierstil seit Anfang der neunziger Jahre seinen festen Platz. Lassen Sie uns zumindest einen kurzen Blick auf die zentralen Begriffe werfen: Abstraktion, Kapselung, Vererbung, Polymorphismus und modulare Gestaltung.
Abstraktion
Die Abstraktion findet die wesentlichen Eigenschaften einer Gruppe von Objekten, die diese von anderen Objekten unterscheidet. Das Ziel ist die Schaffung einer Klasse. Dies ist der zentrale Begriff der OOP. Klassen abstrahieren Objekte, die ähnliche Eigenschaften und Verhaltensweisen haben. Aus Sicht des Softwareentwicklers sind Klassen "Baupläne" für Objekte. Ein Objekt ist die Konkretisierung einer Klasse. Man spricht auch vom Erzeugen einer Instanz der Klasse. Klassen enthalten Member-Funktionen und Member-Variablen. Öffentliche ("public") Member-Funktionen einer Klasse nennt man auch Methoden. Member-Variablen bezeichnet man auch als Attribute. Der "Bau" der Objekte erfolgt in der Konstruktor-Funktion, kurz Konstruktor genannt. Der Konstruktor trägt in C++ den gleichen Namen wie die Klasse. Ein typisches Objekt in der Windowsprogrammierung ist das Fenster. Es gibt verschiedenste Arten von Fenstern, aber die gleichen Eigenschaften und Reaktionen, die ein Fenster ausmachen, sind in der MFC-Hierarchie in der Klasse CWnd verdichtet. Die Definition dieser Klasse finden Sie in der Datei afxwin.h:
class CWnd : public CCmdTarget { // sehr viele Member };
Die Definition der Klasse CWnd ist zu unübersichtlich, um diese hier darzustellen. Als Beispiel für eine MFC-Klasse zeigen wir eine Kindklasse von CWnd, nämlich die MFC-Klasse CButton:
class CButton : public CWnd { DECLARE_DYNAMIC(CButton) // Constructors public: CButton(); BOOL Create(LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID); // Attributes UINT GetState() const; void SetState(BOOL bHighlight); int GetCheck() const; void SetCheck(int nCheck); UINT GetButtonStyle() const; void SetButtonStyle(UINT nStyle, BOOL bRedraw = TRUE); #if (WINVER >= 0x400) HICON SetIcon(HICON hIcon); HICON GetIcon() const; HBITMAP SetBitmap(HBITMAP hBitmap); HBITMAP GetBitmap() const; HCURSOR SetCursor(HCURSOR hCursor); HCURSOR GetCursor(); #endif // Overridables (for owner draw only) virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); // Implementation public: virtual ~CButton(); protected: virtual BOOL OnChildNotify(UINT, WPARAM, LPARAM, LRESULT*); };
Kapselung
Eine der Grundeigenschaften der Objektorientierung ist die Kapselung (encapsulation). Was ist das eigentlich genau? Durch Kapselung werden Details hinter einer definierten Schnittstelle verborgen. Die Schnittstelle kann dadurch erhalten werden, wenn sich Interna ändern müssen. Nachfolgend versuche ich, in kleinen Schritten an konkreten einfachen Beispielen zu zeigen, was Kapselung in der Praxis bedeuten kann. Hierbei geht es vorrangig um das Verständnis für die Kapselung von Objekten mit Hilfe von C++-Klassen. Sie werden aber sehen, daß die Kapselung genau genommen ein weit verbreiteter Prozeß ist, der in gewisser Wiese auch durch die Einführung von Variablen und Funktionen bereits statt findet. Beginnen wir mit der C++-Programmierung. Die Basis soll folgendes einfache Konsolenprogramm bilden:
int main() { return 0; };
Sie benutzen hier einfach die Funktion main(...). Sie müssen dies machen, damit die Sache ins Rollen kommt.
Das ist Ihre Schnittstelle zu C/C++. Auch das ist in gewissem Sinne bereits Kapselung! main(...) ist die Schnittstelle zum Compiler. Diese Schnittstelle ist schon über viele Jahre für Konsolen-Programme konstant, obwohl sich Editoren und Compiler ständig gewandelt haben. Im nächsten Schritt geben wir den String "Hallo" auf dem Bildschirm aus. Wir verwenden hierzu cout.#include <iostream> using namespace std; int main() { cout << "Hallo" << endl; return 0; };
So fängt man typischerweise an, C++ zu lernen. Sie müssen hierzu nicht wissen, was iostream genau ist und wie es intern aufgebaut ist. Sie benutzen einfach den damit bereit gestellten Code cout << ... << endl; . Sie verwenden es, und es funktioniert. iostream kapselt die notwendigen Funktionalitäten, die Zeichen auf dem Bildschirm auszugeben. Klarer Fall von Kapselung! Die Schnittstelle nach außen ist cout.
Wir erhöhen die Komplexität, indem wir den auszugebenden String zunächst in einer Variable speichern. Diese Variable ist vom Typ char-Array mit der Bezeichnung Text:
#include <iostream> using namespace std; int main() { char Text[255]; char *pText = Text; pText = "Gruss aus der Variablen Text"; cout << pText << endl; return 0; };
cout << pText << endl; ist die entscheidende Zeile. cout<<...<<endl "kapselt" die Ausgabe, Das Array, auf das pText zeigt, kapselt den konkreten String. Diese Zeile kann unverändert bleiben, wenn in iostream etwas geändert wird oder ein anderer konstanter String zugeordnet wird.
Die beiden oben durchgeführten Aktionen
• Zuordnung eines Strings zu Text[...]
• Ausgabe des Inhalts von Text[...] mittels cout
werden nachfolgend in eigene Funktionen eingebunden.Damit "kapseln/umhüllen" wir zum Beispiel die Ausgabe mittels cout:
#include <iostream> using namespace std; char Text[255]; char* pText = Text; void SetzeText( char* txt ) { pText = txt; }; void Ausgabe ( ) { cout << pText << endl; }; int main() { SetzeText("Gruss aus der Variablen Text mittels Funktion"); Ausgabe(); return 0; };
Den beiden Funktionen in der Funktion main() sieht man nicht an, wie sie realisiert werden. Das ist in gewisser Weise ebenfalls Kapselung.
Jetzt sind wir endlich bei der OOP angelangt. Wir "kapseln/umhüllen" das Ganze noch einmal durch den Einsatz einer Klasse. Die Zeiger-Variable pText wird Member-Variable. Die beiden Funktionen werden Member-Funktionen. Wir kapseln insbesondere die Member-Variable pText der Klasse X. Das erledigt das kleine Wort "private:" für uns. So einfach kann man Member-Variablen innerhalb einer C++-Klasse kapseln. Die beiden Funktionen stellen die Schnittstelle dar:
#include <iostream> using namespace std; class X { private: char* pText; public: void Ausgabe ( ) { cout << pText << endl; }; void SetzeText( char* txt ) { pText = txt; }; }; int main() { X MyX; MyX.SetzeText("Gruss aus dem Inneren der Klasse MyX mittels Funktion"); MyX.Ausgabe(); return 0; };
Im Inneren des Objektes von der Klasse X liegt nun gekapselt die Zeiger-Variable pText. Ein direkter Zugriff mittels MyX.pText ist nicht mehr erlaubt! Dafür stehen an der Schnittstelle die Member-Funktionen bereit.
Wozu ist die Kapselung eigentlich gut? Betrachten Sie folgenden Code:
class X { //geheim }; #include "X.h" int main() { X MyX; MyX.SetzeText("Gruss aus dem Inneren der Klasse MyX mittels Funktion"); MyX.Ausgabe(); return 0; };
Sie müssen absolut nicht wissen, wie die Klasse X intern aufgebaut ist. Alles was Sie benötigen, ist die Information über die Verwendung der beiden Member-Funktionen
void X::SetzeText( char* ) void X::Ausgabe( )
Hierbei ist das Wissen über die Member-Variable pText nicht wichtig. Sie können diese sowieso nicht direkt ansprechen. Das bedeutet, daß die Aufgabe der Erstellung des main-Programmes und die Pflege der Klasse X völlig getrennt gesehen werden können. Die Schnittstelle sind die Prototypen der beiden Member-Funktionen.
Die Grundidee der Kapselung ist somit sichtbar. Es geht um die Aufteilung des Programmcodes in einzeln pflegbare Module. Damit wird Sourcecode mehrfach verwendbar und übersichtlicher.Man könnte z.B. ein Sprachmodul verwenden, daß Strings als Sound ausgibt. Die Realisierung würde dann in Ausgabe(...) erfolgen. Ebenso könnte man den String von hinten nach vorne ausgeben. Es gibt ja Leute, die rückwärts reden können. Für diese Leute wäre ein solches Programm nützlich.
Normalerweise wird ein Verwender der Klasse X aber wollen, daß wir die "Funktion" der Member-Funktionen im Kern nicht verändern. Eine gewünschte "Ausgabe rückwärts" sollte daher zu einer neuen Member-Funktion
void X::AusgabeRückwaerts( ) führen. Der innere Aufbau interessiert den Anwender hierbei nicht. Er fordert nur, daß es "funktioniert".Der Zugriff auf Funktionen und Variablen wird bei der Definition der Klasse festgelegt:
public, protected oder private.private: es kann nur von innerhalb der Klasse zugegriffen werden.
public: man kann auch von außen auf die Daten der Klasse zugreifen.
protected : es können nur abgeleitete Klassen von außen zugreifen.Daten und private Funktionen werden im Rahmen einer Klasse so "gekapselt", dass ein Zugriff ausschließlich über öffentliche Member-Funktionen (Methoden) erfolgt. Das Innere ist dadurch vom Äußeren getrennt, und genau dies ist der Vorteil der OOP. Attribute sollten möglichst privat sein. Durch eindeutig definierte Schnittstellen lassen sich Aufgaben zwischen verschiedenen Objekten teilen.
Vererbung
Man kann in C++ eine Klasse von einer anderen ableiten. Damit "erbt" die Kind-Klasse alle Member-Variablen und -Funktionen der Eltern-Klasse. Auf diese Weise lassen sich ganze Klassenhierarchien aufbauen. Die MFC sind ein solches Gebilde mit über 200 Klassen. Man unterscheidet Einfach- und Mehrfachvererbung. Innerhalb der MFC gibt es nur die Einfachvererbung. Jede Klasse "erbt" nur von einer Elternklasse. Dadurch entsteht eine baumartige Klassenhierarchie. Die oberste Elternklasse der MFC ist CObject. Davon sind die meisten MFC-Klassen direkt oder indirekt abgeleitet. Daneben gibt es bei den MFC eine kleinere Zahl von Klassen, die entweder "alleinstehend" sind oder eine andere Abstammungslinie als die von CObject besitzen:
Von CObject abgeleitet:
CObject > CCmdTarget > CWnd > CToolBar
CObject > CCmdTarget > CWnd > CView > CScrollView > CFormView > CHtmlViewAlleinstehend:
CString, CTime, CPoint, CArchive, CMultiLock
Andere Abstammungslinie als CObject:
CCmdUI > COleCmdUI
Polymorphismus
Polymorphismus bedeutet Vielgestaltigkeit. In der OOP bedeutet dies, dass mehrere Klassen in einer Klassenhierarchie den gleichen Funktionsnamen verwenden können. Polymorphismus basiert auf zwei Mechanismen:
• Objekte abgeleiteter Klassen können ineinander umgewandelt werden.
• Virtuelle Funktionen erlauben es, mittels Basisklassen-Zeiger die für das jeweilige Objekt passende Member-Funktion einer abgeleiteten Klasse aufzurufen.Virtuelle Funktionen
Virtuelle Funktionen werden erst zur Laufzeit, also nicht bereits zum Kompilierzeitpunkt, an eine Klasse gebunden. Das Programm entscheidet, welche Funktion für ein spezielles Objekt gewählt werden muss. Dadurch kann zur Laufzeit die richtige Version einer abgeleiteten Klasse aufgerufen werden. Man deklariert eine virtuelle Funktion in der Basisklasse. In abgeleiteten Klassen kann diese überschrieben werden. Entscheidend dabei ist, dass man mit einem Zeiger vom Typ der Basisklasse, der auf ein Objekt der abgeleiteten Klasse zeigt, automatisch die richtige Methode, also die der abgeleiteten Klasse und nicht die der Basisklasse, aufruft. Virtuelle Funktionen gehorchen somit dem Prinzip der „späten Bindung“. Für virtuelle Funktionen werden in C++ sogenannte V-Tables (virtual function table) verwendet.
Schauen wir uns an einigen Beispielen an, wie dieser wichtige Mechanismus funktioniert. Im nachfolgenden Programm verfügen wir über eine Basisklasse, zwei davon abgeleitete Kindklassen und zwei davon jeweils abgeleitete Enkelklassen. Alle Klassen besitzen die Funktion virt_function(), die unter gleichem Namen "vielgestaltig" (polymorph) implementiert ist:
#include <iostream> using namespace std; class Basisklasse { public: virtual void virt_function() { cout << "Basis sagt Hallo" << endl; }; }; class Kindklasse1 : public Basisklasse { public: void virt_function() { cout << "Kind1 sagt Hallo" << endl; } }; class Kindklasse2 : public Basisklasse { public: void virt_function() { cout << "Kind2 sagt Hallo" << endl; } }; class Enkelklasse1 : public Kindklasse1 { public: void virt_function() { cout << "Enkel1 sagt Hallo" << endl; } }; class Enkelklasse2 : public Kindklasse2 { public: void virt_function() { cout << "Enkel2 sagt Hallo" << endl; } }; int main() { Basisklasse basis; Kindklasse1 kind1; Kindklasse2 kind2; Enkelklasse1 enkel1; Enkelklasse2 enkel2; Basisklasse *pZeiger = &basis; pZeiger->virt_function(); pZeiger = &kind1; pZeiger->virt_function(); pZeiger = &kind2; pZeiger->virt_function(); pZeiger = &enkel1; pZeiger->virt_function(); pZeiger = &enkel2; pZeiger->virt_function(); return 0; }
In dem vorstehenden Programm wird für jedes Objekt die richtige Version der Funktion virt_function aufgerufen. Bei virtuellen Funktionen entscheidet nicht der Typ des Zeigers, sondern der Typ des Objekts über die verwendete Funktion. Damit klar wird, das dies durch den Bezeichner "virtual" ausgelöst wird, lassen wir ihn im nachfolgenden Beispiel versuchsweise weg:
class Basisklasse { public: /*virtual*/ void virt_function() { cout << "Basis sagt Hallo" << endl; }; };
Nun wird für alle Objekte unabhängig vom Typ immer die Funktion der Basisklasse aufgerufen. Eine „späte Bindung“ in Abhängigkeit vom Objekttyp erfolgt nicht. Ohne virtuelle Funktion gelingt dies nur mit dem richtigen Objekttyp:
int main() { ... basis.virt_function(); kind1.virt_function(); kind2.virt_function(); enkel1.virt_function(); enkel2.virt_function(); return 0; }
... oder mit Zeigern auf die jeweilige abgeleitete Klasse:
int main() { ... (&basis)->virt_function(); (&kind1)->virt_function(); (&kind2)->virt_function(); (&enkel1)->virt_function(); (&enkel2)->virt_function(); return 0; }
Nachfolgend noch einige Hinweise zu virtuellen Funktionen:
Soll die virtuelle Funktion in einer abgeleiteten Klasse überschrieben, also neu definiert, werden, müssen Typ des Rückgabewertes und Zahl und Art der Parameter genau übereinstimmen.
Die Kennzeichnung „virtual“ kann in den abgeleiteten Klassen entfallen.
Nicht jede abgeleitete Klasse muss eine virtuelle Funktion redefinieren.Abstrakte Klassen
Virtuelle Funktionen in einer Basisklasse werden manchmal nicht definiert, weil sie keinen Sinn ergeben. Man kann dies ausdrücken, indem man sie bei der Deklaration als rein virtuelle Funktion mit einem angehängten =0 kennzeichnet. Dadurch wird die Basisklasse abstrakt. Man kann keine Objekte abstrakter Klassen erzeugen:
#include <iostream> using namespace std; class Basisklasse // abstrakt { public: virtual void virt_function()=0; // rein virtuell }; class Kindklasse : public Basisklasse { public: void virt_function() { cout << "Kind sagt Hallo" << endl;} }; int main() { // Basisklasse basis; // Keine Instanz einer abstrakten Klasse erlaubt Kindklasse kind; Basisklasse *pZeiger = &kind; pZeiger->virt_function(); return 0; }
Eine Klasse, die mindestens eine rein virtuelle Funktion besitzt, ist eine abstrakte Klasse. Es ist nicht möglich, Objekte abstrakter Klassen zu erzeugen. Probieren Sie es aus. Eine abgeleitete Klasse muss die in der Basisklasse als rein virtuell deklarierte Funktion definieren oder sie wiederum als rein virtuell (=0) kennzeichnen.
In der MFC-Klassenhierarchie ist z.B. die Ansichtsklasse CView eine abstrakte Klasse. Ein Beispiel virtueller Funktionen aus der MFC-Klassenhierarchie sehen Sie hier:
MFC-Klassenhierarchie: CView > CScrollView > CFormView > CHtmlView
virtual void CView::OnDraw( CDC* pDC ) = 0; // abstrakte Klasse virtual void CHtmlView::OnDraw( CDC* pDC );
Dadurch, dass die Deklaration der virtuellen Funktion OnDraw mit =0 endet, wird CView zu einer abstrakten Klasse. Objekte solcher Klassen können nicht erstellt werden. Wenn man in einem MFC-Programm folgende Konstruktion eines Objektes dieser Klasse versucht
CView MeineAnsicht;
dann erhält man den Hinweis, dass es sich bei CView um eine abstrakte Klasse handelt:
"CView" : Instanz von abstrakter Klasse kann aufgrund nachfolgender Elemente nicht erstellt werden: 'void __thiscall CView::OnDraw(class CDC *)' : Rein virtuelle Funktion wurde nicht definiert.
Modulare Gestaltung
Das richtige Zusammenpacken zusammengehöriger Klassen und Funktionen in einem Modul ist in der Praxis von besonderer Wichtigkeit, damit man in einem Programm z.B. mit einer Anweisung #include <modul.h> alle zu einer Aufgabe passenden Klassen zur Hand hat.
Betrachten wir ein Beispiel aus der Programmierung mit den MFC. Will man Multithreading einsetzen, so inkludiert man die MFC-Headerdatei afxmt.h und schon verfügt man über die notwendigen Klassen:
CSyncObject, CMutex, CSemaphore, CEvent, CCriticalSection, CSingleLock und CMultiLock.
Sie sollten bei der Erzeugung eigener Klassen ebenfalls auf eine geschickte modulare Gestaltung der Header- und Implementationsdateien achten. Das erleichtert die Programmierung und erhöht die Wiederverwendbarkeit der Module.