Compartir tecnología

[Lenguaje C] - Explicación detallada del preprocesamiento (Parte 2)

2024-07-12

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

Prefacio

en el último número[Lenguaje C] - Explicación detallada del preprocesamiento (Parte 2) Durante el estudio, presentamos en detalle el conocimiento relevante de las macros en el preprocesamiento. Creo que todos ganarán mucho. No se preocupe, sigamos aprendiendo otros conocimientos sobre el preprocesamiento en este número.

7. # y ##

7.1 # Operador

  • # 运算符 Convierte un argumento de una macro en una cadena literal.Se permite aparecer en la lista de sustitución de macros con parámetros.
  • # 运算符La operación realizada puede entenderse como "stringificación".

¿Qué significa eso?
Primero hagamos un presagio:

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

¿Cuál es la diferencia entre las dos líneas de código anteriores? Echemos un vistazo juntos:

Insertar descripción de la imagen aquí

Como puede ver, el efecto de dos cuerdas y una cuerda es el mismo.C语言会把两个字符串天然连成一个字符串, agregar un espacio en el medio es inútil.

Ahora hay una escena 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

Descubrimos que la lógica de las tres líneas de código es muy相像Si, pero hay些许不同
  
Entonces pensamos que, dado que son tan similares, ¿podríamos ponerlos?封装成一个函数, para facilitar su uso?
Pero las funciones no pueden realizar esta función.
entonces ¿qué debemos hacer?
Podemos intentar usar macros para solucionarlo.

#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 de la operación:

Insertar descripción de la imagen aquí

descubrimos nnnorteorteorteorteorteorteorte No ha cambiado, entonces, ¿cómo debo modificarlo?
Aquí es donde se debe utilizar nuestro operador #:# Convierte un argumento de una macro enliteral de cadena,Ahora mismo nnnorteorteorteorteorteorteorte convertirse “ n ” “n”norteorteorteorteorteorteorte

En este momento, podemos usar el método de empalme para convertirnos

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

  
no lo sabes?Lo entenderás después de leer la explicación a continuación.
Insertar descripción de la imagen aquí

  

7.2 ## operador

  ## se puede ubicar en élLos símbolos de ambos lados se combinan en un solo símbolo., que permite la definición de macros.从分离的文本片段创建标识符## se llamamarcar pegado .Tal conexión debe producir una合法indicador; de lo contrario, el resultado no estará definido.
  
Hemos dicho antes que para escribir una función que encuentre el valor mayor de dos números, es necesario escribir diferentes funciones para diferentes tipos de datos.

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

  

Insertar descripción de la imagen aquí

Esto es inevitablemente demasiado engorroso y existen muchas similitudes entre las dos funciones.
¿Hay alguna forma de crear rápidamente dicha función?como una funciónMohoIgual, solo aplica una función y saldrá
  
Podemos escribir uno como este.

#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 de la operación:

Insertar descripción de la imagen aquí

  
También podemos CCG CCGgramoC.C. Observar los resultados preprocesados ​​en el entorno..i archivo, tener una comprensión más intuitiva de

Insertar descripción de la imagen aquí

Por supuesto, la función generada de esta manera también es incómoda de depurar.

Entonces aquí ## ¿Qué papel juega?

agregar ##, el compilador pensará que son un símbolo
Echemos un vistazo a Kanga.## Efecto:

Insertar descripción de la imagen aquí

  

8. Convención de nomenclatura

En términos generales, la sintaxis para usar funciones y macros es muy similar, por lo que语言本身没法帮我们区分二者
  
Uno de nuestros hábitos habituales es:

  • Nombra la macrotodas las mayúsculas
  • Nombre de la funciónNo utilices todas las mayúsculas

Por supuesto, estas reglas de nomenclatura no son absolutas.
Por ejemplo compensar compensaroadja Esta macro está escrita en minúsculas.
  
Nota compensar compensaroadja Se utiliza para calcular el desplazamiento de los miembros de la estructura con respecto a la posición inicial de la estructura.
  

Nueve,# indefinido indefinidonorteorteorteorteorteorteortedmiF

  # indefinido indefinidonorteorteorteorteorteorteortedmiF Las instrucciones se utilizan paraEliminar una definición de macro

Insertar descripción de la imagen aquí
El código anterior se utiliza en la línea 169.# indefinido indefinidonorteorteorteorteorteorteortedmiF Macro eliminada MAX.No hay ningún problema al llamar a las líneas 168 antes de la eliminación, pero se informará un error al llamar a las líneas 170 después de la eliminación.

  

10. Definición de la línea de comando

