掘根宝典之c语言预处理器(#define,#include,#,##,#undef,#if,#ifdef,#elif,#endif,defined函数)

目录

#define

 #define(宏定义命令)

define与typedef作用域的的区别:

 #define定义宏

注意 

#define 替换规则 

注意 

宏和函数对比 

宏的缺点:

宏和函数的对比 

#运算符

注意点

##运算符

注意点

#include(文件包含命令)

#undef

#if(条件编译)

#ifdef

#ifndef

#elif

#endif

defined函数(与if等结合使用)


#define

 #define(宏定义命令)

格式:#define 标识符 字符串

并不是所有情况下#define所定义的字符串都会被替换,有一种特殊情况:被替换的字符串在""内

代码示例:

#include <stdio.h>#define MAX 10int main()
{printf("MAX");
}

还有需要注意的一点是,不管是在某个函数内,还是在所有函数之外(不太建议把#define写在函数内),#define作用域都是从定义开始直到整个文件结尾(这一点和typedef就区别很大)

#define(宏定义)----由预处理器来处理

typedef----在编译阶段由编译器处理

代码举例:

#include <stdio.h>void fun();
int main()
{#define MAX intMAX a = 10;printf("%d", a);fun();
}void fun()
{MAX b = 10;printf("%d", b);
}

define与typedef作用域的的区别:

typedef:

如果放在所有函数之外,它的作用域就是从它定义开始直到文件尾

如果放在某个函数内,它的作用域就是从它定义开始直到该函数结尾

#define:

不管是在某个函数内,还是在所有函数之外,作用域都是从定义开始直到整个文件结尾 (不管是typedef还是define,其作用域都不会扩展到别的文件,即使是同一个程序的不同文件,也不能互相使用)

这里说下题外话#define叫宏定义,但是在笔者的认识里对声明和定义的理解是:声明不分配内存,定义才分配内存,所以#define虽然名字里面有“定义”两个字,但并不占存储空间(为什么不叫宏声明···)

总结:#define和声明、定义都不同,宏定义不占内存空间,因为宏在预处理阶段就会被替换掉,到了编译的阶段是没有宏存在的,它在预编译阶段就被处理了

 #define定义宏


#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏 (define macro) 

#define 定义宏可分为两种

一种是不带参数的宏定义,这也就是上面提到的使用#define定义标识符
第二种是带参数的宏定义,其定义格式如下

#定义   宏名(参数表)           内容
#define name( parament-list ) stuff


其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中 

#define ADD(a,b) a + b


这个例子定义了一个名为ADD的宏 ,宏的参数有两个 a 和 b,这个宏所要实现的功能是将 a 和 b 相加

如何使用这个宏呢?对比下面两段代码

#define ADD(a,b) (a + b)int main()
{int a = 10;int b = 20;int sum = ADD(a, b);printf("%d\n", sum);return 0;
}
int ADD(int x, int y)
{return x + y;
}int main()
{int a = 10;int b = 20;int sum = ADD(a, b);printf("%d\n", sum);return 0;
}


其实,宏的使用和我们已知的C语言中的函数类似,都需要进行传参,对比函数版本的求两数之和,可以看到在main函数中,两者的实现方式是几乎一样的,但在具体的实现方式却很不一样

宏定义的程序中,预处理器会将ADD(a,b)替换成 (a + b),.i文件中的代码为

int main()
{int a = 10;int b = 20;int sum = (a + b);printf("%d\n", sum);return 0;
}


宏定义是将 a + b这样一个求和操作重新命名并置于main函数中,而ADD函数是通过函数调用来实现求和,使用了新的函数栈帧 

注意 

参数列表的左括号必须与name紧邻
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分 

#define ADD (a,b)


 在这个宏定义中,ADD与()并不直接相连,中间存在空格,因此(a,b)不是宏ADD的参数,而是变成了stuff
考虑下面的代码

#define SQUAREA(x) (x * x)
#define SQUAREB(x) ((x) * (x)) int main()
{int ansa = SQUAREA(3 + 1);int ansb = SQUAREB(3 + 1);printf("%d\n%d\n", ansa, ansb);return 0;
}


ansa 和 ansb 的输出分别是 7 和 16

这一点似乎出人意料,实际上,宏就是替换,并且是相当直接的替换,上述代码中最重要的一行代码被分别解释为

int ansa = (3 + 1 * 3 + 1)     // 7
int ansb = ((3 + 1) * (3 + 1)) // 16


因此,所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用

#define 替换规则 


在程序中扩展#define定义符号和宏时,需要涉及几个步骤

  • 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换 
  • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
  • 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程


注意 

宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

宏和函数对比 


一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。那我们平时的一个习惯是: 

把宏名全部大写
函数名不要全部大写 

宏通常被应用于执行简单的运算,比如在两个数中找出较大的一个 

#define MAX(a, b) ((a)>(b)?(a):(b))


那为什么不用函数来完成这个任务?

原因有二: 

用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹 
更为重要的是函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的


宏的缺点:

  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
  • 宏是没法调试的
  • 宏由于类型无关,也就不够严谨
  • 宏可能会带来运算符优先级的问题,导致程容易出现错
  • 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到
#define MALLOC(num, type)\
(type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int*)malloc(10 * sizeof(int));


宏和函数的对比 

C语言的宏和函数都是用于在程序中重复使用一段代码的工具,但它们有一些不同之处。

宏(Macro)是一种在程序编译阶段进行文本替换的机制。宏定义使用#define关键字,可以将一个表达式、一段语句或者一组语句定义为一个宏。当程序中使用到宏的时候,预处理器会将宏的定义部分替换为实际的代码。宏的优点是代码简洁、执行效率高,但它也有一些缺点,例如宏定义在替换时不会进行类型检查,容易引发一些错误。

函数是一段具有特定功能的代码块,由函数名、参数列表和函数体组成。函数可以被调用多次,它接受参数并返回一个结果。函数的定义和调用发生在程序运行时,而不是编译时,因此函数的执行效率较宏较低。函数的优点是封装性好、可读性强,可以进行类型检查,容错性好。

下面是宏和函数的一些对比:

  1. 参数传递方式:宏在替换时是直接对文本进行替换,不会对参数进行计算,而函数是将参数的值传递给函数体进行计算。

  2. 类型检查:宏在替换时不会进行类型检查,容易引发错误,而函数在定义时可以指定参数的类型,并进行类型检查。

  3. 可读性:函数的代码较宏更可读,因为函数名和参数列表可以直接表达函数的功能,而宏的替换文本可能不够清晰。

  4. 作用域:宏的作用域是全局的,可以在程序的任何地方使用,而函数的作用域可以通过函数的作用域修饰符(如static)进行限制。

  5. 执行效率:宏的代码在编译时就会被替换,因此执行效率较高,而函数的代码在运行时才会被执行,因此执行效率较宏较低。

总的来说,宏适用于一些简单的代码替换,可以提高代码的执行效率,但可读性和容错性较差;函数适用于复杂的代码块,可以提高代码的封装性、可读性和容错性,但执行效率较宏较低。在实际编程中,应根据具体情况选择使用宏还是函数。

#运算符

"#"运算符在C语言中被用作预处理指令的一部分,称为"宏运算符"或"字符串化运算符"。

在C语言中,预处理器是在编译之前对源代码进行处理的工具。预处理器指令以"#"开头,使用一些特殊的预处理指令来操作代码,例如定义宏、包含头文件等。

"#"运算符用于将宏参数转换为字符串常量。当将宏参数包含在双引号内并使用"#"运算符时,它会将参数的值转换为字符串。

以下是一个示例,展示了#运算符的使用方式:

#include <stdio.h>#define STR(x) #xint main() {int num = 10;double pi = 3.14159;char* str1 = STR(num);char* str2 = STR(pi);printf("str1: %s\n", str1);  // 输出结果为 "str1: num"printf("str2: %s\n", str2);  // 输出结果为 "str2: pi"return 0;
}

在上面的示例中,我们定义了一个宏STR,它接受一个参数并使用#运算符将参数转换为字符串常量。

main函数中,我们声明了一个整型变量num和一个双精度浮点型变量pi,并分别将它们的值赋给str1str2变量。

使用STR宏,我们将变量名转换为字符串常量,并将结果赋值给对应的str变量。

最后,使用printf函数打印str1str2的值,分别输出"str1: num"和"str2: pi"。

请注意,#运算符只能用于将参数转换为字符串,不能用于执行其他计算。它是在预处理阶段进行处理的,而不是在程序运行时进行。

需要注意的是,"#"运算符只能用于预处理指令中,不能直接在C代码中使用。

它的主要作用是在编译之前对源代码进行处理,而不是在运行时执行的运算符。

注意点

在C语言中,#运算符是一种预处理运算符,称为"字符串化运算符"。它的作用是将宏参数转换为一个字符串。

下面是#运算符的注意事项:

  1. #运算符只能用于宏定义中的参数,用于将参数转换为字符串。不能用于变量或表达式。

  2. 使用#运算符时,宏定义中的参数必须用括号括起来,以确保宏调用中传递的参数被正确转换为字符串。例如:

#define STRINGIFY(x) #xint main() {int num = 10;printf("%s\n", STRINGIFY(num));  // 输出结果为 "num"return 0;
}

