Condivisione della tecnologia

[Linguaggio C] - Spiegazione dettagliata della preelaborazione (Parte 2)

2024-07-12

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

Prefazione

nell'ultimo numero[Linguaggio C] - Spiegazione dettagliata della preelaborazione (Parte 2) Durante lo studio, abbiamo introdotto in dettaglio la conoscenza rilevante delle macro nella preelaborazione. Credo che tutti ne trarranno vantaggio. Non preoccuparti, continuiamo ad apprendere altre conoscenze sulla preelaborazione in questo numero.

7. # e ##

7.1 # Operatore

  • # 运算符 Converte un argomento di una macro in una stringa letterale.È consentito apparire nell'elenco di sostituzione delle macro con parametri.
  • # 运算符L'operazione effettuata può essere intesa come “stringificazione”

Che cosa significa?
Facciamo prima una premessa:

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

Qual è la differenza tra le due righe di codice precedenti? Diamo un'occhiata insieme:

Inserisci qui la descrizione dell'immagine

Come puoi vedere, l'effetto di due corde e di una corda è lo stesso.C语言会把两个字符串天然连成一个字符串, aggiungere uno spazio al centro è inutile.

Ora c'è una scena come questa:

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

Abbiamo scoperto che la logica delle tre righe di codice è molto相像Sì, ma ci sono些许不同
  
Quindi abbiamo pensato, dato che sono così simili, potremmo metterli封装成一个函数, per facilità d'uso?
Ma le funzioni non possono svolgere questa funzione.
allora cosa dovremmo fare?
Possiamo provare a utilizzare le macro per risolverlo

#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

  
risultato dell'operazione:

Inserisci qui la descrizione dell'immagine

scopriamo non-negligenzaN Non è cambiato, quindi come dovrei modificarlo?
Qui è dove dovrebbe essere utilizzato il nostro operatore #:# Converte un argomento di una macro instringa letterale,Proprio adesso non-negligenzaN diventare “n” “n”N

In questo momento, possiamo usare il metodo di giunzione per diventare

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

  
non lo so?Lo capirai dopo aver letto la spiegazione qui sotto.
Inserisci qui la descrizione dell'immagine

  

7.2 ## operatore

  ## può essere localizzato in luiI simboli su entrambi i lati sono combinati in un unico simbolo, che consente la definizione di macro从分离的文本片段创建标识符## è chiamatosegnare l'incollaggio .Tale connessione deve produrre a合法indicatore, altrimenti il ​​risultato non è definito.
  
Abbiamo detto prima che per scrivere una funzione che trova il valore più grande tra due numeri, è necessario scrivere funzioni diverse per tipi di dati diversi.

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

  

Inserisci qui la descrizione dell'immagine

Questo è inevitabilmente troppo macchinoso e ci sono molte somiglianze tra le due funzioni.
C'è un modo per creare rapidamente una funzione del genere?come una funzioneMuffaLo stesso, basta applicare una funzione e verrà fuori
  
Possiamo scriverne uno così

#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

  
risultato dell'operazione:

Inserisci qui la descrizione dell'immagine

  
Possiamo anche gcc gccGc.c. Osservare i risultati preelaborati nell'ambiente.i file, avere una comprensione più intuitiva

Inserisci qui la descrizione dell'immagine

Naturalmente, anche la funzione generata in questo modo è scomoda da eseguire il debug.

Poi qui ## Che ruolo gioca?

aggiungere ##, il compilatore penserà che siano un simbolo
Diamo un'occhiata a Kanga## Effetto:

Inserisci qui la descrizione dell'immagine

  

8. Convenzione di denominazione

In generale, la sintassi per l'utilizzo di funzioni e macro è molto simile, quindi语言本身没法帮我们区分二者
  
Una delle nostre abitudini abituali è:

  • Assegna un nome alla macrotutto maiuscolo
  • Nome della funzioneNon utilizzare tutti i tappi

Naturalmente, queste regole di denominazione non sono assolute
Per esempio compensare compensarelloeeff..seT Questa macro è scritta tutta in minuscolo
  
Nota compensare compensarelloeeff..seT Viene utilizzato per calcolare l'offset degli elementi della struttura rispetto alla posizione iniziale della struttura.
  

Nove,# indefinito indefinitoioNDeF

  # indefinito indefinitoioNDeF Le istruzioni servono aRimuovere una definizione di macro

Inserisci qui la descrizione dell'immagine
Il codice sopra viene utilizzato sulla riga 169# indefinito indefinitoioNDeF Macro rimossa MAX.Non si verificano problemi quando si chiamano le linee 168 prima della rimozione, ma verrà segnalato un errore quando si chiamano le linee 170 dopo la rimozione.

  

10. Definizione della riga di comando

