Ir para o conteúdo

Última atualização:2026-10-12

Macros e compilação condicional em C++

Sobre o autor:

Conta oficial do WeChat:

wechat

1.Símbolos especiais em macros

Dividem-se em vários tipos: #, ##, \

1.1 Operador de stringificação (#)

Ao usar um # antes de um parâmetro em uma macro, o pré-processador converte esse parâmetro em um array de caracteres; em outras palavras: # significa "transformar em string"; o # que aparece na definição de uma macro converte o parâmetro que o segue em uma string.

Nota: ele só pode ser usado em macros com parâmetros de entrada e deve ser colocado antes do nome do parâmetro no corpo da definição da macro.

Por exemplo:

#define exp(s) printf("test s is:%s\n",s)
#define exp1(s) printf("test s is:%s\n",#s)
#define exp2(s) #s 
int main() {
    exp("hello");
    exp1(hello);

    string str = exp2(   bac );
    cout<<str<<" "<<str.size()<<endl;
    /**
     * 忽略传入参数名前面和后面的空格。
     */
    string str1 = exp2( asda  bac );
    /**
     * 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
     * 用每个子字符串之间以一个空格连接,忽略剩余空格。
     */
    cout<<str1<<" "<<str1.size()<<endl;
    return 0;
}

O código acima mostra o uso básico e as regras de tratamento de espaços; as regras de tratamento de espaços são as seguintes:

  • Espaços antes e depois do nome do parâmetro de entrada são ignorados.
string str = exp2(   bac );
cout<<str<<" "<<str.size()<<endl;

Saída:

bac 3
  • Quando há espaços entre os nomes dos parâmetros de entrada, o compilador conecta automaticamente as subcadeias, unindo-as com um único espaço entre cada uma e ignorando os espaços excedentes.
string str1 = exp2( asda  bac );
cout<<str1<<" "<<str1.size()<<endl;

Saída:

asda bac 8

1.2 Operador de concatenação de símbolos (##)

O "##" é uma forma de conexão por separação: sua função é primeiro separar e depois forçar a conexão, transformando múltiplos parâmetros formais da definição da macro em um único nome de parâmetro real.

Pontos de atenção:

(1) Ao conectar parâmetros formais com ##, os espaços antes e depois do ## são opcionais.

(2) O nome de parâmetro real resultante da conexão deve ser um nome de parâmetro realmente existente ou uma definição de macro já conhecida pelo compilador.

(3) Se o parâmetro após o ## for ele próprio uma macro, o ## impede a expansão dessa macro.

Exemplo:

#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s)  //gc_s必须存在
// 注意事项2
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_  ##  s)  //gc_s必须存在
// 注意事项1
#define gc_hello1 "I am gc_hello1"
int main() {
    // 注意事项1
    const char * gc_hello = "I am gc_hello";
    expA(hello);
    expB(hello1);
}

1.3 Operador de continuação de linha ()

Quando a macro definida não cabe completa em uma linha, pode-se usar "\" para indicar que a definição da macro continua na linha seguinte.

Atenção para deixar um espaço antes do .

#define MAX(a,b) ((a)>(b) ? (a) \
   :(b))  
int main() {
    int max_val = MAX(3,6);
    cout<<max_val<<endl;
}

O código acima está em: sig_examp.cpp

2.Uso de do{...}while(0)

2.1 Evitar distorção semântica

Por exemplo:

#define fun() f1();f2();
if(a>0)
    fun()

Expandida, essa macro se torna:

if(a>0)
    f1();
    f2();

A intenção era executar f1 e f2 quando a>0, mas, na prática, f2 é executado sempre — eis o erro.

Para resolver esse tipo de problema, ao escrever código, geralmente pode-se usar o bloco {}.

Assim:

#define fun() {f1();f2();}
if(a>0)
    fun();
// 宏展开
if(a>0)
{
    f1();
    f2();
};

Mas note que, após a expansão da macro acima, sobra um ponto e vírgula, e a sintaxe real não fica muito correta. (Embora compile e execute sem problemas; o normal é não ter o ponto e vírgula.)

2.2Evitar o uso de goto no fluxo de controle

Em algumas funções, talvez precisemos fazer algum trabalho de limpeza antes do return, como liberar o espaço de memória alocado com malloc no início da função; usar goto sempre foi um método simples:

int f() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
#ifndef DEBUG
    int error=1;
#endif
    if(error)
        goto END;
    // dosomething
END:
    cout<<"free"<<endl;
    free(p);
    return 0;
}

Mas, como o goto não segue a estruturação da engenharia de software e pode tornar o código difícil de entender, muitas pessoas desaconselham seu uso; nesse caso, podemos usar do{...}while(0) para fazer a mesma coisa:

int ff() {
    int *p = (int *)malloc(sizeof(int));
    *p = 10; 
    cout<<*p<<endl;
    do{ 
#ifndef DEBUG
        int error=1;
#endif
        if(error)
            break;
        //dosomething
    }while(0);
    cout<<"free"<<endl;
    free(p);
    return 0;
}

Aqui, a parte principal da função é envolvida por do{...}while(0), usando break no lugar de goto, com o trabalho de limpeza depois do while: obtém-se o mesmo efeito, e a legibilidade e a manutenibilidade do código ficam muito melhores que as do código com goto acima.

2.3 Evitar avisos causados por macros

No kernel, devido a limitações das diferentes arquiteturas, macros vazias são usadas com frequência. Na compilação, essas macros vazias geram warnings; para evitá-los, podemos usar do{...}while(0) para definir macros vazias:

#define EMPTYMICRO do{}while(0)

2.4 Definir um único bloco de função para realizar operações complexas

Se você tem uma função complexa, com muitas variáveis, e não quer criar uma função nova, pode usar do{...}while(0), escrevendo o seu código dentro dele, onde é possível definir variáveis sem se preocupar com conflitos de nome com variáveis de antes ou de depois na função. Esse caso refere-se a usar a mesma variável em vários pontos (com significados diferentes em cada um); podemos restringir o escopo em cada do-while, por exemplo:

int fc()
{
    int k1 = 10;
    cout<<k1<<endl;
    do{
        int k1 = 100;
        cout<<k1<<endl;
    }while(0);
    cout<<k1<<endl;
}

O código acima está em: do_while.cpp

Artigo de estudo: https://www.cnblogs.com/lizhenghn/p/3674430.html

Tópicos relacionados

Comentários