在上面的示例中,宏STRINGIFY使用#运算符将参数x转换为字符串。在main函数中,我们声明了一个整型变量num,然后将num作为参数传递给STRINGIFY宏,并使用printf函数打印转换后的结果。输出结果为字符串"num"。

  1. 如果宏参数本身包含.运算符或其他特殊字符,那么#运算符将忽略这些特殊字符,将它们作为普通字符处理。例如:
#define STRINGIFY(x) #xint main() {printf("%s\n", STRINGIFY(x.y));  // 输出结果为 "x.y"return 0;
}

在上面的示例中,STRINGIFY宏的参数是x.y,这里的.运算符没有被解释为结构体成员访问符,而是作为普通字符处理。

需要注意的是,#运算符是在预处理阶段进行处理的,它的作用是将宏参数转换为字符串,而不是在程序运行时进行

##运算符

在C语言中,##运算符是一种预处理运算符,称为"连接运算符"或"符号粘贴运算符"。

它的作用是将两个符号连接在一起,形成一个新的符号。

以下是##运算符的使用示例:

#include <stdio.h>#define CONCAT(a, b) a##b
#define A(n) x##nint main() {int number = 10;float value = 3.14;int number_value = CONCAT(number, value);  // 将number和value连接在一起形成新的符号number_valueprintf("number_value: %d\n", number_value);  // 输出结果为 10(假设整数和浮点数的大小和表示方式相同)int A(1)=14;//变成int x1=14;
int A(2)=12;//变成int x2=12;return 0;
}

