详解C语言—预处理

这篇具有很好参考价值的文章主要介绍了详解C语言—预处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、预处理

1、预定义符号介绍

2、预处理指令 #define

#define 定义标识符: 

#define 定义宏:

#define 替换规则

3、预处理操作符#

4、预处理操作符##

5、带副作用的宏参数

6、宏和函数对比

二、命名约定

三、预处理指令 #undef

四、命令行定义

五、条件编译 

1、单分支#if:

2、多分支#if:

3、判断是否被定义

4、嵌套指令

六、文件包含

头文件被包含的方式:

嵌套文件包含:

小结


一、预处理

1、预定义符号介绍

__FILE__      //进行编译的源文件

__LINE__     //文件当前的行号

__DATE__    //文件被编译的日期

__TIME__    //文件被编译的时间

__STDC__    //如果编译器遵循ANSI C,其值为1,否则未定义
这些预定义符号都是语言内置的,程序员可直接使用。

#include <stdio.h>

int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	//printf("%d\n", __STDC__);//当前VS是不支持ANSI C
	return 0;
}

详解C语言—预处理,详解C语言,c语言,开发语言

2、预处理指令 #define

#define 定义标识符: 

#define 宏名 值或代码片段

#define 是用来定义宏的预处理指令。宏是一种在源代码中定义的符号,可以用来代表一个常量、一个表达式或一段代码片段。宏的定义通常在源代码文件的顶部,以便在编译之前被预处理器处理。  

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ ,  \
                          __DATE__,__TIME__ )  

#define 定义宏:

带参数的宏:宏也可以带有参数,允许你创建可重用的代码片段。例如:

#define SQUARE(x) ((x) * (x))

 SQUARE 宏接受一个参数x,并返回x的平方。在使用时,你可以这样调用宏:int result = SQUARE(5);,它会被展开为 int result = ((5) * (5));

 接下来我们看一个例子:

#define SQUARE(x)  x * x

如果我们写成这样不带括号, 对函数传入参数 5 + 1 :SQUARE( 5 + 1 );
得到的结果将是 11,而不是36,计算过程:5 + 1 * 5 + 1 得到结果为11。

#define SQUARE(x) (x) * (x)

当我们加上括号,计算过程:(5+1)*(5+1) 得到结果为36.  

这样定义看似没有问题了,我们再看一个例子: 

#define SQUARE(x) (x) + (x)
int main()
{
    int a = 5;
    printf("%d\n" ,10 * DOUBLE(a));
    return 0;
}

 这种情况下计算过程为 10 * (5) + (5)); 得到结果为55,并不是我们想要的100。

这时再对参数整体添加一对括号即可解决问题:

#define SQUARE(x) ((x) * (x))

通过这几个例子我们理解了为什么要加那些那些括号。 

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

#define 替换规则

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

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

注意:

宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

  • 解释:当预处理器搜索由#define定义的符号时,它并不会搜索或替换字符串常量中的内容。这是因为字符串常量在C中是不可更改的文本序列,不应受到#define定义的符号的影响。
  • #include <stdio.h>
    
    #define VALUE 42
    
    int main() {
        int x = VALUE;
        printf("The value of x is: %d\n", x);
        
        printf("This is a string with VALUE: \"%d\"\n", VALUE);
        
        return 0;
    }

    在上面的示例中,我们定义了一个名为VALUE的符号,它被定义为整数42。然后,我们在程序中使用了这个符号。请注意以下两个地方:

  • 在第5行,我们使用了VALUE来初始化整数变量x。在这里,#define定义的符号VALUE被替换为42,因此x的值是42

  • 在第7行,我们在字符串常量中包含了VALUE。在这里,VALUE并不会被替换为42,而是保持不变。因此,字符串中的内容是"This is a string with VALUE: \"VALUE\""

  • 所以,尽管#define定义的符号VALUE在程序中的某些地方被替换为其定义的值,但它并不会影响字符串常量中的内容。字符串常量中的文本保持不变,不受符号替换的影响。这是为了确保字符串常量的内容始终保持不变。

3、预处理操作符#

我们由一个例子来引入讲解: 

int main()
{
	int a = 20;
	printf("the value of a is %d\n", a);
	
	return 0;
}

如何通过#define实现上述代码中printf语句的相同功能呢?

通过前面的学习可以很轻松实现#define定义宏,代码如下: 

#define Print(n) printf("the value of n is %d",n)
int main()
{
	int a = 20;
	printf("the value of a is %d\n", a);
	Print(a);
	return 0;
}

详解C语言—预处理,详解C语言,c语言,开发语言

