您好、欢迎来到现金彩票网!
当前位置:众彩 > 分支限界 >

C语言的那些小秘密之预处理

发布时间:2019-05-31 15:31 来源:未知 编辑:admin

  的一个重要知识点,它能改善程序设计的环境,有助于编写易移植、易调试的程序。因此,我们有必要掌握好命令,在自己编程的时候灵活的使用它,使得编写的程序结构优良,更加易于调试和阅读。接下来我尽可能的把中重要知识点向读者讲解清楚,使读者能够在自己以后编程的过程中熟练的使用预处理命令。

  它们之间的区别在于:文件名系统到头文件目录查找文件, 文件名则先在当前目录查找,如果没有才到头文件目录查找;当然我们也可以使用在命令行来指定头文件路径方法。还要注意就是如果在源文件包含的头文件之间出现调用的情况,那么被调用的头文件要出现在调用头文件的前面。

  相信上面这个格式大家并不陌生,下面还是来看看如何使用吧。当然在讲解之前我们的看看使用过程中的如下几个注意要点:

  (2)宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件开头部分,直到用#undef命令终止宏定义的作用域

  (3)不要在字符串中使用宏,如果宏名出现在字符串中那么将按照字符串进行处理

  我们在此主要是介绍下宏的作用域问题,当在以上代码中注释掉#undef N时,接下来的打印语句能够正常的打印出;但是当我们没有注释掉#undef N的时候就会出现error C2065: N : undeclared identifier错误,提示N没有定义。接下来看看带参数的宏的使用。

  (3)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存

  (4)宏的哑实结合(所谓的哑实结合类似于函数调用过程中实参替代形参的过程)不存在类型,也没有类型转换。

  在上面的两个宏中我们发现有这么一句代码(void) (&_x == 可能不少读者有点发懵的感觉,这啥意思呢?!其实我们细细分析就知道,首先看看“==”,这是一个逻辑表达式,它要求两边的比较类型必须一致,如果我们的&x和&y类型不一致,如一个为char*,另一个为int*,不是同一个类型,当我们使用gcc编译的时候就会出现警告信息,vc6则会报错error C2446: == : no conversion from char * to int *。这句代码(void) (&_x == 在此的功能就相当于执行一个简单的判断操作,我们用来判断x和y的类型是否一致。别小看了这句代码,如果学会了使用它会给你的代码带来不少的便捷。下面给出一个小小的事例:

  这儿的结果没有了我们之前的那句hello world!!!,可以看出这个时候函数并没有被调用,这是因为我们使用了#define print() ((void)(3)),使得之后调用函数print()转换为了一个空操作,所以这个函数在接下来的代码中都不会被调用了,就像被“冲刷掉”了一样。看到这儿你是不是想起我们之前的那篇《C语言的那些小秘密之断言》了呢,我们同样可以使用这种方法来实现断言的关闭,方法与之类似,在此就不再讲解了,有兴趣的读者可以自己试试。讲到这儿似乎应该结束了,但是细心的读者会有另外一个疑惑?在#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == _x _y ? _x : _y; })中,我们为什么要使用像typeof(y) _y = (y)这样的转换呢?而不直接使用typeof(x)==typeof(y)或者(void) (&x == x y ? x : y; 呢?如果我们使用typeof(x)==typeof(y)就好比使用了char==int一样,这是不允许的。我们使用一个typeof(y) _y = (y)这样的转换,这是为了防止x和y为一个表达式的情况,如x=i++之类的,如果不转换的话i++就多执行了几次操作,得到的不是我们想要的结果,但是如果我们使用了typeof(y) _y = (y)这样的转换,那就不会出现这样的问题了。下面我们来看看如何使用宏定义实现变参,先看看实现方法。

  看看上面的宏,其中“...”指可变参数。实现的可变参数的实现方式就是使用“...”所代表的内容替代__VA_ARGS__,看看下面的代码就知道了。

  temp在此的作用为设定输出字符串的格式,后边“...”为可变参数。现在问题来了,我们在宏定义中为什么要使用“##”呢?如果我们没有使用##会怎么样呢?看看下面的代码:

  为什么会出现上面的错误呢,现在我们来分析下,我们进行下宏替换,print(hello world\n)就变为了fprintf(stdout, hello world\n,)这样我们就发现了后面出现了一个逗号,所以导致了错误,如果有“##”就不会出现这样的错误了,这是因为如果可变参数被忽略或为空的时候,“##”操作将使预处理器去除掉它前面的那个逗号。如果存在可变参数时候,它也能正常工作。讲了“##”,我们当然也要讲讲“#”。先来看看下面一段代码:

  我们发现在运行结果中打印出了出错的文件名、函数名、以及行号。采用宏定义来检测函数的返回值是否正确,仅仅是为了体现出我们要讲解的宏,所以代码做了最大的简化工作,读者在自己编写代码时候要学会这样的检测方式。“#”的作用就是将其后面的宏参数进行字符串化操作,就是在宏变量进行替换之后在其左右各加上一个上双引号,这就使得#p变味了p我们发现这样的话刚好两边的“”就消失了。下面来看看最后一个知识点条件编译。

  条件编译命令#if、#else、#elif、#endif、#ifdef、#ifndef,条件编译指令的意思很简单,跟我们学习的if语句类似。

  功能:当“标识符”已经被#define命令定义过,则编译程序段1,否则编译程序段2。

  功能:当“标识符”未被#define命令定义过,则编译程序段1,否则编译程序段2。

  学习了条件编译指令之后,我们在调试代码的时候,就不要再随心所欲的删减代码了,如果我们不想某段代码被编译就可以使用条件编译指令来将其注释掉。如:

  就可以实现代码的注释了,需要的时候也可以将其启用,而不会为需要重新编辑代码时,发现已被删除而头疼了。

  其中值得注意的地方为,常量表达式在编译时求值,所以表达式只能是常量或者已经定义过的标识符,不能为变量,也不可以为那些在编译时候求值的操作符,如sizeof。

  看看上面的代码我们的表达式为变量a时并没有打印出来,所以我们不能在其后的表示中使用变量。如果我们使用sizeof操作符会怎么样呢?为了加深印象看看下面的代码后结果吧。

  所以我们在使用条件编译的时候要牢记这两点,常量表达式不能为变量和含有sizeof等在编译时求值的操作符。

  编译的时候我们可以在编译输出窗口中看到了输出“FDSA 已经定义过了”,通过这种方式我们可以在一些我们想要的地方输出很多我们需要的信息。

  如果我们在头文件的开头部分加入这条指令,那么就能保证我们的头文件仅仅被编译一次。

  设定字节的对齐长度,这个指令我们在《C语言的那些小秘密之字节对齐》中已经讲解了,在此不再复述。

  表示不显示M和N号的警告信息,H号警告信息只报告一次,把K号警告信息作为一个错误来处理。

  到此关于预处理的讲解就结束了。由于本人水平有限,博客中的不妥或错误之处在所难免,殷切希望读者批评指正。同时也欢迎读者共同探讨相关的内容,如果乐意交流的话请留下你宝贵的意见。

http://jigsawesl.com/fenzhixianjie/366.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有