C语言宏相关知识

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

但上述的宏具有一个明显的缺点:当遇到 ifwhile 等语句且不使用花括号仅调用宏时,实际作用范围在宏的第一个分号后便结束。即 a = bb = 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

这里的代码将根据是否定义了WINDOWSLINUX宏来决定哪段代码被编译

3.文件包含(File Inclusion)

文件包含指令用来将一个文件的内容插入到另一个文件中。通常用于将头文件(包含函数原型和宏定义等)的内容包含在源文件中。

#include <stdio.h>  // 包含标准输入输出库的头文件
#include "myheader.h"  // 包含用户自定义的头文件

以上是C预处理器提供的三种常用功能,预处理器还提供其他指令,比如#undef来取消宏定义,#pragma来提供编译器特定的指令等

posted @   Carrot_Rui  阅读(147)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示