预定义符号
C语言设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。
例子:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
//源文件的位置,文件名
printf("%s\n", __FILE__);
//这条代码的行号
printf("%d\n", __LINE__);
//日期
printf("%s\n", __DATE__);
//时间
printf("%s\n", __TIME__);
//检测是否支持ansic标准
printf("%d\n", __STDC__);
//电脑显示未定义,就是不是支持,支持的话会打印出1,
//vs2022不支持
}
效果:
#define定义常量
格式
#define 被替换的变量替换内容
用法
如:
#define a 10+2
#define str hello world
//可以是常量,字符串,甚至是一串代码
//替换关键字
#define reg register
//自动补加break
#define CASE break;case
//替换一段代码
#define DEBUG_PRINT printf("file:%s\t" line:%d\t \date:%s\t time:%s\n",\__FILE__,__LINE__,__DATE__,__TIME__)
特别注明:#define 被替换 替换后面最好不加分号;(避免出现格式问题)。
#define定义宏
#define机制包括了⼀个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏 (definemacro)。它允许在代码中创建符号常量或简单的函数替换。
宏的申明方式:
#define name(参数) 表达式
#define MACRO_NAME value
其中,MACRO_NAME
是宏的名称,value
是宏的值或替换文本。
例子:
#define SQUARE(x) ((x)*(x))
#define square(a) (a)*(a)
#include<stdio.h>
int main()
{
int a = 5;
printf("%d\n", square(a + 1));
}
结果是36.
特别注意
- 宏的参数中如果有操作符,和宏的内容中的操作符因为运算符有优先级的问题,可能导致运算顺序不达预期。
- 解决方法:宏在书写时,给参数,整个表达式都带上括号。
带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
例如:
#define a 1
b=a+1;//a=1,无改变a值
b=++a;//a=2,永久性改变a值
宏的替换规则
简单的文本替换:当预处理器遇到宏的名称时,会将其替换为宏的值。这是最基本的宏替换规则。
参数替换:如果宏定义包含参数,预处理器会将调用宏时提供的实际参数替换到宏定义中的参数位置。
宏展开:预处理器会递归地展开宏。这意味着,如果宏的值本身包含其他宏,预处理器会继续展开这些嵌套的宏,直到没有更多的宏需要展开为止。
#include <stdio.h>
#define MULTIPLY(a, b) ((a) * (b))
#define SQUARE(x) MULTIPLY(x, x)
int main()
{
int result = SQUARE(3 + 2);
printf("Result: %d\n", result);
return 0;
}
在这个例子中,SQUARE
宏调用了MULTIPLY
宏。当预处理器展开SQUARE(3 + 2)
时,它会进行如 下的宏展开过程:
SQUARE(3 + 2)
展开为MULTIPLY(3 + 2, 3 + 2)
MULTIPLY(3 + 2, 3 + 2)
展开为((3 + 2) * (3 + 2))
- 最终展开为
((3 + 2) * (3 + 2))
,得到最终的表达式((3 + 2) * (3 + 2))
宏的作用域:宏的作用域从宏定义的位置开始,一直到文件末尾或宏的#undef
指令为止。在作用域内,预处理器会对宏进行替换。
宏的优势
可以传类型,如
#define MALLOC(num, type)\
(type *)malloc(num sizeof(type))
...
//使⽤
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 sizeof(int));
#和##
#运算符
#运算符将宏的⼀个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。 #运算符所执行的操作可以理解为“字符串化”。
如
#include<stdio.h>
#define PRINT(val,format) printf("the value of " #val " is "format"\n",val)
//相当于printf("the value of""a""is""%d""\n",a)
int main()
{
int a = 10;
PRINT(a, "%d");
return 0;
}
##运算符
## 可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称 为记号粘合这样的连接必须产生⼀个合法的标识符。否则其结果就是未定义的。
例子
宏定义一个加法函数。
#include<stdio.h>
#define GENERIC_MAX(type) type type##_max(type x, type y)\
{\
return (x>y?x:y);\
}
//##相当于把int和_max连起来,语法,不连起来type_max是一个整体,替换不了
//int int_max(int x,int y){return (x>y?x:y)}
GENERIC_MAX(int)
int main()
{
int m = int_max(3, 5);
printf("%d\n", m);
return 0;
}
命名约定
- 把宏的名字全部大写
- 把函数的名字不全部大写
#undef
用于移除一个宏定义
此时,我们可以发现第二次打印显示MAX未声明,说明MAX的宏定义已经被移除。
条件编译指令
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
#include<stdio.h>
#define __a__
int main()
{
int i = 0;
int arr[10] = { 0 };
for (i = 0; i < 10; i++)
{
arr[i] = i;
#ifdef __a__
printf("%d\n", arr[i]);
#endif __a__
}
}
当我们注释掉#define __a__时,#ifdef __a__和#endif __a__之间的语句不会被执行。a可以是其他表达式。
常见的条件编译指令
-
普通
#if 常量表达式 //... #endif //常量表达式由预处理器求值。 如: #define __DEBUG__ 1 #if __DEBUG__ //.. #endif
-
多个分支的条件编译
#if 常量表达式 //... #elif 常量表达式 //... #else //... #endif
-
判断是否被定义
#if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
-
嵌套指令
#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"add.c"
库文件
#include<stdio.h>
嵌套文件包含
假如有一个本地文件被重复包含、引入,编译压力就会很大,这时,我们就可以引用条件编译解决。
或者,在引入前,加上
#pragma once