一、预定义符号
--FILE--:进行编译的源文件;
--LINE--:行号;
--DATE--:日期;
--TIME--:时间;
--STOC--:存在为1,否则未定义。
二、#define定义常量
#define+名字+内容(后面不建议加分号,因为这些错误在gcc环境下预处理后观察.i文件可以看到)
#include <stdio.h>
int main()
{printf("%s\n %d\n %s\n %s\n",\__FILE__\,__LINE__\,__DATE__\,__TIME__);return 0;
}
注:这里的”\“是续行符,这样可以将错行的数据打印。
三、#define定义宏
#define包括一个规定,允许将参数替换到文本(内容)中,这种现象叫宏(macro)或定义宏(define...)
申明方式:#define 名字(参数列表) 内容
例如:
#define SQUAR(n) n*n//定义n的平方
注:1.n不可以是表达式,比如5+1,它会变成5+1*5+1=11,(直接替换)写宏一定不要吝啬括号;
2.参数列表左括号必须紧贴名字,不能有空,否则()终会被理解为内容的一部分;
3.宏是一种常用于定义一组源代码的文本替换规则,可以称其是一种编程技术或机制
四、带有副作用的宏参数
当宏参数在宏的定义中出现超过一次,若参数有副作用,使用这个宏时可能出现危险。
副作用就是表达式求值时出现的永久性效果。
例如:
int a = 10;
int b = a + 1; //b=11,a=10,a不会发生改变,不带副作用
int c = ++a; //c=11,a=11,a会发生改变,带副作用
注:给宏传参的参数尽量不要带副作用。
写一个宏,求两个数的较大值
#include <stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 10;int b = 20;int c = MAX(a, b);//c=20,若换为MAX(a++,b++),则c=21,a=11,b=22。return 0;
}
注:在三目操作符中,如果x表达式的值大于y表达式的值,则后面只会计算x表达式。
五、宏的替换规则
#define M 100
#define MAX (x,y)
int m = MAX(M,15);
1.先检查M和15,M为宏定义的参数,先将其替换为100;
2.替换文本后再插入原本位置;
3.最后对结果再次扫描,看其是否没替换干净,没干净再重复上述步骤。
注:1.宏参数与#define定义中可以出现其他#define定义的符号,但宏不可以出现递归;
2.当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索,比如printf("MAX(M,15)"),这里的M并不会被替换为100。
六、宏和函数的对比
1.优点
求两个数的较大值,宏更具有优势。
原因是:
1.用于调用函数和从函数返回的代码可能比实际执行该小型计算需要的时间更多(速度快);
2.函数参数必须声明为特定的类型,只能在类型合适的表达式上使用,与宏的参数类型无关。
执行函数:
1.调用函数的准备工作; 12条指令
2.执行函数的核心运算; 8条指令
3.从函数调用中返回值。 4条指令
宏省去了1和3两个步骤,并且核心运算两者差不多。
注:宏有时候可以做到函数做不到的事情,比如宏的参数可以出现类型,但函数做不到。
例如:
#include <stdio.h>
#define MALLOC(n,type) (type*)malloc(n*sizeof(type))
int main()
{int* p = MALLOC(10, int);return 0;
}
注:向函数中传a++和++a都是最后赋值给a,只是传进去的值不同。
2.缺点
1.每次使用宏时,一份宏定义插入程序中,除非宏比较短,否则会大幅增加代码的长度;
2.宏无法调试;
3.宏由于参数类型无关,因此它不够严谨;
4.宏可能带来优先级的问题,导致容易出错。
注:函数的参数都是计算好传过去的。
七、#和##
1.#运算符
#运算符将宏的一个参数转换为字符串字面量,它只允许出现带参数的宏的替换列表中。
例如:
int a = 10;
printf("the value of a is %d", a);
int b = 20;
printf("the value of b is %d", b);
上述代码无法变成一个函数,但他可以变成一个宏。
注:字符串天然可以连在一起比如:printf("abc")可以写作printf("a""b""c")。
将其改为宏可以为:
#include <stdio.h>
#define PRINT(n) printf("the value of "#n" is %d\n",n)//两个字符串构成一个整体的字符串
int main()
{int a = 10;PRINT(a,a);int b = 20;PRINT(a,b);return 0;
}
2.##运算符
##可以把它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符,##被称为记号粘合。
例如:写一个函数求两个数中的较大值,这个数可以是任意类型。
#include <stdio.h>
//这样在调用int_max或float_max函数时就不需要写多个函数了
#define GENERIC_MAX(type)\
type type##_max(type x,type y)\
{\return x>y?x:y;\
}
GENERIC_MAX(int);
GENERIC_MAX(float);
int main()
{int a = int_max(10, 20);printf("%d\n", a);float b = float_max(2.1, 0.5);printf("%.1f", b);return 0;
}
注:##粘合后的必须是一个合法的标识符。
八、命名约定
命名约定就是一个命名习惯,比如:
1.把宏名全部大写;
2.函数名不要全部大写。
特例:offsetof——宏
九、#undef
这条指令用于移除一个宏定义。
例如:
#define M 100
printf("%d", M);//可以使用
#undef M
printf("%d", M);//不可以使用
十、命令行定义
许多C编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程(只能在VS code上演示)
当我们根据同一个源文件要编译出一个程序的不同版本时,这个特性有点用,(加入某程序声明一个数组,若内存有限,我们要一个很小的数组,若另一个机器内存大些,则要一个大点的数组)
编译指令:(Linux环境演示)
gcc -D sz=10 programe.c
int arr[sz];
for(int i=0;i<sz;i++)
{
arr[i]=i+1;
printf("%d",arr[i]);
}
十一、条件编译(满足条件参与编译,不满足不参与)
例如:调试性代码,删了可惜,留着也没用。
int main()
{
#if 1 :这里为1,编译下面语句,为0则不编译。(可以是常量表达式,不可以是变量是因为运行时才创建变量)
printf("hehe");
}
#end if
#if define(ZHANGSAN)看其是否定义ZHANGSAN,定义成什么值无所谓。(也可以改为ZHANGSAN)
注:1.#if、#end if可以充当注释的作用;
2.条件编译一般应用于跨平台的编译。
多个分支的条件编译:
#if
.....
#elif
......
#elif
......
#endif
嵌套指令:
#if define(...)
#ifdef...
......
#endif
#elif define(...)
#ifdef...
......
#endif
#endif
十二、头文件的包含
1.头文件的包含有两种形式:
1.#include<~.h>——库文件包含,一般指标准库头文件的包含;
2.#include"~.h"——本地文件包含,一般指自己创建的头文件包含。
查找策略:
先在源文件所在目录下擦护照,若未找到,编译器就像查找库函数头文件一样在标准位置找头文件,找不到就提示编译错误。
2.标准头文件的路径:""形式
Linux环境:
/usr/include(/usr/include $ ls)查找代码
VS环境:
//VS2013默认路径:
C:\program Files(x86)\Microsoft Visual Stdio 12.0\VC\include
但是VS2022改了很多
注:库文件也可以使用""形式包含,但是查找效率较低,且不容易区分库文件和本地文件。
3.库文件包含:<>形式
直接去标准路径下查找,找不到提示错误
4.嵌套文件包含:
一个头文件包含几次test.i文件就放入几次,有时可能无意间将头与文件包含多次。
例如:
要解决这个问题可以写:(防止头文件多次包含)
#ifndef __TEST__H__(这里写两个__是习惯原因)
#define __TEST_H__
//代码主体...
#end if
更简单的为:
#progrma once
//代码主体