首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【C语言】预处理

  • 25-02-16 21:01
  • 4345
  • 10778
blog.csdn.net

在这里插入图片描述

个人主页点这里~


预处理

  • 一、预处理符号
  • 二、#define定义常量
  • 三、#define定义宏
  • 四、带有副作用的宏参数
  • 五、宏替换的规则
  • 六、宏与函数的对比
    • (一)、宏的优势
    • (二)、宏的劣势
    • (三)、宏和函数的对比
  • 七、#和##
    • 1、#运算符
    • 2、##运算符
  • 八、命名约定
  • 九、#undef
  • 十、命令行定义
  • 十一、条件编译
  • 十二、头文件的包含
    • 1、头文件被包含的方式
      • ①本地文件包含
      • ②嵌套文件包含
  • 十三、其他预处理指令

一、预处理符号

__FILE__ //进⾏编译的源⽂件
__LINE__ //⽂件当前的⾏号
__DATE__ //⽂件被编译的⽇期
__TIME__ //⽂件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
  • 1
  • 2
  • 3
  • 4
  • 5

以上是C语言设置的一些预定义符号,是可以直接使用的,预定义符号在预处理阶段处理

二、#define定义常量

基本语法:

#define name stuff
  • 1

例子:

#define MAX 1000  //将MAX赋值为1000
#define reg register //为 register这个关键字,创建⼀个简短的名字reg
#define do_forever for(;;) //⽤更形象的符号来替换⼀种实现
//for(;;)是无条件的for循环,是一个死循环
#define CASE break;case //在写case语句的时候⾃动把 break写上。
  • 1
  • 2
  • 3
  • 4
  • 5

当我们在使用#define的时候,变量有两项,一是name,二是stuff,而stuff中的值将会代替代码中所有的name,相当于是办公软件word里边的替换,所以我们遇到以下的问题,就可以一下解决出来:

#define MAX 1000;
#define MAX 1000
  • 1
  • 2

我们说,这两种被定义的方式是不同的,上边的第一个定义可以用来完成以下代码:

