【C语言进阶】预处理详解

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

引言

对预处理的相关知识进行详细的介绍

 【C语言进阶】预处理详解,C语言进阶,c语言,算法,linux               ✨ 猪巴戒:个人主页✨

               所属专栏:《C语言进阶》

        🎈跟着猪巴戒,一起学习C语言🎈

目录

引言

预定义符号

#define定义常量

#define定义宏

带有副作用的宏参数

宏替换的规则

宏函数的对比

#和## 

#运算符

##运算符

命名约定

#undef

条件编译

头文件的包含

嵌套文件的包含


预处理又叫预编译,预处理是编译过程中的第一个步骤,主要是处理以#开头的预编译指令。

        

预定义符号

C语言设置了一些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。

__FILE__    //进行编译的源文件的文件名
__LINE__    //文件当前的行号
__DATE__    //文件被编译的日期
__TIME__    //文件被编译的时间
__STDC__    //如果编译器崔寻ANSI C,其值为1,否则就是未定义。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __TIME__);
	printf("%s\n", __DATE__);

	return 0;
}

【C语言进阶】预处理详解,C语言进阶,c语言,算法,linux

         

#define定义常量

#define name stuff

在预编译的过程中,会用stuff来代替name。

比如下面的MAX就是要被替换的name,在预处理的阶段,MAX会被替换成100。

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

后面的stuff不仅可以是数字,也可以是一个长句。

#define PRINT printf("hello world!\n")
int main()
{
	PRINT;
	return 0;
}

【C语言进阶】预处理详解,C语言进阶,c语言,算法,linux

        

#define定义宏

#define定义宏,不仅可以完成语句的替换,还可以将参数传进去。

#define name(parament_list) stuff

parament_list表示的就是参数,有了参数的加入,stuff的表达更加丰富。

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

比如说:

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

这里的SQUARE(x)中的 x 就是参数,将参数a代替x,然后带入后面的表达式。

这里的SQUARE(a),经过替换就是 a+a. 

【C语言进阶】预处理详解,C语言进阶,c语言,算法,linux

但是这种定义宏的写法是存在瑕疵的,#define定义宏的书写一定要在各个参数上加上括号,在整体的表达式加上括号。

下面的书写方式才是正确的。

#define SQUARE(x) ((x)+(x))

接下来看两个错误的示范:

1.

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

这里的本来预想的结果是11*11=121,但是预处理并不是首先将参数进行计算,而是简单地将参数进行替换,SQUARE(a+1)会被替换成a+1*a+1(10+1*10+10),所以最后地结果是21。

解决办法就是

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

2.

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

这里本来预想的结果是200,但是将参数进行替换,10*SQUARE(a)->10*a+a,结果就是110.

解决办法就是

#define SQUARE(x) (x+x)

总结:

为了避免宏定义中,参数中操作符或临近操作符之间不可预料的相互作用。我们应该毫不吝啬地在参数和参数表达式整体加上括号。

        

带有副作用的宏参数

刚刚讲了定义宏是什么,接下来是带有副作用的宏参数。如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

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

多次使用宏定义,那就会导致x的值发生变化,最终导致的结果就会大相径庭。

#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
	int x = 4;
	int y = 5;
	int z = MAX(x++, y++);
	printf("%d %d %d\n", x, y, z);
	return 0;
}

【C语言进阶】预处理详解,C语言进阶,c语言,算法,linux

 这里x++,y++作为参数,MAX(x++,y++)会被替换成((x++)>(y++)?(x++):(y++)),首先x++和y++比较大小,x的值会变成5,y的值会变成6,那么后面的y++表达式就会被执行,因为是前置++,先执行后++,所以结果是6,但是y的值变成了7。

        

宏替换的规则

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

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

注意:

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

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

        

宏函数的对比

属性 #define定义宏 函数
代码长度 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 函数代码只出现与一个地方,每次执行函数调用的都是同一个地方
执行速度 更快 存在函数的调用和返回函数栈帧的时间,速度会慢一些
操作符优先级 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。 函数参数只在函数调用的时候直接将参数的值传递给函数。
带有副作用的参数 参数可能被代替到宏体中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预料的结果 函数参数只在传参的时候对参数进行求值
参数类型 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用与任何参数类型 函数的参数是与类型有关的,不同的参数类型就要不同的函数,所以比较严格
调试 宏是不方便调试的 函数是可以逐语句调试
递归 宏是不能递归的  函数是可以递归的

         

#和## 

#运算符

#运算符将宏的一个参数转换为字符串字面量,它仅允许出现在带参数的宏的替换列表中。

