11/01/1999
Enhver, der har skrevet selv en smule kode i C eller C++, har stødt på hash-symbolet (#). Det er mest kendt for at indlede præprocessor-direktiver som #include <iostream> eller #define PI 3.14159. Disse linjer, som K&R i deres klassiske bog "The C Programming Language" kaldte "compiler-control lines", er instruktioner til en speciel del af kompileringsprocessen. Men hvad nu hvis jeg fortalte dig, at # og dens dobbeltgænger, ##, har skjulte superkræfter inde i #define-makroer? Disse to operatorer, kendt som Stringizing- og Token-Pasting-operatorerne, er kraftfulde værktøjer, der kan manipulere din kode på et meta-niveau, før den overhovedet når compileren. I denne artikel dykker vi ned i, hvad disse mystiske operatorer gør, hvordan de fungerer, og hvordan du kan bruge dem til at skrive mere fleksibel og dynamisk kode.

Hvad er en Præprocessor i C++?
Før din C++-kildekode bliver oversat til maskinkode af compileren, gennemgår den en indledende forberedelsesfase. Denne fase håndteres af præprocessoren. Præprocessorens job er at scanne kildekoden for specifikke instruktioner, kendt som direktiver, der alle starter med et #-tegn. Den udfører simple tekstbaserede operationer baseret på disse direktiver, før den egentlige kompilering går i gang. Det er essentielt en slags avanceret "søg og erstat"-mekanisme, der forbereder koden til compileren. De mest almindelige opgaver for præprocessoren inkluderer:
- Inkludering af header-filer: Når du skriver
#include <filnavn>, finder præprocessoren den angivne fil og indsætter hele dens indhold direkte i din kildekode. - Makro-ekspansion: Ved brug af
#definekan du definere konstanter eller små kodestykker (makroer). Præprocessoren erstatter alle forekomster af makroens navn med dens definerede indhold. - Betinget kompilering: Direktiver som
#if,#ifdef,#else, og#endifgiver dig mulighed for at inkludere eller ekskludere dele af koden fra kompilering baseret på visse betingelser. Dette er yderst nyttigt til at skrive kode, der skal fungere på tværs af forskellige platforme eller i forskellige konfigurationer (f.eks. debug vs. release).
Det er i denne kontekst, specifikt inden for #define-direktivet, at # og ## operatorerne kommer i spil. De er ikke direktiver i sig selv, men operatorer, der fungerer under makro-ekspansionen.
Stringizing-operatoren ('#'): Gør Argumenter til Strenge
Stringizing-operatoren, repræsenteret ved et enkelt '#', er et smart værktøj, der kun kan bruges i definitionen af en makro, der tager argumenter. Dens funktion er enkel, men utrolig nyttig: Den konverterer et makroargument til en streng-literal. Med andre ord, den tager det, du sender til makroen, og sætter automatisk dobbelte anførselstegn omkring det, så det bliver til en C-stil streng.
Syntaks og Eksempel
Syntaksen er ligetil. Du placerer simpelthen # foran en makroparameter i definitionen.

#define MAKRO_NAVN(parameter) #parameter
Lad os se på et praktisk eksempel. Forestil dig, at du vil lave en makro, der udskriver et hvilket som helst stykke tekst som en streng, uden at du selv skal tilføje anførselstegnene.
#include <stdio.h> // Definer en makro, der bruger Stringizing-operatoren #define PRINT_AS_STRING(x) printf(#x "\n") int main() { // Kalder makroen med en sætning PRINT_AS_STRING(Hej Verden, dette er en test!); // Kalder makroen med et tal og en variabel int værdi = 42; PRINT_AS_STRING(Værdien er 42); return 0; } Før kompilering vil præprocessoren transformere main-funktionen til følgende:
int main() { printf("Hej Verden, dette er en test!" "\n"); int værdi = 42; printf("Værdien er 42" "\n"); return 0; } Outputtet fra programmet vil være:
Hej Verden, dette er en test! Værdien er 42
Dette er især nyttigt til debugging. Forestil dig, at du vil udskrive en variabels navn sammen med dens værdi. Uden Stringizing-operatoren ville du skulle skrive navnet manuelt som en streng. Med # kan du automatisere det:
#include <stdio.h> #define PRINT_VAR(var) printf(#var " = %d\n", var) int main() { int score = 95; PRINT_VAR(score); return 0; } Dette vil udskrive: score = 95. Præprocessoren omdanner PRINT_VAR(score); til printf("score" " = %d\n", score);.
Token-Pasting-operatoren ('##'): Sammenføjning af Kodedele
Hvis # er til at skabe strenge, så er ##, kendt som Token-Pasting-operatoren (eller token-sammenføjning), til at bygge kode. Denne operator giver dig mulighed for at "lime" to tokens sammen for at danne et helt nyt, enkelt token under makro-ekspansionen. Et token er den mindste meningsfulde enhed af kode, som compileren forstår, f.eks. et variabelnavn, et tal, et nøgleord eller et symbol.
Syntaks og Eksempel
Du bruger ## mellem to tokens i en makro-definition for at kombinere dem.

#define MAKRO_NAVN(arg1, arg2) arg1##arg2
Et klassisk eksempel er at skabe variabel- eller funktionsnavne dynamisk. Antag, at du har en række variabler, der følger et bestemt navngivningsmønster, som port_1, port_2, osv.
#include <stdio.h> #define CREATE_VAR_NAME(base, num) base##num int main() { int port_1 = 20; int port_2 = 80; // Her bliver CREATE_VAR_NAME(port_, 1) til tokenet 'port_1' printf("Værdien af port_1 er: %d\n", CREATE_VAR_NAME(port_, 1)); // Og her bliver det til 'port_2' printf("Værdien af port_2 er: %d\n", CREATE_VAR_NAME(port_, 2)); return 0; } Præprocessoren vil omdanne kaldene til:
printf("Værdien af port_1 er: %d\n", port_1); printf("Værdien af port_2 er: %d\n", port_2); Kraften i dette ligger i evnen til at generere kode dynamisk. Man kan bruge den til at skabe en serie af variabelnavne, funktionsnavne eller endda til at sammensætte komplekse strukturer baseret på makroparametre. Dette ses ofte i store frameworks og biblioteker for at reducere gentagen kode (boilerplate) og skabe generiske funktioner.
Sammenligning: # vs. ##
For at gøre forskellene helt klare, er her en direkte sammenligning af de to operatorer:
| Egenskab | Stringizing Operator (#) | Token-Pasting Operator (##) |
|---|---|---|
| Symbol | # | ## |
| Funktion | Konverterer et makroargument til en streng-literal. | Sammenføjer to tokens til et enkelt nyt token. |
| Input | Et enkelt makroargument. | To tokens (kan være argumenter eller literaler). |
| Output | En streng (const char*). | Et gyldigt C++ token (f.eks. et variabelnavn, funktionsnavn). |
| Eksempel | #define TO_STR(x) #xTO_STR(hello) → "hello" | #define CONCAT(a,b) a##bCONCAT(var, 1) → var1 |
Ofte Stillede Spørgsmål (FAQ)
Hvad er forskellen på et #-direktiv og #-operatoren?
Selvom de bruger samme symbol, er deres kontekst og funktion helt forskellige. Et # i starten af en linje (f.eks. #include, #define) signalerer et præprocessor-direktiv – en kommando til præprocessoren. #-operatoren, derimod, kan kun bruges inde i definitionen af en #define-makro til at omdanne et argument til en streng.
Kan jeg bruge disse operatorer uden for en #define makro?
Nej. Både # (Stringizing) og ## (Token-Pasting) er specifikt designet til at fungere under makro-ekspansion. At forsøge at bruge dem i almindelig C++-kode vil resultere i en kompileringsfejl.

Hvad sker der, hvis resultatet af ## ikke er et gyldigt token?
Hvis sammenføjningen af to tokens med ## resulterer i noget, der ikke er et gyldigt C++ token (f.eks. 12##abc, som ville blive til 12abc, hvilket ikke er et gyldigt identifikatornavn), vil præprocessoren generere en fejl, og kompileringen vil mislykkes.
Er det god praksis at bruge disse operatorer?
Det afhænger af situationen. I moderne C++ foretrækkes ofte templates og constexpr-funktioner frem for komplekse makroer, da de er type-sikre og lettere at debugge. Dog er der stadig situationer, især inden for systemprogrammering, embedded systemer og store kodebaser, hvor makroer med # og ## er den mest effektive eller eneste måde at opnå en bestemt form for kodegenerering eller debugging-output på.
Konklusion
Præprocessor-operatorerne # (Stringizing) og ## (Token-Pasting) er specialiserede, men kraftfulde værktøjer i C++ programmørens værktøjskasse. De åbner op for et niveau af meta-programmering, der tillader dynamisk kodemanipulation direkte i præprocessor-fasen. Stringizing-operatoren er et uvurderligt redskab til at skabe selv-dokumenterende debug-udskrifter, mens Token-Pasting-operatoren muliggør elegant kodegenerering og reducerer boilerplate-kode. Selvom moderne C++ tilbyder alternativer, er en grundlæggende forståelse af disse klassiske C-funktioner stadig essentiel for enhver seriøs C++ udvikler, der ønsker at forstå, hvordan koden transformeres fra kilde til eksekverbar fil.
Hvis du vil læse andre artikler, der ligner # og ## i C++: Fra Direktiv til Operator, kan du besøge kategorien Sundhed.
