C++
Präprozessor
Suche…
Einführung
Der C-Präprozessor ist ein einfacher Textparser / -ersatz, der vor der eigentlichen Kompilierung des Codes ausgeführt wird. Wird verwendet, um die Sprache C (und später C ++) zu erweitern und zu vereinfachen. Sie kann verwendet werden für:
ein. Andere Dateien mit #include
einschließen
b. Definieren Sie ein Textersetzungsmakro mit #define
c. Bedingte Kompilierung mit #if
#ifdef
d. Plattform- / Compilerspezifische Logik (als Erweiterung der bedingten Kompilierung)
Bemerkungen
Präprozessoranweisungen werden ausgeführt, bevor Ihre Quelldateien an den Compiler übergeben werden. Sie sind in der Lage, eine bedingte Logik auf sehr niedrigem Niveau auszuführen. Da Präprozessor-Konstrukte (z. B. objektähnliche Makros) nicht wie normale Funktionen eingegeben werden (der Vorverarbeitungsschritt erfolgt vor der Kompilierung), kann der Compiler keine Typprüfung erzwingen. Daher sollten sie sorgfältig verwendet werden.
Fügen Sie Wachen ein
Eine Headerdatei kann in anderen Headerdateien enthalten sein. Eine Quelldatei (Übersetzungseinheit), die mehrere Header enthält, kann daher indirekt einige Header mehr als einmal enthalten. Wenn eine solche Headerdatei, die mehr als einmal enthalten ist, Definitionen enthält, erkennt der Compiler (nach der Vorverarbeitung) eine Verletzung der One-Definition-Regel (z. B. §3.2 des C ++ - Standards von 2003) und gibt daher eine Diagnose aus, und die Kompilierung schlägt fehl.
Die Verwendung von "Include Guards", die manchmal auch als Header Guards oder Macro Guards bezeichnet werden, wird der Mehrfacheinbeziehung verhindert. Diese werden mit dem Prä - Prozessor implementiert #define
, #ifndef
, #endif
Direktiven.
z.B
// Foo.h
#ifndef FOO_H_INCLUDED
#define FOO_H_INCLUDED
class Foo // a class definition
{
};
#endif
Der Hauptvorteil der Verwendung von include-Guards besteht darin, dass sie mit allen standardkonformen Compilern und Vorprozessoren zusammenarbeiten.
Include-Guards verursachen jedoch auch Probleme für Entwickler, da sichergestellt werden muss, dass die Makros in allen in einem Projekt verwendeten Kopfzeilen eindeutig sind. Wenn zwei (oder mehr) Header FOO_H_INCLUDED
als Include-Guard verwenden, verhindert der erste dieser Header, der in einer Kompilierungseinheit enthalten ist, effektiv, dass die anderen Header eingeschlossen werden. Besondere Herausforderungen werden eingeführt, wenn ein Projekt eine Reihe von Bibliotheken von Drittanbietern mit Header-Dateien verwendet, die zufällig gemeinsame Guards enthalten.
Es muss auch sichergestellt werden, dass die in Include-Guards verwendeten Makros keinen Konflikt mit anderen in Header-Dateien definierten Makros verursachen.
Die meisten C ++ - Implementierungen unterstützen auch die #pragma once
Direktive, mit der sichergestellt wird, dass die Datei nur einmal in einer einzigen Kompilierung enthalten ist. Dies ist eine De-facto- Standardrichtlinie , aber sie ist nicht Teil eines ISO-C ++ - Standards. Zum Beispiel:
// Foo.h
#pragma once
class Foo
{
};
#pragma once
zwar #pragma once
einige Probleme mit include - Guards #pragma once
vermeidet, ist ein #pragma
- per Definition in den Standards - inhärent ein compilerspezifischer Hook und wird von Compilern, die es nicht unterstützen, ignoriert. Projekte, die #pragma once
sind schwieriger auf Compiler zu portieren, die dies nicht unterstützen.
Eine Reihe von Codierungsrichtlinien und Sicherheitsstandards für C ++ rät ausdrücklich davon ab, den Präprozessor zu verwenden, mit #include
von #include
Include-Header-Dateien oder zur Platzierung von Include-Guards in Headern.
Bedingte Logik und plattformübergreifendes Handling
Kurz gesagt geht es bei der bedingten Vorverarbeitungslogik darum, Codelogik für die Kompilierung mithilfe von Makrodefinitionen verfügbar zu machen oder nicht zur Verfügung zu stellen.
Drei prominente Anwendungsfälle sind:
- verschiedene App-Profile (z. B. Debug, Release, Testing, Optimized), die Kandidaten derselben App sein können (z. B. mit zusätzlicher Protokollierung).
- Plattformübergreifende Kompilierungen - eine einzige Codebasis, mehrere Kompilierungsplattformen.
- Verwendung einer gemeinsamen Codebasis für mehrere Anwendungsversionen (z. B. Basic-, Premium- und Pro-Versionen einer Software) - mit geringfügig unterschiedlichen Funktionen.
Beispiel a: Plattformübergreifender Ansatz zum Entfernen von Dateien (illustrativ):
#ifdef _WIN32
#include <windows.h> // and other windows system files
#endif
#include <cstdio>
bool remove_file(const std::string &path)
{
#ifdef _WIN32
return DeleteFile(path.c_str());
#elif defined(_POSIX_VERSION) || defined(__unix__)
return (0 == remove(path.c_str()));
#elif defined(__APPLE__)
//TODO: check if NSAPI has a more specific function with permission dialog
return (0 == remove(path.c_str()));
#else
#error "This platform is not supported"
#endif
}
Makros wie _WIN32
, __APPLE__
oder __unix__
werden normalerweise durch entsprechende Implementierungen vordefiniert.
Beispiel b: Aktivieren der zusätzlichen Protokollierung für einen Debugbuild:
void s_PrintAppStateOnUserPrompt()
{
std::cout << "--------BEGIN-DUMP---------------\n"
<< AppState::Instance()->Settings().ToString() << "\n"
#if ( 1 == TESTING_MODE ) //privacy: we want user details only when testing
<< ListToString(AppState::UndoStack()->GetActionNames())
<< AppState::Instance()->CrntDocument().Name()
<< AppState::Instance()->CrntDocument().SignatureSHA() << "\n"
#endif
<< "--------END-DUMP---------------\n"
}
Beispiel c: Aktivieren Sie ein Premium-Feature in einem separaten Produkt-Build (Hinweis: Dies ist nur zur Veranschaulichung gedacht. Es ist häufig eine bessere Idee, ein Feature freischalten zu lassen, ohne dass eine Anwendung erneut installiert werden muss.)
void MainWindow::OnProcessButtonClick()
{
#ifndef _PREMIUM
CreatePurchaseDialog("Buy App Premium", "This feature is available for our App Premium users. Click the Buy button to purchase the Premium version at our website");
return;
#endif
//...actual feature logic here
}
Einige häufige Tricks:
Symbole zum Aufrufzeitpunkt definieren:
Der Präprozessor kann mit vordefinierten Symbolen (mit optionaler Initialisierung) aufgerufen werden. Zum Beispiel führt dieser Befehl ( gcc -E
nur den Präprozessor aus).
gcc -E -DOPTIMISE_FOR_OS_X -DTESTING_MODE=1 Sample.cpp
Verarbeitet Sample.cpp auf dieselbe Weise, als wäre dies der #define OPTIMISE_FOR_OS_X
wenn #define OPTIMISE_FOR_OS_X
und #define TESTING_MODE 1
oben in Sample.cpp hinzugefügt würden.
Sicherstellen, dass ein Makro definiert ist:
Wenn ein Makro nicht definiert ist und sein Wert verglichen oder geprüft wird, nimmt der Präprozessor fast immer im Stillen an, dass der Wert 0
. Es gibt einige Möglichkeiten, damit zu arbeiten. Ein Ansatz besteht darin, anzunehmen, dass die Standardeinstellungen als 0 dargestellt werden, und dass Änderungen (z. B. am App-Build-Profil) explizit vorgenommen werden müssen (z. B. ENABLE_EXTRA_DEBUGGING = 0, Satz -DENABLE_EXTRA_DEBUGGING = 1 zum Überschreiben). Ein anderer Ansatz besteht darin, alle Definitionen und Vorgaben explizit zu machen. Dies kann durch eine Kombination der #ifndef
und #error
:
#ifndef (ENABLE_EXTRA_DEBUGGING)
// please include DefaultDefines.h if not already included.
# error "ENABLE_EXTRA_DEBUGGING is not defined"
#else
# if ( 1 == ENABLE_EXTRA_DEBUGGING )
//code
# endif
#endif
Makros
Makros werden in zwei Hauptgruppen eingeteilt: objektähnliche Makros und funktionsähnliche Makros. Makros werden zu Beginn des Kompilierungsvorgangs als Token-Ersetzung behandelt. Dies bedeutet, dass große (oder sich wiederholende) Codeabschnitte in ein Präprozessormakro abstrahiert werden können.
// This is an object-like macro
#define PI 3.14159265358979
// This is a function-like macro.
// Note that we can use previously defined macros
// in other macro definitions (object-like or function-like)
// But watch out, its quite useful if you know what you're doing, but the
// Compiler doesnt know which type to handle, so using inline functions instead
// is quite recommended (But e.g. for Minimum/Maximum functions it is quite useful)
#define AREA(r) (PI*(r)*(r))
// They can be used like this:
double pi_macro = PI;
double area_macro = AREA(4.6);
Die Qt-Bibliothek verwendet diese Technik, um ein Metaobjektsystem zu erstellen, indem der Benutzer das Makro Q_OBJECT an der Spitze der benutzerdefinierten Klasse, die QObject erweitert, deklariert.
Makronamen werden normalerweise in Großbuchstaben geschrieben, um sie leichter vom normalen Code unterscheiden zu können. Dies ist keine Voraussetzung, sondern wird von vielen Programmierern lediglich als guter Stil betrachtet.
Wenn ein objektartiges Makro gefunden wird, wird es als einfaches Kopieren und Einfügen erweitert, wobei der Name des Makros durch seine Definition ersetzt wird. Wenn ein funktionsähnliches Makro gefunden wird, werden sowohl der Name als auch die Parameter erweitert.
double pi_squared = PI * PI;
// Compiler sees:
double pi_squared = 3.14159265358979 * 3.14159265358979;
double area = AREA(5);
// Compiler sees:
double area = (3.14159265358979*(5)*(5))
Daher werden funktionsähnliche Makroparameter häufig in Klammern eingeschlossen, wie in AREA()
oben. Dadurch werden Fehler vermieden, die während der Makroerweiterung auftreten können, insbesondere Fehler, die durch einen einzelnen Makroparameter verursacht werden, der aus mehreren tatsächlichen Werten besteht.
#define BAD_AREA(r) PI * r * r
double bad_area = BAD_AREA(5 + 1.6);
// Compiler sees:
double bad_area = 3.14159265358979 * 5 + 1.6 * 5 + 1.6;
double good_area = AREA(5 + 1.6);
// Compiler sees:
double good_area = (3.14159265358979*(5 + 1.6)*(5 + 1.6));
Beachten Sie auch, dass aufgrund dieser einfachen Erweiterung die an Makros übergebenen Parameter sorgfältig beachtet werden müssen, um unerwartete Nebenwirkungen zu vermeiden. Wenn der Parameter während der Auswertung geändert wird, wird er jedes Mal geändert, wenn er im erweiterten Makro verwendet wird. Dies ist normalerweise nicht das, was wir wollen. Dies gilt auch, wenn das Makro die Parameter in Klammern einschließt, um zu verhindern, dass die Erweiterung irgendetwas beschädigt.
int oops = 5;
double incremental_damage = AREA(oops++);
// Compiler sees:
double incremental_damage = (3.14159265358979*(oops++)*(oops++));
Außerdem bieten Makros keine Typsicherheit, was zu schwer verständlichen Fehlern in Bezug auf Typenkonflikte führt.
Da Programmierer normalerweise Zeilen mit einem Semikolon abschließen, werden Makros, die als eigenständige Zeilen verwendet werden sollen, häufig zum "Schlucken" eines Semikolons entworfen. Dies verhindert, dass unbeabsichtigte Fehler durch ein zusätzliches Semikolon verursacht werden.
#define IF_BREAKER(Func) Func();
if (some_condition)
// Oops.
IF_BREAKER(some_func);
else
std::cout << "I am accidentally an orphan." << std::endl;
In diesem Beispiel unterbricht das unbeabsichtigte Doppel-Semikolon den if...else
Block und verhindert, dass der Compiler das else
mit dem if
übereinstimmt. Um dies zu verhindern, wird das Semikolon aus der Makrodefinition weggelassen, wodurch das Semikolon unmittelbar nach seiner Verwendung "verschluckt" wird.
#define IF_FIXER(Func) Func()
if (some_condition)
IF_FIXER(some_func);
else
std::cout << "Hooray! I work again!" << std::endl;
Wenn Sie das nachgestellte Semikolon nicht angeben, kann das Makro auch verwendet werden, ohne die aktuelle Anweisung zu beenden, was von Vorteil sein kann.
#define DO_SOMETHING(Func, Param) Func(Param, 2)
// ...
some_function(DO_SOMETHING(some_func, 3), DO_SOMETHING(some_func, 42));
Normalerweise endet eine Makrodefinition am Ende der Zeile. Wenn ein Makro jedoch mehrere Zeilen abdecken muss, kann am Ende einer Zeile ein Backslash verwendet werden, um dies anzuzeigen. Dieser umgekehrte Schrägstrich muss das letzte Zeichen in der Zeile sein. Dies weist den Präprozessor an, dass die folgende Zeile in der aktuellen Zeile verkettet werden sollte und sie als eine einzige Zeile behandelt. Dies kann mehrmals hintereinander verwendet werden.
#define TEXT "I \
am \
many \
lines."
// ...
std::cout << TEXT << std::endl; // Output: I am many lines.
Dies ist besonders nützlich bei komplexen funktionsähnlichen Makros, die möglicherweise mehrere Zeilen abdecken müssen.
#define CREATE_OUTPUT_AND_DELETE(Str) \
std::string* tmp = new std::string(Str); \
std::cout << *tmp << std::endl; \
delete tmp;
// ...
CREATE_OUTPUT_AND_DELETE("There's no real need for this to use 'new'.")
Bei komplexeren funktionsartigen Makros kann es nützlich sein, ihnen einen eigenen Bereich zu geben, um mögliche Namenskollisionen zu verhindern oder Objekte am Ende des Makros zu zerstören, ähnlich einer tatsächlichen Funktion. Ein üblicher Ausdruck dafür ist do while 0 , wobei das Makro in einem do-while- Block eingeschlossen ist. Auf diesen Block folgt im Allgemeinen kein Semikolon, sodass ein Semikolon verschluckt werden kann.
#define DO_STUFF(Type, Param, ReturnVar) do { \
Type temp(some_setup_values); \
ReturnVar = temp.process(Param); \
} while (0)
int x;
DO_STUFF(MyClass, 41153.7, x);
// Compiler sees:
int x;
do {
MyClass temp(some_setup_values);
x = temp.process(41153.7);
} while (0);
Es gibt auch verschiedene Makros. Ähnlich wie bei variadischen Funktionen benötigen diese eine variable Anzahl von Argumenten und erweitern diese dann alle anstelle des speziellen Parameters "Varargs", __VA_ARGS__
.
#define VARIADIC(Param, ...) Param(__VA_ARGS__)
VARIADIC(printf, "%d", 8);
// Compiler sees:
printf("%d", 8);
Beachten Sie, dass __VA_ARGS__
während der Erweiterung an einer beliebigen Stelle in der Definition platziert werden kann und korrekt erweitert wird.
#define VARIADIC2(POne, PTwo, PThree, ...) POne(PThree, __VA_ARGS__, PTwo)
VARIADIC2(some_func, 3, 8, 6, 9);
// Compiler sees:
some_func(8, 6, 9, 3);
Im Fall eines variadischen Parameters mit Null-Argumenten behandeln unterschiedliche Compiler das nachfolgende Komma unterschiedlich. Einige Compiler wie Visual Studio schlucken das Komma ohne besondere Syntax. Bei anderen Compilern wie GCC müssen Sie ##
unmittelbar vor __VA_ARGS__
. Aus diesem Grund ist es ratsam, variadische Makros bedingt zu definieren, wenn die Portabilität ein Problem darstellt.
// In this example, COMPILER is a user-defined macro specifying the compiler being used.
#if COMPILER == "VS"
#define VARIADIC3(Name, Param, ...) Name(Param, __VA_ARGS__)
#elif COMPILER == "GCC"
#define VARIADIC3(Name, Param, ...) Name(Param, ##__VA_ARGS__)
#endif /* COMPILER */
Preprozessor-Fehlermeldungen
Kompilierungsfehler können mit dem Präprozessor generiert werden. Dies ist aus einer Reihe von Gründen nützlich, zu denen einige gehören: Benachrichtigen eines Benutzers, wenn er sich auf einer nicht unterstützten Plattform oder einem nicht unterstützten Compiler befindet
zB Rückgabefehler, wenn die gcc-Version 3.0.0 oder früher ist.
#if __GNUC__ < 3
#error "This code requires gcc > 3.0.0"
#endif
zB Fehler beim Kompilieren auf einem Apple-Computer.
#ifdef __APPLE__
#error "Apple products are not supported in this release"
#endif
Vordefinierte Makros
Vordefinierte Makros sind diejenigen, die der Compiler definiert (im Gegensatz zu denen, die der Benutzer in der Quelldatei definiert). Diese Makros dürfen vom Benutzer nicht neu definiert oder undefiniert werden.
Die folgenden Makros sind durch den C ++ - Standard vordefiniert:
-
__LINE__
enthält die Zeilennummer der Zeile dieses Makro auf verwendet wird, und kann durch die geändert werden#line
Richtlinie. -
__FILE__
enthält den Dateinamen der Datei dieses Makro in verwendet wird, und kann durch die geändert werden#line
Richtlinie. -
__DATE__
enthält das Datum (im Format"Mmm dd yyyy"
) der"Mmm dd yyyy"
, wobei Mmm so formatiert ist, als wäre es durch einen Aufruf vonstd::asctime()
. -
__TIME__
enthält die Zeit (im Format"hh:mm:ss"
) der__TIME__
. -
__cplusplus
wird durch (konforme) C ++ - Compiler beim Kompilieren von C ++ - Dateien definiert. Sein Wert ist die Standardversion, mit der der Compiler vollständig konform ist, dh199711L
für C ++ 98 und C ++ 03,201103L
für C ++ 11 und201402L
für C ++ 14-Standard.
-
__STDC_HOSTED__
ist auf1
festgelegt, wenn die Implementierung gehostet wird , oder0
wenn sie freistehend ist .
-
__STDCPP_DEFAULT_NEW_ALIGNMENT__
enthält ein literalsize_t
, das die Ausrichtung ist, die für einen Aufruf des__STDCPP_DEFAULT_NEW_ALIGNMENT__
operator new
.
Außerdem dürfen die folgenden Makros durch Implementierungen vordefiniert werden und sind möglicherweise vorhanden:
-
__STDC__
hat eine implementierungsabhängige Bedeutung und wird normalerweise nur beim Kompilieren einer Datei als C definiert, um die vollständige Konformität mit dem C-Standard anzuzeigen. (Oder niemals, wenn der Compiler dieses Makro nicht unterstützt.)
-
__STDC_VERSION__
hat eine implementierungsabhängige Bedeutung und der Wert ist normalerweise die C-Version, ähnlich wie__cplusplus
die C ++ - Version ist. (Oder ist nicht einmal definiert, wenn der Compiler dieses Makro nicht unterstützt.) -
__STDC_MB_MIGHT_NEQ_WC__
ist auf1
definiert, wenn die Werte der engen Kodierung des__STDC_MB_MIGHT_NEQ_WC__
möglicherweise nicht den Werten ihrer breiten Entsprechungen entsprechen (z. B. if(uintmax_t)'x' != (uintmax_t)L'x'
) -
__STDC_ISO_10646__
ist definiert, wennwchar_t
als Unicode codiert ist, und erweitert sich zu einer Ganzzahlkonstante in der FormyyyymmL
, die die letzte unterstützte Unicode-yyyymmL
angibt. -
__STDCPP_STRICT_POINTER_SAFETY__
ist auf1
definiert, wenn die Implementierung strikte__STDCPP_STRICT_POINTER_SAFETY__
(__STDCPP_STRICT_POINTER_SAFETY__
ist die__STDCPP_STRICT_POINTER_SAFETY__
gelockert ) -
__STDCPP_THREADS__
ist1
, wenn das Programm mehr als einen Ausführungsthread haben kann (gilt für freistehende Implementierungen - gehostete Implementierungen können immer mehr als einen Thread haben)
Erwähnenswert ist auch __func__
, bei dem es sich nicht um ein Makro, sondern um eine vordefinierte funktionslokale Variable handelt. Es enthält den Namen der Funktion, in der es verwendet wird, als statisches Zeichenarray in einem implementierungsdefinierten Format.
Zusätzlich zu diesen standardmäßigen vordefinierten Makros können Compiler über einen eigenen Satz vordefinierter Makros verfügen. Um diese zu lernen, muss man sich auf die Dokumentation des Compilers beziehen. Z.B:
Einige Makros dienen lediglich der Abfrage einiger Funktionen:
#ifdef __cplusplus // if compiled by C++ compiler
extern "C"{ // C code has to be decorated
// C library header declarations here
}
#endif
Andere sind sehr nützlich für das Debuggen:
bool success = doSomething( /*some arguments*/ );
if( !success ){
std::cerr << "ERROR: doSomething() failed on line " << __LINE__ - 2
<< " in function " << __func__ << "()"
<< " in file " << __FILE__
<< std::endl;
}
Und andere zur trivialen Versionskontrolle:
int main( int argc, char *argv[] ){
if( argc == 2 && std::string( argv[1] ) == "-v" ){
std::cout << "Hello World program\n"
<< "v 1.1\n" // I have to remember to update this manually
<< "compiled: " << __DATE__ << ' ' << __TIME__ // this updates automagically
<< std::endl;
}
else{
std::cout << "Hello World!\n";
}
}
X-Makros
Eine idiomatische Technik zum Erzeugen sich wiederholender Codestrukturen zur Kompilierzeit.
Ein X-Makro besteht aus zwei Teilen: der Liste und der Ausführung der Liste.
Beispiel:
#define LIST \
X(dog) \
X(cat) \
X(racoon)
// class Animal {
// public:
// void say();
// };
#define X(name) Animal name;
LIST
#undef X
int main() {
#define X(name) name.say();
LIST
#undef X
return 0;
}
welches vom Präprozessor in folgendes erweitert wird:
Animal dog;
Animal cat;
Animal racoon;
int main() {
dog.say();
cat.say();
racoon.say();
return 0;
}
Da Listen größer werden (sagen wir mehr als 100 Elemente), hilft diese Technik, übermäßiges Kopieren und Einfügen zu vermeiden.
Quelle: https://en.wikipedia.org/wiki/X_Macro
Siehe auch: X-Makros
Wenn Sie ein nahtlos irrelevantes X
vor der Verwendung von LIST
nicht definieren möchten, können Sie auch einen Makronamen als Argument übergeben:
#define LIST(MACRO) \
MACRO(dog) \
MACRO(cat) \
MACRO(racoon)
Nun legen Sie explizit fest, welches Makro beim Erweitern der Liste verwendet werden soll, z
#define FORWARD_DECLARE_ANIMAL(name) Animal name;
LIST(FORWARD_DECLARE_ANIMAL)
Wenn für jeden Aufruf des MACRO
zusätzliche Parameter erforderlich sind - konstant in Bezug auf die Liste, können variadische Makros verwendet werden
//a walkaround for Visual studio
#define EXPAND(x) x
#define LIST(MACRO, ...) \
EXPAND(MACRO(dog, __VA_ARGS__)) \
EXPAND(MACRO(cat, __VA_ARGS__)) \
EXPAND(MACRO(racoon, __VA_ARGS__))
Das erste Argument wird von der LIST
bereitgestellt, während der Rest vom Benutzer beim Aufruf der LIST
bereitgestellt wird. Zum Beispiel:
#define FORWARD_DECLARE(name, type, prefix) type prefix##name;
LIST(FORWARD_DECLARE,Animal,anim_)
LIST(FORWARD_DECLARE,Object,obj_)
wird zu erweitern
Animal anim_dog;
Animal anim_cat;
Animal anim_racoon;
Object obj_dog;
Object obj_cat;
Object obj_racoon;
#pragma einmal
Die meisten, aber nicht alle C ++ - Implementierungen unterstützen die #pragma once
Direktive, mit der sichergestellt wird, dass die Datei nur einmal in einer einzigen Kompilierung enthalten ist. Es ist nicht Teil eines ISO C ++ - Standards. Zum Beispiel:
// Foo.h
#pragma once
class Foo
{
};
#pragma once
zwar #pragma once
einige Probleme mit include - Guards #pragma once
vermeidet, ist ein #pragma
- per Definition in den Standards - inhärent ein compilerspezifischer Hook und wird von Compilern, die es nicht unterstützen, ignoriert. Projekte, die #pragma once
müssen modifiziert werden, um standardkonform zu sein.
Bei einigen Compilern - insbesondere bei solchen, die vorkompilierte Header verwenden - kann #pragma once
zu einer erheblichen Beschleunigung des Kompilierungsprozesses führen. In ähnlicher Weise erzielen einige Vorprozessoren eine Beschleunigung der Kompilierung, indem sie nachverfolgen, welche Kopfzeilen über Wachen verfügen. Der Nettonutzen, wenn sowohl #pragma once
als auch #pragma once
verwendet werden, hängt von der Implementierung ab und kann entweder eine Erhöhung oder Verkürzung der Kompilierungszeiten darstellen.
#pragma once
Kombination mit Include Guards das empfohlene Layout für Headerdateien beim Schreiben von MFC-basierten Anwendungen unter Windows. Es wurde von Visual Studio's add class
add dialog
add windows
. Daher ist es sehr üblich, sie in C ++ Windows-Antragstellern kombiniert zu finden.
Präprozessoroperatoren
#
Operator #
oder der String-Operator wird verwendet, um einen Makro-Parameter in ein String-Literal zu konvertieren. Es kann nur mit Makros verwendet werden, die Argumente haben.
// preprocessor will convert the parameter x to the string literal x
#define PRINT(x) printf(#x "\n")
PRINT(This line will be converted to string by preprocessor);
// Compiler sees
printf("This line will be converted to string by preprocessor""\n");
Der Compiler verkettet zwei Zeichenfolgen, und das letzte Argument printf()
ist ein Zeichenfolgenliteral mit einem Zeilenvorschubzeichen am Ende.
Der Präprozessor ignoriert die Leerzeichen vor oder nach dem Makroargument. Die folgende Druckaussage liefert also dasselbe Ergebnis.
PRINT( This line will be converted to string by preprocessor );
Wenn für den Parameter des Zeichenfolgenlitals eine Escape-Sequenz wie vor einem doppelten Anführungszeichen () erforderlich ist, wird er automatisch vom Präprozessor eingefügt.
PRINT(This "line" will be converted to "string" by preprocessor);
// Compiler sees
printf("This \"line\" will be converted to \"string\" by preprocessor""\n");
##
Operator oder Token-Einfügeoperator wird verwendet, um zwei Parameter oder Token eines Makros zu verketten.
// preprocessor will combine the variable and the x
#define PRINT(x) printf("variable" #x " = %d", variable##x)
int variableY = 15;
PRINT(Y);
//compiler sees
printf("variable""Y"" = %d", variableY);
und die endgültige Ausgabe wird sein
variableY = 15