PS-Trainer C - Entwicklung
Lehreinheit Nr. 4
Homepage von PS-Trainer - C-Entwicklung - Bibliotheken - an PS-Trainer
PS-Trainer PS-Trainer

Strukturen und Makros:
Ziele dieser Lehreinheit sind zwei durchaus unterschiedliche Themengebiete: Die Arbeit mit Strukturen und Makors charakterisiert den Übergang von "Mickymaus"-Programmen zu (beginnender) professioneller Arbeit.

Strukturen:
Sie sind bereits mit den grundlegenden Typen von Variablen vertraut: int, float und char. Dazu noch einige Varianten dieser Typen wie long, unsigned, double usw.
Mit diesen Typen arbeiten sie in 3 Formen:
als einzelne Variable,
als Felder (arrays) von gleichartigen Variablen und
als Pointer auf diese Variablentypen.
Wenn sie an Projekten größerer Komplexität arbeiten, wird auch die Stuktur ihrer Daten komplizierter: Um eine Person (in der IT) zu beschreiben, benötigen sie z.B. eine Menge unterschiedlicher Daten: Zahlen, Texte, Datum-angaben, vielleicht sogar Bilder...
Strukturen dienen dazu, komplexe Daten auf einfache Art abzubilden und damit zu arbeiten. Sie definieren z.B. die Stuktur person (einmalig), und können fortan mit komplexen Daten der type person genauso arbeiten wie bisher mit int oder char !

Makros:
Sie kennen bereits Funktionen (Unterprogramme) und sind mit ihnen vertraut. Das sind eigene Codesegmente, die bei Bedarf (wenn sie "aufgerufen" werden) in Funktion gesetzt werden, danach wieder inaktiv sind. Funktionen erhalten vom aufrufenden Programm (caller) Daten (Argumente) und sind in der Lage, Daten (in der Regel nur eine Variable) an das aufrufende Programm zurückzugeben.
Diese selbständigen Einheiten funktionieren nach fixen Regeln: In C++ etwa werden prinzipiell Kopien der vom caller bereitgestellten Argumente an die Funktion weitergegeben.

Makros haben äußerliche Ähnlichkeiten mit Funktionen, sind jedoch etwas ganz Anderes ! Ein Makro ist schlicht die Möglichkeit, häufig verwendete Programm-Texte mit einem Kurz-Namen zu versehen. Sie haben z.B. die Möglichkeit, den Text
printf("Hallo, hier bin ich\n")
mit dem Kurz-Namen Hallo
zu versehen. - Jedesmal, wenn sie dann in ihrem Programm den Befehl Hallo; erteilen, wird an Stelle des Kurz-Namens der gewünschte Text eingesetzt (Das Makro-Stichwort wird expandiert). Das erscheint auf den ersten Blick trivial, aber - richtig angewendet sind Makros ein mächtiges Werkzeug für die Programmierung !


struct
struct [tag] { member-list } [declarators];
[struct] tag declarators;


The struct keyword defines a structure type and/or a variable of a structure type.
A structure type is a user-defined composite type.
It is composed of "fields" or "members" that can have different types.
In C++, a structure is the same as a class except that its members are public by default.

Using a Structure
In C, you must explicitly use the struct keyword to declare a structure. In C++, this is unnecessary once the type has been defined.
You have the option of declaring variables when the structure type is defined by placing one or more comma-separated variable names between the closing brace and the semicolon.

For related information, see class, union, and enum.

Example 1
struct PERSON // Declare PERSON struct type
{
int age;// Declare member types
long ss;
float weight;
char name[25];
} family_member;// Define object of type PERSON

struct PERSON sister;// C style structure declaration
PERSON brother;// C++ style structure declaration

sister.age = 13;// assign values to members
brother.age = 7;
Structure variables can be initialized. The initialization for each variable must be enclosed in braces.

Example 2
struct POINT// Declare POINT structure
{
int x;// Define members x and y
int y;
} spot = { 20, 40 };// Variable spot has
// values x = 20, y = 40
struct POINT there;// Variable there has POINT type

