一.预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//__FILE__ //进行编译的源文件
//__LINE__ //文件当前的行号
//__DATE__ //文件被编译的日期
//__TIME__ //文件被编译的时间
//__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __DATE__);
printf("%s\n", __TIME__);
//printf("%d\n", __STDC__);
return 0;
}
ps:VS2019编译器不遵循ANSI C,故在此未定义会报错,需要屏蔽掉。
二.#define
1.#define定义标识符
语法:#define name stuff
举一些例子:
#define MAX 100
#define red register //为register这个关键字,创建一个简短的名字
#define do_forever for(;;) //for的死循环
#define CASE break;case //在写case语句的时候自动把break加上
//如果定义的stuff太长,可以分几行写,除了最后一行外,每行的后面都加一个反斜杠(\)也就是续写符
#define PRINT printf("file:%s\tline:%d\tdate:%s\ttime:%s\n",__FILE__,__LINE__,__DATE__,__TIME__);
①for的死循环
int main()
{
do_forever
{
printf("haha");
}
return 0;
}
②在case后自动加break
int main()
{
int a = 0;
switch (a)
{
case 1:
CASE 2 :
CASE 3 :
CASE 4 ://如果CASE1 2 3都不执行,直接执行CASE 4不用跳出,因为它是末尾
;
}
return 0;
}
这里有一个问题,在#define定义标识符时,后面要不要加上“ ;”呢?
例如:
#define MAX; #define MAX
答案是否定的,因为如果加上“ ; ”可能出现意想不到的结果,例如:
#define MAX; int main() { int a = 1; int max = 0; if (a == 1) max = MAX; else max = 0; return 0; }
这里会报错,因为if后默认只能跟一条语句,而MAX后本就有一个“ ;”,再手动输入一个“ ;”
会被编译器解读为两条语句。
2.#define定义宏
①宏的定义与申明
宏的定义#define机制包括一个规定,允许把参数替换到文本里,这种实现通常称为宏(macro)。
宏的申明方式:
#define name( parament-list) stuff
ps:参数的左括号必须与name紧邻,否则会被定义为name的符号,但在调用宏时可以不紧邻。
②宏在定义时需要注意的问题
由于宏在引用时会完全替换参数,不进行人和运算,所以存在以下问题:
问题一:
#define SQUARE(x) x*x
int main()
{
printf("%d\n", SQUARE(5+1));
return 0;
}
#define定义了一个宏,用于求得一个整数的平方,再将结果打印出来,显然我们预计的是屏幕上会打印出36,但是否真的会是36呢?
显然,结果并不是25而是11,这是为什么呢?
这里就不得不提到宏的替换规则(尤为需要引起重视的是第二步):
在程序中扩展#define定义符号和宏时,需要以下步骤
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,则它们首先被替换。(并不是说宏可以递归,而是只能替换符号参数)
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替换。(不像函数一样,先对实际参数运算再传到形参里,宏的实参会原封不动地传给形参)
3.最后,再次对文件进行扫描,看看是否包含任何由#define定义的符号,如果有重复以上步骤。
那该如何解决这个问题呢?很简单,导致实际结果与预期不同的原因是富豪的优先级问题,我们只需要在宏定义时加上小括号来改变优先级即可。
这样问题就得到了很好的解决,但只要给各个参数加上小括号就能一劳永逸了吗?
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define SQUARE(x) (x)+(x)
int main()
{
printf("%d\n", 10*SQUARE(5));
return 0;
}
我们想得到100,但却得到了55
因此,这个新的问题警示我们,仅仅给宏的参数加上小括号并不能一劳永逸,我们还需要给整个宏加上小括号才能确保万无一失。
问题二:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define SQUARE(x) ((x)+(x))
int main()
{
printf("%d\n", 10*SQUARE(5));
return 0;
}
结论 :
在定义宏的主体时,不要吝啬小括号,给每个参数和整体加上小括号,改变运算的优先级才能确保结果的正确。
3.#和##
首先我们观察一段代码:
int main()
{
char* p = "hello ""world\n";
printf("hello ""world\n");
printf("%s\n", p);
return 0;
}
由此我们得出一个结论:字符串具有自动连接的特点。
所以我们可以这么写代码:
#define PRINT(FORMAT,VALUE) printf("the value is "FORMAT"\n",VALUE);
int main()
{
PRINT("%d",10);
return 0;
}
当然我们也有另外一种方法就是使用#,#可以把一个宏参数变成对应的字符串 。
①#
#define PRINT(FORMAT,VALUE) printf("the value of "#VALUE " is "FORMAT"\n",VALUE);
int main()
{
int i = 4;
PRINT("%d",i+7);
return 0;
}
②##
##可以把位于它两边的符号合成一个符号,允许宏定义从分离的文本片段创建标识符
#define CAT(x,y) x##y
int main()
{
int Class110 = 2023;
printf("%d\n", CAT(Class, 110));
printf("%d\n", Class110);
return 0;
}
4.带副作用的宏参数
当宏参数在宏的定义中出现超过一次时,如果是带有副作用的参数,那么会产生永久性的不可预测的后果。
x++ //带有副作用的参数
x+1 //不带有副作用的参数
MAX宏可以证明具有副作用参数带来的问题:
#define MAX(a,b) (a)>(b)?(a):(b)
int main()
{
int a = 1;
int b = 2;
int c = MAX(a++, b++);
printf("a=%d\n", a);
printf("b=%d\n", b);
printf("c=%d\n", c);
return 0;
}
这里宏替换下来c = (a++)>(b++)?(a++):(b++);
a(1)与b(2)先进行比较(由于是后置++,所以先使用后++),因为a<b所以三目操作符执行第二个(b),此时a(2) b(3),此时c=b=3,然后由于b又被使用一次,再++一次变为b(4),所以打印结果为a=2 b=4 c=3。
5.宏和函数的对比
①速度
宏比函数在程序的规模和速度方面要强,这里通过实现同一功能,例如两数相加,查看反汇编来验证。
宏的反汇编代码
#define SQUARE(x,y) ((x)+(y))
int main()
{
int c = SQUARE(5, 5);
printf("%d\n", c);
/*int d = Add(5, 5);
printf("%d\n", d);*/
return 0;
}
函数的反汇编代码
#define SQUARE(x,y) ((x)+(y))
int main()
{
int c = SQUARE(5, 5);
//printf("%d\n", c);
int d = Add(5, 5);
printf("%d\n", d);
return 0;
}
以上的宏和函数实现了相同的功能,但在反汇编代码里,参数的多余宏的,因此宏的运行速度高于函数。
6.命名约定
一般来讲,函数和宏的使用方法相似。所以语言本身无法区分二者。
因此我们平时就有一个习惯是:
把宏名全部大写
函数名不要全部大写
三.#undef
这条指令用于移除一个宏定义。
四.条件编译
某些调试性的代码,保留碍事,删除又可惜,因此我们可以选择性地进行条件编译,其主要应用于跨平台的程序。
1.#if和#endif
#if和#endif可以与if else语句做类比,两者皆符合多分支的语法规则,但#if是选择性编译,在预处理阶段不符合条件的代码会被移除,而if语句是选择性执行,其余不符合条件的代码不会被删除。
基本用法:
#if 常量表达式
#elif 常量表达式
.......
#else
#endif
#define NUM 1
int main()
{
#ifdef NUM == 1 //常量表达式在预处理阶段求值
printf("hehe\n");
#elif NUM == 2
printf("haha\n");
#else NUM != 0
printf("hoho\n");
#endif
return 0;
}
2.判断是否被定义(#ifdef和#ifndef)
#if defined(symbol)
#ifdef symbol
#if !definrd(symbol)
#ifndef symbol
ps:以上符号需与#endif成对使用才符合语法规则。
①定义NUM
#define NUM 1
int main()
{
#ifdef NUM
printf("hehe\n");
#else
printf("haha\n");
#endif // NUM
return 0;
}
②未定义NUM
//#define NUM 1
int main()
{
#ifdef NUM
printf("hehe\n");
#else
printf("haha\n");
#endif // NUM
return 0;
}
五.文件包含
1.头文件被包含的方式
- 本地文件包含
#include"stdio"
查找策略:先在源文件的目录底下找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找。
找不到就提示编译错误。
- 库文件包含
#include<stdio.h>
查找策略:直接在标准路径下查找,找不到会提示编译错误。
ps:事实上,头文件的包含都可以使用本地文件包含的方式,但那样效率不高,所以不建议那么使用。
2.头文件的反复包含问题
每个头文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
或者
#pragma once
以上两种方法可以避免头文件的重复引入。