C语言宏定义、宏函数、内置宏与常用宏

前言:

在C语言中,变量类型、循环控制、基础语法等与其他高级语言基本无异;而C语言(C++)特有的两把双刃剑指针宏定义/宏函数使得C语言在底层开发中披荆斩棘、无所不能。这两个概念涉及范围比较广,其分支点也比较多,可谓星罗棋布,但这每颗星都足以照亮C语言因其开发周期、可维护性、可移植性等问题而显的黯淡的天空,使得这门语言灵活多变、操作犀利,令人难以揣摩却也深深着迷。

首先,C的第一把双刃剑:指针,这也是所有学C的人,最先会接触、最多接触也最无法避免、也是最为之魂牵梦萦茶饭不思的概念;包括一级指针、二级指针、数组指针、指针数组、函数指针、甚至函数指针数组、函数指针数组指针等,怪异称呼说不尽道不清,指东指西、指其所想指,所能指、所不能指。尤其是函数指针、以及函数指针数组等在C的高级特性中(一般是用来进行适配层函数挂载,驱动分发)时常会被用到。

而宏则是更为锋利的一把双刃剑,由于疏于习练,至今尚未参透其中奥秘。今天且稍作总结,记录学历过程之烦恼万千与趣味无穷。

关于宏的内容大部分摘自:C语言宏定义,内置宏,FILE,LINE,## 用法

一、下面列举一些成熟软件中常用的宏定义:

1,防止一个头文件被重复包含

#ifndef COMDEF_H
#define COMDEF_H

//头文件内容 …
#endif

2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。

typedef  unsigned long int  uint32;      	/* Unsigned 32 bit value */

3,得到指定地址上的一个字节或字

#define  MEM_B( x )  ( *( (byte *) (x) ) )
#define  MEM_W( x )  ( *( (word *) (x) ) )

4,求最大值和最小值

#define  MAX( x, y )  ( ((x) > (y)) ? (x) : (y) )
#define  MIN( x, y )  ( ((x) < (y)) ? (x) : (y) )

5,得到一个field在结构体(struct)中的偏移量

#define FPOS( type, field )   ( (dword) &(( type *) 0)-> field )

6,得到一个结构体中field所占用的字节数

#define FSIZ( type, field ) sizeof( ((type *) 0)->field )

7,按照LSB格式把两个字节转化为一个word

#define  FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )

8,按照LSB格式把一个word转化为两个字节

#define  FLOPW( ray, val ) \
(ray)[0] = ((val) / 256); \
(ray)[1] = ((val) & 0xFF)

9,得到一个变量的地址(word宽度)

#define  B_PTR( var )  ( (byte *) (void *) &(var) )
#define  W_PTR( var )  ( (word *) (void *) &(var) )

10,得到一个字的高位和低位字节

#define  WORD_LO(xxx)  ((byte) ((word)(var) & 255))
#define  WORD_HI(xxx)  ((byte) ((word)(var) >> 8))

11,返回一个比X大的最接近的8的倍数

#define RND8( x )       ((((x) + 7) / 8 ) * 8 )

12,将一个字母转换为大写

#define  UPCASE( ch ) ( ((ch) >= ’a' && (ch) <= ’z') ? ((ch) - 0×20) : (ch) )

13,判断字符是不是10进值的数字

#define  DECCHK( ch ) ((ch) >= ’0′ && (ch) <= ’9′)

14,判断字符是不是16进值的数字

#define  HEXCHK( ch ) \
(((ch) >= ’0′ && (ch) <= ’9′) || \
((ch) >= ’A' && (ch) <= ’F') || \
((ch) >= ’a' && (ch) <= ’f') )

15,防止溢出的一个方法

#define  INC_SAT( val )  (val = ((val)+1 > (val)) ? (val)+1 : (val))

16,返回数组元素的个数

#define  ARR_SIZE( a )  ( sizeof( (a) ) / sizeof( (a[0]) ) )

17,对于IO空间映射在存储空间的结构,输入输出处理

#define inp(port)         (*((volatile byte *) (port)))
#define inpw(port)        (*((volatile word *) (port)))
#define inpdw(port)       (*((volatile dword *)(port)))

#define outp(port, val)   (*((volatile byte *) (port)) = ((byte) (val)))
#define outpw(port, val)  (*((volatile word *) (port)) = ((word) (val)))
#define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val)))

二、使用一些内置宏跟踪调试:

ANSI标准定义了几个个预定义的宏名。它们包括但不止于:

__LINE__
__FILE__
__DATE__
__TIME__
__STDC__

注: 常用的还有__FUNCTION__等【非标准】,详细信息可查看:Predefined Macros,如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序 也许还提供其它预定义的宏名。可以定义宏,例如:当定义了_DEBUG,输出数据信息和所在文件所在行

#ifdef _DEBUG
#define DEBUGMSG(msg,date) printf(msg);printf("%d%d%d", date, __LINE__, __FILE__)
#else
#define DEBUGMSG(msg,date)
#endif

三、宏定义防止使用时错误:

用小括号包含。

//例如:
#define ADD(a,b) (a+b)