在上面的示例中,我们定义了一个宏CONCAT,它接受两个参数并使用##运算符将两个参数连接在一起。在main函数中,我们声明了一个整型变量number和一个浮点型变量value。然后,使用CONCAT宏将numbervalue连接起来,形成一个新的符号number_value。最后,使用printf函数打印number_value的值,输出结果为10

需要注意的是,##运算符只能用于连接符号,不能用于连接变量或表达式。

它是在预处理阶段进行处理的,而不是在程序运行时进行。

注意点

下面是##运算符的注意事项:

  1. ##运算符只能用于宏定义中,用于将两个参数或符号连接在一起。

  2. ##运算符可以使两个符号连接在一起形成一个新的符号。例如:

#define CONCAT(a, b) a##bint main() {int num = 10;printf("%d\n", CONCAT(num, ber));  // 输出结果为 10return 0;
}

在上面的示例中,CONCAT宏使用##运算符将参数ab连接在一起形成一个新的符号number,然后在main函数中,我们声明了一个整型变量num,并使用CONCAT宏将字符串number连接在一起,最终打印出变量num的值。

  1. 使用##运算符时,宏定义中的参数不需要用括号括起来。例如:
#define CONCAT(a, b) a##bint main() {printf("%d\n", CONCAT(1, 0));  // 输出结果为 10return 0;
}

在上面的示例中,CONCAT宏的参数是10,它们直接通过##运算符连接在一起,最终输出结果为整数10

需要注意的是,##运算符也是在预处理阶段进行处理的,它的作用是将参数或符号连接在一起形成一个新的符号,而不是在程序运行时进行。

#include(文件包含命令)

#include的用法有两种,尖括号<>和双引号""

第一种----尖括号

#include <stdio.h>
  • 1

第二种----双引号

#include "stdio.h"
  • 1

使用尖括号和双引号的区别在于头文件的搜索路径

尖括号:编译器会到系统路径下查找头文件

双引号:编译器会先在当前目录下查找头文件,如果没有找到,再到系统路径下查找

注意事项:

