首页 > 其他分享 >C语言宏相关知识

C语言宏相关知识

时间:2023-11-29 15:46:06浏览次数:34  
标签:tmp 定义 int 代码 知识 C语言 编译 相关 define

$$ 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来提供编译器特定的指令等

标签:tmp,定义,int,代码,知识,C语言,编译,相关,define
From: https://www.cnblogs.com/CarrotRui/p/17863832.html

相关文章

  • 基本数据类型-C语言-2023/11/29
    ......
  • 实型(浮点型) 字符型 枚举型 - C语言-2023/11/29
    实型(浮点型)字符型枚举型......
  • C语言RsaUtil,C语言Rsa验证签名,验签----自测OK
    摘自:https://www.dandelioncloud.cn/article/details/1498198300963708930 //RsaUtil.c#include<string>usingnamespacestd;#include<stdio.h>#include<stdlib.h>#include<string.h>#include<openssl/rsa.h>#include<openss......
  • Base64编码、解码 C语言例子(使用OpenSSL库)
    #include<stdio.h>#include<string.h>#include<unistd.h>#include<openssl/pem.h>#include<openssl/bio.h>#include<openssl/evp.h>intbase64_encode(char*in_str,intin_len,char*out_str){BIO*b64,*bio;......
  • C语言字节对齐 __align(),__attribute((aligned (n))),#pragma pack(n)
    例子:__align(),__attribute((aligned(n))),#pragmapack(n)#include<stdio.h>main(){structA{inta;charb;shortc;};structB{charb;inta;shortc;};#pragmapack(2)/*指定按2字节对齐*/......
  • Linux课堂知识总结5
    在这节课的学习中,我知道了Linux系统进程的概念程序(program)是一个普通文件,是为了完成特定任务而准备好的指令序列与数据的集合,这些指令和数据以“可执行映像”的格式保存在磁盘中。进程(process)是一个已经开始执行但还没终止的程序实例。Linux系统下使用ps命令可以查看到当前正......
  • Danswer 快速指南:不到15分钟打造您的企业级开源知识问答系统
    Kevin公众号「技术狂潮AI」深耕互联网行业十余载,热衷AI技术研究 一、写在前面至于为什么需要做企业知识库,知识问答检索系统,以及现有GPT模型在企业应用中存在哪些劣势,今天在这里就不再赘述了,前面介绍其他构建知识库案例的文章中基本上都有讲过,如果您有兴趣......
  • Linux课堂知识总结6
    在这节课的学习中,我了解了linux标准输入输出:    程序:指令+数据     程序:IO可用于输入的设备:文件,键盘设备,文件系统上的常规文件,网卡等;可用于输出的设备:文件,显示器,文件系统上的常规文件,网卡等,程序的数据流有三种:    输入的数据流:<-- 标准输入(stdin),键盘......
  • fc大语言模型部署+本地知识库
      FCInvokeStartRequestId:930989fb-8910-400d-b981-1de87e89a3e3Info:@serverless-cd/engine:0.0.51,linux-x64,node-v14.19.2plugin@serverless-cd/checkouthasbeeninstalledplugin@serverless-cd/s-setuphasbeeninstalledplugin@serverless-cd/s-deplo......
  • [good]c语言数组的运算
    #include<stdio.h>#include<stdlib.h>#include<time.h>#defineMAX10int**createRandom2DArray(introws,intcols){srand(time(NULL));//初始化随机数生成器int**arr=(int**)(malloc(sizeof(int*)*rows));if(arr==NULL)......