宏
在C语言中宏有三种形式:
- 定义符号常量
- 定义傻瓜表达式
- 定义代码段
在使用宏的过程中需要注意的是,宏的作用仅仅是在预处理阶段对代码进行替换,而非进行运算,所以在使用时,如果出现了我们预期之外的结果,很有可能是宏没有处理好。下面就挨个演示一下。
定义符号常量
着一种形式比较简单只是单纯的替换,这里对该种情况不做过多介绍
#include<stdio.h>
#define PI 3.1415926535int main(){int r = 5;printf("[r] = %d [S] = %f", r, PI * r * r);
}
定义傻瓜表达式
可以通过宏封装一个简易的表达式,但是为什么叫傻瓜表达式呢,正如之前所说,宏只是进行替换,而非进行计算,所以在没有考虑周全的情况下,很容易出错。下面我们以封装一个比较函数为例,返回大的值。
分别用到下面五组示例
#include<stdio.h>
#define MAX(a, b) a > b ? a : b
#define P(func) printf("%s = %d\n", #func, func);int main(){int a = 7;P(MAX(2, 3));P(5 + MAX(2, 3));P(MAX(2 ,MAX(2, 3)));P(MAX(2, 3 > 4 ? 3 : 4));P(MAX(a++, 3));
}
先解释一下代码,主函数部分是我们的测试用例不需要赘述。封装的MAX(a,b)表达式就是我们想要实现的功能。下面的P宏是我为了方便实现的打印的宏,其中#func这个参数的意义就是字符串话,可以使得运行结果更容易观察。
运行结果:
可以看到除了第一个测试用例基本上这些都不是正确答案。为了探究原因我们就需要看到把宏展开,也就是预处理之后的样子。使用gcc -E filename命令即可。
可以看到他们宏仅仅是做了替换,实际的运算顺序与我们预期的不符合,所以我们就需要使用小括号来使得运算顺序满足我们的预期。
#include<stdio.h>
#define MAX(a, b) ((a) > (b) ? a : b)
#define P(func) printf("%s = %d\n", #func, func);int main(){int a = 7;P(MAX(2, 3));P(5 + MAX(2, 3));P(MAX(2 ,MAX(2, 3)));P(MAX(2, 3 > 4 ? 3 : 4));P(MAX(a++, 3));
}
可以看到前四个结果已经达到我们预期的结果了。但是第五个结果与预期不符合。可以从上面预处理之后的代码中可以看出,它的错误并不是计算顺序的原因,而是我们对a++访问之后自增1,而后进行返回的原因,若想修复这个bug我们需要一个中间变量来记住增1前的a值
定义代码段
#include<stdio.h>//#define MAX(a, b)(((a) > (b) ? a : b))#define MAX(a, b)({\__typeof(a) _a = a;\__typeof(b) _b = b;\_a > _b ? _a : _b;\
})#define P(func) printf("%s = %d\n", #func, func);int main(){int a = 7;P(MAX(2,3));P(5 + MAX(2,3));P(MAX(2,MAX(2,3)));P(MAX(2,3 > 4 ? 3 : 4));P(MAX(a++,6));
}
运行结果:
这里解释一下新出现的t__typeof,功能就是提取变量类型的关键字,然后进行定义,也就是说 __typeof(a) _a = a;改行代码与 int _a = a;相同。当宏有多行的时候,需要使用\来链接。
内置的宏
宏 | 说明 |
---|---|
__ DATE __ | 日期Mmm dd yyy |
__ FILE __ | 文件名 |
__ LINE __ | 行号 |
__ TIME __ | 时间:hh:mm:ss |
__ func __ | 函数名(非标准) |
__ Func __ | 函数名(非标准) |
__ PRETTY_FUNCTION __ | 更详细的的函数信息(非标准) |
其中非标准的含义是指不同的环境下可能会出现不同的结果,或者是不存在该宏。比如我是在ubantu系统下运行的,没有__Func__的宏
#include<stdio.h>
#define P(a) printf("[%s] = %s\n", #a, a);int main(){P(__DATE__);P(__FILE__);printf("[__LINE__] = %d", __LINE__);P(__TIME__);P(__func__);P(__PRETTY_FUNCTION__);
}
条件式编译
在宏当中也可以实现条件分支的功能实现代码剪裁
函数 | 说明 |
---|---|
#ifdef DEBUG | 是否定义了DEBUG |
#ifndef DEBUG | 是否没定义了DEBUG |
#if MAX_N ==5 | 宏MAX_N 是否等于5 |
#ieif MAX_N ==4 | 否则宏MAX_N 是否等于4 |
#else | |
#endif |
打印日志信息
编写一个程序,通过宏实现一个log()功能打印,打印所在文件,行号,函数,以及内容。当需要DEBUG时log()可以使用,不需要DEBUG时,则log()函数无任何作用
#include<stdio.h>#define DEBUG
#ifdef DEBUG
#define log(frm, args...){\printf("[FILE]:%s [LINE]:%d [func]:%s [content]:", __FILE__, __LINE__,__func__);\printf(#frm, ##args);\printf("\n");\
}
#else
#define log(frm, args...)
#endifint add(int a, int b){return a + b;
}int main(){log(add(1 , 5));
}输出结果:
>> [FILE]:log.c [LINE]:24 [func]:main [content]:add(1 , 5)
"#"和”##“的作用:
- ”#“是将后面的内容字符串化,保证输出
- ”##“是连接作用,可以连接两个变量名,在这里是保证当log传入参数只有一个时,不会报错(int a, b, ab; a##b 是与ab等价的)
泛型宏(Generic macro)
#include<stdio.h>
#define TYPE(a) _Generic((a), \int : "%d\n",\double: "%lf\n", \char * : "%s\n"\
)int main(){int a = 1;double b = 4.0;char string[] = "hello";printf(TYPE(a),a);printf(TYPE(b), b);printf(TYPE(string), string);
}运行结果:
>> 1
4.000000
hello
从上图可以看到除了泛型宏以外,自定义的函数在主函数之外也可以运行,而且会先于主函数运行。这是因为我们在函数上面为其添加了属性使其可以能够单独运行。尽管函数可以独立运行,但是一个程序是不可以没有主函数的,否则会有编译错误。
补充:字符串
在预处理阶段,不只是宏,还有头文件也会被展开,在这里简单介绍一下一个比较重要的头文件<string.h>,该头文件包含对于字符串的操作比较方便。
函数 | 说明 |
---|---|
strlen(str) | 计算字符串长度,以\0作为结束符 |
strcmp(str1, str2) | 字符串比较 |
strcpy(dest, src) | 字符串拷贝 |
strcmp(str1, str2, n) | 安全字符串比较 |
strcpy(dest, src, n) | 安全字符串拷贝 |
memcpy(str1, str2, n) | 内存拷贝 |
memcmp(str1, str2, n) | 内存比较 |
memset(str,c, n) | 内存设置 |