用do{}while(0)语句包含多语句防止错误(注意while(0)后无分号).

//例如:
#difne DO(a,b) a+b; a++;
//应写成:
#difne DO(a,b) do{a+b; a++;}while(0)

为什么需要do{…}while(0)形式?大致有以下几个原因:

1),空的宏定义避免warning:
#define foo() do{}while(0)

2),存在一个独立的block,可以用来进行重复性变量定义,进行比较复杂的实现。

3),如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现:

#define foo(x)
action1();
action2();
//在以下情况下:
if(NULL == pPointer)
    foo();
//就会出现action2必然被执行的情况,而这显然不是程序设计的目的。

4),以上的第3种情况用单独的{}也可以实现,但是为什么一定要一个do{}while(0)呢,看以下代码:

#define switch(x,y) {int tmp; tmp=x;x=y;y=tmp;}
if(x>y)
   switch(x,y);
else        //error, parse error before else
   otheraction();

在把宏引入代码中,会多出一个分号,从而会报错。使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低,【但是并非所有情况都用这种形式,有些情况不需要,有些情况则不能够够】。

四、宏中#和##的用法

1、一般用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.例如:

#include<cstdio>
#include<climits>
using namespace std;

#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)

int main()
{
	/* 输出字符串vck */ 
	printf(STR(vck));						
	
	/* 2e3 输出:2000 */ 
	printf("%d\n", CONS(2,3));					
	return 0;
}

2、当宏参数是另一个宏的时候

需要注意的是凡宏定义里有用###的地方宏参数是不会再展开.

(1)、没有’#'和’##’的情况

#define TOW      (2)
#define MUL(a,b) (a*b)

printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));

这行的宏会被展开为:
printf("%d*%d=%d\n", (2), (2), ((2)*(2)));
MUL里的参数TOW会被展开为(2).

(2)、当有’#'或’##’的时候

#define A          (2)
#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)

printf("int max: %sn",  STR(INT_MAX));    // INT_MAX #include<climits>
printf("%s\n", CONS(A, A));               // compile error

第一个printf()这行会被展开为:
printf(“int max: %s\n”, #INT_MAX);
第二个printf()则是:
printf("%s\n", int(AeA)); //编译错误

INT_MAXA都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏;加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.

#define A           (2)
#define _STR(s)     #s
#define STR(s)      _STR(s)                 // 转换宏
#define _CONS(a,b)  int(a##e##b)
#define CONS(a,b)   _CONS(a,b)       		// 转换宏

printf(“int max: %s\n”, STR(INT_MAX));
输出为: int max: 0x7fffffff
STR(INT_MAX) –> _STR(0x7fffffff) 然后再转换成字符串;

printf("%d\n", CONS(A, A));
输出为:200
CONS(A, A) –> _CONS((2), (2)) –> int((2)e(2))

(3)、’#'和’##’的一些应用特例:

①、合并匿名变量名,例:

#define  __ANONYMOUS1(type, var, line)  type  var##line
#define  _ANONYMOUS0(type, line)  __ANONYMOUS1(type, _anonymous, line)
#define  ANONYMOUS(type)  _ANONYMOUS0(type, __LINE__)

ANONYMOUS(static int);
//即: 
static int _anonymous70;  	//70表示该行行号;

①第一层:ANONYMOUS(static int); –> __ANONYMOUS0(static int, LINE);
②第二层:–> ___ANONYMOUS1(static int, _anonymous, 70);
③第三层:–> static int _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

②、填充结构

#define  FILL(a)   {a, #a}

enum IDD{OPEN, CLOSE};
typedef struct MSG{
	IDD id;
	const char * msg;
}MSG;
MSG _msg[] = {
	FILL(OPEN), 
	FILL(CLOSE)
};
//相当于:
MSG _msg[] = {
	{OPEN, “OPEN”},
	{CLOSE, ”CLOSE“}
};

③、记录文件名

#define  _GET_FILE_NAME(f)   #f
#define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)
static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);

④、得到一个数值类型所对应的字符串缓冲大小

#define  _TYPE_BUF_SIZE(type)  sizeof #type
#define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)
char  buf[TYPE_BUF_SIZE(INT_MAX)];

char buf[_TYPE_BUF_SIZE(“0x7fffffff”)];
char buf[sizeof “0x7fffffff”];
这里相当于:
char buf[11];

五、__VA_ARGS__##__VA_ARGS__

__VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。##__VA_ARGS__ 宏,在__VA_ARGS__前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的逗号去掉的作用,否则会编译出错。(摘自:#、##、__VA_ARGS__和##__VA_ARGS__的作用