但我们的输出语句是the value of n is 20,并不是我们传入的参数 a ,

那怎么修改 #define 把参数插入到字符串?

我们通过添加双引号将“参数前后字符串”分隔成“独立的两个字符串”。再在参数前添加 # 使其在宏展开时被替换为参数 n

#define Print(n) printf("the value of "#n" is %d",n)
int main()
{
	int a = 20;
	printf("the value of a is %d\n", a);
	Print(a);
	return 0;
}

详解C语言—预处理,详解C语言,c语言,开发语言

如果我们要输出不同格式,那怎么修改#define呢?

添加一个参数代替格式化字符即可解决。

#define Print(n,format) printf("the value of "#n" is " format "\n",n)
int main()
{
	float f = 4.5f;
	printf("the value of a is %f\n", f);
	Print(f, "%f");
	return 0;
}

详解C语言—预处理,详解C语言,c语言,开发语言

4、预处理操作符##

##可以把位于它两边的符号合成一个符号。

它允许宏定义从分离的文本片段创建标识符。

看下面的例子就明白了: 

#define CAT(x,y) x##y
int main()
{
	int Class110 = 110;
	printf("%d\n", Class110);
	printf("%d\n", CAT(Class, 110));
	return 0;
}

两种输出形式结果一样: 详解C语言—预处理,详解C语言,c语言,开发语言 

5、带副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能 出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

例如:

x+1;//不带副作用
x++;//带有副作用

我们看下面的例子:

#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
	int a = 5;
	int b = 6;
	int c = MAX(a++, b++);
	printf("a=%d\nb=%d\nc=%d\n", a, b, c);
	return 0;
}

 替换宏的文本:c=MAX( (a++)>(b++) ? (a++):(b++) ) 首先判断 ab 的大小,之后a与b自增:a=6 b=7b 大于 a 则 c 被赋值为MAX的返回值 b的值7 即( c=7 ), 赋值之后b进行自增,b=8

输出结果: 

详解C语言—预处理,详解C语言,c语言,开发语言

6、宏和函数对比

宏通常被应用于执行简单的运算。

比如:在两个数中找出较大的一个

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

int Max(int x, int y)
{
	return (x > y ? x : y);
}

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

原因有二:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。

所以宏比函数在程序的规模速度方面更胜一筹详解C语言—预处理,详解C语言,c语言,开发语言

2. 更为重要的是函数的参数必须声明为特定的类型。

所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。

宏是类型无关的

宏的缺点:当然和函数相比宏也有劣势的地方:

  1. 宏由于类型无关,也就不够严谨。
  2. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  3. 宏是没法调试的。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
  5. 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。比如下面的例子:
#define Malloc(num,type) (type*)malloc(num*sizeof(type))

int main()
{
	int* p = (int*)malloc(126 * sizeof(int));
	int* p = Malloc(126, int);
	return 0;
}

 宏和函数详细对比: 详解C语言—预处理,详解C语言,c语言,开发语言

二、命名约定

一般来讲函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者 

一般习惯宏名全部大写,函数名不要全部大写。

#define MAX(x,y) ((x)>(y)?(x):(y))

int Max(int x, int y)
{
	return x > y ? x : y;
}

三、预处理指令 #undef

这条指令用于移除一个宏定义。  

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

 示例:

#define MAX(x,y) ((x)>(y)?(x):(y))

int main()
{
	int c = MAX(3, 5);
	printf("%d\n", c);
#undef MAX
	c = MAX(5, -5);
	printf("%d\n", c);
	return 0;
}

 当我们运行时,程序报错

详解C语言—预处理,详解C语言,c语言,开发语言

我们想要修改 c 的值,宏MAX已经失效了,所以程序报错“MAX”未定义。 

四、命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。

在gcc环境下演示:

 详解C语言—预处理,详解C语言,c语言,开发语言

详解C语言—预处理,详解C语言,c语言,开发语言

 gcc 文件名.c -D SZ=10 -o 文件名,通过这种操作在命令行中定义符号。

五、条件编译 

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
#if 是一个预处理指令,用于根据指定的条件编译代码块。
#if 常量表达式
 //...这里是条件为真时要编译的代码
#endif

在这个结构中,#if 后面可以跟一个常量表达式,如果这个表达式的值为非零(true),那么就会编译 #if 和 #endif 之间的代码,否则,这部分代码会被预处理器直接忽略,不参与编译。

常量表达式是在预处理阶段求值的,它通常包括常量、宏和运算符,但不能包括任何需要运行时计算的东西。

1、单分支#if:

