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.