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 von std::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, dh 199711L für C ++ 98 und C ++ 03, 201103L für C ++ 11 und 201402L für C ++ 14-Standard.
c ++ 11
  • __STDC_HOSTED__ ist auf 1 festgelegt, wenn die Implementierung gehostet wird , oder 0 wenn sie freistehend ist .
c ++ 17
  • __STDCPP_DEFAULT_NEW_ALIGNMENT__ enthält ein literal size_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.)
c ++ 11
  • __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 auf 1 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, wenn wchar_t als Unicode codiert ist, und erweitert sich zu einer Ganzzahlkonstante in der Form yyyymmL , die die letzte unterstützte Unicode- yyyymmL angibt.
  • __STDCPP_STRICT_POINTER_SAFETY__ ist auf 1 definiert, wenn die Implementierung strikte __STDCPP_STRICT_POINTER_SAFETY__ ( __STDCPP_STRICT_POINTER_SAFETY__ ist die __STDCPP_STRICT_POINTER_SAFETY__ gelockert )
  • __STDCPP_THREADS__ ist 1 , 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:

c ++ 11
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


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow