Compartilhamento de tecnologia

[Linguagem C] - Explicação detalhada do pré-processamento (Parte 2)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Prefácio

na última edição[Linguagem C] - Explicação detalhada do pré-processamento (Parte 2) Durante o estudo, apresentamos detalhadamente o conhecimento relevante de macros no pré-processamento. Acredito que todos ganharão muito. Não se preocupe, vamos continuar aprendendo outros conhecimentos sobre pré-processamento nesta edição.

7. # e ##

7.1 # Operador

  • # 运算符 Converte um argumento de uma macro em uma string literal.É permitido aparecer na lista de substituição de macros por parâmetros.
  • # 运算符A operação realizada pode ser entendida como “stringificação”

O que isso significa?
Vamos fazer um prenúncio primeiro:

int mian()
{
	printf("hello"   "worldn");
	printf("helloworldn");
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Qual é a diferença entre as duas linhas de código acima? Vamos dar uma olhada juntos:

Insira a descrição da imagem aqui

Como você pode ver, o efeito de duas cordas e uma corda é o mesmo.C语言会把两个字符串天然连成一个字符串, adicionar um espaço no meio é inútil.

Agora há uma cena como esta:

int main()
{
	int a = 1;
	printf("The value of a is %dn", a);

	int b = 20;
	printf("The value of b is %dn", b);

	float f = 8.5f;
	printf("The value of f is %fn", f);

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Descobrimos que a lógica das três linhas de código é muito相像Sim, mas existem些许不同
  
Então pensamos, já que eles são tão parecidos, poderíamos colocá-los封装成一个函数, para facilidade de uso?
Mas as funções não podem realizar esta função.
então o que devemos fazer?
Podemos tentar usar macros para resolver isso

#define Print(n, format) printf("The value of n is " format "n", n)

int main()
{
	int a = 1;
	Print(a, "%d");
	//printf("The value of a is %dn", a);

	int b = 20;
	Print(b, "%d");
	//printf("The value of b is %dn", b);

	float f = 8.5f;
	Print(f, "%f");
	//printf("The value of f is %fn", f);

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

  
resultado da operação:

Insira a descrição da imagem aqui

nos descobrimos nãonãoãoãoãoãoãoão Não mudou, então como devo modificá-lo?
É aqui que nosso operador # deve ser usado:# Converte um argumento de uma macro emstring literal,Agora mesmo nãonãoãoãoãoãoãoão tornar-se “ n ” “ n”nãoãoãoãoãoãoão

Neste momento, podemos usar o método de emenda para nos tornarmos

#define Print(n, format) printf("The value of " #n " is " format "n", n)
  • 1

  
não sabe?Você entenderá depois de ler a explicação abaixo.
Insira a descrição da imagem aqui

  

7.2 ## operador

  ## pode estar localizado neleOs símbolos em ambos os lados são combinados em um símbolo, que permite a definição de macro从分离的文本片段创建标识符## é chamadocolagem de marcas .Tal conexão deve produzir um合法indicador, caso contrário o resultado será indefinido.
  
Já dissemos que para escrever uma função que encontre o valor maior de dois números, precisamos escrever funções diferentes para tipos de dados diferentes.

int int_max(int x, int y)
{
	return x > y ? x : y;
}
float float_max(float x, float y)
{
	return x > y ? x : y;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  

Insira a descrição da imagem aqui

Isto é inevitavelmente muito complicado e há muitas semelhanças entre as duas funções.
Existe alguma maneira de criar rapidamente essa função?como uma funçãoMofoMesmo, basta aplicar uma função e ela sairá
  
Podemos escrever um assim

#define GENERIC_MAX(type) 
		type type##_max(type x, type y)
		{
			return x > y ? x : y;
		}
  • 1
  • 2
  • 3
  • 4
  • 5

  

#define GENERIC_MAX(type) 
		type type##_max(type x, type y)
		{
			return x > y ? x : y;
		}

GENERIC_MAX(int);    //相当于定义了一个函数int_max
GENERIC_MAX(float);  //相当于定义了一个函数float_max

int main()
{
	int r1 = int_max(3, 5);
	printf("%dn", r1);
	float r2 = float_max(2.3f, 7.6f);
	printf("%fn", r2);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  
resultado da operação:

Insira a descrição da imagem aqui

  
Nós também podemos ccg ccggcc Observe os resultados pré-processados ​​no ambiente.i arquivo, tenha uma compreensão mais intuitiva de

Insira a descrição da imagem aqui

Claro, a função gerada desta forma também é inconveniente para depurar.

Então aqui ## Que papel isso desempenha?

adicionar ##, o compilador pensará que eles são um símbolo
Vamos dar uma olhada em Kanga## Efeito:

Insira a descrição da imagem aqui

  

8. Convenção de Nomenclatura

De modo geral, a sintaxe para usar funções e macros é muito semelhante, então语言本身没法帮我们区分二者
  
Um dos nossos hábitos habituais é:

  • Nomeie a macrotudo em maiúsculas
  • Nome da funçãoNão use todas as letras maiúsculas

Claro, essas regras de nomenclatura não são absolutas
por exemplo deslocamento deslocamentooffsepara Esta macro está escrita em letras minúsculas
  
Observação deslocamento deslocamentooffsepara É utilizado para calcular o deslocamento dos membros da estrutura em relação à posição inicial da estrutura.
  

Nove,# indefinido indefinidovocênãoãoãoãoãoãoãoeee

  # indefinido indefinidovocênãoãoãoãoãoãoãoeee As instruções são usadas paraRemover uma definição de macro

Insira a descrição da imagem aqui
O código acima é usado na linha 169# indefinido indefinidovocênãoãoãoãoãoãoãoeee Macro removida MAX.Não há problema ao chamar as 168 linhas antes da remoção, mas um erro será relatado ao chamar as 170 linhas após a remoção.

  

10. Definição de linha de comando

Muitos compiladores C (não incluindo VS) oferecem a capacidade de definir símbolos na linha de comando.Usado para iniciar o processo de compilação
  
  Por exemplo : Este recurso é útil quando queremos compilar diferentes versões de um programa baseado no mesmo arquivo fonte. (Suponha que um array de um determinado comprimento seja declarado em um programa. Se a memória da máquina for limitada, precisaremos de um array muito pequeno, mas se a memória de outra máquina for maior, o array que precisamos pode ser maior)

Insira a descrição da imagem aqui

Insira a descrição da imagem aqui

A definição da linha de comando está emestágio de pré-processamentoProcessado, na fase de pré-processamento, no código acima tamanho tamanhotamanho O valor de foi determinado

  

11. Compilação condicional

Ao compilar um programa, é muito difícil compilar ou abandonar um (um grupo de instruções).方便 de.porque podemos usardiretiva de compilação condicional
  
A instrução de compilação condicional é este código我想让你编译就编译,不想让你编译你就不要编译了 . Podemos definir uma condição para ele. Se a condição for verdadeira, este código participará da compilação. Se a condição for falsa, este código não será compilado.
  
Por exemplo:
Seria uma pena deletar algum código de depuração, mas seria um impedimento para mantê-lo, para que possamos compilá-lo seletivamente.
  

Insira a descrição da imagem aqui

  
Instruções de compilação condicional comumente usadas:

11.1. Compilação condicional de ramo único

#if 常量表达式
    //···
#endif
  • 1
  • 2
  • 3

Insira a descrição da imagem aqui

  

11.2. Compilação condicional multi-ramificação

#if  常量表达式
	//···
#elif  常量表达式
	//···
#else
	//···
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Qual afirmação é verdadeira, essa afirmação é executada

#define M 1
int main()
{
#if M == 0
	printf("hellon");
#elif M == 1
	printf("worldn");
#elif M == 2
	printf("csdnn");
#endif
	printf("886n");

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

  

11.3. Determine se está definido.

#if defined(symbol)
#ifdef symbol

//上面两个的反面
if !defined(symbol)
#ifndef symbol
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Insira a descrição da imagem aqui

  

11.4. Instruções aninhadas

#if defined(OS_UNIX)

	#ifdef OPTION1
		unix_version_option1();
	#endif
	
	#ifdef OPTION2
		unix_version_option2();
	#endif
	
#elif defined(OS_MSDOS)

	#ifdef OPTION2
		msdos_version_option2();
	#endif
	
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  

12. Inclusão de arquivos de cabeçalho

12.1 Como os arquivos de cabeçalho são incluídos

(1) Como os arquivos locais são incluídos

# include "filename"
  • 1

Estratégia de pesquisa: primeiro emO diretório do projeto onde os arquivos de origem estão localizadosSe o arquivo de cabeçalho não for encontrado, o compilador procurará o arquivo de cabeçalho da função da biblioteca da mesma maneira.Pesquisa de localização padrãoarquivo principal
Se você não conseguir encontrá-lo novamenteErro de compilação
  
Linux Linuxeuemux O caminho do arquivo de cabeçalho padrão do ambiente (onde o arquivo de cabeçalho é colocado):

 /usr/include
  • 1

Caminho do arquivo de cabeçalho padrão para ambiente VS:

C:Program Files (x86)Microsoft Visual Studio 12.0VCinclude
//这是VS2013的默认路径
  • 1
  • 2

  

(2) O arquivo da biblioteca contém

#include <filename.h>
  • 1

Encontre o arquivo de cabeçalho e vá diretamente paracaminho padrãoVá e pesquise e, se não conseguir encontrar, você receberá um aviso.Erro de compilação
  
Isso significa que também pode ser usado para arquivos de biblioteca?“ ” O formulário contém
a resposta éafirmarSim, mas a pesquisa é feita desta formamenos eficiente, claro que este também é o casonão é fácil distinguirÉ um arquivo de biblioteca ou um arquivo local?
  

12.2 Inclusão de arquivos aninhados

Depois de estudar os anteriores (compilação e vinculação), sabemos que a inclusão de arquivos de cabeçalho na etapa de pré-processamento é直接将该文件的代码拷贝到包含头文件的地方
  
Se um arquivo de cabeçalho for incluído 10 vezes, na verdade será compilado 10 vezes. Se for incluído repetidamente, a pressão na compilação será maior.

teste .c teste.cparaépara.c

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

teste .h teste.hparaépara.o

void test();
struct Stu
{
int id;
char name[20];
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Mas em um projeto, um arquivo será inevitavelmente incluído várias vezes, então como resolver esse problema?
Responder:compilação condicional

#ifndef __TEST_H__
#define __TEST_H__
		//头文件的内容
#endif
  • 1
  • 2
  • 3
  • 4

  
Como entender isso?

  • Ao incluir o arquivo de cabeçalho pela primeira vez, ele deve ser compilado?julgar primeiro
  • O símbolo __TEST_H__ não énão definidoquerercompilar
  • Imediatamente depoisDefinir o símbolo __TEST_H__
  • Em seguida, inclua o arquivo de cabeçalho novamente e encontre __TEST_H__foi definidonão maisPara o arquivo de cabeçalho incluído posteriormente, executecompilar

No entanto, a forma de escrever acima é mais problemática. Existe outra forma de escrever:

#pragma once
  • 1

O efeito é o mesmo do método acima
Isso evita a introdução repetida de arquivos de cabeçalho
  

13. Outras instruções de pré-processamento

#error
#pragma
#line
···
#pragma pack()//在结构体部分介绍
  • 1
  • 2
  • 3
  • 4
  • 5

Amigos interessados ​​podem ler "Anatomia aprofundada da linguagem C"
  
  
  
  
  


   Ok, esse é todo o conhecimento sobre pré-processamento nesta edição. Espero que este blog possa ser útil para você. Ao mesmo tempo, corrija-me se houver algum erro e vamos progredir juntos no caminho do aprendizado da linguagem C!