• C陷阱与缺陷 第6章 预处理器 6.3 宏并不是语句


    assert宏,它的参数是一个表达式,如果该表达式为0,就使程序终止执行,并给出一条适当的出错信息。把assert作为宏来处理,这样就使得我们可以在出错信息中包含文件名和断言失败处的行号。也就是说, 
    assert(x > y);
    在x大于y时什么也不做,在其他情况下则会终止程序。
    第一次尝试: 
    #define assert(e) if (!e) assert_error(__FILE__, __LINE__)
    因为考虑到宏assert的使用人员会加上一个分号,所以在宏定义中并没有包括分号。
    __FILE__和__LINE__是内建于C语言处理器中的宏,它们会扩展为所在文件的文件名和所处代码行的行号。 

    if (x > 0 && y > 0) 
        assert(x > y);
    else 
        assert(y > x);

    上面例子的展开形式: 
    if (x > 0 && y > 0) 
        if (!(x > y)) assert_error("foo.c", 37);
    else 
        if (!(y > x)) assert_error("foo.c", 39);
    将上面的代码做适当的缩排处理,我们就能够看清它实际的流程结构与我们期望的结构有怎样的区别:
    if (x > 0 && y > 0) 
        if (!(x > y))
            assert_error("foo.c", 37);
        else 
            if (!(y > x))
                assert_error("foo.c", 39);
    在宏assert的定义中用大括号把宏整个给括起来,就能避免这样的问题产生: 
    #define assert(e) \
    {
        if (!e) assert_error(__FILE__, __LINE__); 
    }
    这个又带来了新的问题: 
    if (x > 0 && y > 0) 
    {
            if (!(x > y)) assert_error(__FILE__, __LINE__);
    };
    else 
    {
        if (!(x > y)) assert_error(__FILE__, __LINE__);
    };
    在else之前的分号是一个语法错误。要解决这个问题,一个办法是对assert的调用在后面不再跟
    一个分号,但这样的用法显得有些“怪异”: 
    y = distance(p, q);
    assert(y > 0)
    x = sqrt(y);
    这个定义看起来像一个表达式,不是类似于一个语句 
    #define assert(e) \
    ((void)((e) || _assert_error(__FILE__, __LINE__)))
    这个定义实际上利用了||运算符对两侧的操作数依次顺序求值的性质。如果e为true(真),表达式 
    (void)((e) || _assert_error(__FILE__, __LINE__))
    的值没有求出其右侧表达式 
    _assert_error(__FILE__, __LINE__)
    的值的情况下就可以确定最终的结果为真。如果e为false(假),右侧表达式 
    _assert_error(__FILE__, __LINE__)
    的值必须求出,此时_assert_error将被调用,并打印出一条恰当的“断言失败”的出错消息。 

  • 相关阅读:
    webpack 使用与配置
    SpringCloud之gateway动态路由
    GoLang协程与通道---下
    FLUX.1 实测,堪比 Midjourney 的开源 AI 绘画模型,无需本地显卡,带你免费实战
    JavaScript定义函数,创建函数实例时的内部原理
    详解API接口如何安全的传输数据
    JVM内存模型
    Jenkins持续集成
    Mybatis 概述
    我把“大厂面试指南”进行了重新梳理,V2.1 版已上线,耗时 66 天!
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126045345