Molti compilatori C (escluso VS) forniscono la possibilità di definire simboli sulla riga di comando.Utilizzato per avviare il processo di compilazione
  
  Per esempio : Questa funzionalità è utile quando vogliamo compilare diverse versioni di un programma basate sullo stesso file sorgente. (Supponiamo che in un programma venga dichiarato un array di una certa lunghezza. Se la memoria della macchina è limitata, abbiamo bisogno di un array molto piccolo, ma se la memoria di un'altra macchina è più grande, l'array di cui abbiamo bisogno può essere più grande)

Inserisci qui la descrizione dell'immagine

Inserisci qui la descrizione dell'immagine

La definizione della riga di comando è presentefase di preelaborazioneElaborato, in fase di preelaborazione, nel codice sopra taglia tagliataglia Il valore di è stato determinato

  

11. Compilazione condizionale

Quando si compila un programma, è molto difficile compilarne o abbandonarne uno (un gruppo di istruzioni).方便 Di.perché possiamo usaredirettiva di compilazione condizionale
  
L'istruzione di compilazione condizionale è questo codice我想让你编译就编译,不想让你编译你就不要编译了 . Possiamo impostare una condizione per lui. Se la condizione è vera, questo codice verrà compilato. Se la condizione è falsa, questo codice non verrà compilato.
  
Per esempio:
Sarebbe un peccato eliminare parte del codice di debug, ma sarebbe di ostacolo mantenerlo, in modo da poterlo compilare selettivamente.
  

Inserisci qui la descrizione dell'immagine

  
Istruzioni di compilazione condizionale comunemente usate:

11.1. Compilazione condizionale a ramo singolo

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

Inserisci qui la descrizione dell'immagine

  

11.2. Compilazione condizionale multiramo

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

Quale affermazione è vera, quell'affermazione viene eseguita

#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 Determinare se è definito

#if defined(symbol)
#ifdef symbol

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

Inserisci qui la descrizione dell'immagine

  

11.4. Istruzioni nidificate

#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. Inclusione di file di intestazione

12.1 Come sono inclusi i file header

(1) Come vengono inclusi i file locali

# include "filename"
  • 1

Strategia di ricerca: primo a entrareLa directory del progetto in cui si trovano i file di origineSe il file di intestazione non viene trovato, il compilatore cercherà il file di intestazione della funzione di libreria allo stesso modo.Ricerca della posizione standardfile di testa
Se non riesci a trovarlo di nuovoErrore di compilazione
  
Linux LinuxLInuso Il percorso del file di intestazione standard dell'ambiente (dove è posizionato il file di intestazione):

 /usr/include
  • 1

Percorso del file di intestazione standard per l'ambiente VS:

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

  

(2) Il file della libreria contiene

#include <filename.h>
  • 1

Trova il file di intestazione e vai direttamente apercorso standardVai e cerca e, se non riesci a trovarlo, riceverai un messaggio.Errore di compilazione
  
Ciò significa che può essere utilizzato anche per i file di libreria?“ ” Il modulo contiene
la risposta èaffermSì, ma la ricerca viene effettuata in questo modomeno efficiente, ovviamente è anche cosìnon facile da distinguereÈ un file di libreria o un file locale?
  

12.2 Inclusione di file nidificati

Dopo aver studiato i precedenti (compilazione e collegamento), sappiamo che l'inclusione dei file header nella fase di preelaborazione è直接将该文件的代码拷贝到包含头文件的地方
  
Se un file di intestazione viene incluso 10 volte, verrà effettivamente compilato 10 volte. Se viene incluso ripetutamente, la pressione sulla compilazione sarà maggiore.

prova . c prova.cTèT.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

prova . h prova.hTèT.H

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

Ma in un progetto, un file verrà inevitabilmente incluso più volte, quindi come risolvere questo problema?
Risposta:compilazione condizionale

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

  
Come capirlo?

  • Quando si include il file di intestazione per la prima volta, è necessario compilarlo?giudicare prima
  • Il simbolo __TEST_H__ non lo ènon definitoVolerecompilare
  • Subito dopoDefinire il simbolo __TEST_H__
  • Quindi includi nuovamente il file di intestazione e trova __TEST_H__è stata definitanon piùPer il file di intestazione incluso in seguito, eseguirecompilare

Tuttavia, il modo di scrivere sopra è più problematico. Esiste un altro modo di scrivere:

#pragma once
  • 1

L'effetto è lo stesso del metodo sopra
Ciò evita la ripetuta introduzione di file di intestazione
  

13. Altre istruzioni di preelaborazione

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

Gli amici interessati possono leggere "Anatomia approfondita del linguaggio C"
  
  
  
  
  


   Ok, questa è tutta la conoscenza sulla preelaborazione in questo numero. Spero che questo blog possa esserti utile. Allo stesso tempo, per favore correggimi se ci sono errori e facciamo progressi insieme sulla strada dell'apprendimento del linguaggio C!