struct CELL// Declare CELL bit field
{
unsigned character : 8;// 00000000 ????????
unsigned foreground : 3;// 00000??? 00000000
unsigned intensity : 1;// 0000?000 00000000
unsigned background : 3;// 0???0000 00000000
unsigned blink : 1;// ?0000000 00000000
} screen[25][80];// Array of bit fields

Programmieren mit Strukturen:
Da Strukturen größere Einheiten von Daten darstellen, vermeidet man möglichst, sie zwischen Programmteilen zu kopieren. Die Arbeit mit Strukturen ist daher gekennzeichnet durch die Verwendung von Pointern auf Strukturen. - Pointer werden sehr rasch zwischen Programmteilen ausgetauscht.
In C war es weitgehend unmöglich, Strukturen als Argumente an Funktionen zu übergeben, Man konnte ausschließlich mit Pointern arbeiten. In C++ ist es zwar möglich, Strukturen als Argumente von Funktionen zu verwenden, jedoch schlechter Stil. Entnehmen sie den nachfolgenden Beispielen, wie sie Strukturen an Funktionen übergeben und sie dort weiter bearbeiten.
In C ist es erforderlich, bei Verwendung von Strukturen das Schlüsselwort struct einzusetzen. Das ist in C++ nicht mehr erforderlich - wenn sie die Struktur einmal definiert haben, dann können sie die Bezeichnung der Struktur genauso verwenden wie jede andere Type, z.B. int.
Es ist häufig erforderlich, Werte von Struktur-Variablen anzusprechen, wobei die Strukturen selbst nur über Pointer zugänglich sind. Dafür wurde eine spezielle einfache Syntax entwickelt:
In Standard-Syntax sprechen sie z.B. die Variable int kunden_nr einer Struktur mykunde, die über den Pointer kunde so an:
i = (*kunde).kunden_nr;
Vereinfacht können sie äquivalent schreiben:
i = kunde->kunden_nr;
Strukturen können selbstverständlich auch als Elemente anderer (übergeordneter) Strukturen auftreten. So können sie nicht nur aus primitiven Typen (int, char, float...) Strukturen zusammensetzen, sondern auch komplexe Strukturen aus einfacheren Strukturen. Damit erhöht sich die Komplexität und Menge der angesprochenen Daten erheblich - d.h. ihre Befehle werden rasch mächtiger !

Makros sind Elemente einer Programmiersprache, die (mehr oder weniger intelligent) Synonyme (Kurz-Namen) durch Texte ersetzen. Diese Texte machen dann einen Teil ihrer "Programme" aus. - Sie haben also die Möglichkeit, "Programmierte Programme" zu schreiben.
In C++ gibt es mehrere Möglichkeiten, Makros zu definieren und anzuwenden - einige bedienen sich der "preprocessor language", d.h. einer dem eigentlichen C++-Compiler vorgeschalteten Instanz. Diese können sie verwenden, um die Erstellung ihrer C-Programme (teilweise) zu programmieren:
#define
inline

Daneben gibt es noch andere Makro-Möglichkeiten, die jedoch hier nicht ausgeführt werden.

The #define Directive
You can use the #define directive to give a meaningful name to a constant in your program.
The two forms of the syntax are:

Syntax
#define identifier token-stringopt
#define identifier[( identifieropt, ... , identifieropt )] token-stringopt


The #define directive substitutes token-string for all subsequent occurrences of an identifier in the source file. The identifier is replaced only when it forms a token. ( For instance, identifier is not replaced if it appears in a comment, within a string, or as part of a longer identifier.)
A #define without a token-string removes occurrences of identifier from the source file. The identifier remains defined and can be tested using the #if defined and #ifdef directives.
The token-string argument consists of a series of tokens, such as keywords, constants, or complete statements. One or more white-space characters must separate token-string from identifier. This white space is not considered part of the substituted text, nor is any white space following the last token of the text.
Formal parameter names appear in token-string to mark the places where actual values are substituted. Each parameter name can appear more than once in token-string, and the names can appear in any order. The number of arguments in the call must match the number of parameters in the macro definition. Liberal use of parentheses ensures that complicated actual arguments are interpreted correctly.