#define DEBUG 1

#if DEBUG
   // 这里是调试时要编译的代码
#else
   // 这里是非调试时要编译的代码
#endif
  • 在这个例子中,如果 DEBUG 宏被定义且值不为零,那么将会编译第一部分的代码,否则编译第二部分的代码。这种条件编译的方式常用于在调试和发布版本之间切换代码,或者根据不同的平台选择性地包含或排除特定的代码块。
  • 需要注意的是,#if 只是一种条件编译的方式,而且它只能根据常量表达式的真假来进行选择性编译,不能用于运行时的条件判断。如果需要在运行时进行条件判断,应该使用 if 语句。

 在gcc环境中,我们可以看到经过预处理后,没有满足条件的语句,所以没有语句参与编译。

详解C语言—预处理,详解C语言,c语言,开发语言

2、多分支#if:

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

在gcc环境中,我们可以看到经过预处理后,只有满足条件的 printf("hehe\n"); 参与编译。

详解C语言—预处理,详解C语言,c语言,开发语言

if-else 什么条件执行什么代码,#if-#else-#endif 什么条件编译什么代码 

3、判断是否被定义

#if defined(symbol)
#ifdef symbol

#if ! defined(symbol)
#ifndef symbol

这些都是条件编译的预处理指令,用于根据符号是否被定义来选择性地编译代码块。

#if defined(symbol)& #ifdef symbol( #if defined(symbol) 的简写)

这个指令用于检查是否已经定义了名为 symbol 的宏。

如果 symbol 已经被定义,那么 #if defined(symbol) 就被视为真(非零),相应的代码块会被编译。

如果 symbol 没有被定义,那么 #if defined(symbol) 就被视为假(0),相应的代码块会被忽略。

#if !defined(symbol)& #ifndef symbol(#if !defined(symbol) 的简写)

这个指令用于检查是否没有定义名为 symbol 的宏。

如果 symbol 没有被定义,即条件为真,那么 #if !defined(symbol) 就被视为真(非零),相应的代码块会被编译。

如果 symbol 已经被定义,即条件为假,那么 #if !defined(symbol) 就被视为假(0),相应的代码块会被忽略。

示例:

define WIN 0//无论是0还是1,都是被定义

int main()
{
#if defined(WIN)
	printf("windows\n");
#endif


	return 0;
}

输出结果:详解C语言—预处理,详解C语言,c语言,开发语言

因为WIN被定义,所以运行时,printf("windows\n"); 参与编译:

详解C语言—预处理,详解C语言,c语言,开发语言

我们还可以写成这种形式,效果与上面的代码一样。

#define WIN 1

int main()
{
#ifdef WIN
	printf("windows\n");
#endif

	return 0;
}

因为WIN被定义,所以运行时,printf("windows\n"); 参与编译: 

详解C语言—预处理,详解C语言,c语言,开发语言

4、嵌套指令

我们可以将#if defined&#ifdef 和#if嵌套使用: 

#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. 首先,代码检查是否定义了宏 OS_UNIX,如果定义了,表示代码正在运行在类Unix操作系统上。如果没有定义,表示不是Unix系统。

  2. 如果 OS_UNIX 宏被定义,那么代码会进入第一个条件分支,执行与Unix系统相关的代码。

    如果宏 OPTION1 也被定义,就会调用 unix_version_option1() 函数。                                 如果宏 OPTION2 也被定义,就会调用 unix_version_option2() 函数。
  3. 如果 OS_UNIX 宏没有被定义,表示不是Unix系统,那么代码会检查是否定义了宏 OS_MSDOS,如果定义了,表示代码正在运行在MS-DOS操作系统上。如果没有定义,表示不是MS-DOS系统。

  4. 如果 OS_MSDOS 宏被定义,那么代码会进入第二个条件分支,执行与MS-DOS系统相关的代码。

    如果宏 OPTION2 也被定义,就会调用 msdos_version_option2() 函数。

六、文件包含

头文件被包含的方式:

本地文件包:

#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。

 Linux环境的标准头文件的路径:

/usr/include

 VS环境的标准头文件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

库文件包含 :

#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的,可以
但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

嵌套文件包含:

如果出现这样的场景:详解C语言—预处理,详解C语言,c语言,开发语言

  • comm.hcomm.c是公共模块。
  • test1.htest1.c使用了公共模块。
  • test2.htest2.c使用了公共模块。
  • test.htest.c使用了test1模块和test2模块。
这样最终程序中就会出现两份 comm.h 的内容。这样就造成了文件内容的重复。
如何解决这个问题?
答案:条件编译。
每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H__
或者:
#pragma once
就可以避免头文件的重复引入。

