C++基础(7)C/C++ 预处理器

316 阅读5分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第7篇文章,点击查看活动详情

顾名思义,预处理器是在编译之前处理我们的源代码的程序。在 C / C++ 中编写程序和执行程序之间涉及许多步骤。在我们真正开始学习预处理器之前,让我们先看看这些步骤。

您可以在上图中看到中间步骤。程序员编写的源代码首先存储在一个文件中,命名为“ program.c ”。该文件然后由预处理器处理,并生成一个名为“program.i”的扩展源代码文件。该扩展文件由编译器编译并生成名为“program.obj”的目标代码文件。最后,链接器将该目标代码文件链接到库函数的目标代码以生成可执行文件“program.exe”。 

预处理器程序提供预处理器指令,告诉编译器在编译之前预处理源代码。所有这些预处理器指令都以“#”(哈希)符号开头。“#”符号表示任何以“#”开头的语句都将进入预处理程序执行。一些预处理器指令的示例是: #include、 # define#ifndef等。请记住, # 符号仅提供到预处理器的路径,并且诸如 include 之类的命令由预处理器程序处理。例如,#include 将在您的程序中包含额外的代码。我们可以将这些预处理器指令放置在程序中的任何位置。 

预处理器指令有 4 种主要类型:  

  1. 文件包含
  2. 条件编译
  3. 其他指令

现在让我们详细了解这些指令中的每一个。 

1. 宏

宏是程序中具有某些名称的代码片段。每当编译器遇到此名称时,编译器都会将该名称替换为实际的代码段。'#define' 指令用于定义宏。现在让我们借助程序来理解宏定义:

使用 C++

#include <iostream>

// 宏定义
#define LIMIT 5
int main()
{
	for (int i = 0; i < LIMIT; i++) {
		std::cout << i << "\n";
	}

	return 0;
}

使用 C

#include <stdio.h>

// 宏定义
#define LIMIT 5
int main()
{
	for (int i = 0; i < LIMIT; i++) {
		printf("%d \n",i);
	}

	return 0;
}

输出: 

0
1
2
3
4

在上面的程序中,当编译器执行LIMIT这个词时,它会用5代替它。宏定义中的“LIMIT”这个词被称为宏模板,“5”是宏扩展。 

注意:宏定义的末尾没有分号 (;)。宏定义不需要分号结束。

带参数的宏:我们也可以将参数传递给宏。使用参数定义的宏与函数类似。让我们通过一个程序来理解这一点:

使用 C++

#include <iostream>

// 带参数的宏
#define AREA(l, b) (l * b)
int main()
{
	int l1 = 10, l2 = 5, area;

	area = AREA(l1, l2);

	std::cout << "Area of rectangle is: " << area;

	return 0;
}

使用 C

#include <stdio.h>

// 带参数的宏
#define AREA(l, b) (l * b)
int main()
{
	int l1 = 10, l2 = 5, area;

	area = AREA(l1, l2);

	printf("矩形面积为:%d", area);

	return 0;
}

输出: 

矩形面积为:50

从上面的程序我们可以看出,每当编译器在程序中找到AREA(l, b)时,就会用语句(lb)来替换它。不仅如此,传递给宏模板 AREA(l, b) 的值也将在语句 (lb) 中被替换。因此 AREA(10, 5) 将等于 10*5。 

2. 文件包含

这种类型的预处理器指令告诉编译器在源代码程序中包含一个文件。用户可以在程序中包含两种类型的文件: 
头文件或标准文件:这些文件包含预定义函数的定义,如printf()、scanf() 等。必须包含这些文件才能工作有了这些功能。不同的函数在不同的头文件中声明。例如,标准 I/O 函数位于“iostream”文件中,而执行字符串操作的函数位于“字符串”文件中。 
语法

#include<文件名>

其中file_name是要包含的文件的名称。'<' 和 '>' 括号告诉编译器在标准目录中查找文件。 

用户定义的文件:当程序变得非常大时,最好将其分成较小的文件并在需要时包含它们。这些类型的文件是用户定义的文件。这些文件可以包含为:

#include"文件名"

3.条件编译

条件编译指令是一种指令类型,有助于编译程序的特定部分或根据某些条件跳过程序的某些特定部分的编译。这可以借助两个预处理命令“ ifdef ”和“ endif ”来完成。 
语法

#ifdef 宏名称
    声明1;
    声明2;
    声明3;
    .
    .
    .
    声明N;
#万一

如果定义了名为“ macro_name ”的宏,则语句块将正常执行,但如果未定义,编译器将直接跳过该语句块。 

4. 其他指令 

除了上述指令之外,还有两个不常用的指令。它们是: 
#undef 指令:#undef 指令用于取消定义现有宏。该指令的作用是:

#undef 限制

使用此语句将取消定义现有的宏 LIMIT。在此语句之后,每个“#ifdef LIMIT”语句都将评估为假。 

#pragma 指令:该指令是一个特殊用途的指令,用于打开或关闭某些功能。这种类型的指令是特定于编译器的,即它们因编译器而异。下面讨论了一些#pragma 指令: 

  • #pragma startup#pragma exit:这些指令帮助我们指定在程序启动之前(控制传递给 main() 之前)和程序退出之前(就在控制从 main() 返回之前)需要运行的函数. 

注意: 以下程序不适用于 GCC 编译器。

使用 C++

#include <bits/stdc++.h>
using namespace std;
	
void func1();
void func2();

#pragma startup func1
#pragma exit func2

void func1()
{
	cout << "Inside func1()\n";
}

void func2()
{
	cout << "Inside func2()\n";
}

int main()
{
	void func1();
	void func2();
	cout << "Inside main()\n";

	return 0;
}

使用 C

#include <stdio.h>

void func1();
void func2();

#pragma startup func1
#pragma exit func2

void func1()
{
	printf("Inside func1()\n");
}

void func2()
{
	printf("Inside func2()\n");
}

int main()
{
	void func1();
	void func2();
	printf("Inside main()\n");

	return 0;
}

输出:

Inside func1()
Inside main()
Inside func2()

上面的代码在 GCC 编译器上运行时将产生如下输出: 

Inside main()

发生这种情况是因为 GCC 不支持 #pragma 启动或退出。但是,您可以将以下代码用于 GCC 编译器上的类似输出。

使用 C++

#include <iostream>
using namespace std;

void func1();
void func2();

void __attribute__((constructor)) func1();
void __attribute__((destructor)) func2();

void func1()
{
	printf("Inside func1()\n");
}

void func2()
{
	printf("Inside func2()\n");
}

// 驱动程序代码
int main()
{
	printf("Inside main()\n");

	return 0;
}

使用 C

#include <stdio.h>

void func1();
void func2();

void __attribute__((constructor)) func1();
void __attribute__((destructor)) func2();

void func1()
{
	printf("Inside func1()\n");
}

void func2()
{
	printf("Inside func2()\n");
}

int main()
{
	printf("Inside main()\n");

	return 0;
}

#pragma warn 指令: 该指令用于隐藏编译期间显示的警告消息。我们可以隐藏警告,如下所示: 

  • #pragma warn -rvl:该指令隐藏那些在应该返回值的函数没有返回值时引发的警告。
  • #pragma warn -par:该指令隐藏了当函数不使用传递给它的参数时引发的那些警告。
  • #pragma warn -rch:此指令隐藏在代码无法访问时引发的那些警告。例如,在函数中的return语句之后编写的任何代码都是无法访问的。