erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC  ( Stand: 22.08.2001; Links aktualisert am 25.07.2008 )

Anmerkung (2008): Die MFC (Wrapperklassen um WinAPI) werden von MS weiter unterstützt, obwohl es inzwischen moderne Alternativen gibt.


Zurück zum Inhaltsverzeichnis

 

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):



// CAboutDlg-Dialogfeld für Anwendungsbefehl "Info"
class CAboutDlg : public CDialog
{
    public:
    CAboutDlg();
  // Dialogfelddaten
    // {{AFX_DATA(CAboutDlg)
    enum { IDD = IDD_ABOUTBOX };
  // }}AFX_DATA
    // Vom Klassenassistenten generierte Überladungen virtueller Funktionen
    // {{AFX_VIRTUAL(CAboutDlg)
    protected:
    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV-Unterstützung
  // }}AFX_VIRTUAL
    // Implementierung
    protected:
  // {{AFX_MSG(CAboutDlg)
    // }}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

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:"):



class CAboutDlg : public CDialog
{
    public:
      CAboutDlg();
      enum { IDD = IDD_ABOUTBOX };
    protected:
      virtual void DoDataExchange(CDataExchange* pDX);
      DECLARE_MESSAGE_MAP()
};

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:



/*
Die MFC-Klasse CAboutDlg umhüllt die Dialog-Ressource IDD_ABOUTBOX .
Der Aufruf erfolgt im Quellcode durch die Member-Funktion DoModal().
Zur Entkopplung von Header und Quellcode dient die symbolische Konstante IDD.
Im konkreten Fall könnte DoDataExchange(...) und die Message Map entfallen.
DoDataExchange(...) und UpdateData(...) werden zur DDX/DDV-Unterstützung eingesetzt.
*/

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.
 
 
 

Zurück zum Inhaltsverzeichnis