• 杂记,主要包含各种锁


    ① 【shell脚本】

    DT:%G(标准计数周的年份)%m(月份)%d(日)_%H(小时)%M(分钟)%S(秒)
    linux命令大全:
    参考链接:https://www.runoob.com/linux/linux-command-manual.html

    ②【条件编译】

    ifeq:判断两个参数是够相等,相等时条件成立为true,不相等为false
    ifdef:判断变量是否被定义,非空则为真(没有定义的变量为空)
    ifneqifeq关键字相反,用来判断参数是否不相等。
    经典场景
    在 & | && || 之间加上 #ifdef #endif

       171     if (   (a & b)                                                                                                                       
       172         && (          (  (c & (d | e | f) ) && (g&h)   )
       173 #ifdef T_true
       174             || (  (i & j) && (k & (l))  )
       175 #endif
       176                )
       177         ) 
       -----
       #ifdef  宏定义
       函数
       #endif
       //注释:如果该宏定义被定义,则执行下方函数。其他同理,即if 、 ifeq 、ifneq 等等
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当比较的参数不相等时,条件语句才成立,值为true,否则为false。
    参考链接:https://www.zhaixue.cc/makefile/makefile-ifeq.html#:~:text=ifdef

    ③【include、-include、sinclude】

    sinclude:兼容其他的make程序,等价"-include"
    -include:忽略由于包含文件不存在或者无法创建时的错误提示
    当文件不存在时,make在读取完makefile后会试图使用规则来创建该文件,当不能创建
    该文件时,make将提示致命错误并退出,输出以下提示:

    Make:*** No rule to make target ‘’. Stop
    
    • 1

    如果指示符“include”指定的文件不是以斜线开始(绝对路径,如/usr/src/Makefile…),
    而且当前目录下也不存在此文件。
    make将根据文件名试图在以下几个目录下查找:首先,查找使用命令行选项“-I”或
    者“–include-dir”指定的目录,如果找到指定的文件,则使用这
    个文件;
    否则继续依此搜索以下几个目录(如果其存在):
    “/usr/gnu/include”、“/usr/local/include”和“/usr/include”
    参考链接:https://www.cnblogs.com/3me-linux/p/8882232.html

    ④ 【预编译】

    #indef MAIN_DEBUG | main函数的DEBUG测试
    #error “This is main_debug” | #error:预编译指示字,生成编译错误信息,可默认
    #endif

    指令 用途
    .# 空指令,无任何效果
    #include 包含一个源代码文件
    #undef 取消已定义的宏
    #if 如果给定条件为真,则编译下面代码
    #ifdef 如果宏已经定义,则编译下面代码
    #ifndef 如果宏没有定义,则编译下面代码
    #elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
    #endif 结束一个#if……#else条件编译块
    #error 停止编译并显示错误信息
    #define 定义宏,大型工程中,考虑定义某个常量时,必须检查此常量是否已被定义,麻烦
    #if defined 如果被定义,在大型工程中避免直接重复定义更改常量的值,消除警告
    #if !defined 防止头文件被重复包含
    参考链接:https://www.cnblogs.com/herbertchina/p/4306818.html
    重点: #define NFC… (1)
    ()的作用:避免粘包,特别是有的宏在字符串里面替换
    参考链接:https://blog.csdn.net/t18438605018/article/details/111575224
    #error #warning 的作用:
    手搓代码如下:

    #include 
    
    #define PCHAR char*     //宏定义只是替换,如果这样会导致 PCHAR p1,p2; 报错,即p2未定义
    #define TTT   (2)
    #define TT   (3)
    #ifndef TTT             //如果没有定义 TTT,则执行下一行代码
    #error refdefine TTT    //#error:生成错误信息“#error refdefine TTT”,并停止编译,代表禁止此方向
                           //即 #error 的作用是 确保执行方向正确(你所设想的),若错误则提示并报错
    #elif T
    #warning this is TTT.   //若定义了宏定义 T,则生成并提示编译警告信息“#warning this is TTT”,但会继续编译
    #else 
    typedef int* PINT;
    int main()
    {
       //PCHAR p1,p2;      //或者单个定义,亦或者用 typedef,如 PINT p3,p4;
       PCHAR p1;
       PCHAR p2;
    
       char **a = &p1;
       char **b = &p2;
    
       PINT p3,p4;
       int **c = &p3;
       int **d = &p4;
    
       printf("Hello world\n");
       return 0;
       (void)a;        //部分编译器如果已经定义的变量未使用会报警告,(void)变量; 可解决警告,不建议
       (void)b;
       (void)c;
       (void)d;
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    #undef 的作用:
    手搓代码如下:

    #include 
    
    #define TTT (3)
    
    int main()
    {
       printf("TTT:%d\n",TTT);
    #undef TTT
    #warning "use of undeclared identifier 'TTT'"
       printf("TTT:%d\n",TTT);
       printf("Hello world\n");
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    #pragma 的作用:
    示例代码:
    #include

    #pragma once //文件最开始,避免头文件的重复引用,保证每个头文件只编译一次,
    //再加入同名的头文件也没有关系,反正也不编译,且不会报错
    //#define T (1)

    #ifdef T
    #pragma message(“T is defined”) //控制台在编译此处时提示信息,预防定义太多而忘记关键的宏
    #endif

    #pragma warning (disable:4707) //屏蔽4707警告
    #pragma warning (once:4706) //只显示一次4706警告
    #pragma warning (error:164) //将164号警告当作一个错误
    //等价
    #pragma warning (disable:9999;once:0000;error:1314)

    #pragma comment(lib,“user1.lib”) //将 user1.lib 库文件导入本工程中

    #pragma pack(1) //修改字节对齐为 1

    void func(void)
    {
    ;
    }
    int main()
    {
    printf(“Hello world\n”);
    return 0;
    }

    ⑤ifdef _cplusplus extern C的功能

    c++:支持函数重载,会将 函数参数类型 加到编译后的代码中,而不仅仅是函数名
    c:不支持函数重载,编译c代码的函数时不会带上函数的参数类型,一般只包括函数名
    ————————————————————

    #ifdef __cplusplus 
    extern "C" { 
    #endif
    
    //c语法的一段代码(自定义函数)
    
    #ifdef __cplusplus 
    } 
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    备注:不知道是被c调用还是c++调用时,请添加此段代码
    ——————————————
    手搓代码如下

    #include 
    using namespace std;
    #ifdef __cplusplus
    extern "C" {
    #endif
    void func()
    {
        printf("hello world\n");
        
    }
    #ifdef __cplusplus
    }
    #endif
    int main(void)
    {
        func();
        cout<<"Hello world"<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    重点:
    语句:extern int a;
    仅仅是一个变量的声明,其并不是在定义变量a,也并未为a分配空间。
    变量a在所有模块中作为一种全局变量只能被定义一次,否则会出错。

    通常来说,在模块的头文件中对本模块提供给其他模块
    引用的 函数和全局变量 以关键字extern声明

    extern对应的关键字是static,static表明变量或者函数只能在本模块中使用,
    因此,被static修饰的变量或者函数不可能被extern C修饰
    手搓代码如下

    //文件1:
    #include 
    #include 
    
    int a = 8;
    void funx()
    {
        pthread_mutex_t t;
        pthread_mutex_lock(&t);
        int x = a++;
        printf("func:%d\n",x);
        pthread_mutex_unlock(&t);
    }
    //文件2:
    #include 
    
    int main()
    {
        extern int a;
        printf("a:%d\n",a);
        return 0;
    }
    // gcc *.c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    —————————————————————plus 等价 “+”

    __cplusplus是c++定义的宏
    如果是c++调用,extern c声明有效
    如果是c 调用,extern c声明无效
    原因:c++与c的编译方式不一样,“extern C”{ 表示编译器用C语言的模式编译
    参考链接:https://www.cnblogs.com/TurboLemon/p/6364241.html

    拓展:extern的作用:
    https://www.cnblogs.com/lanhaicode/p/10633125.html
    static修饰的函数是否能被外部使用:
    https://blog.csdn.net/weixin_42031299/article/details/115942270
    C语言中static关键字用法和作用:
    https://blog.csdn.net/weixin_42031299/article/details/115587119

    ⚪⚪⚪pthread_self():获取本线程自身的 id 号

    ⑥【读写锁】pthread_rwlock_t 结构体

    pthread_rwlock_init:初始化读写锁
    pthread_relock_rdlock:请求读锁(阻塞、直到获得锁为止)
    pthread_rwlock_tryrdlock:读取非阻塞读写锁中的锁
    pthread_rwlock_wrlock:请求写锁
    pthread_rwlock_trywrlock:写入非阻塞读写锁中的锁
    pthread_rwlock_timedrdlock:锁定写入时的读写锁
    pthread_rwlock_unlock:解除锁定读写锁
    pthread_rwlock_destroy:销毁读写锁
    pthread_rwlock_t 读写锁函数参考链接:
    https://www.cnblogs.com/renxinyuan/p/3875659.html
    读操作可以共享,写操作是排他的,读可以有多个在读,写
    只有唯一个在写,同时写的时候不允许读。具有强读者同步和强写者同步两种形式
    对于读数据比修改数据频繁的应用,用读写锁代替互斥锁可以提高效率,写上锁,读无影响
    实现:http://c.biancheng.net/view/8635.html

    手搓代码如下:

    #include 
    #include 
    /*
    pthread_rwlock_init:初始化读写锁
    pthread_relock_rdlock:读取读写锁中的锁(阻塞、直到获得锁为止)
    pthread_rwlock_tryrdlock:读取非阻塞读写锁中的锁
    pthread_rwlock_wrlock:写入读写锁中的锁
    pthread_rwlock_trywrlock:写入非阻塞读写锁中的锁
    pthread_rwlock_unlock:解除锁定读写锁
    pthread_rwlock_destroy:销毁读写锁
     */
    #include 
    
    static int x = 0;
    //创建读写锁变量
    pthread_rwlock_t  rwlock;
    
    void* read_thread(void* arg)
    {
        printf("1、this is read_thread.\n");
        printf("the read_thread_id is %ld.\n",pthread_self());
        while(1)
        {
            sleep(3);
            //请求读锁
            pthread_rwlock_rdlock(&rwlock);
            printf("x=%d\n",x);
            pthread_rwlock_unlock(&rwlock);
        }
        return NULL;
        (void)arg;
    }
    
    void* write_pthread(void* arg)
    {
        printf("2、this is write_thread.\n");
        printf("the write_thread_id is %lu.\n",pthread_self());
        while(1)
        {
            sleep(3);
            //请求写锁
            pthread_rwlock_wrlock(&rwlock);
            ++x;
            printf("x:%d\n",x);
            pthread_rwlock_unlock(&rwlock);
        }
        return NULL;
        (void)arg;
    }
    int main()
    {
        int i;
        //初始化读写锁
        pthread_rwlock_init(&rwlock,NULL);
        //创建 3 个读取 x 变量的线程
        pthread_t readThread[3];
        for(i = 0; i < 3; ++i)
        {
            pthread_create(&readThread[i],NULL,read_thread,NULL);
        }
        //创建 1 个修改 x 变量的线程
        pthread_t writeThread;
        pthread_create(&writeThread,NULL,write_pthread,NULL);
        //等待各个线程执行完成
        pthread_detach(writeThread);//非阻塞式,分离线程,线程运行完后系统自动回收
        pthread_join(writeThread,NULL);//阻塞式,告诉系统,回收线程
    
        for(int i = 0; i < 3; ++i)
        {
            pthread_join(readThread[i],NULL);
        }
        //销毁读写锁
        pthread_rwlock_destroy(&rwlock);
        return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    ⑦【uint32_t家族】

    typedef  signed  char 	int8_t;
    typedef  short  int 		int16_t;
    typedef  int  		int32_t;
    typedef  long  int 		int64_t;
    typedef  unsigned  char	uint8_t;
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    声明一点*_t是typedef定义的表示标志,是一种规范化的表示
    参考链接:https://blog.csdn.net/ai_faker/article/details/118146275

    ⑧ 【互斥锁】pthread_mutex_t 结构体 保持多个线程同步

    pthread_mutex_init:动态创建互斥锁
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER:静态创建
    pthread_mutexattr_setpshared:设置互斥锁
    pthread_mutexattr_getshared:获得互斥锁的范围(进程间,线程间)
    pthread_mutexattr_settype:设置锁的类型(普通锁、嵌套锁、检错锁、适应锁)
    pthread_mutexattr_gettype:得到锁的类型
    pthread_mutex_destory:释放锁
    pthread_mutex_lock:加锁
    pthread_mutex_unlock:解锁
    pthread_mutex_trylock:测试加锁
    参考链接:https://www.cnblogs.com/eustoma/p/10054783.html
    pthread_detach()即主线程与子线程分离,子线程结束后,资源自动回收
    pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源
    一个可结合的线程能够被其他线程收回其资源和杀死
    一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放
    参考链接:https://www.cnblogs.com/ggzhangxiaochao/p/14415867.html

    ⑧ 【条件变量】 线程可用的同步机制、与信号类似 【同步】

    与互斥量一起使用时,允许线程以无竞争的方式的等待特定的条件发生
    使用场景:
    条件变量要与互斥量一起使用,条件本身是由互斥量保护的
    线程在改变条件之前必须首先锁住互斥量
    数据类型:pthread_cond_t
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER:静态初始化条件变量
    pthread_cond_init:动态初始化
    pthread_cond_wait:等待条件变量
    pthread_cond_timedwait:控制时间等待条件变量
    pthread_cond_signal:至少唤醒一个等待该条件的线程
    pthread_cond_broad:唤醒等待该条件的所有线程
    pthread_cond_destroy:销毁条件变量
    参考链接:
    https://blog.csdn.net/www_dong/article/details/120211090
    https://www.cnblogs.com/harlanc/p/8596211.html

    ⚪⚪⚪条件变量和互斥锁一起使用:
    https://www.jb51.net/article/102764.htm
    http://t.csdn.cn/QU2X7

    手搓代码如下:

    #include 
    #include 
    #include 
    #include 
    
    static int flag = 0;
    
    pthread_mutex_t s_mutex;
    pthread_cond_t cond;
    void* thread_1(void* arg)
    {
        while(1)
        {
            pthread_mutex_lock(&s_mutex);
            flag++;
            pthread_mutex_unlock(&s_mutex);
            if(1000 <= flag)
            {
                pthread_cond_signal(&cond);
            }
        }
        return NULL;
        (void)arg;
    }
    void* thread_2(void* arg)
    {
        while(1)
        {
            pthread_mutex_lock(&s_mutex);
            flag++;
            pthread_mutex_unlock(&s_mutex);
            if(1000 <= flag)
            {
                pthread_cond_signal(&cond);
            }
        }
        return NULL;
        (void)arg;
    }
    #if 1
    void* thread_3(void* arg)
    {
        while(1)
        {
            pthread_mutex_lock(&s_mutex);
            if(1000 <= flag)
            {
                flag--;    
            }
            pthread_mutex_unlock(&s_mutex);
        }
        return NULL;
        (void)arg;
    }
    #endif
    void* thread_4(void* arg)
    {
        while(1)
        {
            sleep(1);
            pthread_mutex_lock(&s_mutex);
            while(1000 <= flag)
            {
                pthread_cond_wait(&cond,&s_mutex);
                printf("flag=%d\n",flag);
                flag = 0;
            }
            pthread_mutex_unlock(&s_mutex);
        }
        return NULL;
        (void)arg;
    }
    int main()
    {
    #if 0
        //静态初始化互斥锁(在编译阶段完成初始化)
        pthread_mutex_t s_mutex = PTHREAD_MUTEX_INITIALIZER; 
        //静态初始化条件变量
        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    #endif
    
        pthread_t id_1,id_2,id_3,id_4;
        pthread_create(&id_1,NULL,thread_1,NULL);
        pthread_create(&id_2,NULL,thread_2,NULL);
        pthread_create(&id_3,NULL,thread_3,NULL);
        pthread_create(&id_4,NULL,thread_4,NULL);
    
        pthread_join(id_1,NULL);
        pthread_join(id_2,NULL);
        pthread_join(id_3,NULL);
        pthread_join(id_4,NULL);
    #if 1
        //动态初始化互斥锁(在运行阶段完成初始化)
        pthread_mutex_t *a_mutex = NULL;
        a_mutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
        pthread_mutex_init(a_mutex,NULL);
        //动态初始化条件变量
        pthread_cond_init(&cond,NULL);
    
        //释放互斥锁
        pthread_mutex_destroy(a_mutex);
        free(a_mutex);
        //释放条件变量
        pthread_cond_destroy(&cond);
    #endif
        return 0;
        (void)s_mutex;
        (void)cond;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    ⑨ 【回调函数】 重温

    分支、回调、根据函数指针调用分支
    int callback_1(int x);
    int callback_2(int x);
    int callback_3(int x);
    void Callback(int y, int (*callback)(int) );
    类似重载、多态的效果,但是实现方法不一样
    参考链接:https://www.runoob.com/w3cnote/c-callback-function.html

    手搓代码如下

    #include 
    int callback_1(int x)
    {
        printf("hello,siri,this is callback_1: x = %d \n",x);
        return 0;
    }
    int callback_2(int x)
    {
        printf("hello,siri,this is callback_2: x = %d \n",x);
        return 0;
    }
    int callback_3(int x)
    {
        printf("hello,siri,this is callback_3: x = %d \n",x);
        return 0;
    }
    void Callback(int x,int (*callback)(int))
    {
        puts("");
        printf("hello,siri.Entering Callback Function.\n");
        callback(x);
        printf("hello,siri.Leaving Callback Function.\n");
        puts("");
    }
    int main()
    {
        int a = 2, b = 4, c = 6;
        printf("Hello world\n");
        Callback(a,callback_1);
        Callback(b,callback_2);
        Callback(c,callback_3);
        printf("Hello world\n");
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    ⑩ 【getaddrinfo()函数、freeaddrinfo()函数】

    gethostbyname():用域名或主机名获取IP地址,仅仅支持IPV4
    不允许调用者指定所需地址类型的任何信息,返回的结构只包含了用于存储IPv4地址的空间
    getaddrinfo():与协议无关的,既可用于IPv4也可用于IPv6
    能够处理名字到地址以及服务到端口这两种转换
    返回的是一个addrinfo的结构(列表)指针而不是一个地址清单。
    这些addrinfo结构随后可由套接口函数直接使用。
    如此以来,getaddrinfo函数把协议相关性安全隐藏在这个库函数内部。
    应用程序只要处理由getaddrinfo函数填写的套接口地址结构,该函数在POSIX规范中定义
    int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
    备注:
    由getaddrinfo返回的所有存储空间都是动态获取的,
    这些存储空间必须通过调用freeaddrinfo返回给系统
    void freeaddrinfo( struct addrinfo *ai );
    参考链接:https://www.cnblogs.com/cxz2009/archive/2010/11/19/1881693.html

    ⑩① 【断言】 assert() ——如果断言为假,则中止程序 宏函数

    #include
    void assert( int expression );
    这个宏可以帮助程序员发现程序中的bug,或者通过崩溃处理异常情况,会产生有限的调试输出
    程序在我的假设条件下,能够正常良好的运作,其实就相当于一个 if 语句
    但是这样写的话,就会有无数个 if 语句,甚至会出现,一个 if 语句的括号从文件头到文件尾,
    并且大多数情况下,我们要进行验证的假设,只是属于偶然性事件,又或者我们仅仅想测试一下,一些最坏情况是否发生,所以这里有了 assert()
    表达式为false(即比较等于零),assert()向stderr打印一个标准错误消息,并通过调用abort()终止程序
    错误消息包括包含assert()调用的文件和函数的名称、调用的源代码行号和参数的文本
    缺点:频繁的调用会极大的影响程序的性能,增加额外的开销
    在调试结束后,可以通过在包含 #include 的语句之前插入 #define NDEBUG 来禁用 assert 调用,
    示例代码如下:
    #include
    #define NDEBUG
    #include
    参考链接:https://www.cnblogs.com/thisway/p/5558914.html
    备注:错误检查后面跟assert(0)的作用:

    #define T_CONFIG(config){ \
    	if( NULL == config){ \
    		error(...); \
    		assert(0); \
    	} \
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 捕捉逻辑错误。可以在程序逻辑必须为真的条件上设置断言。除非发生逻辑错误,否则断言对程序无任何影响。即预防性的错误检查,在认为不可能的执行到的情况下加一句ASSERT(0),如果运行到此,代码逻辑或条件就可能有问题。
    2. 程序没写完的标识,放个assert(0)调试运行时执行到此为报错中断,好知道成员函数还没写完。
      参考链接:https://blog.csdn.net/xiaodoubao124/article/details/46804319

    ⑩② 【地址重叠】memcpy函数拷贝内存时需要分类讨论,防止地址重叠
    地址重叠:即将拷贝的空间和即将被拷贝的空间重叠了一部分

    例如:
    【[][][][1][2] [3][4][5][][] [][][][][] 】
    | |
    src dest 从左到右开始复制时会发送内容丢失,即源空间占据了拷贝空间的一部分
    src 参考链接:http://t.csdn.cn/wA56U

    手搓代码如下(含断言应用)

    #include 
    #include 
    void my_memcpy(void *dest,const void *src,size_t n)
    {
        char *pdest = (char *)dest;
        const char *psrc = (char *)src;
    
        assert(dest);
        assert(src);
    
        if((dest > src) && (dest < src + n))
        {
            pdest = dest + n - 1;
            psrc = src + n - 1;
            while(n--)
                *pdest-- = *psrc--;
        }else
        {
            while(n--)
                *pdest++ = *psrc++;
        }
    }
    int main()
    {
        char dest[20],src[20] = "hello";
        my_memcpy(dest,src,10);
        printf("dest:%s\n",dest);
        printf("Hello world\n");
        return 0;
    }
    #if 0
    memcpy的这种实现方式只适用于
    地址向前重叠(src > dest && dest + count > src)的情况,
    如果是地址向后重叠(dest > src && src + count > dest)的情况,
    应该是从后向前拷贝。所以memcpy的实现应该分两种情况的
    #endif
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    ⑩③ 【setsockopt()函数、getsockopt()函数】重温

    int setsockopt(int sockfd,
    int level,
    int opt_name,
    const void *opt_val,
    socklen_t opt_len);

    int getsockopt(int sockfd,
    int level,
    int optname,
    const void *optval,
    socklen_t option);

    setsockopt():用于任意类型、任意状态套接口的设置选项值

    setsockopt(socket,
    被设置的选项的级别[SOL_SOCKET],
    准备设置的选项,
    有哪些取值[取决于level],
    选项值的最大长度[入口] || 选项值的实际长度[出口]);

    功能描述:
    https://www.cnblogs.com/eeexu123/p/5275783.html
    https://www.cnblogs.com/warren-liuquan/p/3557701.html

    ⑩④ 【RPC】 usb间传输

    解决多服务器调用的复杂情况,尽量让远程的调用变得简介透明,让团队能够专注于业务开发
    实现RPC关键点:
    1)信息传递
    识别对方的IP、端口、方法名、参数等都需要经过处理,调用IO时是否阻塞
    2)数据序列化
    通过网络流传输协议,数据必须进行序列化和反序列化
    序列化框架的选择:文本协议、二进制协议、压缩二进制协议
    3)网络通信
    各种RPC的实现框架都使用TCP协议作为网络通信的基本方法
    参考链接:https://www.csdn.net/tags/MtTaMg4sNDU4MTY2LWJsb2cO0O0O.html
    RPC的思路流程:
    程序中需要远程调用的方法调用客户端句柄,传输所需参数
    调用本地系统网络部分,发送信息
    消息传输
    服务器端接收到信息,将信息传给处理此操作的服务器端句柄
    句柄调用相应方法,操作数据(可以采用监视器模式)
    得到结果,返回结果给句柄
    句柄调用服务器的本地网络,返回信息
    信息返回
    客户端本地网络将信息发送给客户端句柄
    句柄将结果返回给方法

    ⑩⑤ 【#pragma once 与 #ifndef、#define、#endif 的区别】

    #pragma once:常用的C/C++预处理指令
    只要在头文件的最开始加入这条预处理指令,就能够保证头文件只被编译一次

    #ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,
    也能保证内容完全相同的两个文件不会被不小心同时包含。
    当然,缺点就是如果不同头文件的宏名不小心“撞车”,
    可能就会导致头文件明明存在,编译器却硬说找不到声明的状况。

    #pragma once则由编译器提供保证:
    同一个文件不会被编译多次。
    注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。
    带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。
    对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。
    当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。

    方式一由语言支持所以移植性好,方式二 可以避免名字冲突
    参考链接:https://www.cnblogs.com/qiang-upc/p/11407364.html

    ⑩⑥ typeof() 关键字 —C语言高级用法

    1)不用知道函数返回什么类型,可以使用typeof()定义一个用于接收该函数返回值的变量
    2)在宏定义中动态获取相关结构体成员的类型
    3)也可直接取得已知类型
    4)typeof + (指针或者值) 代替 数据类型
    参考链接:https://blog.csdn.net/rosetta/article/details/90741468

    手搓代码如下:

    // 方法1实例:
    #include 
    #include 
    #include 
    
    struct apple{
        int weight;
        int color;
    }L;
    
    struct apple* get_apple_info()
    {
        struct apple *a1;
        a1 = (struct apple *)malloc( sizeof(L) );
        if(a1 == NULL)
        {
            printf("malloc error.\n");
            return NULL;
        }
    
        a1->weight = 2;
        a1->color = 1;
    
        return a1;
    }
    
    int main()
    {
        //不知道函数返回什么类型,用 typeof()定义用于接收函数返回值的变量
        typeof(get_apple_info()) r1 = get_apple_info();
        //定义一个变量r1,用于接收函数get_apple_info()返回的值,
        //由于该函数返回的类型是:struct apple *,
        //所以变量r1也是该类型。
        //注意,函数不会执行。
    
    //    r1 = get_apple_info();
    
        printf("apple weight:%d\n", r1->weight);
        printf("apple color:%d\n", r1->color);
    
        return 0;
    }
    // 方法2实例:
    #include 
    #if 1
    //在宏定义中动态获取相关结构体成员类型
    #define max(x,y) ({ \
        typeof(x) _max1 = (x); \
        typeof(y) _max2 = (y); \
        (void) (&_max1 == &_max2); \
        _max1 > _max2 ? _max1 : _max2; })
        //如果调用者传参时,两者类型不一致,在编译时就会发出警告 
    #define P  0
    #endif
    
    #if 0
    #define max(x,y) ( (x) > (y) ? (x) : (y) )
    #endif
    int main()
    {
        int a = 3;
        float b = 4.0;
        int r = max(a,b);
    
        printf("r:%d\n",r);
        return 0;
    }
    // 方法3实例:
    #include 
    
    int main()
    {
        int a = 2;
        //直接取得已知类型
        typeof (int *)p;
        p = &a;
        printf("%d\n",*p);
        return 0;
    }
    // 方法4实例:
    #include 
    
    int main()
    {
        //typeof + (指针或者值) 代替 数据类型
        char *p1;
        typeof (*p1) ch = 'a';//ch为char类型,不是char *类型
        printf("%ld, %c\n", sizeof(ch), ch);//1, a
    
        char *p2;
        typeof (p2) p = "hello world";
        //此时的p才是char *类型,由于在64位机器上,所以指针大小为8字节
        printf("%ld, %s\n", sizeof(p), p);//8, hello world
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    ⑩⑦ 【韦根协议】

    常用格式:标准的26-bit
    其他格式:34-bit、37-bit
    适用于涉及门禁控制系统的读卡器和卡片的许多特性
    没有定义波特率、数据
    基本概念:
    韦根数据输出有两根线组成、分别是DATA0和DATA1,代表’0’和’1’
    输出’0’时:DATA0线上出现负脉冲
    输出’1’时:DATA1线上出现负脉冲
    负脉冲宽度TP=100us;周期TW=1600us
    注意:
    韦根协议只能做到单向传输,读头向控制器传输信息为单向,控制器
    无法给读卡器发送信号,控制器做出反馈还需借助其他工具
    韦根协议的优缺点
    优点
    简单、通用、成本低、技术对接容易
    可以通过其他方式进行加密,或是通过其他接线进行功能叠加
    缺点
    1、韦根协议是一种通用的协议,几乎所有的门禁控制系统都接受,因此安全性低,无法加密,相当于明文代码,可随意复制。
    2、 传输距离短,100m。
    3、单向传输,限制性高

    ⑩⑧ 【信号集函数】

    int sigemptyset(sigset_t *set); 将信号集初始化为空
    int sigfillset(sigset_t *set); 把信号集初始化包含所有已定义的信号
    int sigaddset(sigset_t *set, int signo); 把信号 signo添加到信号集 set 中,成功0,失败-1
    int sigdelset(sigset_t *set, int signo); 把信号 signo从信号集 set 中删除,成功0,失败-1
    int sigismember(sigset_t *set,int signo);
    判断给定的信号 signo 是否是信号集中一个成员,是 1,不是0,无效 -1
    int sigpromask(int how,const sigset_t *set,sigset_t *oset);
    根据参数指定的方法修改进程的信号屏蔽字,新的信号屏蔽字有参数 set指定,将原本信号屏
    蔽字保存在 oset中。
    如果set为空,how 则不一样
    how取值:
    SIG_BLOCK:添加到信号屏蔽字中
    SIG_SETMASK:设置信号屏蔽字为参数 set中的信号
    SIG_UNBLOCK:移除
    备注:调用该函数才能改变进程屏蔽字,之前函数都是改变一个变量的值,不会真正影响进程屏蔽字
    int sigpending(sigset_t *set); 将被阻塞的信号中停留在待处理状态的一组信号写到参数 set指向的信号集中
    int sigsuspend(const sigset_t *sigmask);将进程的信号屏蔽字替换成 sigmask给出的信号集,然后挂起进程的执行
    参考链接:https://www.cnblogs.com/52php/p/5815125.html
    回顾:signal()函数 与 sigaction()函数 、 kill()函数 与alarm()函数
    https://www.cnblogs.com/52php/p/5813867.html
    备注:注意信号处理函数的原型必须为void func(int)

    手搓代码如下
    信号集:

    #include 
    #include 
    #include 
    
    void handler(int sig)
    {
        printf("Handle the signal:%d\n",sig);
    }
    
    int main()
    {
        sigset_t sigset;    //用于记录屏蔽字的信号集
        sigset_t ign;       //用于记录被屏蔽(阻塞)的信号集
        struct sigaction act;
    
        //清空信号集
        sigemptyset(&ign);
        sigemptyset(&sigset);
    
        //向信号集中添加 SIGINT
        sigaddset(&sigset,SIGINT);
    
        //设置处理函数 和 信号集
        act.sa_handler = handler; 
        //指定要与signum关联的操作
        //SIG_DFL(默认操作)、SIG_IGN(忽略此信号)或指向信号处理函数的指针
        
        sigemptyset(&act.sa_mask);//将被屏蔽的信号集初始化为空
        act.sa_flags = 0;       //sa_flags指定一组修改信号行为的标志
        sigaction(SIGINT,&act,0);//用于更改进程在接收到特定信号时所采取的操作
    
        printf("wait the signal SIGINT...1\n");
        pause();
        //导致调用进程(或线程)休眠,直到传递一个信号,
        //该信号要么终止进程,要么导致调用信号捕获函数
    
        //把信号屏蔽字设置为参数 set 中的信号
        sigprocmask(SIG_SETMASK,&sigset,0);
        printf("please press ctrl + C in 10 seconds...2\n");
        sleep(10);
    
        sigpending(&ign);
        if(sigismember(&ign,SIGINT))
        {
            printf("the SIGINT signal has ignored...3\n");
        }
    
        //从信号集中删除信号 SIGINT
        sigdelset(&sigset,SIGINT);
        printf("wait the signal SIGINT...4\n");
    
        //将进程的屏蔽字重新设置,即取消对 SIGINT 的屏蔽
        //挂起进程
        sigsuspend(&sigset);
    
        printf("the app will int 5 seconds...5\n");
        sleep(5);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    信号 signal:

    #include 
    #include 
    // void ( *signal(int sig, void (*func)(int)) )(int);
    // func: void (*)(int) 的函数指针
    // 备注:注意信号处理函数的原型必须为void func(int)
    
    #include 
    
    void func(int sig)
    {
        printf("this is signal 处理函数.只执行一遍\n");
    
        //恢复终端中断信号的 SIGINT 的默认行为
        (void)signal(SIGINT,SIG_DFL);
        //忽略中断信号
        //(void)signal(SIGINT,SIG_IGN);
    
        (void)sig;
    }
    int main()
    {
        (void)signal(SIGINT,func);
        while(1)
        {
            printf("......\n");
            sleep(1);
        }
        printf("Hello world\n");
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    信号改良函数 sigaction:

    #include 
    #include 
    //int sigaction(int sig,const struct sigaction *act,struct sigaction *oact);
    //设置与信号 sig 关联的动作
    //若 oact 不是空指针的话,便用它来保存原先对该信号的动作的位置
    //act 用于设置指定信号的动作
    
    //sigaction 结构体至少包含以下成员
    /*
     *1、 void (*)(int) sa_handler:处理函数指针,相当于 signal 函数的 func 函数
     *2、 sigset_t sa_mask:制定一个信号集,在调用 sa_handler 所指向的信号处理函
        数之前,该信号集将被加入到进程的信号屏蔽字中。
        信号屏蔽字是指当前被阻塞的一组信号,他们不能被当前进程接收到.
     *3、 int sa_flags:信号处理修改器
     sa_flags 结构体:
     SA_NOCLDSTOP 子进程停止时不产生 SIGCHLD 信号
     SA_RESETHAND 对此信号的处理方式在信号处理函数的入口处重置为 SIG_DFL
     SA_RESTART   重启可中断的函数而不是给出EINTR错误
     SA_NODEFER   捕获到信号时不将它添加到信号屏蔽字中
     备注: sa_mask 可消除这一竞态条件(提前被捕获)
     */
    
    #include 
    #define T (20)
    
    void func(int sig)
    {
        printf("this is sigaction function.\n");
        
        (void)sig;
    }
    
    int main()
    {
        struct sigaction act;
        act.sa_handler = func;
    
        // 创建空的信号屏蔽字,即不屏蔽任何信息
        sigemptyset(&act.sa_mask);
    
        // 使 sigaction 函数重置为默认行为
        act.sa_flags = SA_RESETHAND;
    
        sigaction(SIGINT,&act,0);
    
        int a = T;
        while(a--)
        {
            printf("Hello World!\n");
            sleep(100);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    kill()函数 和 alarm()函数:

    #include 
    #include 
    #include 
    #include 
    #include 
     
    static int alarm_fired = 0;
     
    void ouch(int sig)
    {
        alarm_fired = 1;
    
        (void)sig;
    }
     
    int main()
    {
        pid_t pid;
        pid = fork();
        switch(pid)
        {
        case -1:
            perror("fork failed\n");
            exit(1);
        case 0:          // 子进程  getpid():获取当前进程的 id 号
            sleep(5);
             
            // 子进程真残忍,睡了5秒,醒来的第一件事是向系统报告,请求杀死父进程
            kill(getppid(), SIGALRM);
            exit(0); 
        default:         // 父进程  getppid():获取当前进程的父进程的 id 号
            ;
        }
         
         // 设置处理函数
        signal(SIGALRM, ouch);
        while(!alarm_fired)
        {
            printf("Hello World!\n");
            sleep(1);
        }
        if(alarm_fired)
            printf("\nI got a signal %d\n", SIGALRM);
     
        exit(0);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    DEBUG、LINEfunc、##VA_ARGS
    参考链接:
    https://www.cnblogs.com/lixiaohui-ambition/archive/2012/08/21/2649052.html
    手搓代码如下:

    #include 
    #include 
    
    #if 0			//选择一
    #define __debug_printf(flag, fmt, ...) do { \
    	printf("%d | %s() : \n", __LINE__, __func__); \
    	printf(fmt,  ##__VA_ARGS__); \
    } while (0)
    #else		//选择二
    #define __debug_printf(flag, fmt, ...) do { \
        printf("File: "__FILE__", Line: %d: \n" fmt "\n", __LINE__, ##__VA_ARGS__);  \
    } while (0)
    #endif
    /* 错误级别日志打印 */
    #define __debug_error(level, fmt, ...) do { \
        if (level < 0) \
            __debug_printf("error", fmt, ##__VA_ARGS__); \
    } while (0)
    
    /* 警告级别日志打印 */
    #define __debug_warn(level, fmt, ...) do { \
        if (level > 0) \
            __debug_printf("warn", fmt, ##__VA_ARGS__); \
    } while (0)
    
    /* 调试级别日志打印 */
    #define __debug_info(level, fmt, ...) do { \
        if (level == 0) \
            __debug_printf("info", fmt, ##__VA_ARGS__); \
    } while (0)
    
    
    void array_printf(unsigned char*data, int len) {
    	printf("{");
    	for(int i = 0; i < len; ++i){
    		printf("%02x%s", data[i], i<(len)-1 ? ":" : " ");
    	}
    	printf("}\n");
    }
    
    static inline void __debug_hexdump(const char *title, unsigned char *data, int length) {
        printf("\n%s: \n", title);
        array_printf(data, length);
        //printf("\n");
    }
    
    #define debug_hexdump(level, title, data, len) do { \
        if (level > 2) \
            __debug_hexdump(title, data, len); \
    } while (0)
    
    #if 1
    #define __DEBUG__  
    #ifdef __DEBUG__  
    #define DEBUG(msgmat,...) printf("File: "__FILE__", Line: %d: " msgmat "\n", __LINE__, ##__VA_ARGS__)  
    //#define DEBUG(format,...) printf("FILE: "__FILE__", LINE: %d: " format "\n", __LINE__)  
    /*
     * ##__VA_ARGS__的作用:
    debug.c:51:34: warning: format ‘%s’ expects a matching ‘char *’ argument [-Wformat=]
       51 | #define DEBUG(format,...) printf("FILE: "__FILE__", LINE: %d: " format "\n", __LINE__)
          |                                  ^~~~~~~~
    debug.c:59:5: note: in expansion of macro ‘DEBUG’
       59 |     DEBUG("LL: %s",str);
          |     ^~~~~
    
     */
    #else  
    #define DEBUG(format,...)  
    #endif  
    int main() {  
        unsigned char str[]="Hello world";  
        char old[]="old_log";
        unsigned char *data = str;
        DEBUG("LL: %s",str); 
        printf("FINE:%s,LINE:%d,func:%s\n",__FILE__,__LINE__,__func__);
    //    __debug_hexdump(old,data,sizeof(str));    //直接调用__debug_hexdump()
    //    debug_hexdump(2,old,data,sizeof(str));    //不打印
        debug_hexdump(3,old,data,sizeof(str));      //打印
    //    __debug_printf(0,"flag\n");               //验证debug打印函数
        __debug_info(0,"this is normal output.\n");                    
        __debug_warn(1,"this is warn.\n");
        __debug_error(-1,"this is err.\n");
        return 0;  
    }  
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
  • 相关阅读:
    【Handler机制分析】
    Java 随笔 代理模式 3-cglib
    PID与ADRC
    MyBatis 查询数据库
    关于使用KEIL建立STM32项目(附带建立好的工程以及注意事项)
    JUC锁: ReentrantReadWriteLock详解
    wpa_supplicant介绍
    YoloV5-SPD+TensorRT:基于YoloV5-SPD的小目标检测算法部署
    【微服务|SCG】Spring Cloud Gateway快速入门
    spring5.0 源码解析(day06) initApplicationEventMulticaster();
  • 原文地址:https://blog.csdn.net/hold_the_key/article/details/127987860