你会几种好用函数姿势入门(定义函数语言你会好用)「函数使用技巧」

前面几节较详细的介绍了 C 语言指针的用法,尤其在上一节,我们讨论了如何利用 C 语言实现“伪类”
有朋友评论说这样没有意义,的确,在上一节的例子中的确没有什么意义,伪类”甚至还让本来简单的代码变得更繁杂了
但是,在较大的复杂项目中,使用 C 语言“伪类”封装还是司空见惯的
事实上,linux 内核源代码中有大量使用“伪类”的地方,本节不再讨论“伪类”
谈到较大的项目,就不得不提一下“宏定义”了,较大的项目都会用大量的宏定义来组织代码,随便找一个开源项目,打开它的源代码头文件,看看能发现多少宏定义
你可能用过 #define N 20 这种宏定义,看起来宏定义只不过是做个替换而已,其实里面有比较复杂的规则
本节打算谈一谈宏定义,是因为宏定义在 C 语言中非常重要,也非常有用,有时有些实现甚至非宏定义莫属
函数式宏定义像 #define N 20 这种宏定义称为“变量式”宏定义,N 可以像变量一样使用,但是 N 属于常量表达式
实际上,还有一种可以像函数一样使用的宏定义,可称之为“函数式宏定义”,请看如下代码:#define MIN(a, b) ( (a)<(b)?:(a):(b) )x = MIN(3&0x0f, 5&0x0f)将 x = MIN(3&0x0f, 5&0x0f) 表达式展开,得:x = ( (3&0x0f)<(5&0x0f)?(3&0x0f):(5&0x0f) )d = a?b:c 这个表达式的意思是,if(a) d=b; else d=c;可以看出,函数式宏定义 MIN 可以像函数一样使用,两个实参被替换到宏定义形参 a 和 b 的位置了
应当注意,函数式宏定义和真正的函数是有区别的:函数式宏定义的参数没有类型,预处理时不做参数类型检查,所以使用时要确保类型正确
函数式宏定义本身不会被编译为函数,调用时就是直接把宏定义替换过来,而不是简单的几条传参和 call 指令,所以函数式宏定义编译生成的目标会比真正的函数大
定义函数式宏定义要非常小心,如果 MIN 定义成 #define MIN(a, b) ( a<b? a:b ),则 x = MIN(3&0x0f, 5&0x0f) 展开就成了 x = ( 3&0x0f<5&0x0f?3&0x0f:5&0x0f ),运算符的优先级就错了,不会得出正确结果
读者思考一下,外层括号能否省略?因为调用函数式宏定义就是简单替换,所以如果 MIN(i++, j++) 时,展开就是 ( (i++)<(j++)?(i++):(j++) ),i和j自加的次数是不确定的
如果是 MIN 真正的函数,则 i 和 j 确定是只自加一次
在 linux 内核中,函数式宏定义通常使用 do{…}while(0) 包裹:#define do_something(i) \ do{ \ i ++; \ printf(\"i = %d\n\", i); \ }while(0)为什么呢?请看下面这个例子,就明白了:if(i>5) do_something(i);else printf(\"test\n\");如果没有使用 do{…}while(0) 包裹,把 do_something 展开后,变为:if(i>5) i++; printf(\"i = %d\n\", i);else printf(\"test\n\");printf(“i = %d\n”, i); 这句没有被包含在 if 判断语句里,而且 else 语句并没有与 if 配对,所以编译会报错
那能否在宏定义时,使用 {} 包裹呢?还是上面的例子,使用 {} 包裹展开后:if(i>5){ i++; printf(\"i = %d\n\", i);}; // ;else printf(\"test\n\");虽然 printf(“i = %d\n”, i); 这句被包含在 if 判断语句里了,但是 do_something(i); 最后的 “;”会被展开到 {} 后面,这样表示 if 的判断结束了,else 依然没有与 if 配对,还是会编译报错
那 do_something(i); 后面的这个“;”不写不就行了吗?的确,不写就没有错误了,但是不写 “;”,看起来就不像函数调用了,对不?整个语句显得怪怪的,哪天顺手一加,就又错了
C 语言宏定义的 ## 运算符请看实例:#define test(a, b) a##btest(he, llo)test(he, llo) 预处理后,就相当于 hello
再请看:#include <stdio.h>#define test(a) print##avoid print1(){ printf(\"hello 1\n\");}void print2(){ printf(\"hello 2\n\");}int main(){ test(1)(); test(2)(); return 0;}显然,“##”是一个特殊的拼接符,说它特殊是因为它拼接的并不是字符串,以上代码执行结果如下:利用 define 也可以实现一些不定参数的函数式宏定义,请看:// 开发时#define debug(format, ...) printf(format, ## __VA_ARGS__)// 发行时#define debug(format, ...) 这么定义以后,debug 就可以像 printf 一样使用了
在开发阶段,需要打印出信息时,使用 debug 代替 printf
这么做的好处是,开发完毕不再需要打印信息时,只需要简单的把 debug 定义为空即可,而无需再去挨个删除 printf
以后还想打印信息,再把 printf 添加回来即可
有时候,函数式宏定义可以做到函数难以实现的事现在的 C 语言及其编译器支持了很多有趣的关键字,例如:__FUNCTION__ 表示函数名__LINE__ 表示所在行号等等请看如下代码:#include <stdio.h>int main(){ printf(\"%s %d\n\", __FUNCTION__, __LINE__); return 0;}编译器在编译时,会自动的把 “__FUNCTION__” 和“__LINE__”替换为函数名和行号,这样就不用程序员逐个手动输入了,而且代码的可移植性也更强
为了更方便的输出当前位置,我们可以定义函数式宏定义 location:#include <stdio.h>#define location() \ do{ \ printf(\"fun: %s, line: %d\n\", __FUNCTION__, __LINE__);\ }while(0)void test(){ location();}int main(){ test(); location(); return 0;}打印出位置是有用的,它能帮助我们在大型项目的复杂代码中快速的找到出错的函数,出错的行号
(类似于 “__LINE__”的关键字还有一些,留给读者自行查阅了)在 windows 环境中开发,各种 IDE 把程序员照顾的很好
但是有些错误,即使是 IDE 也无法定位,以后会看到这样的例子
另外,嵌入式开发中,可不一定有合适的 IDE 给程序员使用
location 是一个函数式宏定义,所以调用它,就相当于把代码展开到调用位置,所以它可以打印出 test 中的位置,也可以打印出 main 中的位置
如果 location 是一个真正的函数,输出结果就不同了,请看:这是为什么呢?原因留给读者自己分析了
欢迎在评论区一起讨论,质疑
文章都是手打原创,每天最浅显的介绍C语言,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦
你会几种好用函数姿势入门(定义函数语言你会好用)
(图片来源网络,侵删)

联系我们

在线咨询:点击这里给我发消息