#运算符所所执行的操作可以理解为“字符串化”。

#define PRINT(n) printf("the value of "#n"is %d",n)

之所以通过#运算符进行字符串化,是因为在字符串中,#define是不会进行替换的。

printf("the value of ""a""is %d",a);

##运算符

##运算符可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创立标识符。##被称为记号粘连。

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

float float_max(float x, float y)
{
	return x > y ? x : y;
}

由于类型的不同,这个比较大小的功能必须分为两个函数进行实现。

下面是一个定义宏,通过下面的定义宏将类型作为参数,替换函数中的类型。

其中\是换行符,type_max如果直接书写就是函数名称了,分开书写,然后用##来粘合,就可以实现替换函数名。

#define GENERIC_MAX(type)\
type type##_max(type x,type y)\
{\
    return (x>y?x:y);\
}

GENERIC_MAX(int)
GENERIC_MAX(float)//等同于上面两个函数的代码

         

命名约定

一般来说函数和宏语法很相似,所以语言本身没法帮助我们区分二者。

那我们平时的一个习惯是

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

        

#undef

我们直到#define name就是定义一个常量,#undef就是取消宏定义。

#define MAX 100
#undef MAX     //这里往后MAX就取消宏定义,不能直接使用MAX了

        

条件编译

条件编译指令有

#ifdef #if #endif #elif #else #ifndef

#if 表达式 语句: 后面加表达式,和if的意思一样,但是这个步骤是在预处理的时候完成的,如果表达式为假,就不会出现在后面的文件中。

#elif 表达式 语句:和else if的意思一样,和#if搭配使用。

#else 语句:和else的意思一样。

#endif :不同的是我们在使用完条件编译指令后,要加上#endif,表示预处理完成。

#ifdef  :  意思:如果后面被定义,就执行后面的语句。

#ifndef : 意思:如果后面没被定义,就执行后面的语句。

int main()
{
    #ifdef MAX
        printf("haha\n");
    #endif
    return 0;
}
#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

         

头文件的包含

#incldue <filename>

库函数通过<>包含,会在指定的标准头文件位置查找头文件。

#include "filename"

通过”“包含,会先在源文件所在目录下查找,如果头文件未找到,编译器就会像查找库函数文件的方式一样去标准的头文件位置查找。

所以库函数也可以用”“来包含,但是这样查找的效率就慢,也不容易区分库文件和本地头文件。

        

嵌套文件的包含

#include指令会将头文件进行包含,在预处理阶段实现,但是如果是多个源文件的交互合作,那么程序会对同一个头文件多次包含,那么就会在大大增加程序包含的代码量。

所以我们通过条件编译解决头文件多次引用问题:

ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif 

//或者
#pragma once

当头文件被第一次引用时,就会定义__TEST_H__,那么在第二次打开的时候,__TEST_H__就已经被定义,因为#ifndef,头文件就不会再次包含。文章来源地址https://www.toymoban.com/news/detail-818918.html

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

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

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

相关文章

  • C语言预处理详解

    上一篇博客中我们讲了C语言的编译与链接,在编译过程中有三个小阶段:预处理、编译、汇编。 本篇博客将详细讲述预处理部分的有关知识点 。 在C语言中,C语言本身设置了⼀些预定义符号,可以直接使⽤ ,预定义符号的处理也是在预处理期间进行的。 在这里介绍几个常

    2024年04月15日
    浏览(57)
  • C语言:预处理详解

    创作不易,来个三连呗! C语⾔设置了⼀些预定义符号, 可以直接使⽤ ,预定义符号也是在预处理期间处理的。 __FILE__ //进⾏编译的源⽂件 __LINE__ //⽂件当前的⾏号 __DATE__ //⽂件被编译的⽇期 __TIME__ //⽂件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

    2024年01月19日
    浏览(49)
  • 详解C语言—预处理

    目录 一、预处理 1、预定义符号介绍 2、预处理指令 #define #define 定义标识符:  #define 定义宏: #define 替换规则 3、预处理操作符# 4、预处理操作符## 5、带副作用的宏参数 6、宏和函数对比 二、命名约定 三、预处理指令 #undef 四、命令行定义 五、条件编译  1、单分支#if:

    2024年02月08日
    浏览(57)
  • 【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日
    浏览(43)
  • 【C语言:编译、预处理详解】

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

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

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

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

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

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

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

    2024年04月16日
    浏览(43)
  • 【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日
    浏览(38)
  • C语言中程序的编译(预处理操作)+链接详解(详细介绍程序预编译过程)

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

    2023年04月09日
    浏览(39)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包