• 利用C的&&(逻辑与)和||(逻辑或)设计三元分支运算符


    最近在学习Linux基础知识,从鸟哥的的Linux私房菜里学到了不少的东西。在学习Shell Script一章的时候,发现鸟哥用了这样的一个语法

    test -e /dmtsai && echo "exist" || echo "Not exist"

    鸟哥解释是为检测并标示/dmtsai文件夹是否存在用的。

    一开始我,我想可能是&&和||在Linux的Shell Script里面有不同的含义吧,可能这样的语法相当于C语言中的条件运算符( : ? )

    但是怎么想都觉得&&||在任何程序语言中都应该是逻辑运算符才是。

    仔细看了后边的内容,也肯定&&和||在Shell Script里面也一样是逻辑运算符,因此再重新认真分析上边的语句之后,才发现了这个奥秘。因此我将其引入到C语言中,用&&和||来构建能够生成分支语句的宏(macro)

    虽然宏在C语言中似乎都是被限制有时甚至禁止使用的,《Effective C++》一书中开篇就讲述了宏定义带来的问题。但是还是要承认宏定义技巧之高超,妙处之频出,合理利用对代码的简化还是有很多的好处的。

    特别是在学习FreeBSD源码时,看到编写Unix/Linux的达人们写的用宏来做匿名结构体定义,用宏来操作指针链表的增删排序,实在是敬仰之心悠然起敬。

    回到本文的主题,之所以可以利用&&和||这两个逻辑运算符来实现一个分支语句,主要是基于C语言的三点语言特性:

    1. C语言里面,任何一个语句(statement)都是有值的;
    2. C语言可以自动将数值类型隐式转换成逻辑值:真(true)或假(false);
    3. 在逻辑运算中,之前的运算已经可以断定逻辑结果,后边的运算将不再继续进。

    "任何一个语句都是有值"

    举例来说:

    赋值语句(a=10;),它的语句值是就是赋值语句的左值,即等号左边最后得到的值;

    函数调用,那么其值就是函数的返回值了;

    逗号语句(a-=10,b+=1,a*=10+b;)其值就是,它的值是最后一个表达式的值,例子中最后一个表达式又是赋值语句,因此其值就是最后a的值了;

    等等。

    "数值类型隐式转换成逻辑值"

    第二条也是比较C比较有趣的特性。如果放在判定条件(while, if)或者逻辑条件里作为参数,那么除了0,0.0,null, '\0'这些会被判定为false外,其他的无论是正数负数还是任意字符,任意其他地址值,都会被认定为true

    "最短逻辑判定"

    第三条主要意思是:

    对于 a&&b,如果a的值是假,那么显然无论b是什么值整个结果都是假,因此b的运算将不再进行;对于a||b,如果a的值是真,b也不会进行运算了。

    所以比如有

    int x=10,y=8;
    (++x==11)||(++y==9)

    最后会发现x变成了11,而y还是8,因为++y并没有执行。

    当然,如果"&&"和"||"以及"!" 同时出现在逻辑算式中,我们还要根据优先度和结合率来判定哪些语句会被忽略执行。


    结合上述三点,于是就可以构建出了下边这样的宏:

    #define IFF(CONDITION, STATEMENT1,STATEMENT2) \
    (\
        (\
            (CONDITION)\
            &&((STATEMENT1)||1)\
        )\
        ||(STATEMENT2)\
    )

    其实也可以简化成如下定义:

    #define IFF(CON, S1, S2) (((CON)&&((S1)||1))|| (S2))

    这个宏有三个参数:CON(条件Condition),S1(语句Statement1),S2(语句Statement2)

    IFF宏分析

    当条件CON为真的时候,宏就需要去执行语句S1,如果S1最后的结果可以判别为真,那么整个||之前的都被认定为真,因此S2就会被忽略执行;如果S1的结果被判定为假,因为之后跟着||1,所以还是会得到真值,S2依旧会被忽略。

    当条件CON为假的时候,因为是用&&连接,所以宏就会忽略S1而去执行S2,无论S2的结果被判定为真还是假,我们都不在意了,因为这个宏的目的主要是判定执行哪个语句而不是要其返回值。

    IFF宏调用测试

    来看一个调用的例子:

    int main()
    {
        int a=30, b =10;
        int max, min;
        max = min =0;
        IFF(a>b, {max = a; min = b;} , {max = b; min = a;});    
        printf("max: %d, min: %d\n",max,min);
        
        return 0;
    }

    最后输出

    max: 30, min: 10;

    从上边的程序可以看出几点:

    1.语句2确实没有被执行,否则第二句的执行结果会覆盖第一句的结果;

    2.可以用花括号将一段程序传给该宏来执行,而不只是简单的普通参数。

    再看一个嵌套调用的例子:

    int main()
    {
        int a=10, b=30, c=20;
        int max;
        
            IFF(a>b
            , IFF(a>c, printf("max:%d\n", a),printf("max:%d\n",c))
            , IFF(b>c, printf("max:%d\n", b),printf("max:%d\n",c))
            );
        printf("max:%d\n",max);
            
        return 0;
    }

    最后的输出结果是

    max:30

    说明程序正确执行,也说明我们还可以直接将函数的调用传给宏。

    存在的问题

    C语言中,除了基本的数据类型之外,还有两种特殊的类型:自定义类型(struct)和空类型(void,但不是空指针void *).

    对于void类型,一般不会用在定义数据上,多用来指示函数的返回值,用以表示该函数不需要返回值,只是执行一段代码过程。对于void类型,C就无法将其转换成逻辑真或假,因此对于返回void的函数就无法用来作为 IFF宏的实参了。

    因为前面嵌套调用的例子中,printf()函数的返回值是整型,返回输出的字符数量,所以可以将该函数作为IFF宏的参数

    但假如我们有如下代码

    void Voidfunc()
    {
        printf("I am the void function\n");    
        return;
    }

    int main()
    {
        IFF(21, Voidfunc(), 7);
        return 0;
    }

    我们就会得到:

    error: void value not ignored as it ought to be

    因为C无法自动转换void的类型成为逻辑类型。

    同样的对于结构体(struct)类型,C也无法自动将其转换成逻辑类型进行逻辑运算,因此也就无法作为IFF宏的参数了。

    当然,因为IFF宏接受代码段,所以我们可以用花括号来包裹这个函数,并放上一个辅助语句来避开这个问题,即将上述调用改成

    IFF(2, {Voidfunc(); 1;}, 7);

    于是,我们就可以成功在IFF宏里调用返回值为void的函数了。



    P.S.

    本文只是探索C语言及其宏定义的趣味性,以及程序各种有趣的实现方式,并非讨论其应用价值,因此测试代码相对比较简单甚至有些没有逻辑性。如有疏漏,还望指正。

  • 相关阅读:
    ASP.NET对IIS中的虚拟目录进行操作
    QLive EULA
    Windows Phone在模拟器中去除Debug信息
    Windows Phone区别应用是App还是Game
    UI Automation By Microsoft
    UIA: Choose item in Combobox
    身份证号码验证 C#
    Windows Phone 获取窗口大小
    重载 Sort, ==, !=
    C++枚举类型
  • 原文地址:https://www.cnblogs.com/ider/p/condition_statement_with_logical_conjunction_and_disconjunction.html
Copyright © 2020-2023  润新知