深度学习程序的预处理

目录

引入

1、预定义符号

 1、为什么oj的编译器是clang和gcc呐?

        1、vs测试

         2、gcc测试

 2、#define

1、#define定义标识符

1、#define的花样使用

2、续行符\的使用

3、预处理文件的内容展示和为什么头文件不能重复包含

4、vs下如何生成预处理后的文件?

5、为什么#define定义的标识符常量后不能跟分号;

2、#define定义宏

1、初次使用

 2、为什么要宏体要加括号()?

 3、#define替换规则

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

注意

 4、#和##

1、引入1

2、引入2​编辑

3、#详解(解决函数不能打印传值前的变量名称以及引入1具体作用)

 4、##详解

 5、带副作用的宏参数

6、宏和函数的对比

引入:

宏的缺点:

宏厉害的表现

宏和函数的对比图

 7、命名约定

3、undef

4、命令行定义

5、条件编译

常见的条件编译指令

1.if...endif

 2.多个分支的条件编译

3.判断标识符是否被定义

第一种写法(defined()、!defined())

第二种写法(ifdef、ifndef)

 一般条件需要执行的内容不用像if那样加大括号{}。

4.嵌套指令

6、文件包含

防止头文件多次包含的两种方法

1.本地文件包含

2.库文件包含

7、其他预处理指令


引入

预处理是文本操作,博主认为预处理能够省去很多冗余代码,也能够使用标识符常量来更好的表明代码含义,深度学习预处理能够提高代码玩家们的程序设计能力。

         预处理前                                                                                预处理后

1、预定义符号

这些预定义符号都是语言内置的

        

 

这些预定义符号可以用来写日志。

 1、为什么oj的编译器是clang和gcc呐?

使用__STDC__来测试编译器是否遵循C标准

vs不遵循,gcc遵循 clang也遵循等等。

        1、vs测试

         2、gcc测试

 

 2、#define

1、#define定义标识符

1、#define的花样使用

语法:
        #define name stuff

例如:

        

#define 定义的内容在编译的预处理阶段会进行文本替换。 

下面的是因为有的语言switch case 不使用break,代码玩家就设计了这种写法,省事了许多。

2、续行符\的使用

注意:续行符的后面是不能有其他东西的!

3、预处理文件的内容展示和为什么头文件不能重复包含

前一万多行都是包含的头文件展开

这里的#define定义的标识符常量直接进行了文本替换

所以,尽量避免重复包含相同的头文件,时间和空间都会影响!

4、vs下如何生成预处理后的文件?

使用完成后要把选项改回去,不然还是编译不了,只能编译到预处理后的结果。

预处理后的文件在当前项目的Debug文件中后缀为.i。

5、为什么#define定义的标识符常量后不能跟分号;

 这个虽然没有报错,但是是因为文本替换后return 0前有两个分号,第二个分号是个空语句,所以不起什么作用。

 从上可以看出#define为什么不要加‘;’,存在很大的隐患!

2、#define定义宏

语法:#define 宏名(参数) 宏体

注意:参数的(左括号一定要紧挨着宏名,否则#define SUM (a,b) ((a)+(b))就变成了标识符了。

1、初次使用

上述展示是否有问题呐?向下看解读!

 2、为什么要宏体要加括号()?

 宏替换,不是先计算后替换,而是先替换后计算

上图解析:宏替换X*X   ->    5+1*5+1=11

正确写法:

 因为存在优先级的原因,宏体当中的因子如果是一个整体就要加括号(),外层也要加(),为什么呐?再给大家一个案例:

为什么宏体外还要加大括号?

还是优先级的原因导致,正确写法:

 

 3、#define替换规则

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意

1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归(例如SUM自己调用自己,不要把宏和函数混淆)。

2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索(字符串里的内容如果出现标识符常量时不会被替换)。

 4、#和##

1、引入1

        

打印内容是什么?

这个字符串是会合并在一起的。

2、引入2

 这个代码的目的是为了表明函数不能够把变量名打印出来,只能够吧变量值打印出来,x是固定的。

3、#详解(解决函数不能打印传值前的变量名称以及引入1具体作用)

使用 # ,把一个宏参数变成对应的字符串!

这里我使用上述的问题来解释#的厉害!

#n相当于"n",因为宏替换的特性是文本替换,我变量名时,检测参数后,文本会插入到本身位置

print(a)-->printf("the value of""a"" is %d",n);

基于引入1进行的具体实践

如果类型不同需要打印呐?宏替换!字符串合并!

 4、##详解

 在宏体中将相邻的两个符号合并成一个符号

 5、带副作用的宏参数

大家先算一下下列代码m、a、b打印的值

 

 答案解析:后置++是先使用后++,宏替换后,判断5>4?先使用原来的a和b,然后++,此时a是6,b是5,逻辑上该走?a++:这里的a++,先使用,所以返回值是6,然后++。后面的b++没执行,所以最终a加了两次,b加了一次,m = 6,a =7,b = 5

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

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

所以我们在设计宏的时候要避免带有副作用的宏参数。

6、宏和函数的对比

宏的封装能够大大减少代码量,更加方便。宏处理小程序比函数快和小。

引入:

比如:比较两个数,求最大值。

 从步骤明显看出宏对小程序的easy,并且宏是没有类型检查的,函数有,对于重复操作的逻辑,一个宏就可以搞定不同类型,所以宏比函数在程序的规模和速度方面更胜一筹。

更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反

之这个宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

宏的缺点:

当然和函数相比宏也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

2. 宏是没法调试的。

3. 宏由于类型无关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏厉害的表现

方便、简洁!

宏和函数的对比图

在C++的内联函数是把宏和函数合体了。

 7、命名约定

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

 

 但并不是全都按照这个规则。例如:ofsetof是个宏,getchar有些实现也是宏

3、undef

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

4、命令行定义

 在命令行定义一个标识符常量

vs不好演示,这里使用Linux环境演示

 编译错误,缺少SZ,并且for循环里定义int i仅仅在C99标准模式下使用,所以在for循环外定义int i就行了。

 所以直接在命令行添加标识符SZ

编译指令:gcc -D ARRAY_SIZE=10 programe.c

 执行结果展示

 这样就可以在命令行随意设置标识符常量值。

5、条件编译

满足条件就编译,不满足就不编译

常见的条件编译指令

以endif来结束一个条件编译。

1.if...endif

 也能看出来条件为假,VS会将此部分颜色变淡

 2.多个分支的条件编译

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

3.判断标识符是否被定义

第一种写法(defined()、!defined())

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
 

第二种写法(ifdef、ifndef)

 

 一般条件需要执行的内容不用像if那样加大括号{}。

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

6、文件包含

防止头文件多次包含的两种方法

1.本地文件包含

#include "filename"

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

2.库文件包含

#include <filename.h>

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

7、其他预处理指令

#error
#pragma
#line
...
#pragma pack()在结构体部分介绍。

参考《C语言深度解剖》学习

-------------------------------------------------本章内容完-----------------------------------------------------------------

欢迎大家在评论区留言讨论,补充知识,让内容更加丰富,让细节更加饱满,让知识海洋不断扩大!

  • 35
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值