Technologieaustausch

[C-Sprache] - Detaillierte Erläuterung der Vorverarbeitung (Teil 2)

2024-07-12

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

Vorwort

in der letzten Ausgabe[C-Sprache] - Detaillierte Erläuterung der Vorverarbeitung (Teil 2) Während des Studiums haben wir das relevante Wissen über Makros in der Vorverarbeitung ausführlich vorgestellt. Ich glaube, dass jeder viel gewinnen wird. Machen Sie sich keine Sorgen, wir lernen in dieser Ausgabe weiterhin weiteres Wissen über die Vorverarbeitung.

7. # und ##

7.1 # Operator

  • # 运算符 Konvertiert ein Argument eines Makros in ein String-Literal.Es darf in der Ersetzungsliste von Makros mit Parametern erscheinen.
  • # 运算符Die durchgeführte Operation kann als „Stringifizierung“ verstanden werden.

Was bedeutet das?
Machen wir zunächst eine Vorahnung:

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

Was ist der Unterschied zwischen den beiden oben genannten Codezeilen? Werfen wir gemeinsam einen Blick darauf:

Fügen Sie hier eine Bildbeschreibung ein

Wie Sie sehen, ist die Wirkung von zwei Saiten und einer Saite gleich.C语言会把两个字符串天然连成一个字符串, ist das Hinzufügen eines Leerzeichens in der Mitte nutzlos.

Jetzt gibt es eine Szene wie diese:

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

Wir haben festgestellt, dass die Logik der drei Codezeilen sehr gut ist相像Ja, aber es gibt sie些许不同
  
Da sie sich so ähnlich sind, dachten wir uns, könnten wir sie doch so nennen?封装成一个函数, aus Gründen der Benutzerfreundlichkeit?
Aber Funktionen können diese Funktion nicht ausführen.
Was sollen wir dann tun?
Wir können versuchen, Makros zu verwenden, um es zu lösen

#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

  
Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

wir entdecken neinN Es hat sich nicht geändert. Wie soll ich es also ändern?
Hier sollte unser #-Operator verwendet werden:# Konvertiert ein Argument eines Makros inString-Literal,Im Augenblick neinN werden „ n “ „n“N

Zu diesem Zeitpunkt können wir die Spleißmethode verwenden

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

  
Weiß nicht?Sie werden es verstehen, nachdem Sie die Erklärung unten gelesen haben.
Fügen Sie hier eine Bildbeschreibung ein

  

7.2 ##-Operator

  ## kann in ihm lokalisiert werdenDie Symbole auf beiden Seiten werden zu einem Symbol zusammengefasst, was die Definition von Makros ermöglicht从分离的文本片段创建标识符## wird genanntMarkieren Sie das Kleben .Eine solche Verbindung muss eine herstellen合法Indikator, andernfalls ist das Ergebnis undefiniert.
  
Wir haben bereits gesagt, dass Sie zum Schreiben einer Funktion, die den größeren Wert zweier Zahlen ermittelt, unterschiedliche Funktionen für unterschiedliche Datentypen schreiben müssen.

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

  

Fügen Sie hier eine Bildbeschreibung ein

Dies ist zwangsläufig zu umständlich und es gibt viele Ähnlichkeiten zwischen den beiden Funktionen.
Gibt es eine Möglichkeit, eine solche Funktion schnell zu erstellen?wie eine FunktionSchimmelDas Gleiche gilt, wenden Sie einfach eine Funktion an und sie wird angezeigt
  
Wir können so etwas schreiben

#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

  
Operationsergebnis:

Fügen Sie hier eine Bildbeschreibung ein

  
Wir können auch gcc gccGcc Beobachten Sie vorverarbeitete Ergebnisse in der Umgebung.i Datei, haben ein intuitiveres Verständnis davon

Fügen Sie hier eine Bildbeschreibung ein

Natürlich ist die auf diese Weise generierte Funktion auch beim Debuggen unpraktisch.

Dann hier ## Welche Rolle spielt es?

hinzufügen ##, wird der Compiler denken, dass es sich um ein Symbol handelt
Werfen wir einen Blick auf Kanga## Wirkung:

Fügen Sie hier eine Bildbeschreibung ein

  

8. Namenskonvention

Im Allgemeinen ist die Syntax für die Verwendung von Funktionen und Makros sehr ähnlich语言本身没法帮我们区分二者
  
Eine unserer üblichen Gewohnheiten ist:

  • Benennen Sie das Makroalles in Großbuchstaben
  • FunktionsnameVerwenden Sie nicht alle Großbuchstaben

Natürlich sind diese Namensregeln nicht absolut
Zum Beispiel Versatz VersatzÖffseT Dieses Makro ist ausschließlich in Kleinbuchstaben geschrieben
  
Notiz Versatz VersatzÖffseT Es wird verwendet, um den Versatz der Strukturelemente relativ zur Startposition der Struktur zu berechnen.
  

Neun,# undef undefSieNDtF

  # undef undefSieNDtF Anweisungen sind gewohntEntfernen Sie eine Makrodefinition

Fügen Sie hier eine Bildbeschreibung ein
Der obige Code wird in Zeile 169 verwendet# undef undefSieNDtF Makro MAX entfernt.Beim Aufruf der 168 Leitungen vor dem Entfernen gibt es kein Problem, beim Aufruf der 170 Leitungen nach dem Entfernen wird jedoch ein Fehler gemeldet.

  

10. Befehlszeilendefinition

