1.预定义符号
c语言设置了一些预定义符号,可以直接使用,预定义符号都是在预处理期间处理的
__FILE__ // 进⾏编译的源⽂件__LINE__ // ⽂件当前的⾏号__DATE__ // ⽂件被编译的⽇期__TIME__ //⽂件被编译的一瞬间的时间__STDC__ // 如果 编译器遵循 ANSI C ,其值为 1 ,否则未定义
//当前编译器:vs2022
int main() {
printf("file:%s\n", __FILE__);
printf("line:%d\n", __LINE__);
printf("date:%s\n", __DATE__);
printf("time:%s\n", __TIME__);
printf("stdc:%d\n", __STDC__);//在vs2022中并定义,说明vs2022不完全支持c99
return 0;
}
2.#define定义常量
基本语法:
# define 常量名 常量值
例如,定义一个表示圆周率的常量,可以这样做:
#include<stdio.h>
#define PI 3.1415926
int main(){
printf("圆的圆周率为:%lf",PI)
return 0;
}
当我们在进过预处理之后PI就会被直接替换成3.1415926
使用define定义标识符的建议
当我们用#define定义标识符的时候,最好不要在后面加上分号( ;)
因为这可能会为我们造成些许麻烦,例如:
#include<stdio.h>
#define a = 100;
#define b = 1;
int main() {
//例子a
printf("%d ", a);
//预处理后的代码:printf("%d ",a;);这显然是错误的
//例子b
int m = 0;
if (1)
m = b; //预处理后的代码: m = 1;;我们都知道ife lse语句如果不带大括号
else //那么它后面只能跟一条语句,而这样写却有了两条语句,明显是错误的
m = -b
return 0;
}
所以建议不要加上分号( ;) ,这样容易导致问题。
3.#define定义宏
# define 宏名(参数列表) 宏体
其中的 参数列表 是⼀个个由逗号隔开的符号表,它们可能出现在宏体中。
注意:
下面是一个简单的宏定义,该宏用于计算两个数的最大值:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
在这个例子中MAX是宏名,a和b是参数。宏体是条件表达式,它使用三元运算符 ?:来觉定谁更大
在使用这个宏的时候,我们可以像函数一样调用它:
#include<stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main(){
int x = 5;
int y = 10;
int max_val = MAX(x, y); // 这会被预处理器替换为 ((x) > (y) ? (x) : (y))
return 0 ;
}
要注意的是,由于宏只是文本替换,所以使用宏的时候需要格外小心
在宏的定义中参数应该被括号,以避免替换文本后发生的优先级问题
例如:
#include<stdio.h>
#define MAX(a, b) a > b ? a : b
int main(){
int x = 5;
int y = 10;
int z = 10;
int max_val = MAX(x , y + z);
return 0 ;
}
展开后得到:
x > y + z ? x + z : y + z
这通常是我们所期望的行为,因为 y + z 的优先级高于比较级操作 > 。
然而,宏中的表达式包含其他优先级较低的操作符,问题就会出现
#include<stdio.h>
#define MAX(a, b) a > b ? a : b
int main(){
int x = 5;
int y = 10;
int z = 10;
int max_val = MAX(x , y + z) * 2;
return 0 ;
}
展开后得到:
x > y + z ? x + z : y + z * 2
这里,由于乘法 * 的优先级高于加法+,y + z * 2实际上会被解释为c + (d * 2),这可能不是我们的初衷。正确的计算顺序应该是(c + d) * 2。
为了避免这种问题,我们应该始终在宏定义中为参数和操作符使用括号,以确保正确的运算顺序。这就是为什么在原始的MAX宏定义中,每个参数和整个条件表达式都被括号包围起来的原因。
4.带有副作用的宏参数
x + 1; // 不带副作⽤x++; // 带有副作⽤
MAX宏可以证明具有副作⽤的参数所引起的问题。
#include<stdio.h>
#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;
}
输出结果:x=6 y=10 z=9
这是为什么呢?主要就是参数带有副作用的结果
文本替换后:
z = ( (x++) > (y++) ? (x++) : (y++));
// x++ > y++ 吗?即 6 > 9 ?为假,x++不执行,执行y++
//y++为先使用后++,z = 9 之后y++ y = 10
//即x = 6 z = 9 y = 10
5.宏替换的规则
- 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先
- 被替换。
- 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
- 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
6.宏和函数的对比
宏通常被被应用为简单的运算
例如,在找两个数,哪个大的时候,通常会用到宏来完成
#define MAX(a,b) ((a) > (b) ? (a) : (b))
那么为什么不用函数来完成呢 ?
原因有二 :
- 用于调用函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度方面更胜一筹。
-
更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之 这个宏怎可以适⽤于整形、长整型、浮点型等可以⽤于 > 来⽐较的类型。 宏的参数是类型无关的
- 每次使用宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序 的长度。
- 宏是没法调试的。
- 宏由于类型无关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。⽐如:宏的参数可以出现类型,但是函数做不到例如:
#define MALLOC(num, type)\
(type )malloc(num sizeof(type))
...
//使⽤
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 sizeof(int));
参数列表写个类型?nono这是函数不敢的事情
宏和函数的对比
7.奇怪的 # 和 ##
7.1#运算符
在c语言中,当你使用双引号( " " )来定义字符串常量的时,如果多个常量紧挨着写在一起,没有使用任何操作符和分隔符,编译器会自动的将它们连成一个单一的字符串常量。
例如:
#include<stdio.h> int main() { char* str = "Hello """"World"; printf("%s", str); return 0; }
输出结果:Hello World
#运算符的作用就是将带宏的某个参数转换为一个用双引号扩起来的字符串常量。它仅允许出现在带参数的宏的宏体中。
#运算符也被称为"字符串化"运算符。
它在你想要将一个宏参数作为字符串处理时非常有用
例子一:
#define EXAMPLE(x) #x
int main() {
printf("%s", EXAMPLE(Hello world));
return 0;
}
当我们把 Hello world,替换至宏体中,就变成了# Hello world,它转化成 "Hello world"
#define EXAMPLE(x) #x int main() { printf("%s", "Hello world"); return 0; }
例子二:
#define PRINT(n) printf("the value of "#n " is %d", n);
当我们把a替换到宏的体内时,就出现了#a,⽽#a转换为"a",字符串代码就会被预处理为:printf("the value of ""a" " is %d", a);
输出:the value of a is 10
7.2 ## 运算符
## 可以把位于它两边的符号合成⼀个符号 ,## 被称为记号粘合
这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。
例如:
#define EXAMPLE1(x) x_max //这样写,x_max属于一个整体,参数x并不可以替换宏体x_max中的x
#define EXAMPLE2(x) x##_max//这样写,相当于x 和_max两个标识符合为一体,而x可以被参数x所替换
利用 ## 我们就可以写出一些函数模板
int int_max(int x, int y)
{
return x>y?x:y;
}
float float_max(float x, float y)
{
return x>yx:y;
}
这样写起来太繁琐了,我们可以利用宏来写一个比较大小函数的模板:
//宏定义
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}
使用这个模板我们就可以创建出不同数据比较大小的函数
GENERIC_MAX(int) //替换到宏体内后int##_max ⽣成了新的符号 int_max做函数名
GENERIC_MAX(float) //替换到宏体内后float##_max ⽣成了新的符号 float_max做函数名
int main()
{
//调⽤函数
int m = int_max(2, 3);
printf("%d\n", m);
float fm = float_max(3.5f, 4.5f);
printf("%f\n", fm);
return 0;
}
输出结果:
34.500000
8.宏和函数的命名约定
9.#undef
#undef 用于移除一个宏定义
#define NAME(x) x + x
#undef NAME
//如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。
10.条件编译
C语言的条件编译代码允许程序员根据特定的条件或者定义来决定是否编译某一部分代码。
#ifdef 和 #endif
#ifdef 和 #endid : 检查是否定义了某个宏
#ifdef _DEBUG_ //也可以写成:#if defined _DEBUG_ 这两种写法没有区别
//_DEBUG_被定义的时候,编译这一段代码
#endif
#ifndef 和 # endif
#ifndef 和 # endif :检查是否没有定义某个宏
#ifndef _DEBUG_ //也可以写成:#if !defined _DEBUG_ 这两种写法没有区别
//_DEBUG_没有被定义的时候,编译这一段代码
#endif
#if、#elif、#else 和 #endif
#if、#elif、#else 和 #endif:基于表达式的条件编译。
#if defined(MACRO_NAME)
// 当MACRO_NAME被定义时,编译这部分代码
#elif defined(ANOTHER_MACRO)
// 当ANOTHER_MACRO被定义时,编译这部分代码
#else
// 当以上条件都不满足时,编译这部分代码
#endif
11.头文件的包含
11.1头文件被包含的方式:
11.1.1 本地文件包含
#include "filename"
查找策略:先在源文件所在⽬录下查找,如果该头⽂件未找到,编译器就像查找库函数头⽂件⼀样在标准位置查找头文件。
如果找不到就提示编译错误
11.1.2 库文件包含
#include <filename.h>
11.2 防止头文件多次包含的方法
我们都知道#include可以使得另一个文件被编译
#ifndef __TEST_H__
#define __TEST_H__
//头⽂件的内容
#endif //__TEST_H__
或者这样写:
#pragma once