Muchos compiladores de C (sin incluir VS) brindan la capacidad de definir símbolos en la línea de comando.Se utiliza para iniciar el proceso de compilación.
  
  Por ejemplo : Esta característica es útil cuando queremos compilar diferentes versiones de un programa basadas en el mismo archivo fuente. (Supongamos que se declara una matriz de cierta longitud en un programa. Si la memoria de la máquina es limitada, necesitamos una matriz muy pequeña, pero si la memoria de otra máquina es más grande, la matriz que necesitamos puede ser más grande)

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí

La definición de la línea de comando está enetapa de preprocesamientoProcesado, en la etapa de preprocesamiento, en el código anterior tamaño tamañotal Se ha determinado el valor de

  

11. Compilación condicional

Al compilar un programa, es muy difícil compilar o abandonar uno (un grupo de declaraciones).方便 de.porque podemos usardirectiva de compilación condicional
  
La instrucción de compilación condicional es este código.我想让你编译就编译,不想让你编译你就不要编译了 . Podemos establecer una condición para él. Si la condición es verdadera, este código se compilará. Si la condición es falsa, este código no se compilará.
  
Por ejemplo:
Sería una lástima eliminar algún código de depuración, pero sería un obstáculo para conservarlo, de modo que podamos compilarlo selectivamente.
  

Insertar descripción de la imagen aquí

  
Instrucciones de compilación condicional de uso común:

11.1. Compilación condicional de una sola rama.

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

Insertar descripción de la imagen aquí

  

11.2. Compilación condicional de múltiples ramas.

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

Cual enunciado es verdadero, ese enunciado se ejecuta

#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. Determinar si está definido

#if defined(symbol)
#ifdef symbol

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

Insertar descripción de la imagen aquí

  

11.4. Instrucciones anidadas

#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. Inclusión de archivos de cabecera

12.1 Cómo se incluyen los archivos de encabezado

(1) Cómo se incluyen los archivos locales

# include "filename"
  • 1

Estrategia de búsqueda: primero enEl directorio del proyecto donde se encuentran los archivos fuente.Si no se encuentra el archivo de encabezado, el compilador buscará el archivo de encabezado de la función de biblioteca de la misma manera.Búsqueda de ubicación estándararchivo principal
Si no puedes encontrarlo de nuevoError de compilación
  
Linux Linuxyoenux La ruta del archivo de encabezado estándar del entorno (donde se coloca el archivo de encabezado):

 /usr/include
  • 1

Ruta de archivo de encabezado estándar para el entorno VS:

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

  

(2) El archivo de la biblioteca contiene

#include <filename.h>
  • 1

Busque el archivo de encabezado y vaya directamente aruta estándarVaya y busque, y si no puede encontrarlo, recibirá un mensaje.Error de compilación
  
¿Significa esto que también se puede utilizar para archivos de biblioteca?“ ” El formulario contiene
la respuesta esafirmarSí, pero la búsqueda se hace de esta manera.menos eficientePor supuesto, este también es el caso.no es fácil de distinguir¿Es un archivo de biblioteca o un archivo local?
  

12.2 Inclusión de archivos anidados

Después de estudiar lo anterior (compilación y vinculación), sabemos que el archivo de encabezado se incluye en la etapa de preprocesamiento.直接将该文件的代码拷贝到包含头文件的地方
  
Si un archivo de encabezado se incluye 10 veces, en realidad se compila 10 veces. Si se incluye repetidamente, la presión sobre la compilación será mayor.

prueba . c prueba.caesa.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

prueba . h prueba.haesa.yo

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

Pero en un proyecto, un archivo inevitablemente se incluirá varias veces, entonces, ¿cómo resolver este problema?
Respuesta:compilación condicional

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

  
¿Cómo entenderlo?

  • Al incluir el archivo de encabezado por primera vez, ¿debería compilarse?juez primero
  • El símbolo __TEST_H__ no esno definidadesearcompilar
  • Inmediatamente despuésDefinir el símbolo __TEST_H__
  • Luego incluya el archivo de encabezado nuevamente y busque __TEST_H__ha sido definidono másPara el archivo de encabezado incluido más adelante, realicecompilar

Sin embargo, la forma de escribir anterior es más problemática. Hay otra forma de escribir:

#pragma once
  • 1

El efecto es el mismo que el método anterior.
Esto evita la introducción repetida de archivos de encabezado.
  

13. Otras instrucciones de preprocesamiento

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

Amigos interesados ​​pueden leer "Anatomía en profundidad del lenguaje C"
  
  
  
  
  


   Bien, ese es todo el conocimiento sobre el preprocesamiento en este número. Espero que este blog pueda resultarle útil. Al mismo tiempo, corríjame si hay algún error y ¡progresemos juntos en el camino del aprendizaje del lenguaje C!