Viele C-Compiler (außer VS) bieten die Möglichkeit, Symbole in der Befehlszeile zu definieren.Wird verwendet, um den Kompilierungsprozess zu starten
  
  Zum Beispiel : Diese Funktion ist nützlich, wenn wir verschiedene Versionen eines Programms basierend auf derselben Quelldatei kompilieren möchten. (Angenommen, in einem Programm wird ein Array mit einer bestimmten Länge deklariert. Wenn der Maschinenspeicher begrenzt ist, benötigen wir ein sehr kleines Array. Wenn jedoch der Speicher einer anderen Maschine größer ist, kann das benötigte Array größer sein.)

Fügen Sie hier eine Bildbeschreibung ein

Fügen Sie hier eine Bildbeschreibung ein

Die Befehlszeilendefinition befindet sich inVorverarbeitungsphaseVerarbeitet in der Vorverarbeitungsphase im obigen Code sz szsz Der Wert von wurde ermittelt

  

11. Bedingte Kompilierung

Beim Kompilieren eines Programms ist es sehr schwierig, eines (eine Gruppe von Anweisungen) zu kompilieren oder aufzugeben.方便 von.weil wir es gebrauchen könnenAnweisung zur bedingten Kompilierung
  
Die Anweisung zur bedingten Kompilierung ist dieser Code我想让你编译就编译,不想让你编译你就不要编译了 . Wir können eine Bedingung für ihn festlegen. Wenn die Bedingung wahr ist, wird dieser Code kompiliert. Wenn die Bedingung falsch ist, wird dieser Code nicht kompiliert.
  
Zum Beispiel:
Es wäre schade, einen Teil des Debugging-Codes zu löschen, aber es würde uns daran hindern, ihn beizubehalten, damit wir ihn gezielt kompilieren können.
  

Fügen Sie hier eine Bildbeschreibung ein

  
Häufig verwendete Anweisungen zur bedingten Kompilierung:

11.1. Bedingte Kompilierung mit einem Zweig

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

Fügen Sie hier eine Bildbeschreibung ein

  

11.2. Bedingte Kompilierung mit mehreren Zweigen

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

Welche Aussage wahr ist, diese Aussage wird ausgeführt

#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. Bestimmen Sie, ob es definiert ist

#if defined(symbol)
#ifdef symbol

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

Fügen Sie hier eine Bildbeschreibung ein

  

11.4. Verschachtelte Anweisungen

#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. Einbindung von Header-Dateien

12.1 Wie Header-Dateien eingebunden werden

(1) Wie lokale Dateien eingebunden werden

# include "filename"
  • 1

Suchstrategie: First inDas Projektverzeichnis, in dem sich die Quelldateien befindenWenn die Header-Datei nicht gefunden wird, sucht der Compiler auf die gleiche Weise nach der Header-Datei der Bibliotheksfunktion.StandardstandortsucheKopfdatei
Wenn Sie es nicht wieder finden könnenKompilierungsfehler
  
L inux LinuxMInux Der Standard-Header-Dateipfad der Umgebung (wo die Header-Datei platziert wird):

 /usr/include
  • 1

Standard-Header-Dateipfad für die VS-Umgebung:

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

  

(2) Die Bibliotheksdatei enthält

#include <filename.h>
  • 1

Suchen Sie die Header-Datei und gehen Sie direkt zuStandardpfadGehen Sie auf die Suche, und wenn Sie es nicht finden können, erhalten Sie eine Eingabeaufforderung.Kompilierungsfehler
  
Bedeutet das, dass es auch für Bibliotheksdateien verwendet werden kann?“ ” Das Formular enthält
die Antwort istaffimJa, aber die Suche erfolgt auf diese Weiseweniger effizient, das ist natürlich auch sonicht leicht zu unterscheidenHandelt es sich um eine Bibliotheksdatei oder eine lokale Datei?
  

12.2 Einbindung verschachtelter Dateien

Nachdem wir die vorherigen Schritte (Kompilierung und Verknüpfung) studiert haben, wissen wir, dass die Einbeziehung von Header-Dateien in die Vorverarbeitungsphase erforderlich ist直接将该文件的代码拷贝到包含头文件的地方
  
Wenn eine Header-Datei zehnmal eingebunden wird, wird sie tatsächlich zehnmal kompiliert. Wenn sie wiederholt eingebunden wird, ist der Kompilierungsdruck größer.

Test. c test.cTesT.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

test . h test.hTesT.H

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

In einem Projekt wird eine Datei jedoch zwangsläufig mehrmals eingebunden. Wie kann dieses Problem gelöst werden?
Antwort:bedingte Kompilierung

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

  
Wie ist es zu verstehen?

  • Sollte die Header-Datei beim ersten Einbinden kompiliert werden?Richter zuerst
  • Das Symbol __TEST_H__ ist nichtnicht definiertwollenkompilieren
  • Direkt danachDefinieren Sie das Symbol __TEST_H__
  • Fügen Sie dann die Header-Datei erneut ein und suchen Sie __TEST_H__Wurde definiertnicht mehrFühren Sie für die später eingefügte Header-Datei Folgendes auskompilieren

Die obige Schreibweise ist jedoch schwieriger. Es gibt eine andere Schreibweise:

#pragma once
  • 1

Der Effekt ist der gleiche wie bei der obigen Methode
Dadurch wird die wiederholte Einführung von Header-Dateien vermieden
  

13. Weitere Vorverarbeitungsanweisungen

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

Interessierte Freunde können lesen „Detaillierte Anatomie der C-Sprache“
  
  
  
  
  


   Okay, das ist alles Wissen über die Vorverarbeitung in dieser Ausgabe. Ich hoffe, dieser Blog kann Ihnen hilfreich sein. Bitte korrigieren Sie mich gleichzeitig, wenn es Fehler gibt, und lassen Sie uns gemeinsam auf dem Weg zum Erlernen der C-Sprache vorankommen!