南京模板建网站哪家好,几个免费建立网站的平台,传奇手游网站大全,什么网站可以做设计目录
预处理符号
#define
#define定义标识符
#define定义宏
#define的替换规则
#与##
带副作用的宏参数
宏和函数的对比
undef
命令行定义
条件编译
文件包含
头文件被包含的方式
本地文件包含
库文件包含
嵌套文件包含 预处理符号
__FILE__ //进行编译的源…目录
预处理符号
#define
#define定义标识符
#define定义宏
#define的替换规则
#与##
带副作用的宏参数
宏和函数的对比
undef
命令行定义
条件编译
文件包含
头文件被包含的方式
本地文件包含
库文件包含
嵌套文件包含 预处理符号
__FILE__ //进行编译的源文件__LINE__ //文件当前的行号__DATE__ //文件被编译的日期__TIME__ //文件被编译的时间__STDC__ //如果编译器遵循ANSI C其值为1否则未定义
这些预定义符号都是C语言内置的。
输出一下这些预定义符号
#includestdio.hint main()
{printf(%s\n, __FILE__);printf(%d\n, __LINE__);printf(%s\n, __DATE__);printf(%s\n, __TIME__);return 0;
} #define
#define定义标识符
#define定义标识符在我们平时写代码的过程中经常运用例如
#define MAX 1000
#define reg register //为 register这个关键字创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长可以分成几行写除了最后一行外每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf(file:%s\tline:%d\t \date:%s\ttime:%s\n ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )
注意在#define的时候末尾最好不要加 ;
#define MAX 100
#define MAX 100;
如果加上 ; 可能会引发这样的问题
#define MAX 100;
int main()
{int a 0;int max 0;if (a 0)max MAX;elsemax 0;return 0;
} 其原因在于在预编译时MAX会被替换为100; 而不是100预编译后编译器得到的代码实际上是这个样子的
int main()
{int a 0;int max 0;if (a 0)max 100;;//注意elsemax 0;return 0;
}
所以为了避免这样的语法问题尽量不用在末尾加; #define定义宏
#define机制包含了一个规定允许把参数替换到文本中这种实现方式通常称为宏macro或定义宏define macro。
下面是宏的声明方式
#define name(parament-list) stuff
其中的parament-list是一个由逗号隔开的符号表它们可能出现在stuff中。
注意
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在参数列表就会被解释为stuff的一部分。
例如
#define SQUARE(x) x*x
这个宏可以接收一个参数x 。
这时我们将如下代码置于程序中
SQURE(5);
在程序预处理阶段就会被编译器替换为这样的语句
5*5
代码实现
#define SQUARE(x) x*x
int main()
{printf(%d\n, SQUARE(5));return 0;
}
到这里看似很完美的思路实际上是存在漏洞的。
再来一组用例
SQUARE(51); 乍一看你可能觉得这段代码将打印36这个值但是结果却是11。
仔细分析一下在替换文本时参数x被替换为51所以这条语句实际上变成了
printf(%d\n, 5 1 * 5 1);所以想要严谨一点最好在宏定义上加上两个括号
#define SQUARE(x) (x)*(x)
类似的还有
#define DOUBLE(X) ((x)(x)) //双层括号 避免如5*xx
总结用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。 #define的替换规则
在程序中扩展#define定义符号和宏时需要涉及几个步骤。
在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值替换。最后再次对结果文件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。
注意
宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏不能出现递归。当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。 #与##
假设我们现在想定义一个具有打印功能的宏 PRINT。
首先我们得认知到字符串是有自动连接的功能的例如
printf(Hello World!);
那么我们的宏PRINT是否可以这样完成
#define PRINT(FORMAT,VALUE) printf(the value is FORMAT\n,VALUE)int main()
{int a 0;PRINT(%d, a);return 0;
}
很显然这种写法是错误的 正确的做法
#define PRINT(FORMAT,VALUE) printf(the value is FORMAT\n,VALUE); 这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中。
当然除此之外还有另外一个技巧是
使用 # 把一个宏参数变成对应的字符串。
比如
#define PRINT(FORMAT,VALUE)\printf(the value of #VALUE is FORMAT\n, VALUE) 同样的
#define PRINT(FORMAT,VALUE)\printf(the value of #VALUE is FORMAT\n, VALUE)
int main()
{int a 0;PRINT(%d, a100);return 0;
} ##的作用
##可以把位于它两边的符号合成一个符号
它允许宏定义从分离的文本片段中创建标识符。
例如
#define ADD_TO_SUM(num, value)\sum##num valueint main()
{int sum5 0;ADD_TO_SUM(5, 10);//作用是给sum5增加10.return 0;
} 带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候如果参数带有副作用那么你在使用这个宏的时候就
可能出现危险导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如
x1; //不带副作用x; //带副作用
MAX宏可以证明具有副作用的参数所引起的问题:
#define MAX(a, b) ( (a) (b) ? (a) : (b) )
int main()
{int x 5;int y 8;int z MAX(x, y);printf(x%d y%d z%d\n, x, y, z);//输出的结果是什么return 0;
}
经过预处理之后实际上代码是这个样子
z ( (x) (y) ? (x) : (y)); 宏和函数的对比
宏经常被应用于简单的运算。比如在两个数中找出最大的一个。
#define MAX(a, b) ( (a) (b) ? (a) : (b) )
那为什么不用函数来完成这个任务呢
原因有两个
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。
当然宏和函数相比还有劣势的地方
每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。宏是没办法调试的。宏由于类型无关也就意味着不够严谨。宏可能带来运算符优先级的问题了导致程序容易出现错误。
宏有时候可以做到函数做不到的事情。比如宏的参数可以出现类型但是函数做不到。
#define MALLOC(num, type)\(type*)malloc(num * sizeof(type))
int main()
{int* newNodeMALLOC(10, int);//类型作为参数return 0;}
预编译后的代码 int* newNode (int*)malloc(10 * sizeof(int));undef
这条指令用于移除一个宏定义。
#undef NAME
//如果现存的一个名字需要被重新定义那么它的旧名字首先要被移除。
例如
#define MAX 100int main()
{printf(%d\n, MAX);//此时正常使用#undef MAXprintf(%d, MAX); //此时会报错return 0;
} 命令行定义
许多C的编译器提供了一种能力允许在命令行中定义符号。用于启动编译过程。
例如当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候这个特性有点用处。假定某个程序中声明了某个长度的数组如果机器内存有限我们需要一个很小的数组但是另外一个机器内存大些我们需要一个数组能够大些。 编译的时候指定MAX10 编译的时候指定MAX20 条件编译
在编译一个程序的时候我们如果要将一条语句一组语句编译或者放弃是很方便的。因为我们有条件编译指令。
例如
调试性的代码删除可惜保留又碍事所以我们可以选择性的编译。
#include stdio.h
#define __DEBUG__
int main()
{int i 0;int arr[10] { 0 };for (i 0; i 10; i){arr[i] i;
#ifdef __DEBUG__printf(%d\n, arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__}return 0;
}
#ifdef如果定义了某个内容就执行。
#endif结束ifdef。
常见的条件编译指令
1.
#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 文件包含
我们已经知道#include指令可以使另一个文件被编译。就像它实际出现于#include指令的地方一样。
这种替换的方式很简单
预处理器先删除这条指令并用包含文件的内容替换。
这样一个源文件被包含10次那就实际被编译10次。
头文件被包含的方式
本地文件包含
#include filename
查找策略先在源文件所在目录下查找如果该头文件未找到编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
Linux的标准头文件的路径
/usr/include
VS环境的标准头文件的路径
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
注意按照自己的安装路径去寻找。
库文件包含
#include filename.h
查找头文件直接去标准路径下去寻找如果找不到就提示编译错误。
这样是不是可以说对于库文件也可以用 的形式包含
答案是可以。
但是这样做查找的效率低些当然这样也不容易区分是库文件和本地文件了。 嵌套文件包含
如果出现这样的场景 comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。
如何j解决这个问题
答案条件编译。
在每个文件的开头这样写
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
或者
#pragma once
就可以避免头文件的重复引用。