Wichtige Grundlagen von C und C++
Um in die Windows-Programmierung mit MS VisualC++ (VC++) und MFC einzusteigen, müssen Sie nicht alle Sprachelemente von C und C++ kennen. Sie können jedoch nicht von Null auf Hundert starten. Daher nachfolgend der Versuch, auch Anfängern ohne zuviel Ballast den Weg zur Windows-Programmierung mit C++ aufzuzeigen:
Empfehlenswert für Anfänger ist das C++-Tutorial von Wolfgang Schröder.
Sehr gut ist auch das Tutorial von Volkard Henkel. Um relativ rasch in die Windows-Programmierung einzusteigen, sollten man meines Erachtens insbesondere folgende Kapitel dieses Tutorials durchgehen:
Lektion
4:
Die erste Anwendung
Lektion
5:
Variablen
Lektion
6:
Typen
Lektion
7:
Funktionen
Lektion
8:
Ganze Zahlen und Fließkommazahlen in einem Ausdruck
Lektion
9:
if und else
Lektion
10:
Block
Lektion
11:
else dem richtigen if zuordnen
Lektion
12:
while-Schleife
Lektion
13:
for-Schleife
Lektion
14:
do-Schleife
Lektion
16:
Rekursion
Lektion
17:
Logische Ausdrücke
Lektion
18:
else if
Lektion
19:
switch
Lektion
20:
char, short, int und long
Lektion
21:
integer overflow
Lektion
22:
signed und unsigned
Lektion
23:
Zeichen
Lektion
24:
Datenstrukturen
Lektion
25:
Klassen
Lektion
28:
Arrays
Lektion
32:
Zeiger
Lektion
39:
new und delete
Lektion
40:
Arrays und Zeiger
Lektion
41:
new[] und delete[] (für Arrays)
Lektion
53:
Die Klasse Rational
Lektion
54:
Typumwandlungs-Konstruktor
Lektion
76:
Vererbung
Lektion
78:
Polymorphie
Lektion
79:
Der Scope-Resolution-Operator
Lektion
80:
Abstrakte Basisklasse
Weitere empfehlenswerte C++-Tutorials:
http://tutorial.schornboeck.net/
http://de.geocities.com/throni3/
Alternativ können Sie mein Tutorial C++-Konsolen-Programme durcharbeiten, das an kleinen Konsolen-Beispielen in die Grundlagen von C++ einführt.
Für die Windows-Programmierung entscheidend ist, dass
Sie
zu Beginn den vom Anwendungs- und Klassen-Assistenten
erzeugten Programmcode
verstehen. In diesem Abschnitt werden daher bevorzugt Beispiele aus
MFC-Code
zur Veranschaulichung verwendet.
Programmcode und Kommentare
Um Programmcode besser
verständlich
zu machen, setzen Programmierer Kommentare ein.
Einzeilige Kommentare werden hierbei
durch das C++-Symbol //
und mehrzeilige Kommentare
durch die C-Symbole /*
und */
kenntlich gemacht.
Nachfolgend sehen Sie die Definition
einer von CDialog abgeleiteten Klasse für das Info-Fenster,
wie sie vom Anwendungs-Assistenten
erzeugt wird (Listing 1a):
Listing 1a
Manche Programmierer empfinden die Kommentare der
Assistenten eher als störend für die Lesbarkeit ihrer
Programme.
Zumindest beeinflussen Kommentare nicht die
Geschwindigkeit
oder die Größe der vom Compiler erzeugten binären
Dateien.
Sie sollen dem Leser (und dem Assistenten) den Code
zugänglich machen.
Zur Verdeutlichung des Unterschiedes folgt das obenstehende Listing ohne Kommentare (und auch ohne das doppelte "protected:"):
Listing 1b
Vergleicht man Listing 1b mit Listing 1a, so
versteht
man Leute, die von Hand gemachte Programme anschaulicher finden als den
von MFC-Assistenten erzeugten Code. Unabhängig davon, wie Sie
hierzu
stehen, sollten Sie in Ihrem eigenem Code möglichst viele
Kommentare
unterbringen, damit nicht nur andere, sondern vor allem Sie selbst den
Code auch nach längerer Zeit noch verstehen und in andere
Anwendungen
übertragen können. So könnte z.B. ein von Hand gemachtes
Listing 1b sich durch Kommentare in das untenstehende
Listing 1c verwandeln:
class
CAboutDlg
: public CDialog
{
public:
CAboutDlg(); // Konstruktor
enum { IDD = IDD_ABOUTBOX }; // symbol.
Konstante
protected:
virtual void DoDataExchange( CDataExchange* pDX ); //
für DDX und DDV
DECLARE_MESSAGE_MAP() //
Nachrichtentabelle
};
Listing 1c
Versuchen Sie, Kommentare und Programmcode
optisch
deutlich zu trennen. Hilfreich sind Kommentare auch zur Kennzeichnung
schließender geschweifter Klammern,
insbesondere
bei tiefer Verschachtelung, z.B.:
function (
... )
{
if( ... )
{ ...
if( ... )
{ ...
... weitere Verschachtelungen ...
} // end of if
} // end of if
} //
end of function
Variablen - Deklaration, Definition, Datentypen
Ein C++-Programm verarbeitet Daten. Diese Daten sind Variablen zugeordnet und belegen hierbei Speicher. Die Art der binären Darstellung und der Speicherbedarf hängt hierbei vom Datentyp ab. In C oder C++ muß jede Variable mit Datentyp und Namen "deklariert" - sozusagen dem Compiler vorgestellt - werden. Mussten in C Variablen immer am Anfang einer Funktion deklariert werden, so gilt diese Einschränkung für C++ nicht. Deklarationen können in C++ auch innerhalb von Funktionen erfolgen. Datentypen unterscheiden sich in bereits vorgegebene und benutzerdefinierte Typen. Zu den benutzerdefinierten Typen gehören z.B. eigene Klassen, Arrays, Zeiger (Pointer) und Aufzählungstypen. In VC++ bereits vorhanden sind z.B. char, short, long, float, double, BOOL, HICON, CString, CBitmap, CRect und CPoint.
Zunächst einige Beispiele für Deklarationen:
BOOL
m_bClose;
short m_nZahl;
int
nLWTyp[26];
HICON m_Icon1;
CAboutDlg
dlgAbout;
Eine Deklaration erzeugt einen Namen, einen zugeordneten Speicherbereich und legt abhängig vom Datentyp die binäre Darstellung des Wertes fest. Zu Beginn befinden sich in diesem Speicherbereich zufällige Werte. Erst durch eine Definition einer Variable erhält diese einen festgelegten Wert. Der wichtigste Operator hierfür ist der Zuweisungsoperator "=". Zunächst einige Beispiele:
m_bClose =
FALSE;
m_nZahl =
0;
nLWTyp[
zaehler
] = ::GetDriveType( str[ zaehler ] + ":" ); //
innerhalb einer Schleife
m_Icon1 =
AfxGetApp()->LoadIcon( IDI_ICON1 );
In vielen Fällen werden Deklaration und Definition kombiniert, hier einige Beispiele:
BOOL bOk =
FALSE;
int nResponse
= dlg.DoModal();
Beim Einstieg in C++ und MFC begegnet Ihnen eine Vielzahl von Datentypen einschließlich der MFC-Klassen. Wichtig sind zu Beginn die Typen für Zahlen, Zeichen, Zeichenketten (Strings) und Wahrheitswerte.
Beginnen wir mit den Zahlen. Bei den Zahlen gibt
es keinen Typ, der alle Zahlen von minus unendlich bis plus unendlich
abdeckt.
Obere und untere Grenze hängt vom zur Verfügung stehenden
Speicherplatz
ab. Hier gibt es prinzipiell zwei Typen, nämlich Ganzzahl- und
Fließkomma-Datentypen.
Bei den Ganzzahltypen unterscheidet man in "signed" (positive und
negative
Zahlen) und "unsigned" (nur positive Zahlen inclusive null).
Ganzzahlen (Integer)
"Signed" Ganzzahl-Typen:
Ganzzahl-Datentyp | Speicherbedarf | Wertebereich | |
char | CHAR | 1 Byte | - 128 bis 127 |
short | SHORT | 2 Byte | - 32.768 bis 32.767 |
int | INT | 4 Byte | - 2.147.483.648 bis 2.147.483.647 |
long | LONG | 4 Byte | - 2.147.483.648 bis 2.147.483.647 |
"Unsigned" Ganzzahl-Typen:
Ganzzahl-Datentyp | Speicher-bedarf | Wertebereich | |||
unsigned char | UCHAR,
BYTE |
1 Byte | 0 bis 255 | ||
unsigned short | USHORT,
WORD |
2 Byte | 0 bis 65.535 | ||
unsigned int | UINT | 4 Byte | 0 bis 4.294.967.295 | ||
unsigned long | ULONG,
DWORD |
4 Byte | 0 bis 4.294.967.295 | ||
Die grundsätzlichen Datentypen char, short, int und long sind in C++ festgelegt. Ein char-Datentyp ist ein Byte ( = 8 Bit ) groß und wird eingesetzt, um kleine ganze Zahlen oder den ASCII- bzw. ANSI-Zeichensatz darzustellen. Das Zeichen 'C' besitzt z.B. den Zahlencode 67. Gibt man den Typ char an, verwendet der Compiler normalerweise signed char. Bezüglich short ist festgelegt, dass dieser Datentyp mindestens so groß sein muss wie char, jedoch nicht größer als int. In VC++ werden für short 2 Bytes reserviert. Der Datentyp int muss mindestens so groß wie short sein, darf jedoch nicht größer als long sein. Bei VC++ entspricht int dem Datentyp long mit der Größe von 4 Bytes. Über diese Grundtypen hinaus werden in der Windows-Programmierung weitere Bezeichnungen für diese Datentypen mittels typedef festgelegt, die man in den Dateien winnt.h bzw. windef.h findet:
typedef
char CHAR;
typedef short SHORT;
typedef int INT;
typedef long LONG;
typedef unsigned char UCHAR;
typedef unsigned char BYTE;
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef unsigned int UINT;
typedef unsigned long ULONG;
typedef unsigned long DWORD;
Zusätzlich sollte man wissen, dass short
und short int äquivalente Bezeichnungen sind,
ebenso wie long und long int.
Zur ersten Orientierung bezüglich der
Ganzzahlen
ergibt sich folgende einfache Übersicht:
8 bit | 16 bit | 32 bit | |
signed | signed char | short | long = int |
unsigned | BYTE | WORD | DWORD |
Wenn Sie sich über das Zusammenwachsen von int und long wundern, so sollten Sie wissen, dass bei älteren Compilern short int 8 bit, int 16 bit und long int 32 bit umfasste. In der 32-bit-Welt von Windows 95, Windows 98 oder Windows NT hat sich diese Skala nach oben verschoben, so dass die Abstufung short - int - long aus den Fugen geriet. Nun hört die Computerwelt bei 32 bit nicht auf. Was ist eigentlich mit Ganzzahlen, die größer sind als 2.147.483.647 (signed) bzw. 4.294.967.295 (unsigned)? Testen Sie einmal folgenden Programmcode:
INT32
i32_Zahl;
i32_Zahl
= 2147483647;
INT64
i64_Zahl;
i64_Zahl
= 18446744073709551615;
Er passiert problemlos den Compiler. Woran liegt
dies? In der Header-Datei basetsd.h
(suchen Sie sie in Ihren Projekten in der
Dateiübersicht
bei "Externe Abhängigkeiten")
finden Sie folgende Festlegungen (verkürzte
Darstellung):
// The following types
are
guaranteed to be signed and 32 bits wide.
typedef int LONG32;
typedef int INT32;
// The following types
are
guaranteed to be unsigned and 32 bits wide.
typedef unsigned int
ULONG32;
typedef unsigned int
DWORD32;
typedef unsigned int UINT32;
// The following types
are
guaranteed to be signed and 64 bits wide.
typedef __int64 LONG64;
typedef __int64 INT64;
// The following types
are
guaranteed to be unsigned and 64 bits wide.
typedef unsigned __int64
ULONG64;
typedef unsigned __int64
DWORD64;
typedef unsigned __int64
UINT64;
In der Hilfe finden Sie zusätzlich unter
"Data
Type Ranges" folgende interessante Übersicht (verkürzte
Darstellung):
Type Name | Bytes | Other Names | Range of Values |
__int8 | 1 | char,
signed char |
–128 to 127 |
__int16 | 2 | short,
short int, signed short int |
–32,768 to 32,767 |
__int32 | 4 | signed,
signed int |
–2,147,483,648 to 2,147,483,647 |
__int64 | 8 | none | –9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
Die orientierende Übersicht könnte sich
damit in folgende Darstellung wandeln:
8 bit | 16 bit | 32 bit | 64 bit | |
signed | signed char | short | INT32 LONG32 |
INT64 LONG64 |
unsigned | BYTE | WORD | DWORD32 UINT32 ULONG32 |
DWORD64 UINT64 ULONG64 |
Wie Sie an dieser Betrachtung der Ganzzahlen
erkennen,
schleppt VC++ eine Menge historischen Ballast ( 8bit à
32 bit ) mit sich herum. Durch die Einbindung des Datentyps INT64
können Sie jetzt vorzeichenbehaftete Ganzzahlen von -264 bis
264-1 = 18.446.744.073.709.551.615 darstellen. Dies reicht
auf
jeden Fall für die Welt der Giga- und Terabytes.
Compilerspezifische
Festlegungen bezüglich Ganzzahlen findet man übrigens
generell
in limits.h.
Fließkommazahlen
Als grundsätzliche Typen für Fließkommazahlen findet man bereits in C die Datentypen float mit 4 Byte, double mit 8 Byte und long double mit 10 Byte. Zusätzlich kann man für float und double auch die Großschrift-Bezeichner verwenden aufgrund von:
typedef
double DOUBLE;
typedef float FLOAT;
Der Datentyp float verwendet für die Mantisse 24 bit, für den Exponenten 7 bit und für das Vorzeichen ein bit und kann daher Werte im Bereich 3.4 * 10-38 bis 3.4 * 1038 aufnehmen. Die Darstellung ist auf 6 Nachkommastellen begrenzt.
Der Datentyp double verwendet für die Mantisse 53 bit, für den Exponenten 10 bit und für das Vorzeichen ein bit und kann daher Werte im Bereich 1.7 * 10-308 bis 1.7 * 10308 aufnehmen. Die Darstellung ist bis auf 15 Nachkommastellen genau.
Der Datentyp long double verwendet für die Mantisse 64 bit, für den Exponenten 15 bit und für das Vorzeichen ein bit und kann daher Werte im Bereich 1.2 * 10-4932 bis 1.7 * 104932 aufnehmen. Die Darstellung ist bis auf 18 Nachkommastellen genau. Dieser Datentyp steht aber nicht überall zur Verfügung.
Wer sich für detaillierte Festlegungen
seines
Compilers interessiert, der zieht bezüglich Fließkommazahlen
float.h
zu Rate.
Wahrheitswerte
Bezüglich der Wahrheitswerte findet man folgende Definitionen in windef.h:
typedef
int BOOL;
#define FALSE 0
#define TRUE 1
Sie sehen, dass man hier einfach den
Datentyp
int (4 Byte) in BOOL, die Zahl 1 in TRUE und die Zahl 0 in FALSE
umtauft,
und schon besitzt man logische Flags, die nur die
Zahlen 0 und 1 annehmen.
Datentypen für Zeichen und Zeichenketten (ANSI und Unicode)
Windows 95 und Windows 98 verwenden den
8-bit-ANSI-Zeichensatz zur Speicherung von Zeichen und Zeichenketten
(Strings).
Windows NT setzt dagegen den
16-bit-Unicode-Zeichensatz
ein. Dies hat Vorteile für asiatische Sprachen und z.B. Kyrillisch.
Der ideale Datentyp für ANSI- und
Unicode-Zeichenketten ist die MFC-Klasse CString. Sie sollten diese
Klasse
in eigenen Entwicklungen daher bevorzugt verwenden. Detaillierte
Informationen
zur MFC-Klasse CString finden Sie in der Hilfe ausgehend von "CString
Class
Members".
Für einzelne Zeichen findet man den klassischen Datentyp char.
Beispiel:
char cZeichen = 'A'; //
8-bit-Zeichensatz
TCHAR tcZeichen = 'A'; //
16-bit-Zeichensatz
Bei char handelt es sich um die 8-bit-Codierung für Zeichen. Im Zeitalter des 16-bit-Unicode wählt man besser den modernen Datentyp TCHAR, der sowohl ANSI (mit char) als auch Unicode (mit wchar_t) abdeckt. Wenn Sie Strings unbedingt in Character-Arrays verarbeiten müssen, dann wählen Sie besser TCHAR-Arrays.
Mit dem Makro _T(...) kann man konstante Strings so bereit stellen, dass diese sowohl für ANSI als auch für UNICODE vorbereitet sind.
Beispiel:
CString strZeichenkette = _T( "Astring" );
Ist das Symbol _UNICODE definiert, so wandelt
der Präprozessor den Ausdruck _T( "String" ) in L"String" um.
Ohne _UNICODE ergibt sich das klassische
"String".
Weitere Details finden Sie in der Header-Datei tchar.h und in der Hilfe.
Benutzerdefinierte Datentypen
Mit dem Befehl typedef kann man neue Namen für Datentypen festlegen. Beispiel:
typedef double KOMMAZAHL;
KOMMAZAHL x, y, z;
x = 935.2547887;
y = 1034339.21293;
z = -3.23452346574;
Wenn Sie eigene Namen für Datentypen
verwenden
möchten, sollten Sie für die Bezeichner durchgehend
Großbuchstaben
verwenden und die Datentypen-Definitionen in einer separaten
Header-Datei,
z.B. "EigeneDatentypen.h", abspeichern, die Sie dann gezielt in Ihre
Projekte
einbinden können.
Operatoren
Variablen dienen der Speicherung von Daten.
Für
Verknüpfungen und Änderungen sorgen die Operatoren.
Zuweisungsoperatoren
Die einfache Zuweisung erfolgt mit "=". Es gibt
zusätzlich
eine Reihe kombinierter Zuweisungsoperatoren,
die am Beispiel "+=" erklärt werden. Folgende
beiden Zeilen sind äquivalent:
a += b;
a = a + b;
Wie Sie sehen, sind diese kombinierten
Zuweisungsoperatoren
nicht nur unklar, sondern auch überflüssig. Sie sollten sie
zwar kennen, aber wohl eher vermeiden.
Vergleichsoperatoren und logische Operatoren
Ein häufiger logischer Fehler, den der Compiler nicht erkennen kann, ist die Verwechslung des Zuweisungsoperators "=" mit dem Vergleichsoperator "==". Wenn Sie also eine Variable auf die Übereinstimmung mit TRUE, FALSE oder einem anderen Wert abfragen wollen, dann müssen Sie "==" anstelle "=" verwenden. Daneben gibt es noch den Operator "!=" für ungleich und die größer/kleiner-Vergleichsoperatoren ">", ">=", "<" und "<=".
Bei logischen Verknüpfungen gibt es den
unären
Operator "!" für logisches NICHT sowie die beiden binären
Operatoren
"&&" für logisches UND und "||" für logisches ODER.
Das
Ergebnis dieser logischen Operationen ist dann entweder der logische
Wert
TRUE oder FALSE.
Bitoperatoren
Insbesondere die nichtexclusive ODER-Verknüpfung spielt in der Windows-Programmierung eine wichtige Rolle, z.B. bei der Kombination von Styles. Folgende Bitoperatoren werden verwendet:
"&" UND
"|" nicht-exclusives ODER
"^" exclusives ODER
"~" NICHT
(bitweises Komplement)
"<<" Linksverschiebung
">>" Rechtsverschiebung
Die Schiebeoperatoren "<<" und ">>" verschieben ihren ersten Operanden um soviele Bit-Positionen nach links oder rechts, wie der rechte Operand angibt.
// Beispiel
für ">>":
int x = 16; // Ausgangswert: 16 =
0x00010000
x >> 3; // Endwert: 2 = 0x00000010
Inkrement- und Dekrementoperator
Der Inkrementoperator "++" erhöht eine Variable im Normallfall um 1. Bei Zeigern erhöht er die Adresse jedoch um die Größe der Variable, so dass der Zeiger dann auf den Anfang der nächsten Variable eines Arrays zeigt. Entsprechend funktioniert der Dekrementoperator "--", nur umgekehrt.
Beispiel:
int y = 5; //
Ausgangswert 5
y++; //
Wert: 6
y--; //
Wert: 5
Sizeof-Operator
Mit diesem Operator ermittelt man die Größe in Bytes.
Beispiele:
int iZahl;
int iSize = sizeof( iZahl ); //
iSize:
4
char a;
int iSize = sizeof( a ); // iSize:
1
Bedingungsoperator
Dieser Operator "? :" hat 3 Operanden und funktioniert nach folgendem Prinzip:
expression ? b : c
Ergibt der Ausdruck expression den Wert TRUE, dann erhält der Gesamtausdruck den Wert von b. Ist das Ergebnis von expression jedoch FALSE, dann erhält der Gesamtausdruck den Wert von c.
In der Datei windef.h findet man z.B. folgende Definitionen, die das Vorgehen verdeutlichen:
#define max( a, b ) ( ((a) >
(b)) ?
(a) : (b) )
#define min( a, b ) ( ((a) < (b))
? (a) : (b) )
Adress-Operator
Der unäre Operator "&"
ermittelt
die Speicheradresse des nachfolgenden Operanden.
Dieser Operator ist
dort
wichtig, wo ein Zeiger auf ein Objekt erwartet wird.
Klassen
Eine der wesentlichen Unterschiede
zwischen
C und C++ ist die objekt-orientierte Programmierung OOP.
In C findet man Strukturen. Beispiel:
struct
Person
{
char Vorname[60];
char Mittelname[20];
char Nachname[60];
char Geburtsname[60];
int Alter;
double Gewicht;
};
In C++ findet man hier zusätzlich
die Klasse.
An dieser Stelle soll nur das Grundprinzip
erklärt werden. Wieso braucht man überhaupt Klassen und was
leisten
diese?
Wenn Sie nur eine einfache Zahl abspeichern wollen,
reicht eine Variable vom Datentyp int oder double aus.
Für ein einzelnes Zeichen benützen Sie
den Datentyp TCHAR. Wollen Sie jedoch mehrere zusammengehörige
Daten und Funktionen in einem gemeinsamen
Datentyp abspeichern, dann ist die Klasse genau der richtige
Datentyp.
Schauen wir uns zur Verdeutlichung die MFC-Klasse
CFileDialog an. In der Klassendefinition finden wir Member-Variablen
(Attribute)
und vor allem viele Member-Funktionen (Methoden).
class
CFileDialog
: public CCommonDialog
{
DECLARE_DYNAMIC(CFileDialog)
public:
//
Attributes
OPENFILENAME m_ofn; // open file
parameter
block
//
Constructors
CFileDialog(BOOL bOpenFileDialog, // TRUE
for FileOpen, FALSE for FileSaveAs
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd* pParentWnd = NULL);
//
Operations
virtual int DoModal();
//
Helpers for parsing file name after successful return
// or during Overridable callbacks if OFN_EXPLORER is set
CString GetPathName() const; //
return
full path and filename
CString GetFileName() const; //
return
only filename
CString GetFileExt() const; //
return only ext
CString GetFileTitle() const; // return
file
title
BOOL GetReadOnlyPref() const; // return
TRUE
if readonly checked
//
Enumerating multiple file selections
POSITION GetStartPosition() const;
CString GetNextPathName(POSITION& pos) const;
//
Helpers for custom templates
void SetTemplate(UINT nWin3ID, UINT nWin4ID);
void SetTemplate(LPCTSTR lpWin3ID, LPCTSTR lpWin4ID);
//
Other operations available while the dialog is visible
CString GetFolderPath() const; // return
full
path
void SetControlText(int nID, LPCSTR lpsz);
void HideControl(int nID);
void SetDefExt(LPCSTR lpsz);
//
Overridable callbacks
protected:
friend UINT CALLBACK _AfxCommDlgProc(HWND, UINT, WPARAM, LPARAM);
virtual UINT OnShareViolation(LPCTSTR lpszPathName);
virtual BOOL OnFileNameOK();
virtual void OnLBSelChangedNotify(UINT nIDBox, UINT iCurSel, UINT
nCode);
//
only called back if OFN_EXPLORER is set
virtual void OnInitDone();
virtual void OnFileNameChange();
virtual void OnFolderChange();
virtual void OnTypeChange();
//
Implementation
#ifdef _DEBUG
public:
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
BOOL m_bOpenFileDialog; // TRUE for file
open,
FALSE for file save
CString m_strFilter; //
filter string
//
separate fields with '|', terminate with '||\0'
TCHAR m_szFileTitle[64]; // contains file
title after return
TCHAR m_szFileName[_MAX_PATH]; //
contains
full path name after return
OPENFILENAME* m_pofnTemp;
virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);
};
Das ist eine ganze Menge Stoff... ! Aber so sieht
die rauhe Wirklichkeit nun eben mal aus.
Analysieren Sie diese Klassendefinition in Ruhe.
Wenn wir das Gerüst extrahieren,
dann ergibt sich folgendes Bild von dieser Klasse:
//Skelett
der MFC-Klasse CFileDialog
class
CFileDialog : public CCommonDialog
{
public:
...
protected:
...
};
Nach dem Schlüsselwort "class" erscheint der Name der Klasse und nach dem Doppelpunkt folgt ein Zugriffsspezifizierer (public, protected, private) und der Name der Elternklasse. Die Member-Variablen und -Funktionen der Klasse werden innerhalb der geschweiften Klammern festgelegt. Den Schlusspunkt bildet das Semikolon nach der schließenden geschweiften Klammer.
Die Member-Variablen werden z.B. auch Daten, Datenelemente, Variablen, Attribute und die Member-Funktionen z.B. auch Funktionen, Elementfunktionen, Methoden genannt. Meint man sowohl Variablen als auch Funktionen, so spricht man z.B. allgemein von den Elementen einer Klasse.
Der Zugriffsspezifizierer nach dem Doppelpunkt
vor
dem Namen der Elternklasse (Basisklasse) regelt, mit welchem Recht in
der
Kindklasse auf "geerbte" Elemente der Basisklasse zugegriffen werden
darf:
Privileg in
der Basisklasse:
Ableitungs-Zugriffsspezifizierer |
public | protected | private |
public | public | protected | kein Zugriff |
protected | protected | protected | kein Zugriff |
private | private | private | kein Zugriff |
Sie sehen, dass auf Elemente mit dem Zugriffsspezifizierer "private" in der Basisklasse kein Zugriff von außen, also weder von einer fremden noch von der abgeleiteten Klasse, erfolgen darf. Solche Elemente kann man nur innerhalb der Basisklasse ansprechen. Auf Elemente mit dem Zugriffsspezifizierer "public" in der Basisklasse darf man entsprechend dem Ableitungs-Zugriffsspezifizierer zugreifen.
In der MFC-Hierarchie ist der Ableitungs-Zugriffsspezifizierer "public" üblich. Damit bleibt das Zugriffsprivileg auf public- bzw. protected-Elemente nach der Vererbung unverändert.
Will man von einer nicht abgeleiteten Klasse auf
Elemente zugreifen, dann geht dies nur, wenn diese als "public"
spezifiziert
sind.
Wurde z.B. ein Objekt der Klasse CFileDialog erzeugt
durch die Anweisung
CFileDialog fd;
dann kann man auf die Member-Variable m_ofn dieser Klasse durch Angabe von
fd.m_ofn
zugreifen. Der Punkt-Operator trennt hierbei Objekt und Element.
Hat man einen Zeiger auf das Objekt, z.B. durch die Anweisungen
CFileDialog fd;
CFileDialog* pfd;
pfd = &fd;
dann kann man mit dem Pfeil-Operator auf ein Element des Objektes fd zugreifen:
pfd->m_ofn
Objekte entstehen mit Hilfe von Konstruktoren,
die
den gleichen Namen tragen, wie die Klasse selbst.
Bei Funktionen erlaubt C++ sogenannte Überladungen.
Das sind mehrfache Definitionen mit verschiedenen Parametern.
Dies findet man in den MFC besonders häufig
bei Konstruktoren.
Betrachten wir dies z.B. in der Klasse CDialog,
einer Kindklasse von CWnd,
dann finden wir drei Varianten für den
Konstruktor
dieser Klasse,
also die Member-Funktion CDialog::CDialog(...):
class
CDialog
: public CWnd
{ ...
public:
CDialog();
...
CDialog( LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL );
CDialog( UINT nIDTemplate, CWnd* pParentWnd = NULL );
...
In der Deklaration kann man bereits Werte
für
Parameter vorgeben. Beim Aufruf der Member-Funktion müssen dann
für
diese Parameter nur bei Abweichungen Werte angegeben werden.
Probleme???
Falls Sie noch mehr Stoff zum Thema OOP benötigen, dann folgen hier noch einige Tipps:
http://www.c-plusplus.de/ooa___oop.htm
Erlenkötter:
C++. Objektorientiertes Programmieren von Anfang an.