关于#define的使用方法总结

#define 预处理指令

#define 是 C 和 C++ 编程语言中的预处理指令,用于定义宏(macro)。宏是一种预处理器功能,它允许程序员定义一个标识符(通常是大写字母),该标识符可以被替换为一段代码、表达式或常量值。

一、#define宏定义

#define叫做宏定义,语法格式:
#define 名字 值
例如:#define PI 3.1415926

注意事项:

  • 存储方式#define 宏定义不分配内存,它们只是在编译时替换文本。
  • 没有分号#define 指令末尾不需要分号,分号会被视为值的一部分。
  • 作用域#define 声明之后在整个文件或宏的作用域内有效,直到 #undef 指令出现或者文件结束。

定义一个宏名字之后,可以在其他宏定义中使用,例如
#define OEN 1
#define TWO OEN+ONE
#define THREE OEN+TWO

二、查看预处理文件

gcc -E define.c -o define.i
对 define.c 文件执行预处理操作,并将预处理后的结果输出到 define.i 文件中。
这通常用于查看宏定义展开、文件包含等情况,而不是为了生成可执行文件或目标代码。
  • gcc: 这是 GNU 编译器集合的命令行工具,用于编译 C 和 C++ 程序。
  • -E: 这是 GCC 的一个选项,代表“预处理”(Preprocess)。使用这个选项时,GCC 将执行预处理操作,但不会进行编译。预处理操作包括宏展开、文件包含(#include)的处理等。
  • define.c: 这是要预处理的 C 语言源文件。GCC 将读取这个文件,并应用预处理指令。
  • -o: 这个选项后面跟着输出文件的名称。在这里,它指定了预处理后的结果应该被写入哪个文件。
  • define.i: 这是预处理后生成的文件的名称。由于使用了 -E 选项,GCC 不会生成目标代码文件(通常是 .o 文件),而是生成一个包含了所有预处理操作结果的文件。

三、#define 的使用方法

1、定义常量:#define 最常见的用法是定义常量,这些常量在程序中可以被多次引用,而其值在编译时就已经确定。

#define PI 3.1415926

2、字符串化操作:将宏转换为字符串。

#define FILE_PATH "/home/orangepi/project/"
使用FILE_PATH替换/home/orangepi/project/

3、头文件保护:#ifndef#define#endif 是 C 和 C++ 预处理器指令的一部分,通常一起使用来实现头文件保护(也称为 “include guards” 或 “include sentinels”)。这种机制可以防止头文件被多次包含到同一个源文件中,从而避免编译错误和重定义问题。

#ifndef _HEADER_FILE_H_
#define _HEADER_FILE_H_

// 头文件内容,例如函数声明、类定义、宏定义等

#endif 
/*当你的头文件被包含时,预处理器会检查这个宏是否已经定义。如果尚未定义,它将定义这个宏,并处理头文件中的所有内容。如果已经定义,
预处理器将跳过整个头文件的内容,防止它被再次包含。
这种机制对于避免因重复包含头文件而导致的编译错误非常重要,特别是在大型项目中,头文件之间可能会相互依赖,导致复杂的包含关系。使用
头文件保护可以确保每个头文件只被编译一次。*/

4、宏函数:使用 #define 可以定义宏函数,这些宏在预处理阶段展开,替换为它们的参数表达式,从而减少函数调用的开销。

1.无参宏

#define debug printf("hello world")
int main{
	debug;
	return 0;
}

2.带参宏:

语法:不是进行简单的字符串替换,还要进行参数替换

#define宏名(形参列表) 字符串
#define debug(s) printf("%s\n",s)
int main{
	debug("hello world");
	return 0;
}

【注意】:要注意算数的优先级
如果宏定义中包含表达式,需要小心处理副作用。

例:

#include <stdio.h>

#define M 3+2
#define N (3+2)

int main()
{
    int data = 4;

    printf("data * M = %d\n",data * M);
    printf("data * N = %d\n",data * N);
    return 0;
}
/*运行结果:
data * M = 14
data * N = 20*/

使用gcc -E define.c -o define.i查看生成的预处理文件define.i:
M = 4*3 +2 = 14
N = 4*(3+2) = 20

在这里插入图片描述

宏和函数的区别:

  1. 和函数不同,宏的参数没有数据类型,因为是文本替换
  2. 因为是文本展开,相比函数没有执行调度的开销,效率要高
  3. 使用有参数的宏函数时,参数在替换文字中要用括号包围,以免收到运算符优先级的影响
  4. 函数的参数是有类型的,存在类型检查,但是宏的参数没有类型与类型检查;
  5. 函数可以递归,而宏不可以递归;
  6. 对于参数而言,宏的参数是直接替换的,所以会有一些 参数具有副作用,而函数的参数是临时拷贝的,没有副作用的情况

四、C语言宏中“#”和“##”的用法

1、#:字符串化操作符

作用:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串
【注意】:其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。

#define example1(instr) printf("this is :%s\n",#instr)
#define example2(instr) #instr
当使用宏定义时:
example1(abc);
//将会展开成:printf("this is:%s\n","abc")

string str = example2(abc);
//将会展开成:string str = "abc"

在这里插入图片描述

2、##:符号连接操作符

作用:操作符用于连接两个宏参数。当宏在预处理阶段展开时,## 会将两侧的参数连接起来,合并成一个新的标识符或字符串。

#define exampleNum(n) num##n

int num9 = 9;
int num = exampleNum(9);
//将会扩展成:int num = num9;

【注意】:

  • 当用##连接形参时,##前后的空格可有可无。
  • 连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义。
  • 如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开。
#include <stdio.h>
#include <string.h>

#define STRCPY(a, b) strcpy(a ## _p, #b) 

int main() {
    char var1_p[20];
    char var2_p[30];

    strcpy(var1_p, "aaaa");
    strcpy(var2_p, "bbbb");
    
    STRCPY(var1, var2);//宏展开为strcpy(var1_p, "var2");
    //这会将字符串 "var2" 复制到 var1_p 中,而不是将 var2_p 的内容复制到 var1_p 中。
    //因为 "var2" 不是一个有效的字符串,而是一个文本标记。
    
    STRCPY(var2, var1);//宏展开为strcpy(var2_p, "var1");
    //同样,它尝试将字符串 "var1" 复制到 var2_p 中

    printf("var1 = %s\n", var1_p);
    printf("var2 = %s\n", var2_p);
    return 0;
}
/*运行结果:
var1 = var2
var2 = var1*/

五、常见的宏定义总结

1、得到指定地址上的一个字节或字
#define  MEM_B(x) (*((byte *)(x)))
#define  MEM_W(x) (*((word *)(x)))

2、求最大值和最小值
#define  MAX(x,y) (((x)>(y)) ? (x) : (y))
#define  MIN(x,y) (((x) < (y)) ? (x) : (y))

3、计算一个数的绝对值
#define ABS(a) ((a) < 0 ? -(a) : (a))

4、交换两个变量的值
#define SWAP(a, b, type) do { type SWAP_temp = (a); (a) = (b); (b) = SWAP_temp; } while(0)

5、计算数组大小
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

6、将宏参数转换为字符串
#define STRINGIFY(x) #x

7、连接两个宏参数
#define CONCAT(x, y) x ## y

8、得到一个field在结构体(struct)中的偏移量
#define FPOS(type,field) ((dword)&((type *)0)->field)

9、得到一个结构体中field所占用的字节数
#define FSIZ(type,field) sizeof(((type *)0)->field)

10、不使用sizeof,求intdouble等变量类型占用的字节数
#define Mysizeof(Value) (char*)(&value+1)-(cahr *)&value

11、声明以1年中有多少秒(忽略闰年)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
使用UL或ul用作后缀,表示将结果强制转换为 unsigned long 类型,以确保表达式的结果作为无符号长整型处理。

12、按照LSB格式把两个字节转化为一个Word
#define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])

13、按照LSB格式把一个Word转化为两个字节
#define FLOPW(ray,val) (ray)[0] = ((val)/256); (ray)[1] = ((val) & 0xFF)

14、得到一个变量的地址(word宽度)
#define B_PTR(var)  ((byte *) (void *) &(var))
#define W_PTR(var)  ((word *) (void *) &(var))

15、得到一个字的高位和低位字节
#define WORD_LO(xxx)  ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx)  ((byte) ((word)(xxx) >> 8))

16、断言宏,用于检查程序中的条件
#include <assert.h>
#define ASSERT(condition) assert((condition) && #condition)

六、常考题目

1、已定义#define M(x,y,z) x * y + z, 并且int a = 1, b = 2, c = 3,调用M(a + b, b + c, c + a)的输出结果为(12#include <stdio.h>

#define M(x,y,z) x * y + z 

int main(int argc, char* argv[])
{
    int a = 1, b = 2, c = 3;
    printf("M(a+b, b+c, c+a) = %d\n",M(a+b, b+c, c+a));
	//展开后:printf("M(a+b, b+c, c+a) = %d\n",a+b * b+c + c+a);
    return 0;
}
/*运行结果:
M(a+b, b+c, c+a) = 12*/
计算下列程序的返回值是多少()
#include <stdio.h>

#define product(x) (x * x)

int main(int argc, char* argv[])
{
    int i = 3, j, k;
    
    j = product(i++);//展开后: j = (i++ * i++);
    printf("after j,i = %d\n",i);
    
    k = product(++i);//展开后: k = (++i * ++i);
    printf("after k,i = %d\n",i);
    
    printf("j = %d, k = %d\n",j,k);
    return 0;
}
/*运行结果:
after j,i = 5
after k,i = 7
j = 12, k = 49*/
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值