The second syntax form allows the creation of function-like macros. This form accepts an optional list of parameters that must appear in parentheses. References to the identifier after the original definition replace each occurrence of identifier ( identifieropt, ..., identifieropt ) with a version of the token-string argument that has actual arguments substituted for formal parameters.
The formal parameters in the list are separated by commas. Each name in the list must be unique, and the list must be enclosed in parentheses. No spaces can separate identifier and the opening parenthesis. Use line concatenation—place a backslash (\) before the newline character—for long directives on multiple source lines. The scope of a formal parameter name extends to the new line that ends token-string.
When a macro has been defined in the second syntax form, subsequent textual instances followed by an argument list constitute a macro call. The actual arguments following an instance of identifier in the source file are matched to the corresponding formal parameters in the macro definition. Each formal parameter in token-string that is not preceded by a stringizing (#), charizing (#@), or token-pasting (##) operator, or not followed by a ## operator, is replaced by the corresponding actual argument. Any macros in the actual argument are expanded before the directive replaces the formal parameter.
The following examples of macros with arguments illustrate the second form of the #define syntax:
// Macro to define cursor lines
#define CURSOR (top, bottom) ((top) << 8) | bottom))

// Macro to get a random integer with a specified range
#define getrandom(min, max) \
((rand()%(int)(((max) + 1)-(min)))+ (min))


Arguments with side effects sometimes cause macros to produce unexpected results. A given formal parameter may appear more than once in token-string. If that formal parameter is replaced by an expression with side effects, the expression, with its side effects, may be evaluated more than once. (See the examples under Token-Pasting Operator (##).)

The #undef directive causes an identifier's preprocessor definition to be forgotten. See The #undef Directive for more information.
If the name of the macro being defined occurs in token-string (even as a result of another macro expansion), it is not expanded.

A second #define for a macro with the same name generates an error unless the second token sequence is identical to the first.

Microsoft Specific
Microsoft C/C++ allows the redefinition of a macro, but generates a warning, provided the new definition is lexically identical to a previous definition. ANSI C considers macro redefinition an error. For example, these macros are equivalent for C/C++ but generate warnings:
#define test( f1, f2 ) ( f1 * f2 )
#define test( a1, a2 ) ( a1 * a2 )

END Microsoft Specific

This example illustrates the #define directive:
#define WIDTH 80
#define LENGTH ( WIDTH + 10 )

The first statement defines the identifier WIDTH as the integer constant 80 and defines LENGTH in terms of WIDTH and the integer constant 10. Each occurrence of LENGTH is replaced by (WIDTH + 10). In turn, each occurrence of WIDTH + 10 is replaced by the expression (80 + 10). The parentheses around WIDTH + 10 are important because they control the interpretation in statements such as the following:
var = LENGTH * 20;
After the preprocessing stage the statement becomes:
var = ( 80 + 10 ) * 20;
which evaluates to 1800. Without parentheses, the result is:
var = 80 + 10 * 20;
which evaluates to 280.

Microsoft Specific ®
Defining macros and constants with the /D compiler option has the same effect as using a #define preprocessing directive at the beginning of your file. Up to 30 macros can be defined with the /D option.
END Microsoft Specific

Inline Functions versus Macros
Although inline functions are similar to macros (because the function code is expanded at the point of the call at compile time), inline functions are parsed by the compiler, whereas macros are expanded by the preprocessor. As a result, there are several important differences:
Inline functions follow all the protocols of type safety enforced on normal functions.
Inline functions are specified using the same syntax as any other function except that they include the inline keyword in the function declaration.
Expressions passed as arguments to inline functions are evaluated once. In some cases, expressions passed as arguments to macros can be evaluated more than once.

The following example shows a macro that converts lowercase letters to uppercase:
#include <stdio.h>
#include <conio.h>
#define toupper(a) ((a) >= 'a' && ((a) <= 'z') ? ((a)-('a'-'A')):(a))

void main()
{
char ch = toupper( _getch() );
printf( "%c", ch );
}


The intent of the expression toupper( _getch() ) is that a character should be read from the console device (stdin) and, if necessary, converted to uppercase.
Because of the implementation, _getch is executed once to determine whether the character is greater than or equal to "a," and once to determine whether it is less than or equal to "z." If it is in that range, _getch is executed again to convert the character to uppercase. This means the program waits for two or three characters when, ideally, it should wait for only one.

Inline functions remedy this problem:
#include <stdio.h>
#include <conio.h>

inline char toupper( char a )
{
return ((a >= 'a' && a <= 'z') ? a-('a'-'A') : a );
}

void main()
{
char ch = toupper( _getch() );
printf( "%c", ch );
}


Übungs-Beispiele:
001 Erste Schritte mit Strukturen (in Varianten)
002 Einige praktische Makros

001
Strukturen verwenden
(in Varianten)

An diesem Beispiel sehen sie sowohl einige einfache Makro-Funktionen als auch die Verwendung von Strukturen in verschiedenen Varianten demonstriert:

Mit Hilfe von #define werden hier 4 verschiedene Möglichkeiten vorgestellt, ein Programm zu compilieren:

In diesem Beispiel ist
#define version 1
definiert. Damit wird Version 1 dieses Programms erzeugt.
Wenn sie
#define version 2
...bis...
#define version 4
definieren, so werden andere Versionen erzeugt, ohne daß sie am Code etwas ändern müssen !

Anschließend sehen sie als Demo einen "security check", in dem abgesichert wird, daß version evtl. falsch dimensioniert oder gar gelöscht wird.

/*
Program STRUCTURE-TEST #0

Basic structure handling

Version 1.0 / 2001.10.01
Autor: PS-Trainer, pstrainer@gmx.net
*/

/* compiler option: version 1...4 */
#define version 1

/* security check */
#ifndef version
#define version 1
#endif
#if ((version<1)||(version>4))
#define version 1
#endif

/* standard libraries */
#include <stdio.h>
#include <conio.h>

Hier wird eine Struktur definiert:
Die Type teststruc wird der Einfachheit halber als Struktur von je 2 int Variablen definiert.

/* structure definition */
struct teststruc {
int sernr;
int demo;
};

Es folgen wie üblich die Deklarationen der verwendeten Funktionen: hier der beiden Funktionen printstruc und changestruc.

Je nach gewählter version werden die Argumente dieser Funktionen als Struktur-Typen teststruc oder als Pointer auf ebensolche Strukturen übergeben.

/* function declarations in different versions */
void print_header (char *);
void print_trailer (void);

#if (version<=1)
void printstruc (teststruc);
void changestruc (teststruc);
#else
void printstruc (teststruc*);
void changestruc (teststruc*);
#endif

Das Hauptprogramm (eigentlich: die Funktion main):

Die Struktur tsa der Type teststruc wird deklariert.

Variablen von Strukturen werden so angesprochen: Name_der_Struktur-Punkt-Name_der_Variablen. Damit können sie genauso verfahren wie mit anderen Variablen gewohnt.

/********** main **********/
void main (void) {
char myname[]="STRUCTURE-TEST #0";
// define tsa of type teststruc
teststruc tsa;

print_header(myname);

/* how to handle structure variables */
tsa.sernr = 90;
tsa.sernr+= 9;
tsa.demo= 900 + tsa.sernr;

Nun wird die Struktur getestet:
tsa wird in 4 Versionen an Funktionen (Unterprogramme) übergeben:
printstruc druckt die Struktur
changestruc ändert die Struktur.
Hier version 1:

/* print structure, change it, print again
in 4 versions: */
#if (version<=1)
// copy structure to sub-function
printstruc (tsa);
changestruc (tsa);
printstruc (tsa);

In den Versionen 2...4 wird nicht die Struktur selbst, sondern ein Pointer darauf an die Funktionen übergeben:

Damit ist das kurze Programm auch schon am Ende angelangt.

#else
// copy pointer to structure to sub-function
printstruc (&tsa);
changestruc (&tsa);
printstruc (&tsa);

#endif

print_trailer();
}

Nun folgt die Definition der Funktionen in 4 Varianten:

version 1 erhält als Argument die Struktur selbst - nach C-Konvention eine (private) Kopie davon - die läßt sich natürlich ausdrucken, auch ändern, aber - die Änderung wird vom aufrufenden Programm nicht bemerkt.

Anmerkung: das Makro version wird hier als Argument von printf() eingesetzt - ( gefährlich, wenn version fehlt oder keine Zahl ist)

/* functions printstruc and changestruc in 4 different versions */

#if (version<=1)
// we get a copy (!) of the structure itself:
void printstruc (teststruc a) {
int i,j;
printf("version %d: ",version);
i=a.sernr;
j=a.demo;
printf("i=%d j=%d\n",i,j);
}
void changestruc (teststruc a) {
a.sernr-=9;
a.demo-=99;
}

#endif

version 2 erhält einen Pointer auf die Struktur.
Dieser wird benutzt, um die ganze Struktur auf die eigene Struktur b zu kopieren:
Damit können sie nun einfach verfahren, die Variablen z.B. ausdrucken.

Wenn sie Änderungen durchführen wollen, wie z.B. Funktion changestruc, dann müssen sie die Struktur des callers kopieren, dann verändern, und danach noch einmal zurück kopieren - ein umständliches und (bei großen Datenmengen) zeitraubendes Verfahren.

#if (version==2)
// we get a pointer to the structure
// we copy the structure to our own structure
// we work with our own structure
// we copy the result to the original structure
void printstruc (teststruc *pa) {
int i,j;
teststruc b;
printf("version %d: ",version);
b=*pa;
i=b.sernr;
j=b.demo;
printf("i=%d j=%d\n",i,j);
}
void changestruc (teststruc *pa) {
teststruc b;
b=*pa;
b.sernr-=9;
b.demo-=99;
*pa=b;
}

#endif

version 3 ist bereits sparsamer:
Der vom caller übergebene Pointer auf die Struktur wird in üblicher Pointer-Syntax eingesetzt.

Allerdings müssen sie *pa in Klammern setzen, damit tatsächlich der Wert der enthaltenen Variablen angesprochen wird:

#if (version==3)
// we get a pointer to the structure
// we work with the structure using pointers
void printstruc (teststruc *pa) {
int i,j;
printf("version %d: ",version);
i=(*pa).sernr;
j=(*pa).demo;

printf("i=%d j=%d\n",i,j);
}
void changestruc (teststruc *pa) {
(*pa).sernr-=9;
(*pa).demo-=99;

}

#endif

version 4 arbeitet mit der äquivalenten Syntax (d.h. gleich schnell wie version 3, nur anders formuliert).

#if (version>=4)
// we get a pointer to the structure
// we work with the structure using -> pointers
void printstruc (teststruc *pa) {
int i,j;
printf("version %d: ",version);
i=pa->sernr;
j=pa->demo;

printf("i=%d j=%d\n",i,j);
}
void changestruc (teststruc *pa) {
pa->sernr-=9;
pa->demo-=99;

}

#endif

Zuletzt folgen noch einige Werkzeug-Unterprogramme:

print_header druckt nicht nur einen Titel, sondern erkennt auch, ob sie gerade eine "Debug version" oder eine "Release version" compilieren.

print_trailer setzt in der "Debug version" einen Haltepunkt am Ende von main, damit sie das Konsolen-Fenster betrachten können, bevor es am Ende von main geschlossen wird.

Anm.: Ihr Compiler signalisiert einige Daten der aktuellen "Umgebung" bzw. Einstellung an ihr Programm, wie z.B. das Makro _DEBUG.

void print_header (char *header) {
#ifdef _DEBUG
printf("Debug version ");
#else
printf("Release version ");
#endif
printf("of program %s\n\n",header);
}

void print_trailer (void) {
#ifdef _DEBUG
/* show console window until key pressed */
printf("\nPress any key to continue ");
_getch();
#endif
}

/* ========== eof ========== */


002
Einige praktische Makros

Achtung: Alle hier vorgestellten Beispiele ersetzen den Text des jeweiligen Stichworts in ihrem Programm - D.h. ihr Code verhält sich genauso, als ob sie selbst jedes Stichwort (z.B. durch Kopieren-Einsetzen) durch den expandierten Text ersetzt hätten. - Es werden daher (in diesen Fällen) von ihrem C-Programm keine wirklichen Funktionen aufgerufen !

Selbstverständlich können sie Makro-Stichworte auch bei der Definition anderer Makros verwenden - diese müssen jedoch vorher bereits definiert sein.

Anmerkung: Die Syntax von #define-Makros muss bei Definition nur der Syntax des Präprozessors entsprechen, erst nach Compilierung treten allfällige C-Syntaxfehler (an jeder verwendeten Stelle) auf.

Mathematik:
Iround rundet eine reale Zahl zur nächsten ganzen Zahl,
Dround rundet eine reale Zahl auf die gewünschte anzahl von Nachkomma-Stellen
#define Iround(dx) (int(floor((dx)+0.5)))
#define Dround(dx,id) (floor(((dx)*(pow((10), (double(id))))) + 0.5) / (pow((10),(double(id)))))

// Syntax:
// i=Iround(dx);// e.g. Iround(3.1416)=3
// r2=Dround(r1,id); // e.g. Dround(3.1416,2)=3.14
Zufallszahlen:
Myrnd_01 erzeugt eine reale Zufallszahl (0...1),
Myrnd_lohi eine reale Z-Zahl (lo...hi),
Myrndi_lohi eine ganze Z-Zahl (ilo...ihi).
Myrnd_init_TIME randomisiert mit den Sekunden der aktuellen Zeit,
Myrnd_init_CLOCK mit der Prozesszeit.

#define Myrnd_01 (double(rand())/double(RAND_MAX))
// dx = Myrnd_01;
#define Myrnd_lohi(lo,hi) (Myrnd_01*((hi)-(lo))+(lo))
// dx = Myrnd_lohi(10.5,11.5);
#define Myrndi_lohi(ilo,ihi) (rand() % ((ihi)-(ilo)+1) + (ilo))
// ix = Myrnd_lohi(1,5);
#define Myrnd_init_TIME (srand(time(NULL)))
// Myrnd_init_TIME;
#define Myrnd_init_CLOCK (srand(clock()))
// Myrnd_init_CLOCK;

Zeichen:
upper verwandelt Zeichen in Großbuchstaben,
lower verwandelt Zeichen in Kleinbuchstaben,
isdigit testet, ob ein Zeichen eine Ziffer ist,
isyes testet, ob ein Zeichen Y oder J ist.

#define upper(c) (((c>='a')&&(c<='z')) ? (c-'a'+'A') : c)
// d=upper(c);
#define lower(c) (((c>='A')&&(c<='Z')) ? (c-'A'+'a') : c)
// d=lower(c);
#define isdigit(c) (((c>='0')&&(c<='9'))?1:0)
// if (isdigit(c)) {...}
#define isyes(c) (((c=='Y')||(c=='y')||(c=='J')||(c=='j'))?1:0)
// if (isyes(c)) {...}

Inline-Funktionen:
strupper verwandelt ein char array in Großbuchstaben,
strlower in Kleinbuchstaben.

Sie können in inline-Funktionen sogar eigene Variable deklarieren - diese können jedoch mit bereits vorhandenen Variablen kollidieren - verwenden sie daher ungewöhnliche Namen für diese Variablen.

Anmerkung:
Makros upper und lower müssen vor der Definition dieser Funktionen definiert sein !

inline void strupper (char *t)
{
int strup_i=0;
while (t[strup_i]!='\0') t[strup_i++]=upper(t[strup_i]);
}
// Syntax:
// char *text[100]
// ...
// strupper(text); // convert text to upper case
// ...

inline void strlower (char *t)
{
int strlo_i=0;
while (t[strlo_i]!='\0') t[strlo_i++]=lower(t[strlo_i]);
}


Homepage von PS-Trainer - Entwicklung - an PS-Trainer

Aktuelle Daten dieser Seite Letzte Änderung:
  Geocities