您的位置:首页 > 编程语言 > C语言/C++

C语言中的这个小细节你知道吗?

2021-05-27 23:57 1166 查看

文章目录

  • 1.2算术转换
  • 1.3 操作符属性
  • 总结:
  • 既然题目都说了是小细节,一来就介绍细节多没意思啊,先坑坑大家再详细介绍吧,嘿嘿.直接上7个题吧,看看你能做对几个呢?

    计算型细节

    ①:

    #include <stdio.h>
    int main()
    {
    char a = 3;
    char b = 127;
    char c = a + b;
    printf("结果是:%d",c);
    return 0;
    }

    您想想这个题的答案是多少?先不要看后面的答案哦
    答案是 -126, 嘿嘿,是不是答错了呢?先不要着急,继续看下面的题

    ②:

    #include <stdio.h>
    int main()
    {
    char a = 0xb6;
    short b = 0xb600;
    int c = 0xb6000000;
    if(a==0xb6)
    printf("a");
    if(b==0xb600)
    printf("b");
    if(c==0xb6000000)
    printf("c");
    return 0;
    }

    您想想这个题的答案是什么呢? 先不要看后面的答案哦
    答案是 c,嘿嘿,是不是又回答错误,先不要着急,再看看后面的题

    ③:

    #include <stdio.h>
    int main()
    {
    char c = 1;
    printf("%u\n", sizeof(c));
    printf("%u\n", sizeof(+c));
    printf("%u\n", sizeof(-c));
    return 0;
    }

    您想想这个题的答案是什么呢? 先不要看后面的答案哦
    答案是1 4 4,嘿嘿,是不是又回答错误,先不要着急,再看看后面的题

    表达式细节

    ①:

    #include <stdio.h>
    int main()
    {
    int c = 3;
    int ret = c + --c;
    printf("%d",ret);
    return 0;
    }

    您想想这个题的答案是什么呢? 先不要看后面的答案哦
    答案是 5 或者 4,是

    不确定
    的,嘿嘿,是不是又回答错误,先不要着急,再看看后
    面的题

    ②:

    int main()
    {
    int i = 10;
    i = i-- - --i * ( i = -3 ) * i++ + ++i;
    printf("i = %d\n", i);
    return 0;
    }

    您想想这个题的答案是什么呢? 先不要看后面的答案哦
    答案有很多个,在不同的编译器结果不同,也就是说还是

    不能确定结果
    ,嘿嘿,是不是仍然回答错误了,先不要着急,再看看后面的题

    ③:

    #include <stdio.h>
    int fun()
    {
    static int count = 1;
    return ++count;
    }
    
    int main()
    {
    int answer;
    answer = fun() - fun() * fun();
    printf( "%d\n", answer);//输出多少?
    return 0;
    }

    您想想这个题的答案是什么呢? 先不要看后面的答案哦
    答案是

    不同编译器不同结果
    ,嘿嘿,是不是又回答错误,先不要着急,再看看后面的题

    ④:

    #include <stdio.h>
    int main()
    {
    int i = 1;
    int ret = (++i) + (++i) + (++i);
    printf("%d\n", ret);
    printf("%d\n", i);
    return 0;
    }

    这是最后一个题了,你目前答对了几道了呢??请在评论回答试试.嘿嘿
    这个题的答案是

    不同编译器不同结果

    大家回答对了几道题?欢迎评论

    1.现在正式讲解上面所有的题设计到的内容--------表达式求值

    表达式求值的顺序一部分是由操作符的优先级和结合性决定。
    同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

    1.1隐式类型转换 (整型截断与提升)

    什么是隐式类型转换,整型提升,整型截断?

    C的整型算术运算总是

    至少以满足整型类型的精度
    来进行的。
    为了获得这个精度,表达式中若有
    char
    short
    类型必须在使用之前转换称为整型,这个过程叫做
    整型提升

    .
    一个较大数据类型存储在较小数据类型中的过程叫做
    整型截断
    ,比如整型
    a = 500
    ,但是a把他的值放到了
    字符型b
    中,b不能完全存放a,就会发生
    整型截断

    .
    而这个转换行为叫做
    隐式类型转换

    1.1.1 第一题讲解

    #include <stdio.h>
    int main()
    {
    char a = 3;
    char b = 127;
    char c = a + b;
    printf("结果是:%d",c);
    return 0;
    }

    我们知道计算机中一切都是操作的补码.所以这里也是一样,(不明白原码补码反码的自己去百度下哦).
    其中 正数的 原码 反码 补码 一模一样
    … …负数的 补码是反码加一,反码是原码除了符号位所有位都按位取反

    a只有1个字节,他的补码是

    00000011
    (二进制)
    b只有1个字节,他的补码是
    01111111
    (二进制)
    a加b,进行了算术操作,需要提升到4个字节.而整型提升有两种方式

    • 算术提升:在最前面补符号位数字,直到32位
    • 逻辑提升:无论什么都只补0,直到32位
      大多数计算器都是进行的
      算术提升
      ,这里便讲解
      算术提升
      .

    a算术提升后就是

    00000000000000000000000000000011

    b算术提升后就是
    00000000000000000000000001111111

    a+b的结果是------
    00000000000000000000000010000010

    存进c里面,c又只有1个字节,所以发生整型截断,只存取最后8位
    10000010

    此时c存取的是补码,且c是有符号型,
    记住了!!
    ,打印显示时候会还原位原码的,该补码对应的数字就是
    -126

    下面有张字符类型

    原码补码反码
    的对应理解图

    所以有符号的范围是 [-128,127]
    无符号的范围是[0,255]

    变成圆圈更好理解,自己按照有无符号对应去找就行.

    1.1.2 第二题讲解

    #include <stdio.h>
    int main()
    {
    char a = 0xb6;
    short b = 0xb600;
    int c = 0xb6000000;
    if(a==0xb6)
    printf("a");
    if(b==0xb600)
    printf("b");
    if(c==0xb6000000)
    printf("c");
    return 0;
    }

    0xb6的二进制是

    00000000000000000000000010110110

    存进去a中,发生截断,得到
    1011 0110

    0xb600的二进制是
    00000000 00000000 10110110 00000000

    存进去b中发生截断,得到
    10110110 00000000

    0xb6000000
    10110110 00000000 00000000 00000000

    存进去c中刚刚好.

    a此时存的是补码,a又与0xb6比较,参与了运算,所以又整型提升为

    11111111 11111111 11111111 10110110
    ,

    • 整型提升后的数字与0xb6的二进制不一样,所以不等.

    b此时存的是补码,b又与0xb600比较,参与了运算,所以又整型提升为

    11111111 11111111 10110110 00000000

    • 整型提升后的数字与0xb600的二进制不一样,所以不等

    c此时存的是

    10110110 00000000 00000000 00000000
    ,注意哦~,有人可能会问
    0xb6000000
    是正数啊,
    不!!!!!
    ,字面常量只要不带符号就是默认int类型.所以此时
    0xb6000000
    的整数值是超过int的,就会认为它此时的二进制是负数的补码.所以c与
    0xb6000000
    是相等的.

    1.1.3 第三题讲解

    #include <stdio.h>
    int main()
    {
    char c = 1;
    printf("%u\n", sizeof(c));
    printf("%u\n", sizeof(+c));
    printf("%u\n", sizeof(-c));
    return 0;
    }

    sizeof
    测量的是类型属性的值.
    c
    的类型是
    char
    ,只有一个字节,所以答案是1
    +c
    ,
    +
    是单目操作符,与c在一起发生了整型提升,变成了int,所以是4个字节,答案是 4
    -c
    ,同样的道理,仍然是
    4

    1.2算术转换

    上面我们说到,如果操作数大小 小于int,会发生整型提升,但是如果都是大于等于int大小的类型参与算术运算呢?? 这里就会涉及到

    算术转换
    .
    什么是算术转换呢? 比如下面的例子:

    #include <stdio.h>
    int main()
    {
    int a = -127;
    unsigned int b =  129;
    if(a > b)
    {
    printf("a会大于b");
    }
    return 0;
    }

    结果会打印

    a会大于b

    因为这就是算术转换,即还是需要满足同类型运算,unsigned int 比int 高,所以a的值会默认是 无符号的,a就会比b大.

    算术提升方向:

    1.3 操作符属性

    复杂表达式的求值有三个影响的因素。

    1. 操作符的优先级
    2. 操作符的结合性
    3. 是否控制求值顺序。

    1.3.1 什么是优先级?

    就是决定先计算什么.比如

    d = a + b*c
    . 因为
    *
    的优先级高于
    +
    ,所以,先算
    b*c
    ,再算
    +

    1.3.2 什么是结合性?

    就是同样优先级,就决定从哪个方向计算.比如

    d = a * b * c
    ,因为连续的
    *
    ,优先级已经没有用了,所以此时就是结合性,
    *
    的结合性是从左到右.也就是说先计算
    a*b
    然后计算
    *c
    .

    1.3.3 什么是求值顺序?

    就是只计算哪边.c语言的操作符具有求值顺序的只有寥寥几个,比如

    ||, && , !

    求值顺序
    到底什么意思呢?
    比如a等于0,b等于2,c等于3,
    d = a && b && c
    ,d的值最后是0,但是在运算时候只到a就完结了,因为
    &&
    是只要碰到假就是假,后面的真假已经无关,a为0,是假,所以后面不用再管.这就是求值顺序.

    下面有两个关于求值顺序的小练习:

    #include <stdio.h>
    int main()
    {
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
    }

    答案是

    1 2 3 4
    .理由: a是后置++,先使用a=0的值,一开始就遇到假了,后面不再执行.但是a还是增加了的,因为后置加加是
    先使用再加加

    #include <stdio.h>
    int main()
    {
    int i = 0,a=0,b=2,c =3,d=4;
    i = ++a || ++b || d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
    }

    答案:

    1 2 3 4
    ,前置++,即先加加,a成了1,因为
    ||
    一遇到真就结束,不再管后面真假.所以只有a变化了.

    这里有张操作符属性的表:


    其中优先级从上往下逐渐降低

    1.3.4 第四题讲解

    #include <stdio.h>
    int main()
    {
    int c = 3;
    int ret = c + --c;
    printf("%d",ret);
    return 0;
    }

    ret = c + --c
    中有两个操作符号,先看
    优先级
    ,
    --
    的优先级高于
    +
    ,所以决定了先算–c,但是
    +
    号左边的c是什么时候准备的呢? 我们知道,c语言是编译性语言,在代码写好以后是需要先进行编译为机器语言,然后执行的.那么在编译时候,+号左边的值是在
    --c
    之前就已经编译好了呢,还是
    --c
    之后编译好了呢?这是不确定的.

    • 在vs编译器下答案是 4,他是在
      --c
      之后准备好了c
    • 在gcc编译器下答案是 5,他是在
      --c
      之前准备好了c

    所以: 这是问题代码,我们以后不要写这样的垃圾代码.

    1.3.5 第五题讲解

    int main()
    {
    int i = 10;
    i = i-- - --i * ( i = -3 ) * i++ + ++i;
    printf("i = %d\n", i);
    return 0;
    }

    这个是同样的道理,虽然知道优先级,但是结合性中的操作数什么时候准备不确定,你看看在不同的编译器操作的结果:

    同样是个垃圾代码

    1.3.6 第六题讲解

    #include <stdio.h>
    int fun()
    {
    static int count = 1;
    return ++count;
    }
    
    int main()
    {
    int answer;
    answer = fun() - fun() * fun();
    printf( "%d\n", answer);//输出多少?
    return 0;
    }

    ()
    函数调用符号优先级最高,但是这里有三个,到底先调用哪个呢??这又不确定了.

    • vs编译器是从左往右依次调用的,结果为-10.
    • 但是其他编译器呢??大家去试试gcc,codeblocks,Devc++,你一定会发现完全不一样.

    1.3.7 第七题讲解

    再说这个题之前,博主一定要批判某些高校,为什么呢??学校教大家

    ++
    符号时候是不是最喜欢用这种类型来考大家?在这明确告诉大家,这完全是在浪费时间!!!因为这种代码根本没有意义!!!.
    就是垃圾代码

    #include <stdio.h>
    int main()
    {
    int i = 1;
    int ret = (++i) + (++i) + (++i);
    printf("%d\n", ret);
    printf("%d\n", i);
    return 0;
    }

    首先,

    ()
    括号的优先级最高,但是有三个,到底先计算哪个?
    其次,一个
    i
    的变化,会不会影响另外的?这里也不确定.和第四题我们说的那个编译之前
    c
    到底什么时候准备一样.不确定.!!!

    • vs编译器的值是 12 4
    • gcc编译器的值是 10 4
      不同的编译器不同的值,结果不一样,这样的代码不配叫代码.!!!

    所以,以后看到这种题直接跳过,没有意义.

    总结:

    • 知道了什么是整型提升与截断
    • 知道了什么是算术转换
    • 知道了什么操作符的属性,以后不能写出这种类似的垃圾代码.
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: