• Nem Talált Eredményt

II. fejezet - Moduláris programozás C++ nyelven

4. A C++ előfeldolgozó utasításai

4.3. Makrók használata

Bonyolultabb szerkezetek kialakításához a többirányú elágaztatás megvalósító előfordító utasítást ajánlott használni

#if konstans_kifejezés1 programrész1

#elif konstans_kifejezés2 programrész2

#else

programrész3

#endif

Az alábbi példában a gyártótól függően építünk be deklarációs állományt a programunkba:

#define IBM 1

#define HP 2

#define ESZKOZ IBM

#if ESZKOZ == IBM

#define ESZKOZ_H "ibm.h"

#elif ESZKOZ == HP

#define ESZKOZ_H "hp.h"

#else

#define ESZKOZ_H "mas.h"

#endif

#include ESZKOZ_H

4.3. Makrók használata

Az előfordító használatának legvitatottabb területe a #define makrók alkalmazása a C++ programokban. Ennek oka, hogy a makrók feldolgozásának eredménye nem mindig találkozik a programozó elvárásaival. A makrókat igen széleskörűen alkalmazzák a C nyelven készült programokban, azonban C++ nyelven a jelentőségük csökkent. A C++ nyelv definíciója előnyben részesíti a const és az inline függvénysablonokat a makrókkal szemben. Az alábbi áttekintésben a biztonságos makró-használatot tartjuk szem előtt.

A #define direktívát arra használjuk, hogy azonosítókkal lássuk el a C++ konstans értékeket, a kulcsszavakat, illetve a gyakran használt utasításokat és kifejezéseket. Az előfordító számára definiált szimbólumokat makróknak nevezzük. A C++ forráskódban a makróneveket csupa nagybetűvel ajánlott írni, hogy jól elkülönüljenek a programban használt C++ nevektől.

Az előfeldolgozó soronként megvizsgálja a forrásprogramot, hogy azok tartalmaznak-e valamilyen korábban definiált makrónevet. Ha igen, akkor azt lecseréli a megfelelő helyettesítő szövegre, majd újból megvizsgálja a sort, további makrókat keresve, amit ismételt helyettesítés követhet. Ezt a folyamatot makróhelyettesítésnek

vagy makrókifejtésnek nevezzük. A makrókat a #define utasítással hozzuk létre, és az #undef direktíva segítségével semmisítjük meg.

4.3.1. Szimbolikus konstansok

Szimbolikus konstansok készítéséhez a #define direktíva egyszerű alakját alkalmazzuk:

#define azonosító

#define azonosító helyettesítő_szöveg

Nézzünk néhány példát a szimbolikus konstansok definiálására és használatára!

#define MERET 4

#define DEBUG

#define AND &&

#define VERZIO "v1.2.3"

#define BYTE unsigned char

#define EOS '\0'

int main() { int v[MERET];

BYTE m = MERET;

if (m > 2 AND m < 5)

cout << "2 < m < 5" << endl;

#ifdef DEBUG

cout << VERZIO << endl;

#endif }

Az előfeldolgozás (helyettesítések) eredményeként keletkező forrásfájl:

int main() { int v[4];

unsigned char m = 4;

if (m > 2 && m < 5)

cout << "2 < m < 5" << endl;

cout << "v1.2.3" << endl;

}

C++-ban a fenti szimbolikus konstansok egy része helyett const és typedef azonosítókat is használhatunk:

#define DEBUG

#define AND &&

const int meret = 4;

const string verzio = "v1.2.3";

const char eos = '\0';

typedef unsigned char byte;

int main() { int v[meret];

byte m = meret;

if (m > 2 AND m < 5)

cout << "2 < m < 5" << endl;

#ifdef DEBUG

cout << verzio << endl;

#endif }

4.3.2. Paraméteres makrók

A makrók felhasználási területét lényegesen bővíti a makrók paraméterezésének lehetősége. A függvényszerű, paraméteres makrók definíciójának általános formája:

#define azonosító(paraméterlista) helyettesítő_szöveg

A makró használata (hívása):

azonosító(argumentumlista)

A makróhívásban szereplő argumentumok számának meg kell egyeznie a definícióban szereplő paraméterek számával.

Annak érdekében, hogy a paraméteres makróink biztonságosan működjenek, be kell tartanunk a következő két szabály:

• A makró paramétereit a makró törzsében (a helyettesítő szövegben) mindig tegyük zárójelek közé!

• Ne használjunk léptető (++, --) operátort a makró hívásakor az argumentumlistában!

Az alábbi példaprogramban bemutatunk néhány gyakran használt makró-definíciót és a hívásukat:

// x abszolút értékének meghatározása

#define ABS(x) ( (x) < 0 ? (-(x)) : (x) )

// a és b maximumának kiszámítása

#define MAX(a,b) ( (a) > (b) ? (a) : (b) )

// négyzetre emelés

#define SQR(X) ( (X) * (X) )

// véletlen szám a megadott intervallumban

#define RANDOM(min, max) \

((rand()%(int)(((max) + 1)-(min)))+ (min))

int main() { int x = -5;

x = ABS(x);

cout << SQR(ABS(x));

int a = RANDOM(0,100);

cout << MAX(a, RANDOM(12,23));

}

A RANDOM() makró első sorának végén álló fordított perjel azt jelzi, hogy a makró-definíció a következő sorban folytatódik. A main() függvény tartalma az előfordítást követően egyáltalán nem nevezhető jól olvashatónak:

int main() { int x = -5;

x = ( (x) < 0 ? (-(x)) : (x) );

cout << ( (( (x) < 0 ? (-(x)) : (x) )) * (( (x) < 0 ? (-(x)) : (x) )) );

int a = ((rand()%(int)(((100) + 1)-(0)))+ (0));

cout << ( (a) > (((rand()%(int)(((23) + 1)-(12)))+ (12))) ? (a) : (((rand()%(int)(((23) + 1)-(12)))+ (12))) );

}

A C++ program olvashatóságának megtartásával elérhetjük a makróhasználat összes előnyét (típusfüggetlenség, gyorsabb kód), ha a makrók helyett inline függvényt/függvénysablont használunk:

template <class tip> inline tip Abs(tip x) { return x < 0 ? -x : x;

}

template <class tip> inline tip Max(tip a, tip b) { return a > b ? a : b;

}

template <class tip> inline tip Sqr(tip x) { return x * x;

}

inline int Random(int min, int max) { return rand() % (max + 1 - min) + min; direktívát kell használnunk. Egy makró új tartalommal történő átdefiniálása előtt mindig meg kell szüntetnünk a régi definíciót. Az #undef utasítás nem jelez hibát, ha a törölni kívánt makró nem létezik:

#undef azonosító

Az alábbi példában nyomon követhetjük a #define és az #undef utasítások működését:

Eredeti forráskód Kifejtett programkód

Az eddigi példáinkban a helyettesítést csak különálló paraméterek esetén végezte el az előfeldolgozó. Vannak esetek, amikor a paraméter valamely azonosítónak a része, vagy az argumentum értékét sztringként kívánjuk felhasználni. Ha a helyettesítést ekkor is el kell végezni, akkor a ##, illetve a # makróoperátorokat kell használnunk.

A # karaktert a makróban a paraméter elé helyezve, a paraméter értéke idézőjelek között (sztringként) helyettesítődik be. Ezzel a megoldással sztringben is lehetséges a behelyettesítés, hisz a fordító az egymás mellett álló sztringliterálokat egyetlen sztring konstanssá kapcsolja össze. Az alábbi példában az INFO() makró segítségével tetszőleges változó neve és értéke megjeleníthető:

#include <iostream>

}

x = 23

s = C++ nyelv pi = 3.14259

A ## operátor megadásával két szintaktikai egységet (tokent) lehet összeforrasztani oly módon, hogy a makró törzsében a ## operátort helyezzük a paraméterek közé.

A alábbi példában szereplő SZEMELY() makró segít feltölteni a szemelyek tömb struktúra típusú elemeit:

#include <string>

using namespace std;

struct szemely { string nev;

string info;

};

string Ivan_Info ="K. Ivan, Budapest, 9";

string Aliz_Info ="O. Aliz, Budapest, 33";

#define SZEMELY(NEV) { #NEV, NEV ## _Info }

int main(){

szemely szemelyek[] = { SZEMELY (Ivan), SZEMELY (Aliz) };

}

A main() függvény tartalma az előfeldolgozás után:

int main(){

szemely szemelyek[] = { { "Ivan", Ivan_Info }, { "Aliz", Aliz_Info } };

}

Vannak esetek, amikor egyetlen makró megírásával nem érünk célt. Az alábbi példában a verziószámot sztringben rakjuk össze a megadott szimbolikus konstansokból:

#define FO 7

#define MELLEK 29

#define VERZIO(fo, mellek) VERZIO_SEGED(fo, mellek)

#define VERZIO_SEGED(fo, mellek) #fo "." #mellek

static char VERZIO1[]=VERZIO(FO,MELLEK); // "7.29"

static char VERZIO2[]=VERZIO_SEGED(FO,MELLEK); // "FO.MELLEK"

4.3.5. Előre definiált makrók

Az ANSI C++ szabvány az alábbi, előre definiált makrókat tartalmazza. (Az azonosítók majd mindegyike két aláhúzás jellel kezdődik és végződik.) Az előre definiált makrók nevét nem lehet sem a #define sem pedig az

#undef utasításokban szerepeltetni. Az előre definiált makrók értékét beépíthetjük a program szövegébe, de feltételes fordítás feltételeként is felhasználhatjuk.

Makró Leírás Példa __DATE__ A fordítás dátumát tartalmazó

sztring konstans.

"Oct 02 2013"

__TIME__ A fordítás időpontját tartalmazó sztring konstans.

"10:02:04"

__TIMESTAMP__ A forrásfájl utolsó módosításának dátuma és ideje sztring konstansban"

"Mon Jul 29 07:33:29 2013"

__FILE__ A forrásfájl nevét tartalmazó sztring konstans.

"c:\\preproc.cpp"

__LINE__ A forrásállomány aktuális sorának sorszámát tartalmazó szám konstans (1-től sorszámoz).

1223

__STDC__ Az értéke 1, ha a fordító ANSI C++

fordítóként működik, különben nem definiált.

__cplusplus Az értéke 1, ha C++

forrásállományban teszteljük az értékét, különben nem definiált.