第九章 编译预处理和位运算
预处理命令是指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"); }