使用例如:Linux C实现文件拷贝可变色进度条显示

  • 54
    点赞
  • 281
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目录 前言.............................................................................................................1 目录.............................................................................................................1 第一章 C 预处理器.................................................................................1 第一节 预处理和编译的结构.............................................................................2 第二节 宏定义.....................................................................................................3 一. 不带参数的宏定义...................................................................................3 二. 带参数的宏定义.......................................................................................4 第三节 文件包含(#include 指令) .......................................................................6 第四节 条件编译.................................................................................................7 第五节 其它预处理指令.....................................................................................9 第二章 C 编译器...................................................................................10 第一节 编译的结构...........................................................................................11 第二节 C 编译器的数据调用协议...................................................................12 一. 内存模式和数据在内存中的存储格式.................................................12 二. 与汇编语言程序的接口.........................................................................18 三. SECTION.................................................................................................20 四. 函数调用接口.........................................................................................28 五. 中断函数调用接口.................................................................................34 六. C 编译器的限制...................................................................................36 第三节 C 编译器的特殊扩充...........................................................................38 一. 嵌入汇编...................................
“新概念C语言”突破了以往任何一种语言教材的旧的模式,将教学内容分为入门篇和提高篇两个篇章。在入门篇中只引进程序设计必要的语法现象,达到快速入门。激发兴趣的目的。在入门篇和提高篇之间插一个强化上机实验周,巩固学习内容。在提高篇中完成完整的语法、算法、程序设计思想等教学目的的学习任务。由于学生对语言已具有了初步的了解并掌握了最基本的语法和程序设计思想,能设计较简单的程序,所以在提高篇的学习中,不论对灵活语法的学习和掌握,还是对程序设计思想的掌握都更加容易,从而可以较容易达到教学目标。 第一部分 入门篇 1 第1章 C语言的产生及其工作流程 3 1.1 C语言的产生 3 1.2 程序和程序设计 3 1.2.1 C程序 3 1.2.2 程序设计 4 习题 4 第2章 C程序设计的初步知识 5 2.1 简单C程序的构成和格式 5 2.2 常量、变量和标识符 6 2.2.1 标识符 6 2.2.2 常量 7 2.2.3 用定义一个符号名的方法来代表一个常量 7 2.2.4 变量 8 2.3 整型数据 8 2.3.1 整型常量 8 2.3.2 整型变量 8 2.4 实型数据 9 2.4.1 实型常量 9 2.4.2 实型变量 9 2.5 字符型数据 10 2.5.1 字符常量 10 2.5.2 字符串常量 11 2.5.3 字符变量 11 2.6 算术表达式 11 2.6.1 基本的算术运算符 11 2.6.2 运算符的优先级、结合性和算术表达式 12 2.6.3 强制类型转换表达式 13 2.7 赋值表达式 13 2.7.1 赋值运算符和赋值表达式 13 2.7.2 赋值运算中的类型转换 14 习题 15 第3章 顺序结构程序 16 3.1 复合语句和空语句 16 3.1.1 复合语句 16 3.1.2 空语句 16 3.2 程序举例 16 习题 17 第4章 选择结构 19 4.1 关系运算和逻辑运算 19 4.1.1 C语言中的逻辑值 19 4.1.2 关系运算符和关系表达式 19 4.1.3 逻辑运算符和逻辑表达式 20 4.2 if语句和用if语句构成的选择结构 21 习题 22 第5章 循环结构 25 5.1 for语句和用for语句构成的循环结构 25 5.1.1 for语句构成的循环结构 25 5.1.2 for循环的执行过程 25 5.1.3 有关for语句的说明 25 5.2 循环结构的嵌套 27 习题 28 第6章 函数 30 6.1 函数的定义和返回值 30 6.1.1 函数定义的语法 30 6.1.2 函数的返回值 30 6.2 函数的调用 32 6.2.1 函数的调用方式 32 6.2.2 函数调用时的语法要求 32 6.3 函数的说明 33 6.3.1 函数说明的形式 33 6.3.2 函数说明的位置 34 6.4 调用函数和被调用函数之间的数据传递 34 6.5 库函数 36 6.6 数据输入输出及常用函数 37 6.6.1 printf函数(格式输出函数) 37 6.6.2 scanf函数(格式输入函数) 39 6.6.3 调用putchar和getchar函数输出和输入字符 40 6.7 程序举例 41 习题 43 第7章 数组 45 7.1 一维数组的定义和一维数组元素的引用 45 7.1.1 一维数组的定义 45 7.1.2 一维数组元素的引用 46 7.1.3 一维数组的初始化 46 7.1.4 一维数组的定义和数组元素引用举例 47 7.2 函数之间对一维数组和数组元素的引用 47 7.2.1 数组元素做实参 47 7.2.2 数组名做实参 49 7.3 一维数组应用举例 50 7.4 字符数组 53 7.4.1 字符数组的定义 53 7.4.2 字符数组的初始化 54 7.4.3 字符数组的引用 54 7.4.4 字符串与字符串结束标志 55 7.4.5 字符数组的输入输出 56 7.4.6 字符串处理函数 58 7.4.7 字符数组应用举例 60 习题 61 第8章 文件 63 8.1 C语言文件的概念 63 8.2 打开文件 64 8.3 关闭文件 65 8.4 调用getc(fgetc)和putc(fputc)等常用函数进行输入和输出 65 8.5 判断文件结束函数feof 68 习题 69 第二部分 提高篇 71 第9章 算法 73 9.1 算法 73 9.2 结构化程序设计和模块化结构 74 9.2.1 结

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值