$$ C语言宏$$
1.宏定义:
宏定义又称为宏替换、宏代换,简称“宏”,是C提供的三种预处理功能的其中一种①。其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率。
2.格式:
简单宏定义
格式:#define <宏名/标识符> <字符串>
- eg:
#define PI 3.1415926
定义了PI替换后是3.1415926
带参数的宏定义(除了一般的字符串替换,还要做参数代换)
- 格式:
#define <宏名>(<参数表>) <字符串>
- eg:
#define Sum(a,b) a + b
- 调用:
s = Sum(1, 2); // s = 1 + 2;
3.说明:
(1).宏名一般用大写
(2).宏定义末尾不加分号;
(3).可以用#undef命令终止宏定义的作用域
(4).宏定义可以嵌套
(5).字符串“”中永远不调用宏
\(~~~\) #define NAME zhang
\(~~~\) 程序中有"NAME",它不会被替换
(6).宏替换在编译前进行,不分配内存,变量定义分配内存,函数调用在编译后程序运行时进行,并且分配内存
(7).预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查
(8).使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组大小常用宏定义
(9).实参如果是表达式容易出问题
\(~~~\) #define S(r) r*r
\(~~~\) area=S(a+b);
\(~~~\) 第一步换为area=r*r;
\(~~~\) 第二步换成area=a+b*a+b;
\(~~~\) 当定义为#define S(r)((r)*(r))
时,area=((a+b)*(a+b))
(10).宏名和参数的括号间不能有空格
(11).宏替换之作替换不做计算,不做表达式求解
(12).宏展开不占用运行时间,只占用编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)
4.宏定义其他冷门知识
#define Conn(x,y) x##y // 等价于 x连接y
//int s = Conn(123, 456) , s = 123456
#define ToChar(x) #@x // 等价于 'x'(给x加上单引号)
#define ToString(x) #x // 等价于 "x"(给x加上双引号)
5.条件编译
条件编译允许根据特定的预处理器指令(通常是宏定义)来包含或排除代码段
基本指令:
(1). #ifdef
和 #ifndef
#ifdef
用于检查某个宏是否已定义。如果已定义,则编译随后的代码。#ifndef
则相反,它用于检查某个宏是否未定义。如果未定义,则编译随后的代码。
这两个指令通常用于确保代码仅在特定的编译环境中被包含。
#include <iostream>
using namespace std;
int n;
int main(){
// 如果没有定义 MAX 就定义MAX
#ifndef MAX
#define MAX 0x3f3f3f3f
#endif
cout << MAX << endl; // 输出0x3f3f3f3f
// 如果没定义DEBUG 就定义DEBUG, 否则将DEBUG重新定义
#ifndef DEBUG
#define DEBUG(x) printf ("%d\n", x)
#else
#define DEUBG(x) printf("NO\n")
#endif
DEBUG(1999); // 输出1999
// 如果定义了KKDY 就运行KKDY,否则运行另一个
#ifdef KKDY
// debug code
cin >> n;
for(int i = 1; i <= n; i++) cout << i << ' ';
#else
// release code
printf("NO\n");
#endif
#define KKDY
// 由于 KKDY是在下面定义的,所以KKDY运行的是relea code ( printf("NO\n");)
// 如果 KKDY是在#ifdef KKDY 上面就定义了, KKDY运行的是 debug code
KKDY // 输出 NO
// 用宏定义常数
#define DEFINE_CONST(type, name, value) const type name = value;
DEFINE_CONST(int, MAX_NUM, 100);
DEBUG(MAX_NUM); // 输出上行定义的MAX_NUM
return 0;
}
(2). #if
, #elif
, #else
和#endif
#if
后面可以跟随一个表达式,如果表达式为真,则编译随后的代码。#elif
(即"else if")提供了额外的条件分支。#else
用于所有前面条件均不满足时的情况。#endif
标志着条件编译块的结束。
// 如果你不想运行一段代码可以这么写
// 这是一段完全背包问题代码
#include <iostream>
using namespace std;
#define N 1010
int n, m;
int v[N], w[N];
int f[N][N];
int dp[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
#if 0
for(int i = 1; i <= n; i ++)
for(int j = 0; j <= m; j ++)
for(int k = 0; k * v[i] <= j; k ++)
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]*k] + w[i] * k);
cout << f[n][m] << endl;
#endif
#if 0
for(int i = 1; i <= n; i ++)
for(int j = 0; j <= m; j ++){
f[i][j] = f[i-1][j];
if(j >= v[i]) f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);
}
cout << f[n][m] << endl;
#endif
for(int i = 1; i <= n; i ++)
for(int j = v[i]; j <= m; j ++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout << dp[m] << endl;
return 0;
}
6.函数宏
- (1).错误用法
#define INT_SWAP(a,b) \
int tmp = a; \
a = b; \
b = tmp
但上述的宏具有一个明显的缺点:当遇到 if
、while
等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = b
和 b = tmp
均不受控制语句所作用(事实上第二次使用tmp相当于没定义tmp,会报错)。
因此,在工程中,一般使用三种方式来对函数宏进行封装,分别为 {}
、do{...}while(0)
和 ({})
。
- (2). {} 方式
#define INT_SWAP(a,b)\
{ \
int tmp = a; \
a = b; \
b = tmp; \
}
这样方式不会因为存在if
等影响,但是要注意调用方法
// 错误用法
int a = 1, b = 2;
if(1)
INT_SWAP(a, b); // 错误原因是 ; 将if的作用域终止了
else
cout << "Hello World!" << endl;
// 正确用法一
if(1)
INT_SWAP(a, b)
else
cout << "Hello World!" << endl;
// 正确用法二
if(1){
INT_SWAP(a, b);
}
else
cout << "Hello World!" << endl;
- (3). do{...}while(0) 方式
#define INT_SWAP(a,b) \
do{ \
int tmp = a; \
a = b; \
b = tmp; \
}while(0)
do{..}while(...);
语句末尾一定要加;
因此这样调用函数INT_SWAP(a,b);
不会出错。但是要注意,这个分号不能省略。
// 这时候这样写是错的,要加上分号
if(1)
INT_SWAP(a, b)
else
cout << "Hello World!" << endl;
- (4). ({}) 方式
#define INT_SWAP(a,b) \
({ \
int tmp = a; \
a = b; \
b = tmp; \
})
C 语言规定 ({})
中的最后一条语句的结果为该双括号体的返回值,因此这样写可以支持函数宏像函数一样返回值
int main(){
int a = ({10;1000;});
printf("a = %d\n", a); // a = 1000
}
综上,在 {}
、do{...}while(0)
和 ({})
这三种函数宏的封装方式之中,应尽可能不使用 {}
,考虑兼容性一般选择使用 do{...}while(0)
,当需要函数宏返回时可以考虑使用 ({})
或直接定义函数。
$$注释解释$$
①:在C语言中,并没有内置的预处理功能集,但C语言通过预处理器(preprocessor)提供了一系列预处理指令,它们在编译代码之前对源文件进行处理。预处理器指令以井号(#)为开始,常见的预处理功能包括宏定义、条件编译和文件包含等。下面介绍C语言预处理器提供的三种基本功能:
1.宏定义(Macro Definition)
使用#define预处理指令可以定义宏,这让你可以给一个常数值定义一个名字,或者定义一个宏函数,用以在编译之前替换代码中的某些文本。
例子:
#define PI 3.14159 // 使用PI的时候会自动换成3.14159
#define MAX(a,b) ((a) > (b) ? (a) : (b))// 使用MAX用来取两个值中的较大值。
// 值得一提的是: 在使用宏定义的时候,如果使用了运算符的时候,最好将变量用括号括起来
// 因为宏定义使用的时候,相当于直接把前面的一串全部换成后面一串,由此可能会产生优先级的使用错误
2.条件编译(Conditional Compilation)
条件编译指令允许根据特定条件决定是否编译某部分代码。这在多平台编程中特别有用,可以根据不同的操作系统或编译器定义进行条件编译。
#ifdef WINDOWS
// Windows相关的代码
#elif defined(LINUX)
// Linux相关的代码
#else
#error "Platform not supported."
#endif
这里的代码将根据是否定义了WINDOWS
或LINUX
宏来决定哪段代码被编译
3.文件包含(File Inclusion)
文件包含指令用来将一个文件的内容插入到另一个文件中。通常用于将头文件(包含函数原型和宏定义等)的内容包含在源文件中。
#include <stdio.h> // 包含标准输入输出库的头文件
#include "myheader.h" // 包含用户自定义的头文件
以上是C预处理器提供的三种常用功能,预处理器还提供其他指令,比如#undef
来取消宏定义,#pragma
来提供编译器特定的指令等