小结

希望这篇文章可以帮助你学习和复习预处理与条件编译相关知识,切记!!一定要动手操作!!

代码的理解从敲代码开始哦。只有自己实践过,知识才是属于你的!!!文章来源地址https://www.toymoban.com/news/detail-718868.html

到了这里,关于详解C语言—预处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言】预处理详解

             本文目录 1 预定义符号 2 #define 2.1 #define 定义标识符 2.2 #define 定义宏 2.3 #define 替换规则 2.4 #和## 2.5 带副作用的宏参数 2.6 宏和函数对比 2.7 命名约定 3 #undef 4 命令行定义 5 条件编译 6 文件包含 6.1 头文件被包含的方式 6.2 嵌套文件包含 这些预定义符号都是语言内置

    2024年02月14日
    浏览(39)
  • 【C语言进阶】预处理详解

    对预处理的相关知识进行详细的介绍                  ✨  猪巴戒 :个人主页✨                 所属专栏 :《C语言进阶》         🎈 跟着猪巴戒 ,一起学习C语言🎈 目录 引言 预定义符号 #define定义常量 #define定义宏 带有副作用的宏参数 宏替换的规则 宏函数的

    2024年01月23日
    浏览(42)
  • 【C语言:编译、预处理详解】

    我们都知道,一个程序如果想运行起来要经过编译、链接然后才能生成.exe的文件。 编译⼜可以分解为三个过程: 预处理(有些书也叫预编译)、 编译 汇编 预处理阶段 主要处理那些源文件中以#开始的预编译指令。比如:#include,#define,处理的规则如下: 删除所有的注释

    2024年02月03日
    浏览(48)
  • 【C语言基础】:预处理详解(一)

    一、预定义符号 在C语言中设置了许多的预定义符号,这些预定义符号是可以直接使用的,预定义符号也是在预处理阶段进行处理的。 常见的预定义符号 : 【示例】 : 我们在VS上使用 _ _ STDC _ _ 会发现显示未定义,这也就说明VS的编译器是不完全遵循 ANSI C 的,为了展示效果

    2024年04月22日
    浏览(36)
  • 【c语言】详解c语言#预处理期过程 | 宏定义前言

    c语言系列专栏: c语言之路重点知识整合   创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡𖥦)!!  主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ 代码编译到执

    2024年02月01日
    浏览(46)
  • 初始C语言最后一章《编译、链接与预处理详解》

    感谢老铁们的陪伴和支持,初始C语言专栏在本章内容也是要结束了,这创作一路下来也是很不容易,如果大家对 Java 后端开发感兴趣,欢迎各位老铁来我的Java专栏!当然了,我也会更新几章C语言实现简单的数据结构!不过由于我是Java 技术栈的,所以如果以后有机会学习C

    2024年04月16日
    浏览(39)
  • 【C语言】预处理详解:#define的各种使用方法

    目录 1.#define定义标识符 1.1赋值 1.2   定义 1.3用更形象的符号来替换一种实现 1.4   加续行符换行 1.5#define定义宏 1.6  #define替换的规则 注意事项 2.#和## 3.带有副作用的宏参数 4.函数和宏的对比 #define定义标识符的用法非常简单 name可以由自己来命名,尽量取一些有意义

    2024年02月15日
    浏览(37)
  • C语言中程序的编译(预处理操作)+链接详解(详细介绍程序预编译过程)

    今天我们来学习C语言中程序的编译和链接是如何进行的。 在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码。 本文主要是介绍预编译阶段的相关知识。 1.组成一个程

    2023年04月09日
    浏览(35)
  • C语言之预处理命令使用详解----#if、#endif、#undef、#ifdef、#else、#elif

    查了好久才知道的这个原理,记录一下吧! 参考教程 预处理命令 在接触#if、#undef这类预处理指令前,大部分都都接触过#define、#include等预处理命令,通俗来讲预处理命令的作用就是在编译和链接之前,对源文件进行一些文本方面的操作,比如文本替换、文件包含、删除部分

    2024年02月02日
    浏览(41)
  • C语言——程序环境和预处理(再也不用担心会忘记预处理的知识)

    先简单了解一下程序环境,然后详细总结翻译环境里的编译和链接,然后在总结编译预处理。 在 ANSI C 的任何一种实现中,存在两个不同的环境 翻译环境:这个环境中源代码被转换为可执行的机器指令。 执行环境:执行二进制代码。 计算机如何执行二进制指令? 我们写的C语

    2024年02月09日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包