#define MAX 1000;
#include 
int main()
{
    int a = MAX
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第二个可以完成以下代码:

#define MAX 1000
#include 
int main()
{
    int a = MAX;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

都是可以的,但是我们要注意,当我们想要直接用printf输出MAX的值时,用第一个是不可以的
这是使用第二个定义,正确的做法:

#define MAX 1000
#include 
int main()
{
    printf("%d",MAX);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用第一个定义:

#define MAX 1000;
#include 
int main()
{
    printf("%d",MAX);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们在这里发现,MAX被替换成了1000;
1000;是不能通过printf打印的
所以我们得出一个结论:在使用#define定义数字时,尽量不要加入符号

三、#define定义宏

#define机制包括了一个机制,就是允许把参数替换到文本中,这种实现通常称为宏或者定义宏
宏的声明方式:

#define name( parament-list ) stuff
  • 1

parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中
name与旁边的参数列表的左圆括号必须紧邻,如果二者之间有任何空白存在,参数列表就会被认为是stuff的一部分

举一个求平方的例子:

#define SQUARE( x ) x * x
  • 1

当我们使用SQUARE( 9 )时,编译器就会将它替换成9*9

注意:
在使用宏定义的时候,我们为了不让我们所定义的量出现错误,最好给每个变量都加上括号,不然就会出现错误

例子:

#define SQUARE( x ) x * x
#include 
int main()
{
    int a = 5;
    printf("%d\n" ,SQUARE(a + 1) );
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这会发现一个错误,替换之后变成5+1*5+1,最终的答案是11而不是36
所以最好给每个量都套一个括号

修改后:

#define SQUARE( x ) ((x) * (x))
#include 
int main()
{
    int a = 5;
    printf("%d\n" ,SQUARE(a + 1) );
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

四、带有副作用的宏参数

#define x++;
  • 1

上面这个宏就是一个副作用宏,因为替换之后会持续造成作用,这样就可能会导致危险,简单来说,副作用就是表达式求值的时候会出现的永久性效果
我们来举一个例子

#define MIN(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 6;
y = 9;
z = MIN(x++, y++);//z = ((x++) < (y++) ? (x++) : (y++))
printf("x=%d y=%d z=%d\n", x, y, z);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

结果是x=8 y=10 z = 7
我们先来计算<左右两边的值,x++是先赋值再++,y++也是先赋值再++,x

五、宏替换的规则

1、调用宏的时候,首先对参数进行检查,看是否包含任何由#define定义的符号,如果是,他们首先被替换,也就是首先替换参数
2、然后替换文本随后被插入到程序中原本文本的位置,对于宏,参数名被它们的值所替换,也就是把宏定义的值替换被替换的值
3、最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号,如果是,就重复上述处理过程,也就是再次扫描然后重复上述过程
4、宏参数和#define定义中可以出现其他#define定义的符号,但是宏是不能够递归的
5、在字符串中的#define定义的符号不能被替换

六、宏与函数的对比

(一)、宏的优势

当我们要进行一些简单的计算时,使用宏替换比函数更有优势一些
1、因为不管是简单的还是复杂的计算,使用函数都会在栈中开辟一块空间(在我们之前的博文函数栈帧的创建和销毁一文中有详细的内容,大家有兴趣可以看一下),然后还有销毁空间,在开辟空间之前会有开辟空间之前的指令,这会增长运行时间,而反观用宏替换的方式,直接将代码替换,省去了开辟空间的时间,速度更快
2、使用函数要声明数据类型,所以一个函数只能由特定数据类型的数据使用,但是宏可以使用任意的数据,宏的参数与类型无关,只是一个简单的替换
3、宏的参数可以是类型,函数不行
例子:
( \ 这个符号是连字符,如果代码内容较长,可以用连字符来连接,程序生成的过程中会自动将 \ 去掉并连接上下)

#define MALLOC(num, type)\
 (type*)malloc(num*sizeof(type))
int main()
{
    MALLOC(5,int);//(int*)malloc(5*sizeof(int));
    return 0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(二)、宏的劣势

再进行复杂计算时,使用函数会更有优势一些
1、每次使用宏的时候,宏定义的代码会插入到程序中,在宏较长的情况下可能会导致大幅度增加程序的长度
2、宏无法调试
3、宏与类型无关,这虽然是它的一个优点,也是一个缺点,因为这导致它不够严谨
4、宏可能会带来运算优先级问题,如上面第三条所说,容易导致程序出错

(三)、宏和函数的对比

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序当中,除了非常小的宏之外,程序的长度会大幅度增长每次使用函数时,都调用同一个地方的同一份代码
执行速度更快存在函数栈帧的创建和销毁,相对于宏会慢一些
操作符优先级宏在书写的时候要多加括号,否则会因为临近操作符优先级不同,使目的与代码不匹配的问题表达式的求值容易被预测,不会出现宏一样的前后操作符优先级问题
带有副作用的参数参数可能会被替换到程序中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会造成风险函数参数只在传参的时候求值一次,结果容易被控制
参数类型宏的参数与类型无关,只要对参数的操作合法,就可以使用任何数据类型函数的参数与类型有关,必须严格按照参数类型来进行使用,不同参数类型不同,所需要的函数就不同,尽管它们的任务相同
调试不能调试可逐句调试
递归不能递归可以递归

七、#和##

1、#运算符

#运算符可以将宏的一个参数转换为字符串字面量,它仅允许出现在带参数的宏的替换列表中
简单来说它的功能就是字符串化
例子:
当我们想打印出来一个数字的大小:the value of number is 6
我们可以这样做:

#define PRINT(n) printf("the value of "#n " is %d", n);
int main()
{
    int number = 6;
    PRINT(number);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

它在使用时,#n就是#number,#number就是转换成"number",这时字符串内的代码就会被预处理为:

printf("the value of ""number" " is %d", number);
  • 1

然后正常执行代码,就得到了the value of number is 6

2、##运算符

##被称为记号粘合,它可以把左右两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符,当然这样的连接是要产生一个合法的标识符的,否则其结果就是未定义的,我们可以用这个运算符来写一个类似于宏的函数,这样的函数是可以定义自由定义数据类型的,使用起来是非常方便的

#define GENERIC_MIN(type) \
type type##_min(type x, type y)\
{ \
 return (x<y?x:y); \
}
  • 1
  • 2
  • 3
  • 4
  • 5
GENERIC_MIN(int) //替换到宏体内后int##_min ⽣成了新的符号 int_min做函数名
GENERIC_MIN(float) //替换到宏体内后float##_min ⽣成了新的符号 float_min做函数名
int main()
{
 //调⽤函数
 int m = int_min(6, 3);
 printf("%d\n", m);
 float b = float_min(1.5f, 4.5f);
 printf("%f\n", b);
 return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

八、命名约定

函数与宏的使用比较相似,我们这里约定俗成的规则就是将宏全部大写,函数部分大写,然后其他代码使用小写,这样可以很好的区分宏、函数以及其他代码

九、#undef

#undef 可以移除一个宏定义,如果现存的一个名字需要被重新定义,那么就使用它进行移除

#undef NAME
  • 1

十、命令行定义

许多C的编译器提供了在命令行中定义符号的能力,用于启动编译过程
在这里我们可以调节数组的大小,或者循环次数的大小等

十一、条件编译

我们平常写代码的时候,我们不清楚所写的代码是否能够实现目标时,我们往往会对某一个某块进行调试,但有一些代码是专门用来调试时加上的,删了有些可惜,保留又碍事,这时我们就可以选择性的编译,使用条件编译指令

例子:

#include 
#define __DEBUG__
int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d\n", arr[i]);
#endif
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里的printf函数用来检查赋值是否成功,#ifdef用来检查后边的指令是否被定义,如果被定义了,那么就进行编译,如果未被定义则编译,调试结束之后将#define语句注释掉就行了

常见的条件编译指令

//1.
#if 常量表达式
 //...
#endif
//常量表达式由预处理器求值。
//如:
#define __DEBUG__ 1
#if __DEBUG__
 //..
#endif
//2.多个分⽀的条件编译
#if 常量表达式
 //...
#elif 常量表达式
 //...
#else
 //...
#endif
//3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
//4.嵌套指令
#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
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

十二、头文件的包含

1、头文件被包含的方式

①本地文件包含

#include "filename"
  • 1

查找策略:先在源文件所在目录下查找,如果未找到,就在标准位置查找,即库函数所在的位置,如果找不到就提示编译错误

②嵌套文件包含

我们知道,每一条代码就可能使用一块空间,如果我们在一个大的程序里边写代码时,我们可能多次包含同一个头文件,那么包含了几次,这条代码就编译几次,极大的影响效率,我们可以通过使用条件编译避免头文件的重复引入

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

或

#pragma once
  • 1

十三、其他预处理指令

c语言给我们很多预处理指令,我们工作的过程中可能会用到,大家自行查找学习


今日分享就到这里了~

在这里插入图片描述

注:本文转载自blog.csdn.net的s_little_monster_的文章"https://s-little-monster.blog.csdn.net/article/details/137384038"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top