[深入浅出C语言]预处理补充篇

196 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

前言

      之前发过预处理的文章,感兴趣的可以先去看看,而本文是前面文章的补充,分享一波笔者的学习经验和心得。

      笔者水平有限,难免存在纰漏,欢迎指正交流。

宏定义

数值宏常量

      回答一个问题:为何这些字面值建议定义成宏?

      比如#define PI 3.14

      有些常量需要在文件中多次出现,甚至在多文件中多次出现,使用宏定义的话方便修改,维护成本低。

字符串宏常量

      比如:

#define PATH "C:\Autodesk\AutoCAD_2020_Simplified_Chinese_Win_64bit_dlm\EULA"

      看看有什么问题。

      显然要打印出\需要使用\转义字符,所以应该改成这样:

#define PATH "C:\Autodesk\AutoCAD_2020_Simplified_Chinese_Win_64bit_dlm\EULA"

      太长了能不能换行?

      当然可以,但是必须要有续行符\,而且换行后不要加空格。

去注释和宏替换先后顺序问题

      复习一下程序翻译过程,下面会在Linux下进行测试。

      预处理-E :头文件展开,去注释,宏替换,条件编译

      编译-S : 将C语言编译成为汇编语言

      汇编-c :将汇编翻译成为目标二进制文件

      链接 :将目标二进制文件与相关库链接,形成可执行程序

例1

例2

用 define 宏定义表达式

      函数宏相关的例子

#define SUM(x) (x)+(x)
int main()
{
    printf("%d\n", SUM(10));
    printf("SUM(20)\n");
    return 0;
}

      说到底还是文本替换,x和10在符号层面上没什么不同,SUM(10)被替换成(10)+(10)。

      不过要注意,在字符串中字符会被严格认为是字符串的一部分,失去原有的特殊意义,也就是说宏在字符串中会被解释为字符串的一部分而无法实现宏替换。

用宏替换多行代码

例1

#define INIT_VALUE(a,b)\
a = 0;\
b = 0;

int main()
{
    int flag = 0;
    scanf("%d", &flag);
    int a = 100;
    int b = 200;
	printf("before: %d, %d\n", a, b);
    
    if (flag)
    	INIT_VALUE(a, b);
    else
    	printf("error!\n");
    printf("after: %d, %d\n", a, b);
    return 0;
}

      一编译发现出问题了,我们看看编译前是怎样的

      if后面跟了三条语句(x=0、y=0和空语句),else就无法与if匹配了。

      有人说,那还不简单,在if后面加个花括号不就完了吗?

      然而,你不能确保所有人编写时都遵循if和else后面必加花括号这样的规范,你在编写多语句宏的时候就要考虑其健壮性,比如在这里就要保证不管if后面加不加花括号都能无误。

      有人又要说了,这也不简单吗,在宏定义时加上花括号呗。

      这样又多了个分号,还有办法吗?

      使用do while(0)语句,多出来的分号正好被处理掉。

      这样一来我们就知道:想要写出替换多语句的宏,最好用do while(0)把语句括起来。

#undef

先探讨2个问题:

1. 宏只能在main上面定义吗?

int main()
{
    #define N 100
    printf("%d\n", N);
    return 0;
}

      结论:宏,在哪定义都可以,习惯放在最上面。

2. 在一个源文件内,宏的有效范围是什么?

我们先来看看这个:

      能不能进行宏替换取决于宏是不是在前面已经定义好了。

      结论:宏的有效范围,是从定义处往下有效,之前无效。

      原因:宏替换发生在预处理阶段,是在编译阶段之前,也就是说宏替换就只是从宏定义处开始把后面的宏给进行文本替换。

undef本质作用

例1

      很明显,在#undef后M不再被替换。

例2

      要理解这个例子的话需要知道:宏是从定义处开始往下一个一个进行替换的,如果使用了宏的函数不在宏定义的下面,那么就不会进行宏替换,你可能会觉得函数调用的时候不是进入函数内部了吗,这时候不就可以替换了吗,为什么还必须让函数定义在宏定义之下呢?

      这位小友你可能还没搞清楚一件事,那就是前面提到的宏替换是发生在预处理阶段的,是在编译之前进行的,而函数调用是在程序运行期间实现的,过了这村就没这店了,等你调用函数时宏替换早就过了,不会再有宏替换了。

      结论:undef是取消宏的意思,可以用来限定宏的有效范围。