1、一个 #include 命令只能包含一个头文件

2、同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制

3、头文件包含允许嵌套

(头文件只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误)

#undef

上文提到#define的作用域是从它声明开始到文件结尾,#undef就是取消之前的宏定义(也就是#define的标识符)

格式:#undef 标识符(注意:如果标识符当前没有被定义成一个宏名称,那么就会忽略该指令)

#include <stdio.h>#define MAX 10
int main()
{printf("%d", MAX);
#undef MAX
#define    MAX 20printf("%d", MAX);
}

#if(条件编译)

#if的使用和if else的使用非常相似,一般使用格式如下

#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#else
程序段3
#endif

执行起来就是,如果整形常量表达式为真,则执行程序段1,否则继续往后判断依次类推(注意是整形常量表达式),最后#endif是#if的结束标志

代码示例:

#include "stdio.h"#define MAX 10
int main()
{printf("MAX = %d\n", MAX);#if    MAX == 10printf("MAX已被定义\n");
#elseprintf("MAX未被定义\n");#undef MAX#define    MAX 20
#endifprintf("MAX = %d\n", MAX);return 0;
}

运行结果:
在这里插入图片描述
代码稍加修改:

#include "stdio.h"#define MAX 10
int main()
{
#if    MAX == 1printf("MAX已被定义\n");
#elseprintf("MAX未被定义\n");#undef MAX#define    MAX 20
#endifprintf("MAX = %d\n", MAX);return 0;
}

在这里插入图片描述

  • #if命令要求判断条件为整型常量表达式,也就是说表达式中不能包含变量,而且结果必须是整数;而if后面的表达式没有限制,只要符合语法就行,这是#if和if的一个重要区别

#ifdef

#ifdef的作用是判断某个宏是否定义,如果该宏已经定义则执行后面的代码,一般使用格式如下

#ifdef 宏名
程序段1
#else
程序段2
#endif

