C 语言宏的小练习_实现对google,TEST()框架的小模拟

C 语言宏的小练习_实现对google,TEST()框架的小模拟


今天实现一个小小的练习,实现效果如下

/*************************************************************************
	> File Name: test.c
	> Author:Gin.TaMa 
	> Mail:[email protected] 
	> 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"这个头文件里了

那我们需要实现什么功能呢?

  1. 当输入是 Test(test,iS_prime_func) 输出参数的名字
  2. 可以判断is_prime(2)的输出和预先设定的输出 0 是否一致
  3. 可以统计在Test()函数里总共有几个测试,几个通过。

好,现在看起来就只有这3个需要实现的功能,但是之后可能会出现第四个需要实现的功能就是重命名的问题。

先不谈功能的实现,说一下对宏的认识。

宏是一个神奇的东西,因为作用在代码生成的编译阶段,可以按照一定的规则对字符串进行替换重新组合。可以这么认为,

这个宏,是一种作用于代码编译时间段的字符串操作的工具

宏能做到什么呢?

  1. 替换 函数名,参数列表等 例如

    #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()的功能。从外表来看很简洁

    再加上## 这个用来连接两个字符串的宏的小工具,我们就可以做到很多的有趣的事情。

  2. 利用_ _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却悄悄的执行了

  3. 可以获得代码执行的时候,仅在函数编译时段可以获得的变量名

    #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 替换成了三部分,

  1. 函数声明
  2. 将该函数的指针保存,同时保存一下 用户输入Test() 的参数的字符串
  3. 封装函数,将无参函数换成有个int num 参数的函数,为了统计时找到相对应的测试函数

好了,就是这个样子,注意EXPECT()并不是一个函数替换,而是一个语句替换,其作用域和其所在的函数一样

/*************************************************************************
	> File Name: test.h
	> Author:Gin.TaMa 
	> Mail:[email protected] 
	> 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 看下

猜你喜欢

转载自blog.csdn.net/weixin_39722329/article/details/86497817