第九章 编译预处理和位运算

预处理命令是指C语言设置的像#include这样的在源程序编译之前必须进行处理的命令。C语言的预处理命令均以 # 打头,末尾不加分号,以区别于C语句。

9.1 宏定义

  宏定义就是用一串字符代替名字,这串字符既可以是常数也可以是任何字符串,甚至是可以带参数的宏。例如:

  #define PI 3.14159   PI称作宏名,3.14159称作宏体。

同一源文件,宏名不可以相同。多个源文件宏名可以重名。宏的作用域就是所在的源文件。

9.1.1 符号常量宏定义

  符号常量宏定义,其宏体为常量表达式。预处理时,将把程序中该宏体定义之后的所有宏名用宏体替换。这是一种简单的字符替换,不进行任何计算。

  使用宏替换的好处:

  1)提高了程序的可读性。

  2)易修改性。

9.1.2 带参数的宏定义

  #define  name(parameter - list)  stuff

  对于带参数的宏定义,在编译预处理时,不仅要进行字符串替换而且还要进行参数替换。宏体不仅可以是字符串常数,也可以是表达式或语句组成的字符串。参数列表的左括号必须与宏名紧邻。如果有空白参数列表就会被解释成为stuff的一部分。

注意:

  1)宏名与宏体之间应以空格间隔,所以宏名中不能含有空格。

  2)宏名不能用引号括起来。否则将不进行宏替换。

  3)较长的定义在一行中写不下时,除最后一行外,每一行都在末尾加一个反斜杠 表示要续行,如下所示:

#define DEBUG_PRINT printf("File %s line %d:"
                            " x=%d,y=%d,z=%d",
                            __FILE__,__LINE__,
                            x,y,z)

也可以使用#define指令把一系列语句插到程序中,如下所示:

#define PROCESS_LOOP          
    for(int i=0;i<10;i++)     
    {                         
            sum+=i;           
            if (i > 0)        
                prod *= i;    
    }

  4)对带参数宏定义,宏体及其各个形参应该用圆括号括起来。

  5)宏定义可以写在源程序中的任何地方,但一定要写在程序中引用该宏之前。通常写在一个文件之首。

宏与函数的区别:

  1)时空效率不同。宏替换时要宏体去替换宏名,往往使程序体积膨胀,加大系统的存储开销。但是它不像函数调用要进行参数传递、保存现场、返回等操作,所以时间效率比函数高。所以,通常对简短的表达式,以及调用频繁、要求快速响应的场合,采用宏比采用函数合适。

  2)宏虽可以带参数,但宏替换过程中不像函数那样要进行参数值的计算、传递及结果返回等操作;宏替换只是简单的字符替换不进行计算。因此,一些过程中是不能用宏代替函数的,如递归调用等。

9.1.3 宏定义解除

  宏定义具有全局作用域。为了限制宏的使用范围,可以使用编译预处理命令 #undef来解除已有的宏定义,其一般格式:

  #undef 标识符

其中,标识符是在此之前使用#define定义过的符号常量或带参数的宏,在此命令之后,该宏定义被解除。

9.2文件包含

  文件包含是通过命令#include 把已经进入系统的另一个文件的整个内容嵌入进来。实际是宏替换的延伸。预处理包含这条指令,并用包含文件的内容取而代之。

  文件包含有两种格式:

  格式1:#include "文件标识"

  文件标识中包含有文件路径。按这种格式定义时,预处理程序首先在原来的源文件目录中检索该指定的文件;如果没有找到,则按系统指定的标准方式检索其它文件目录,直到找到为止。

  格式2:#include<文件名>

  按这种格式定义时,预处理程序只按系统规定的标准方式检索文件目录

对自己定义的非标准文件使用格式1,而对系统提供的标准文件常使用格式2。

解决多重包含可以使用条件编译,如果所有头文件都像下面这样编写:

#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1
/*
** All the stuff that you want in the header file
*/
#endif 

那么多重包含的危险就被消除了。当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H 被定义为1。如果头文件被再次包含,通过条件编译,它的所有内容被忽略。

9.3 条件编译

  条件编译是在编译文件之前,根据给定的条件决定编译的范围。满足一定条件时,编译其中的一部分语句,在不满足条件时编译另一部分语句,这就是所谓的 “条件编译”。

9.3.1 #if…#else…#endif指令

  #if 表达式

    程序段 1

  #else

    程序段 2

  #endif

  它的功能是当指定的表达式值为真时,编译程序段1,否则编译程序段2,其中的程序段可以是C语言中合法的语句或命令行。#else部分可以省略,但#if与#endif一定要配对使用。

9.3.2 #ifdef…#else…#endif指令

  #ifdef 标识符

    程序段1

  #else

    程序段2

  #endif

  其作用为:若标识符已被定义(一般指用#define 定义)则编译程序段1,否则编译程序段2。

9.3.3 #ifndef…#else…#endif指令

  #ifndef 标识符

    程序段1

  #else

    程序段2

  #endif

  其作用为:若标识符未被定义,则编译程序段1,否则编译程序段2,与前一种命令形式恰好相反。

  C语言规定,条件编译中#if后面的条件必须是常量表达式,即表达式中参与运算的量必须是常量,在大多数情况下使用由#define定义的符号常量。

9.3.4 条件编译的作用

  1.便于程序的调试

  2.增强程序的可移植性

  3.提高程序的效率

9.5 位运算

  所谓位运算是指,按二进制位进行的运算。

9.5.1 按位运算符

  1.按位与 --- &

    参与运算的两个数据,按二进制位进行“与”运算。如果两个相应的二进制位都为1,则该位的结果值为1,否则为0。

  2.按位或 --- |

    两个对应的二进制位中要有一个为1,该位的结果值为1。对应位均为0时才为0,否则为1。

  3.按位异或 --- ^

    两个对应的二进制位相同时,则结果为0(假),不同时为1(真)。

异或的意思是判断两个对应的位值是否为“异”,为“异”(值不同)就取真(1),否则为假(0)

 交换两个值不用临时变量:

    a = a ^ b;

    b = b ^ a;

    a = a ^ b;   不会越界

  4.按位取反 --- ~

  ~是一个单目运算符,用来对一个二进制位各位翻转。即按位取反,原来为1的位变变成0,原来为0的位变成1。

9.5.2 移位运算符

  1.按位左移 --- <<

  用来将一个数的各二进制位全部左移若干位,低位补0,高位左移后溢出,舍弃不起作用。左移,右边都是填充0。左移一位相当于该数乘以2。但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。

  2.按位右移 --- >>

  右移,如果是无符号数据,左边高位填充0。如果有符号数据,正数,按照符号位0左边高位填充0.负数按照符号位1左边高位填充1。

右移一位相当于该数除以2,右移n位相当于除以2n。

从键盘上输入1个正整数赋值给int变量num,按二进制位输出该数。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
void main()
{
    int num, mask, i;
    scanf("%d", &num);
    mask = 1 << 15; //构造一个最高位为1,其余各位为0的屏蔽字
    for (i = 1; i <= 16; i++)
    {
        putchar(num&mask ? '1' : '0');   //输出最高位的值1或0
        num <<= 1;                        //将次高位移到最高位
        if (i % 4 == 0) putchar(' ');  //四位一组用空格分开
    }
    system("pause");
}
原文地址:https://www.cnblogs.com/Yang-Xin-Yi/p/13578455.html