它的意思是,如果该宏已被定义过,则对程序段1进行编译,否则对程序段2进行编译(这个和上面的#if一样最后都需要#endif),上述格式也可以不用#else,这一点上和if else相同

代码示例:

#include <stdio.h>#define MAX 10
int main()
{
#ifdef MAXprintf("MAX已被定义\n");
#elseprintf("MAX未被定义\n");#undef MAX#define    MAX 20
#endifprintf("MAX = %d\n", MAX);return 0;
}

#ifndef

#ifndef恰好和#ifdef相反

#ifndef 宏名
程序段1
#else
程序段2
#endif

如果该宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译

#include <stdio.h>#define MAX 10
int main()
{
#ifndef MAXprintf("MAX未被定义\n");
#elseprintf("MAX已被定义\n");#undef MAX#define    MAX 20
#endifprintf("MAX = %d\n", MAX);return 0;
}

#elif

#elif相当于if else语句中的else if()语句,需要注意的是该语句是#elif,而不是#elseif

#include <stdio.h>#define MAX 10
int main()
{
#if MAX==0printf("MAX=0");
#elif MAX==10printf("MAX=10\n");
#endifreturn 0;
}

#endif

#endif上面已经用过多次了,需要知道的就是#endif是#if, #ifdef, #ifndef这些条件命令的结束标志.这里就不再赘述了

上面说了8种预处理命令,下面再补充一个预处理函数(注意是函数且该函数有返回值)

defined函数(与if等结合使用)

defined函数的作用是判断某个宏是否被定义,若该宏被定义则返回1,否则返回0,该函数经常与#if #elif #else配合使用,一般使用格式为:

defined 宏名

defined (宏名)----(个人建议,还是加上括号比较好)

上文提到有#ifdef、#ifndef来判断宏名是否被定义,乍一看defined有点多余,其实不然,#ifdef和#ifndef仅能一次判断一个宏名,而defined能做到一次判断多个宏名

代码示例:

#include <stdio.h>#define MAX 10
#define MIN 2
#define AVE 6
int main()
{
#if defined (MAX) && defined (MIN) && defined (AVE)printf("三个宏已全部定义\n");
#elif MAX==10printf("三个宏未全部定义\n");
#endifreturn 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/525817.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Svg Flow Editor 原生svg流程图编辑器(二)

系列文章 Svg Flow Editor 原生svg流程图编辑器&#xff08;一&#xff09; 说明 这项目也是我第一次写TS代码哈&#xff0c;现在还被绕在类型中头昏脑胀&#xff0c;更新可能会慢点&#xff0c;大家见谅~ 目前实现的功能&#xff1a;1. 元件的创建、移动、形变&#xff1b;2…

面向对象——UML图

一、结构图 静态的 &#xff08;一&#xff09;类图 1、图 &#xff08;二&#xff09;对象图 1、如何区分对象和类&#xff1f; &#xff1a;对象 对象名&#xff1a;类 类 &#xff08;三&#xff09;包图*(带*号的是软考从来没考过的&#xff09; &#xff08;四&am…

pytorch 批量归一化BatchNorm的BatchNorm1d和BatchNorm2d理解

BatchNorm即批量归一化&#xff0c;是深度学习中经常用到的加速神经网络训练&#xff0c;加速收敛速度及稳定性的算法&#xff0c;是神经网络训练必不可少的一部分。 BatchNorm作用&#xff1a;在进行批量训练过程中&#xff0c;每个batch具有不同的分布&#xff0c;使数据分布…

光明网发布稿件多少钱?新闻投稿低价渠道推荐,附光明网价格明细表

想要在光明网发稿&#xff1f;不知道费用是多少&#xff1f;媒介多多告诉你答案&#xff01; 在当今数字化时代&#xff0c;媒体平台的重要性日益突出&#xff0c;而光明网作为国内知名的新闻门户网站&#xff0c;吸引了大量的目标受众。许多企业和个人都希望能够在光明网上投…

开源的Java报表库JasperReports介绍

JasperReports 是一个流行的开源 Java 报表库&#xff0c;它允许开发人员创建丰富的、基于 Java 的报表&#xff0c;这些报表可以与多种数据源交互&#xff0c;并且可以很容易地集成到 Java 应用程序中。JasperReports 提供了丰富的功能&#xff0c;包括数据可视化、图表、子报…

<C++>深度剖析菱形继承

​​ 文章目录 什么是菱形继承探究底层 什么是菱形继承 继承关系形如下图的继承即为菱形继承&#xff0c;或者叫钻石继承。 菱形继承的问题&#xff1a;公有继承前提下&#xff0c;如果类A中含有成员变量a&#xff0c;那么类B与类C中都有继承自类A的a&#xff0c;类D中又继承…

膜厚测量仪在半导体应用中及其重要

随着科技的不断发展&#xff0c;半导体行业已成为当今世界的核心产业之一。在这个领域中&#xff0c;半导体膜厚测量仪作为关键设备&#xff0c;其精度和可靠性对于产品质量和生产效率具有至关重要的作用。本文将详细介绍半导体膜厚测量仪的工作原理、应用领域以及其在半导体制…

【Unity】使用ScriptableObject存储数据

1.为什么要用ScriptableObject&#xff1f; 在游戏开发中&#xff0c;有大量的配置数据需要存储&#xff0c;这个时候就需要ScriptableObject来存储数据了。 很多人会说我可以用json、xml、txt&#xff0c;excel等等 但是你们有没有想过&#xff0c;假设你使用的是json&#x…

基于YOLOv8深度学习的葡萄病害智能诊断与防治系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

【蓝桥杯-单片机】基础模块LED和按键

文章目录 【蓝桥杯-单片机】Led、按键等基础模块01 前置准备&#xff08;1&#xff09;新建工程&#xff08;4&#xff09;编写程序 02 基础模块&#xff1a;LED&#xff08;0&#xff09;LED原理图&#xff08;1&#xff09;对P1整体赋值&#xff0c;控制所有的LED灯&#xff…

Unity Shader实现UI流光效果

效果&#xff1a; shader Shader "UI/Unlit/Flowlight" {Properties{[PerRendererData] _MainTex("Sprite Texture", 2D) "white" {}_Color("Tint", Color) (1, 1, 1, 1)[MaterialToggle] PixelSnap("Pixel snap", float…

【react框架】跟我一起速读Next.js官方入门教学课程文档

文章目录 前言目录结构样式方案正常引入样式文件Tailwind方案CSS Modules方案clsx方案 文字和图片优化文字图片 Pages和Layout的机制PagesLayout 通过Link组件改变路由并且拆分打包提供Hooks通过Vercel创建数据未完待续... 前言 对于那些对Next.js一无所知的前端伙伴来说&…