C 语言宏的小练习_实现对google,TEST()框架的小模拟
今天实现一个小小的练习,实现效果如下
/************************************************************************* > File Name: test.c > Author:Gin.TaMa > Mail:1137554811@qq.com > Created Time: 2019年01月15日 星期二 09时26分40秒 ************************************************************************/
#include<stdio.h>
#include"test.h"
int add(int a,int b){
return a + b;
}
int is_prime(int n){
if(n <= 1)return 0;
for(int i = 2;i * i <= n;i ++){
if(n % i == 0)return 0;
}
return 1;
}
TEST(test,is_prime_func){
EXPECT(is_prime(2), 0);
EXPECT(is_prime(-2), 0);
EXPECT(is_prime(15), 0);
EXPECT(is_prime(9973), 1);
}
TEST(test,add_func){
EXPECT(add(1,2),3);
EXPECT(add(3,2),5);
EXPECT(add(5,2),7);
}
int main(){
return RUN_ALL_TEST();
}
运行效果如下
[test:is_prime_func]
is_prime(2) == 0 :False
is_prime(-2) == 0 :True
is_prime(15) == 0 :True
is_prime(9973) == 1 :True
75 :总共: 4 通过 : 3
[test:add_func]
add(1,2) == 3 :True
add(3,2) == 5 :True
add(5,2) == 7 :True
100 :总共: 3 通过 : 3
首先主函数是没有什么独特的地方,唯一独特的是#include"test.h"
那看起来具体功能的实现就放在了"test.h"这个头文件里了
那我们需要实现什么功能呢?
- 当输入是 Test(test,iS_prime_func) 输出参数的名字
- 可以判断is_prime(2)的输出和预先设定的输出 0 是否一致
- 可以统计在Test()函数里总共有几个测试,几个通过。
好,现在看起来就只有这3个需要实现的功能,但是之后可能会出现第四个需要实现的功能就是重命名的问题。
先不谈功能的实现,说一下对宏的认识。
宏是一个神奇的东西,因为作用在代码生成的编译阶段,可以按照一定的规则对字符串进行替换重新组合。可以这么认为,
这个宏,是一种作用于代码编译时间段的字符串操作的工具。
宏能做到什么呢?
-
替换 函数名,参数列表等 例如
#include<stdio.h> #define my_test() void my(){printf("testa\n");} my_test(); int main(){ my(); return 0; }
等价于
#include<stdio.h> void my(){ printf("testa\n");}; int main(){ my(); return 0; }
还可以替换函数的参数
#include<stdio.h> #define my_test() void my(int a){printf("test%d\n",a);} my_test(); int main(){ my(123); return 0; }
那么这不就相当与一个函数封装的过程,
我们通过把一个有参数的函数封装成了无参的函数
那么这样呢。
#include<stdio.h> #define my_test() void my(int a){printf("test%d\n",a);}void my2() my_test(){ printf("I am my_test\n"); }; int main(){ my(123); my2(); return 0; }
我们在文件里就定义了一个my_test()的函数,但是结果上出来了两个函数,甚至只要我们开心定义任意个函数。
这样我们不光封装了my_test函数,还多定义了一个my2函数
那么有什么意思呢?要是我不知道定义后的名字比如my2那么怎么调用呢?
那么这么做如何呢?
我们在封装一个函数叫做domytest(),然后把my,和my2放进去,只给外界留一个domytest()的接口用来调,如何。
#include<stdio.h> #define my_test() void my(int a){printf("test%d\n",a);}void my2() my_test(){ printf("I am my_test\n"); }; int domytest(){ my(123); my2(); } int main(){ return domytest(); }
这样一个看和我们的第一个文件很像,接下来把宏和domytest()封装到一个头文件里,然后就实行了类似TEST()的功能。从外表来看很简洁
再加上## 这个用来连接两个字符串的宏的小工具,我们就可以做到很多的有趣的事情。
-
利用_ _attribute_ _等 来定义函数属性
啥意思呢,是这样的,宏不是作用在预编译的时候,也就是代码执行之前的时间吗,所以宏可以在这个阶段,在代码实际执行之前对一些函数做一些小手脚,从而改变函数执行时的行为。
比如
#include<stdio.h> #define my_test() void my(int a){printf("test%d\n",a);}void my2() my_test(){ printf("I am my_test\n"); }; int domytest(){ my(123); my2(); } __attribute__((constructor)) void before_main() { printf("--- %s\n", __func__); } __attribute__((destructor)) void after_main() { printf("--- %s\n", __func__); } int main(){ return domytest(); }
执行结果为:
--- before_main test123 I am my_test --- after_main
通过对利用宏,我们改变了函数的执行顺序,
或者说,我们执行函数可以不依赖于主函数的调用,我们可以在我们喜欢的任何地方利用宏来实现调用并运行函数。或者说,
宏不仅是一种字符串替换的工具,而是一个不依赖与主函数可以自己执行的强力工具
那么结合上述的函数封装的过程我们能实现什么呢?
比如:我们可以监控用户定义函数的行为,比如用户每定义一次,我们就替换成a,b两个函数,并且a函数可以执行一定的操作。这样,虽然在主函数里,我们没有调用a,但是a却悄悄的执行了
-
可以获得代码执行的时候,仅在函数编译时段可以获得的变量名
#define p(a){\ printf("%s \n",#a);\ } int add(int a,int b){ return a + b; } int main(){ p(add(1,2)); return 0; }
这个函数的输出为:
add(1,2)
没有看错就是这么强:
宏中的#的功能是将其后面的宏参数进行字符串化操作(Stringizing operator),简单说就是在它引用的宏变量的左右各加上一个双引号。
OK了大致上了解了一下宏这个神奇的工具,接下来就是分析这个功能是怎么实现的
接着我们上面说的关于函数封装的思想。
我们的RUN_ALL_TEST()是所有的输出的出口,TEST()要进行展开变成多个函数,同时利用_ _attribute_ _来做一些羞羞的事情,而EXPECT()这个就执行具体的操作吧
先从局部来考虑吧,相对简单的入手尝试一下实现EXPECT()的功能
EXPECT(a,b):
输入:a,要执行的函数,b,期待获得的输出
输出:字符串1:要执行的函数名,字符串2:期待获得的输出 字符串3:执行函数的结果是否和期待的输出一致
实现前回顾一下宏是什么?
宏是一个字符串替换的工具
#define EXPECT(a,b){\ printf("%s\t == %s\t :",#a,#b);\ if(a == b){result_test[num][1]++;printf("True\n");}\ else {printf("False\n");}\ result_test[num][0]++;\ }
OK了。我们做了什么。
我们就是利用给我们的两个字符串进行了组合而已。至于result_test[][]这个数组是用来统计的,接下来会说
接好完成了一个功能点了
接下来实现什么呢?
我们从整体上把握一下,为什么呢?因为吧,接下来的功能的实现依赖于RUN_ALL_TEST()这个函数的设计,如果这个没有设计出来,再谈其他的功能都是瞎扯。
这个我们肯定是需要用函数指针来在RUN_ALL_TEST()里调用上面的几个test函数的
然后在RUN_ALL_TEST里就这么实现吧,假设已经获得了函数的指针,并进行了相关统计
int RUN_ALL_TEST(){
int i = 0;
while(func_test[i]){
printf("[%s:%s]\n",func_name[i],func_name2[i]);
func_test[i](i);
int all = result_test[i][0],pass = result_test[i][1],passf = 1.0 * pass / all * 100 ;
if(passf != 100)
printf("\033[41m %d \033[0m:总共: %d 通过 : %d\n",passf,all,pass);
else
printf("\033[42m %d \033[0m:总共: %d 通过 : %d\n",passf,all,pass);
i ++;
}
return 0;
}
至于实现不就是
利用__attribute__这个属性,来做到在任意地方调用我们的函数
从而,我们在定义函数前把函数的地址储存一下
#define _F_name(test,count,name1,name2) void test##count(int);\ __attribute__((constructor))void add##test##count(){\ func_test[count] = test##count;cpname(count,name1,name2);\ }\ void test##count(int num)
#define F_name(a,b,c,d) _F_name(a,b,c,d)
#define TEST(name1,name2)\ F_name(test, __COUNTER__,name1,name2)
首先,是
#define TEST(name1,name2)\
F_name(test, __COUNTER__,name1,name2)
这个是第四个功能点需要的,将TEST()换个名字,换个全局唯一的名字,不然会重复定义函数名。
接下来是整个代码的核心操作,羞羞的操作
#define _F_name(test,count,name1,name2) void test##count(int);\
__attribute__((constructor))void add##test##count(){\
func_test[count] = test##count;cpname(count,name1,name2);\
}\
void test##count(int num)
我们把 _F_name 替换成了三部分,
- 函数声明
- 将该函数的指针保存,同时保存一下 用户输入Test() 的参数的字符串
- 封装函数,将无参函数换成有个int num 参数的函数,为了统计时找到相对应的测试函数
好了,就是这个样子,注意EXPECT()并不是一个函数替换,而是一个语句替换,其作用域和其所在的函数一样
/************************************************************************* > File Name: test.h > Author:Gin.TaMa > Mail:1137554811@qq.com > Created Time: 2019年01月15日 星期二 13时59分53秒 ************************************************************************/
#include<string.h>
#ifndef _TEST_H
#define _TEST_H
void (*func_test[100])() = {
0};
char func_name[10][100];
char func_name2[10][100];
int result_test[10][2];
#define cpname(count,name1,name2) {\ strcpy(func_name[count],#name1);\ strcpy(func_name2[count],#name2);\ }
#define _F_name(test,count,name1,name2) void test##count(int);\ __attribute__((constructor))void add##test##count(){\ func_test[count] = test##count;cpname(count,name1,name2);\ }\ void test##count(int num)
#define F_name(a,b,c,d) _F_name(a,b,c,d)
#define TEST(name1,name2)\ F_name(test, __COUNTER__,name1,name2)
#define EXPECT(a,b){\ printf("%s\t == %s\t :",#a,#b);\ if(a == b){result_test[num][1]++;printf("True\n");}\ else {printf("False\n");}\ result_test[num][0]++;\ }
int RUN_ALL_TEST(){
int i = 0;
while(func_test[i]){
printf("[%s:%s]\n",func_name[i],func_name2[i]);
func_test[i](i);
int all = result_test[i][0],pass = result_test[i][1],passf = 1.0 * pass / all * 100 ;
if(passf != 100)
printf("\033[41m %d \033[0m:总共: %d 通过 : %d\n",passf,all,pass);
else
printf("\033[42m %d \033[0m:总共: %d 通过 : %d\n",passf,all,pass);
i ++;
}
return 0;
}
#endif
实在不行就G++ -E 看下