例3

      z的值是多少?

int main()
{
#define X 3
#define Y X*2
#undef X
#define X 2
    int z = Y;
    printf("%d\n", z);
    return 0;
}

#undef X
#define X 2 

      相当于重置了一个宏X替换值为2

编译器中宏定义

      除了可以直接在代码中进行宏定义,还可以通过编译器进行宏定义。

      VS下右击项目点开属性

      在属性页面中找到预处理器,可在预处理器定义定义宏

宏的使用建议

      尽量使用普通的函数而非函数宏,因为编译器会对普通函数进行检查而对宏则不会。

      宏定义应当在使用前定义好,除了条件编译。

文件包含

#include究竟干了什么

      #include本质是把头文件中相关内容,直接拷贝至源文件中。当然也会有去注释和条件编译过程。

为何所有头文件,都推荐写入下面代码

#ifndef XXX
#define XXX
//TODO
#endif

      第一次被包含时,宏还没有被定义,这时候#ifndef满足条件,定义宏XXX

      当后面第二次、第三次……第n次被包含时,由于宏XXX已经定义,不满足#ifndef条件,也就不会再定义宏,并且由#ifndef和#endif括起来的代码不保留,也就保证了只在第一次被包含的时候保留了一份代码,防止了后续被重复包含。

      重复包含,会引起多次拷贝,主要会影响编译效率!同时,也可能引起一些未定义错误,但是特别少。

      预处理都是在编译之前起效果的,也就是和后面的链接,运行没有关系。这点要注意

#error 预处理

      核心作用是可以进行自定义编译报错。

示例:

int main()
{
#error 你好啊
#ifndef __cplusplus
#error 老铁,你用的不是C++的编译器哦
#endif
	return 0;
}

#line 预处理

      本质其实是可以定制化你的文件名称和代码行号,较少使用

int main()
{
	printf("%s, %d\n", __FILE__, __LINE__); //这两个都是C预定义符号,分别代表当前文件名和代码行号
#line 60 "hehe.h" //定制化完成
    printf("%s, %d\n", __FILE__, __LINE__);
    
    return 0;
}

#pragma 预处理

#pragma message()

      作用:可以用来进行对代码中特定的符号(比如其他宏定义)进行是否存在进行编译时消息提醒。

#define M 10
int main()
{
#ifdef M
#pragma message("M宏已经被定义了")
#endif

	return 0;
}

#pragma once

      防止头文件被重复包含。

#pragma warning

      不让编译器报错。

#pragma warning(disable:4996) //禁止4996报错,针对VS
int main()
{
    int x = 10;
    scanf("%d", &x);
    printf("hello : %d\n", x);

	return 0;
}

# 符号

      相邻字符串具有自动连接特性:

int main()
{
    printf("hello world""can you see me\n");
    printf("look at me\n""i can hear you\n");
    
    return 0;
}

#的作用

#define STR(s) #s
int main()
{
    char buf[64];
    strcpy(buf, STR(123456));
    printf("%s\n", buf);
    return 0;
}

      那变量行不行呢?

#define STR(s) #s
int main()
{
    int abc = 12345;
    char buf[64];
    strcpy(buf, STR(abc));
    printf("%s\n", buf);
    return 0;
}

      为什么得到的不是12345呢?

      原因很简单,宏替换发生在预处理阶段,而变量的创建与赋值是在程序执行过程中发生的,在替换的时候abc还不是变量名,只是单纯的字面量abc。

## 符号

      将##相连的两个符号,连接成为一个符号

例子:

#define CONT(x,n) (x##e##n)
int main()
{
    printf("%f\n", CONT(1.1,2)); //计算浮点数科学计数法,相当于1.1e2
    return 0;
}

以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~

src=http___c-ssl.duitang.com_uploads_item_201708_07_20170807082850_kGsQF.thumb.400_0.gif&refer=http___c-ssl.duitang.gif