C语言基础05——操作符与表达式求值。整型提升、二进制中1的个数、不使用中间值交换两个变量

目录

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

逻辑操作符

条件操作符/三目操作符

逗号表达式

下标引用、函数调用和结构成员

下标引用:数组名[数组下标]

函数调用:函数名(参数);

访问一个结构体的成员

表达式求值

隐式类型转换

算数转换(自动类型转换)

强制类型转换

操作符的属性

操作符的优先级(从高到低排列)

练习


算术操作符

+-*/%
取余
  • 除了%以外,其他的几个操作符都可以用于整数和浮点数

  • 对于/操作符,如果两个操作数都为整数,执行整数除法,省略小数点。而只要两端有一个浮点数,则进行浮点数除法,小数点不会被省略。

  • %操作符的两个操作数必须为整数。返回的是余数

    #include <stdio.h>
    
    int main()
    {
        //如果是整数之间的除法,则计算出的也是整数,不会出现小数
        int a = 2/4;
        printf("%d\n",a); //输出0,为什么不是0.5?因为这是整数的除法,省略小数点
    
        //即使使用float类型变量接收,因为是整数除法,所以结果存到变量之后,以%f形式打印,输出的也是整数除法的结果+6个小数点位
        float b = 6/5;
        printf("%f\n",b);//1.000000
    
        //正确做法:除数或者被除数只要一个是浮点数,实行的就是浮点数计算。此时的计算结果就是带小数点的。
        //注意:如果这里直接写6.0、5.0、4.0,会被编译器认为是double类型的,而我们是使用的float类型接收,编译器认为double转float会出现精度丢失问题。
        //我们可以在6.0、5.0后面加一个f:6.0f、5.0f就是float类型的了。   float e = 10.0f/3.0f;
        float c = 6.0f/0;
        float d = 2/4.0f;
        //float c = 6.0/5.0 这里也可以两端都写成浮点数。
        printf("6.0/5=%f 、 2/4.0=%f\n",c,d);//6.0/5=1.200000 、 2/4.0=0.500000
    
        //进行取模预算时,%两端都必须是一个整数
        //int m = 7 % 3.0; 编译会报错。所以不能与浮点数取余。
        int m = 7 % 3;//1
        printf("%d",m);
    
        return 0;
    }
    

移位操作符

<<>>
左移操作符右移操作符
  • 注意:移位操作符的操作符只能是整数。并且移位运算符移动的位数只能是整数,不能移动负数位。

    //移位操作符。移动的是二进制位。
    // 例如int类型的数据2,int类型占四个字节,也就是32位二进制。左移一下,2(二进制:10)就变成了4(100)
    #include <stdio.h>
    
    int main()
    {
        //左移运算符:<<  。移动二进制位,左边多余的一位二进制位丢弃,右边最后补0。
        //我们发现,左移一位,数字是原来的2倍。左移三位,数字就是原来的8倍,也就是2的三次方。所以左移一位,数字就*2。
        int a = 3;
        int b = a << 1;
        printf("%d %d\n",b,a << 3); //6 24
    
        //右移运算符 >>。
        //1. 算术右移:右边丢弃,左边补原符号位 。 2.逻辑右移:右边丢弃,左边补0。
        int m = 11;
        int n = m >> 1;
        printf("%d %d\n",n,m >>3);//5 1
    
        /*
         * 计算机中,整数在内存中存储的是补码。一个整数的二进制表示有三种:原码、反码、补码。正整数的源码、反码、补码相同。
         * 负整数的源码、反码、补码计算关系如下:
         * -1的原码:10000000 00000000 00000000 00000001   最高位1表示符号位。
         * -1的反码:11111111 11111111 11111111 11111110   最高位符号位不变,其余二进制位按位取反
         * -1的补码:11111111 11111111 11111111 11111111   反码+1就是补码
         * 因为内存中存储的是补码,而我们打印时,我们选择用%d的方式打印,就是打印原码。所以补码-1得到反码
         * 得到反码后,最高位符号位不变,其余取反。就得到了源码,最高位是符号位,所以打印出了-1
         */
        /*
         * 那我们的编译器是采用算术右移还是逻辑右移呢?我们使用-1来测试一下。因为内存中存储的是补码,
         * 我们将-1右移一位,。如果是算数右移,则左边补符号位,也就是说左边补1。打印出来还是-1
         * 如果是逻辑右移,左边补0,就变成了01111111 11111111 11111111 11111111,打印出来应该是一个很大的数。
         *
         * 打印出-1,说明是算术右移。
         */
        int p = -1;
        int q = p >> 1;
        printf("%d",q);//-1
    
        //右移一位,可以看作是将这个数/2,如果有余数,则省略余数。如25右移一位就是12
        return 0;
    }
    

位操作符

&|^
按位与按位或按位异或
  • 位运算符:按二进制位进行运算。 两端的操作数必须是整数

    //
    #include <stdio.h>
    int main()
    {
        int a = 3;
        int b = 5;
        //按位与  & 。都为1,才为1
        //00000000 00000000 00000000 00000011    3
        //00000000 00000000 00000000 00000101    5
        //00000000 00000000 00000000 00000001    1
        int c = a & b;
        printf("%d\n",c);   //1
    
        //按位或  | 。 有1就为1
        //00000000 00000000 00000000 00000011    3
        //00000000 00000000 00000000 00000101    5
        //00000000 00000000 00000000 00000111    7
        c = a | b;
        printf("%d\n",c);   //7
    
        //按位异或 ^  。 如果对应位相同(如都为1,或都为0),则为0;如果对应位数不相同(一个是1一个是0 或 1个是0一个是1),则为1。
        //00000000 00000000 00000000 00000011    3
        //00000000 00000000 00000000 00000101    5
        //00000000 00000000 00000000 00000110    6
        c = a ^ b;
        printf("%d\n",c);   //6
    }
    
  • 按位异或的使用:不使用中间变量,交换两个变量的值。

    //不使用第三个变量,交换两个变量。
    
    #include <stdio.h>
    
    int main()
    {
        int a =5;
        int b =3;
        printf("交换前:a=%d   b=%d\n",a,b);//交换前:a=5   b=3
        //异或的结果,与原来的其中一个值异或,可以得到另外一个值。
        //111 与011进行异或,得到:110
        a = a ^ b;
        //110与011进行异或,得到101,也就是5,因为要交换,所以赋给b
        b = a ^ b;
        //110与101进行异或,得到011,也就是3,赋给a
        a = a ^ b;
        printf("交换后:a=%d   b=%d\n",a,b);//交换后:a=3   b=5
    
        int c = 4;
        printf("%d %d\n",c^c,c^0);//c^c=0 c^0=4
    
        /*
         * 解析:
         * - 一个数与自己进行异或,则会返回0;如果与0进行异或,则会返回这个数。
         *   也就是说a与b异或返回给a,此时a=a^b,而(a^b)^b就是a的值,因为a的值求出来了,此时b的值就是(a^b)^a
         * - 结论:两个数异或得到第三个数,其中两个异或可以得到零一个。
         *   比如:a^b=c ,那么a^c可以得到b的值 ,b^c 就可以得到a的值。
         */
        //将以上a^b的值定义为一个变量会更好理解:
        int d,e,f;
        d = 10;
        e = 20;
    
        //根据异或原理可以得到:
        f = d^e;
        d = f^e;
        e = f^d;
        printf("交换前:d=%d e=%d\n",d,e);
    
        //但是我们要交换值,并且不能借助第三个变量,就是:
        d = d^e; //此时d中存储的是d与e异或的结果,现在d已经不是原来的值了,但是e还是原来的值。
        //也就是说,现在的d与e异或,得到的是d的值,因为是要完成变量的交换,我们将这个值赋给e
        e = d^e; //此时,e中存储的是原来d的值,d中存储的d^e,
        //现在的d与e异或,得到的就是原来e的值,因为是要完成交换,所以将这个值赋给d
        d = d^e;
        printf("交换后:d=%d e=%d\n",d,e);
        return 0;
    }
    
    //缺陷:当a和b都很大时,a+b可能就会溢出int存储范围。
    //int main()
    //{
    //    int a =5;
    //    int b =3;
    //    printf("交换前:a=%d   b=%d\n",a,b);//交换前:a=5   b=3
    //    a = a+b;
    //    b = a-b;
    //    a = a-b;
    //    printf("交换后:a=%d   b=%d\n",a,b);//交换后:a=3   b=5
    //    return 0;
    //}
    
  • 编写代码实现:求一个整数存储在内存中的二进制的1的个数

    • 自己做的

      //弊端:不能判断负数的1的个数。比如-1,其补码应该有32个1。但是运行出来是1个
      #include <stdio.h>
      
      int main()
      {
          int num =333;
          int count = 0; //二进制位数
          int count1 = 0;//1的位数
          do
          {
              //只要还进入循环,说明这个数转换为二进制还有位。只要进入循环位数就+1。
              count++;
              if(num%2 != 0)
              {
                  //我们发现,当一个数可以整除2的时候,他的二进制的最后一位肯定是0。
                  //最后一位是1,也就是这个数%2不为0的时候,他的二进制位为1的数字位加1。
                  count1++;
              }
              //每次判断完完这个数/2。
              num/= 2;
              //虽然num/2会省略小数点,但是循环条件要是num/2.0,这样最后一次1进来之后虽然1/2是0,但判断条件是1/2.0=0.5
              //0.5不为0,还会再次循环,此时0.5/2=0了,num=0,0/2.0=0.000000。所以循环停止
              //否则1/2的时候=0就少计算了一位。
          }while(num/2.0);
      
          printf("%d转换成二进制有:%d位,其中是1的有:%d位",num,count,count1);
          return 0;
      }
      
    • 使用运算符做

      //需要判断32次,太麻烦了
      #include <stdio.h>
      
      int main()
      {
          int num = 333;
          int i ;
          int count = 0;
          //因为一个int类型的数占四个字节,也就是32位二进制数,所以这里是<32
          for(i=0 ; i<32 ; i++)
          {
              //i是几,就把1左移几位,右边补0,然后跟num做&运算
              //i=0,num与1做&,返回1,条件成立,count+1
              //00000000 00000000 00000001 01001101  333
              //00000000 00000000 00000000 00000001   1
              //00000000 00000000 00000000 00000001   1
              //如i=1,左移1位,右边补0,与num做&,返回0条件不成立,自动进入下一次循环
              //00000000 00000000 00000001 01001101  333
              //00000000 00000000 00000000 00000010   2
              //00000000 00000000 00000000 00000000   0
              //i=2 ,左移2位,右边补0,与num做&,返回1,条件成立,count+1
              //00000000 00000000 00000001 01001101  333
              //00000000 00000000 00000000 00000100   4
              //00000000 00000000 00000000 00000100   1
              //i=3 ,左移3位,右边补0,与num做&,返回1,条件成立,count+1
              //00000000 00000000 00000001 01001101  333
              //00000000 00000000 00000000 00001000   8
              //00000000 00000000 00000000 00001000   1
              //i=4,左移4位,右边补0,与num做&,返回0,条件不成立,自动进入下一次循环
              //00000000 00000000 00000001 01001101  333
              //00000000 00000000 00000000 00010000   16
              //00000000 00000000 00000000 00000000   0
              //...............
              //i=31,左移31位,右边补0,与num做&,返回0,自动下次循环
              //00000000 00000000 00000001 01001101  333
              //10000000 00000000 00000000 00000000  2147483648
              //10000000 00000000 00000000 00000000  0
              if(num & (1 << i))
              {
                  count++;
              }
          }
          printf("该数对应的二进制的个数:%d",count);
          return 0;
      }
      
    • 简化

      /*
       * n&(n-1)   n =15
       * 15的二进制数1111 与 15-1的二进制数1110,按位与运算得到:1110,最右边的1被取了出来
       * 继续循环:
       * - 1110与1101取余,得到:1100,最右边的1被取了出来。
       * - 1100与1010取余,得到:1000,最右边的1被取了出来。
       * - 1000与0111取余,得到:0000,最右边的1被取了出来
       */
      
      #include <stdio.h>
      
      int main()
      {
          int num = -1;
          int i = 0;
          int count = 0;
          while(num)
          {
              //每次取最右侧的1,直到全部取出,num变为0,循环停止。
              num = num&(num-1);
              //每取出一次1,次数+1。
              count++;
          }
          printf("其中1的个数:%d",count);
      
          return 0;
      }
      

赋值操作符

基本的赋值操作符复合赋值符复合赋值符
=+=>>=
-=<<=
*=&=
%=|=
/=^=
  • 赋值操作符的优先级:先执行=右边的表达式,将其执行结果赋给左边的变量

    #include <stdio.h>
    
    int main()
    {
        //赋值操作符 =
        int a =10;
        int b = 20;
        a =20; //重新赋值
    
        //赋值操作符可以连续使用:从最右边往左计算。
        //int c = a = b+30;
        //printf("a=%d  c=%d",a,c);//a=50  c=50
    
        //但是不推荐连续使用。更推荐:
        a = b+30;
        int c =a;
    
        //复合赋值
        int x,y,z;
        x += 10; //与x = x + 10;等效
        y /= 88; //与y = y / 88; 相同
        z = c;
        z >>= 3 ; //与z = z >> 3; 相同
    
        int m,n;
        m ^= n;  //与m = m^n 相同
        return 0;
    }
    

单目操作符

什么是单目操作符?
- a+b,+是操作符,+旁边有两个操作数,在这里的+就被叫做双目操作符。
- 单目操作符:操作数只有一个。
-+sizeof~
逻辑反操作负值正值计算操作数长度,单位:字节对一个数的二进制取反
++&*(类型)
自加1自减1取地址间接访问(解引用)操作符强制类型转换
  • ! 用于取反

    //在C语言中:0表示假,非0表示真。!2就返回0,因为非0就表示真,那!0应该返回哪个数?返回1
    #include <stdio.h>
    int main()
    {
        int c = 0;
        if(!c)
        {
            printf("%d\n",!0);// !0打印出1
            printf("%d\n",!30);// !30打印出0
            printf("%d\n",!220);// !220打印出0
        }
        return 0;
    }
    
  • +与 - 作为单目操作符的使用。

    #include <stdio.h>
    
    int main()
    {
        int a = 10;
        int b = -a;   //-a表示取-的a。
        int c = +b;  //+写了跟不写一样,一般省略,此语句等同于:int c = b;
        printf("a=%d b=%d c=%d",a,b,c);//a=10 b=-10 c=-10
        return 0;
    }
    
  • sizeof操作符符

    /*
     * sizeof 用于操作数的类型长度。单位:字节
     * - sizeof是一个操作符,其在对类型进行操作取长度时,需要把类型放在()里。
     * - 如果是取一个变量的长度,则()可以省略。而函数的()是不可以省略的,说明sizeof不是函数,而是一个操作符。
     *   如,对int类型取长度:sizeof(int)   ;  对int类型的变量a取长度,sizeof a 或 sizeof(a)
     * - sizeof在编译阶段处理,所以其中的表达式不参与运算。
     */
    #include <stdio.h>
    
    int main()
    {
        int a =10;
        int arr[9] = {0};
        printf("%d\n",sizeof(a)); //计算int类型变量a所占空间大小     //4
        printf("%d\n", sizeof(int)); //计算int类型所占空间大小      //4
    
        //sizeof是一个操作符,而不是函数。
        //如果是计算变量所占空间大小,则()可以省略
        printf("%d\n",sizeof a);    //4
        //printf("%d\n", sizeof int));     //如果是一个类型的大小,则不可以省略
    
        //求数组长度
        printf("%d\n", sizeof(arr));  //36字节
    
        //arr数组的类型是:int [9]
        printf("%d\n", sizeof(int [9])); //36
    
        //
        short s = 5;
        a = 10;
        //这里不管后面的值是几,只要最后是要给s的,就是计算s的大小,而s是short类型的,占2个字节。
        printf("%d\n", sizeof(s = a+2)); //2
        /*
         * 这里为什么s还是5呢?因为sizeof()中存放的表达式不参与运算,为什么不参与运算呢?
         * - 程序运行的时候,先编译.c源文件,再经过链接,最后才是运行.exe程序
         * - 而sizeof(s=a+2)是在编译器上执行的,s=a+2并没有运算。在执行sizeof的时候,因为最后计算的还是s的大小
         *   所以sizeof直接返回s的大小,因为已经计算好了大小,所以s=a+2这个表达式就算是已经处理完了。
         * - 在运行的时候,sizeof(s=a+2)表达式表示的已经是2了,而不是一个待执行的表达式
         * - 所以s的值并没有发生变化。
         *
         * - 编译器只是根据sizeof中的表达式s=a+2,其结果最后是放入s中,所以只会计算s的大小,并不执行其中的表达式运算
         *   而并不是真的把a+2的结果赋给s
         * - 而正因为sizeof是编译器处理的,所以其中的表达式并不会被运算。
         */
        printf("%d\n",s);   //5
        
        return 0;
    }
    

    当数组作为函数参数传递进去的时候,实际上是一个指针变量,如果是要计算传递到其他函数中的数组大小,则计算的是一个指针变量。

    #include <stdio.h>
    
    void test(int arr[],char ch[])
    {
        //这里是8,因为数组名中存储的是第一个元素的地址,地址应该是一个指针。
        // 在32位系统中,是4;我们这里是64位的,所以是8个字节。
        printf("test()函数中,传递进的arr大小:%d\n", sizeof(arr));//8
        printf("test()函数中,传递进的ch大小:%d\n", sizeof(ch));//8
    }
    
    int main()
    {
        int arr[10] = {0};
        char ch[10] ={0};
        printf("数组arr的大小:%d\n",sizeof(arr));//40
        printf("数组ch的大小:%d\n", sizeof(ch));//10
        test(arr,ch);
        return 0;
    }
    
  • ~操作符:对一个数的二进制位按取反

    // ~   对一个数的二进制位按取反:把原来的二进制位中的数据:1变成0、0变成1。这里的按位取反,包括符号位取反。
    #include <stdio.h>
    int main()
    {
        /*
         * 计算机中,整数在内存中存储的是补码。一个整数的二进制表示有三种:原码、反码、补码。
         * 正整数的源码、反码、补码相同。负整数的源码、反码、补码计算关系如下:
         * -1的原码:10000000 00000000 00000000 00000001   最高位1表示符号位。
         * -1的反码:11111111 11111111 11111111 11111110   原码的最高位符号位不变,其余二进制位按位取反,得到反码
         * -1的补码:11111111 11111111 11111111 11111111   反码+1就是补码
         */
    
        //如对int类型的a进行~操作,因为int类型占4个字节,也就是32位二进制数。就是32个0,~操作后就变成了32个1
        int a = 0;
        //     00000000 00000000 00000000 00000000 按位取反(所有位的取反,包括符号位)
        //得到:11111111 11111111 11111111 11111111 这个是补码
        //因为内存中存储的是补码,而我们打印时,我们选择用%d的方式打印,就是打印原码。
        //所以补码-1得到反码,然后最高位符号位不变,其余取反。就得到了原码,最高位是符号位,所以打印出了-1
        printf("%d\n",~a);//-1
        return 0;
    }
    

    运用

    #include <stdio.h>
    
    int main()
    {
        int a =13;
        //把a的二进制中的第五位置成1
        a = a | (1<<4);
        // 00000000 00000000 00000000 00001101  13
        // 00000000 00000000 00000000 00010000  16
        //按位或(有1为1)后的结果:
        // 00000000 00000000 00000000 00011101  29
        printf("a=%d \n",a);//29
    
        //把a的二进制中的第五位置成0。
        // 00000000 00000000 00000000 00011101  29
        //与第五位是0、其他位都是1的数进行&与运算(都为1,才为1),此时除了第五位剩下的数我们都不会改变
        // 11111111 11111111 11111111 11101111
    
        //如何得到第五位是0、其他位都是1的数呢?
        //把第五位是1,其他位都是0的数(1左移4位),做~,也就是~(1<<4),再按位与:& ~(1<<4)
        a = a & ~(1 << 4);
        printf("a=%d \n",a);//此时就又得到了:13
    
        return 0;
    }
    
  • ++与--操作符

    #include <stdio.h>
    
    /*
     * ++操作符【自加1】、-–操作符【自减1】
     * ++操作符可以出现在变量前,也可以出现在变量后,无论是变量前还是变量后。只要++运算结束,该变量中的值一定会加1
     *   ++出现在变量后,如a++,规则:先做赋值运算。再对变量中保存的值进行自加1
     *   ++出现在变量前,如++a,规则:先进行自加1运算。再进行赋值操作
     *
     * --操作符可以出现在变量前,也可以出现在变量后,无论是变量前还是变量后。只要--运算结束,该变量中的值一定会减1
     *   --出现在变量后,如a--,规则:先做赋值运算。再对变量中保存的值进行自减1
     *   --出现在变量前,如--a,规则:先进行自减1运算。再进行赋值操作
     */
    
    int main()
    {
        int a= 100;
        //++出现在变量后,先做赋值再加1
        int b= a++;
        printf("%d\n",a);//101
        printf("%d\n",b);//100
    
        //++出现在变量前,先加1再进行赋值
        b = ++a;
        printf("%d\n",a);//102
        printf("%d\n",b);//102
    
        //--出现在变量后,先赋值再加1
        b =a--;
        printf("%d\n",a);//101
        printf("%d\n",b);//102
    
        //--出现在变量前,先加1再进行赋值
        b =--a;
        printf("%d\n",a);//100
        printf("%d\n",b);//100
    
        return 0;
    }
    
  • &取地址操作符与*解引用操作符

    #include <stdio.h>
    
    int main()
    {
        int a = 10;
        //& 用于取地址
        printf("%p\n",&a); //0000001e9cbff754
        
            //编译报错。&的操作数必须是一个变量,不能是一个表达式
    //    printf("%p",&(a+b));
    //    printf("%p",&(a++));
    //    printf("%p",&(++a));
    
        //pa是存放地址的,pa是一个指针变量。因为是存储a的地址,而a是int类型,所以是int*
        int * pa =&a;
    
        //* 解引用操作符、间接访问操作符 ,可以通过*pa找到其指向的变量a,更改其值。
        *pa = 20;
        printf("%d\n",a); //20
        
    
        
        return 0;
    }
    
  • 强制类型转换()

    #include <stdio.h>
    
    int main()
    {
        //3.14是double类型的,而我们要把他存储到int类型的变量pai中。为了使编译通过,可以强制转换类型为int:
        int pai = (int)3.14;
        printf("%d",pai);
        return 0;
    }
    

关系操作符

>>=<<=!===
大于大于等于小于小于等于不相等相等
  • =是赋值运算符 ==是关系运算符

逻辑操作符

&&||
逻辑与逻辑或
#include <stdio.h>

int main() {

    int a = 3;
    int b = 2;

    //&&两端都为真,才为真。有一个为假就为假
    if (a && b) {
        printf("a和b都不是0");
    }
    int c = 0;

    if (a && c) {
        printf("  ");
    } else {
        printf("a和c中有一个是0");
    }


    //  ||两端只要有一个为真就为真,两个为假才为假。
    if (b || c)
    {
        printf("hh");
    }

    int d = 0;
    if(d ||c)
    {
        printf("  ");
    }
    else{
        printf("c和d都为0");
    }

    return 0;
}

&&与||的短路

#include <stdio.h>

int main()
{
    int i = 0;
    int a = 0;
    int b = 2;
    int c = 3;
    int d = 4;
    /*
     * &&在使用的时候,会发生短路与:操作符左边表达式如果执行结果是假(0),就不再执行后面的表达式
     * ||在使用的时候,会发生短路或:操作符右边表达式如果执行结果是真(1),就不再执行后面的表达式
     */

    //这里a++,因为++出现在变量后,所以是先赋值,a++是使用的a的值,a=0,因为是&&,一旦假的出现后面的表达式都不执行。
    //i = a++ && ++b && d++ ;
    //a经过++,a=1,剩下的b、c、d不变
    //printf("a=%d   b=%d   c=%d   d=%d",a,b,c,d);//a=1   b=2   c=3   d=4

    //a++为a的值,也就是0,继续判断++b,++b是3,为真,因为是||,后面的不执行。
    i = a++ || ++b || d++;
    //a经过++,a=1 ; b经过++,b=3。
    printf("a=%d   b=%d   c=%d   d=%d",a,b,c,d);//a=1   b=3   c=3   d=4
    return 0;
}

条件操作符/三目操作符

表达式1?表达式2:表达式3
表达式1为真,表达式2执行;反之,表达式3执行
/*
 * exp1 ? exp2 : exp3
 * 条件 ? 表达式1:表达式2
 *
 * 执行原理:
 * - 条件为真,执行表达式1
 * - 条件为假,执行表达式2。
*/
    
#include <stdio.h>

//找出两个数种较大的那个数
int main()
{
    int a =10;
    int b =20;
    printf("%d",a>b?a:b);  //20
    return 0;
}

逗号表达式

//exp1,exp2,exp3,...,expn
//逗号表达式:每个表达式中间用,隔开。从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//即使最后一个表达式与前面的表达式没有联系,也是从左到右依次执行。
#include <stdio.h>
int main()
{
    int a = 0;
    int b = 3;
    int c = 5;
    int d = (a=b+2,b=c-4,10);
    //逗号表达式,从左向右依次计算,但是只返回最后一个表达式的结果。
    printf("%d\n",d);   //10
    return 0 ;
}
int main()
{
    a = get_val();
    count_val(a);
    
    while (a > 0)
    {
       //业务处理
            a = get_val();
            count_val(a);
    }
    
    //如果使用逗号表达式,改写:
    while (a = get_val(), count_val(a), a>0)
    {
             //业务处理
    }
}

下标引用、函数调用和结构成员

下标引用:数组名[数组下标]

#include <stdio.h>
int main()
{
    int arr[10] = {0,1,2,3,4,5,6,7,8,9};
    //[]就是下标引用操作符。[]的操作数有两个:数组名(arr)、数组下标(4)
    printf("%d\n",arr[4]);
}

函数调用:函数名(参数);

//如果调用的函数不用传参:函数();
int Add(int x,int y)
{
    return x+y;
}
void test()
{
}
int main()
{
    int a = 10;
    int b = 20;
    //函数调用 函数名()
    int ret = Add(a,b);
    test();
    return 0;
}

访问一个结构体的成员

#include <stdio.h>

//创建一个自定义的类型来描述:书
struct Book
{
    //结构的成员 或者叫成员变量
    char name[20];
    char id[10];
    int price;
};

int main()
{
    struct Book b = {"C语言","1101",30};

    //结构体变量名.成员名
    printf("书名:%s\n",b.name);
    printf("书号:%s\n",b.id);
    printf("定价:%d\n",b.price);

    // * 表示后面的pb是一个指针变量、*前面的struct Book是pb指向的b的类型是struct Book
    struct Book * pb = &b;

    //(*pb),*是解引用操作符,通过地址找到对应的b变量并访问。
    //这种方式,写起来麻烦。
    printf("书名:%s\n",(*pb).name);
    printf("书号:%s\n",(*pb).id);
    printf("定价:%d\n",(*pb).price);

    //结构体指针->成员名
    //pb->就等同于b。pb->name表示pb指向的对象的name,pb->id表示pb指向的对象的id.....
    printf("书名:%s\n",pb->name);
    printf("书号:%s\n",pb->id);
    printf("定价:%d\n",pb->price);
    return 0;
}

表达式求值

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

隐式类型转换

  • 整型提升

    - C语言中的整型算数运算总是至少以缺省整型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前需要被转换为普通整形。这种转换称为整型提升。
    
    整型提升的意义:
    - 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
      因此,两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整形操作数的标准时长。
      通用CPU(general-purpose CPU)是难以直接实现两个8比特字节(也就是两个1字节的数)直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
      
    什么时候发生整型提升?
    - 当表达式中的操作数运算时,其数据类型所占大小小于int大小的时候,就进行整型提升
      因为int是用来表达寄寄存器的。
      
    int和long,还与编译器(CPU)有关。int就是用来表达寄存器的。
    - 平时所说的字长,就是说计算机的寄存器是多宽/多少比特。如果是32bit,也就是说寄存器一次可以表达32bit的数据;同时,也表示:CPU在内存中读取数据的时候,一次传递的是32bit。把32bit叫做寄存器的字长,就是因为寄存器一次可以处理的数据就是32个bit。寄存器字长也可能是64bit。
    - 寄存器的字长,在C语言中就反应为int。也就是说int的大小,就是寄存器的大小。这就是为什么在不同的平台/CPU上,int的大小会不同。
    
    
  • 两个char类型相加

    //b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果被截断,然后再存储于a中。
    //如何截断?int有四个字节,char只能存储一个字节。截取int的最后八位,存起来。
        char a,b,c;
        a = b + c;
    

    如何进行整型提升?

    整型提升是按照变量的数据类型的符号位来提升的

    //负数的整型提升:高位补1
    //正数的整型提升:高位补0
    //无符号的整型提升:高位补0。
    
    #include <stdio.h>
    
    int main()
    {
        //负数的整型提升
        char a = -1;
        //整数在内存中存储的是补码。一个整数的二进制表示有三种:原码、反码、补码。
        //-1是int类型数据,其在内存中存储的是:11111111 11111111 11111111 11111111
        //变量a是char类型,占一个字节(8个二进制位),截断取后八位:11111111
        //因为这里的char并不是无符号的,就是说a是有符号的。最高位1为符号位,整型提升的时候,高位补充符号位1
        //整型提升后的结果就是:11111111 11111111 11111111 11111111
    
        //以%d打印,是打印有符号数,所以是打印原码,所以这里a发生整型提升。
        //以上a整型提升的结果:11111111 11111111 11111111 11111111,这是补码
        //补码-1得到反码:11111111 11111111 11111111 11111110
        //反码最高位符号位不变,其余取反:10000000 0000000 0000000 00000001
        //最高位1表示是负数,所以打印出来是-1
        printf("%d\n",a);  //-1
    
    
        //正数的整型提升
        char b = 1;
        //正整数的源码、反码、补码相同。
        //1是int类型数据,其在内存中存储的是:00000000 00000000 00000000 00000001
        //变量b是char类型,占一个字节(8个二进制位),截断取后八位:00000001
        //因为这里的char并不是无符号的,就是说b是有符号的。最高位0为符号位,整型提升的时候,高位补充符号位0
        //整型提升后的结果就是:00000000 00000000 00000000 00000001
    
        //以%d打印,是打印有符号数,所以是打印原码,这里b发生整型提升。
        //以上b整型提升的结果:00000000 00000000 00000000 00000001,这是补码
        //最高位0表示是正数,所以打印出来是1
        printf("%d\n",b);  //1
    
        return 0;
    }
    

    原理

    #include <stdio.h>
    
    int main()
    {
        //3是一个int类型数据:00000000 00000000 00000000 00000011
        //char中只能存储一个字节的,取最后八位,也就是a中存储的是:00000011
        char a = 3;
    
        //127是一个int类型数据:00000000 00000000 00000000 01111111
        //char中只能存储一个字节的,取最后八位,也就是a中存储的是:01111111
        char b = 127;
    
        //发现a和b都是char类型的,都没有达到一个int类型大小,所以会发生整型提升。
        //整型提升是按照变量的数据类型的符号位来提升:只有unsigned修饰的才是无符号位的
        // 这里的char是一个字节,有八位。如果是一个正数,则补0;如果是负数,则补1。
        //这里a中存储的是:00000011,需要转换为4个字节大小,高位全部补0:00000000 00000000 00000000 00000011
        //这里b中存储的是:01111111,需要转换为4个字节大小,高位全部补0:00000000 00000000 00000000 01111111
        //进行相加,得到:00000000 00000000 00000000 10000010。因为c是char类型的,只能存储一个字节,也就是八位
        //所以存储的时候发生截断,取低八位,也就是最后八位:10000010。在内存中存储的是补码
        char c = a + b;
    
        //打印的时候,因为是以%d进行打印,%d打印有符号数,所以是打印原码,所以这里c又发生整型提升。
        //10000010,最高位符号位是1,所以补1,变成11111111 11111111 11111111 10000010
        //补码-1得到反码:11111111 11111111 11111111 10000001
        //反码的最高位符号位1不变,其余取反,得到原码:10000000 00000000 00000000 01111110
        //最高位1是符号位,表示是负数,1111110是126。所以c是-126
        printf("c=%d",c); //c=-126
        return 0;
    }
    
  • 整型提升例1

    #include <stdio.h>
    
    int main()
    {
        //0xb6转换为十进制是00000000 00000000 00000000 10110110。a中存储了:10110110
        //最高位1表示是负数,整型提升补1:11111111 11111111 11111111 10110110,也就是说是个负数
        char a = 0xb6;
        //0xb600,转换为十进制是00000000 00000000 10110110 00000000
        //short中可以存储两个字节,也就是b中存储了:10110110 00000000
        // 最高位1,整型提升补1:11111111 11111111 10110110 00000000。结果是一个负数
        short b =0xb600;
        int c = 0xb6000000;
    
        //a和b都发生了整型提升,变成了负数。c并不发生整型提升,所以输出c
        if(a == 0xb6)
        {
            printf("a");
        }
        if(b == 0xb600)
        {
            printf("b");
        }
        if(c == 0xb6000000)
        {
            printf("c");// c输出
        }
        return 0;
    
    }
    
  • 整型提升案例2

    #include <stdio.h>
    
    int main()
    {
        char c =1;
        //%u是输入输出格式说明符,表示按unsigned int格式输入或输出数据。
        //这里变量c的类型是char,占一个字节
        printf("%u\n", sizeof(c));//1
    
        //+、-、!都是运算符,只要参与运算,就需要进行整型提升,提升之后就占4个字节。
        //虽然sizeof中的表达式并不参与运算,但这里只是计算一个大小。并不是真的运算。只是计算了+c的大小,而不是要+c的结果
        printf("%u\n", sizeof(+c));//4
        printf("%u\n", sizeof(-c));//4
        printf("%u\n", sizeof(!c));//4
        
        //表达式有两个属性
        int a = 2;
        int b = 3;
        
        //这个表达式的属性之一是,值属性。a+b的值是5,这个就是值属性
        //第二个属性:类型属性。a与b都是int类型,a+b这个表达式的值一定也是int属性。
        a + b; 
        
        //所以以上+c这个表达式,发生了整型提升之后,占4个字节。整型提升为什么是占四个字节?因为CPU内整型运算器的操作数的字节长度一般就是int字节的字节长度。
        //sizeof只是计算了+c这个表达式的大小,而不是去运算这个表达式。sizeof在编译阶段执行,执行之后有了值,在运行阶段就不会再去运行sizeof()内的表达式。
        
        return 0;
    }
    

算数转换(自动类型转换)

如果两个不同类型的数就行运算,那么其中一个操作数就要转换为另一个操作数的类型,否则无法进行运算。这种转换就称为寻常算数转换。一般会自动转换为表达数字范围较大的类型。

//类型精度从高到低:
long double > double > float > unsigned long int > long int > unsigned int > int > short  > char 
    
//如果某个操作数的类型在上面中排名较低,那么就要首先转换为另一个操作数的类型后,才能进行运算。

//注意:算数转换也需要合理,否则会出现问题。
//如:
float f = 3.14;
int num = f; //隐式转换,会有精度丢失

//对于printf来说,任何小于int的类型会被转换为int,float会被转换为double
//但是scanf不会,要输入short,格式说明符应该为%hd

强制类型转换

//要把一个量强制类型转换为另一个值,语法:(类型)值
int main()
{
    //例如:
    int a = (int)10.32;
    short s = (short)32;

    //注意安全性:小范围类型不能表达超过其范围的量。
    printf("%d\n",(short)327889);//209

    //强制类型转换,只是将某个值/某个变量的值,强制转换为另一个类型。计算出的值=原来的值在要转换类型中的值。
    //原来的值的类型和值并没有发生改变
    int m = 2147484;
    short n = (short)m;
    printf("%d\n",n);//-15204
    printf("%d\n",m);//2147484
    return 0;
}

操作符的属性

复杂表达式的求值有三个因素的影响:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序?

两个相邻的操作符先执行哪个?取决于他们的优先级。如果他们的优先级相同,则取决于他们的结合性。

操作符的优先级(从高到低排列)

结果类型:表达式的结果是什么类型的
- lexp表示左值表达式,rexp表达右值表达式。
- 左值意味着一个位置,而右值意味着一个值。所以,在使用右值的地方也可以使用左值,但是使用左值的地方不能使用右值


结合性:
- N/A,表示不考虑结合性
- L—R,表示从左向右运行
- R—L,表示从右向左运行

是否控制求值顺序:
- 一般为否
- &&操作符,如果左边表达式为假,则不执行右边的表达式
- ||操作符,如果左边表达式为真,则不执行左边的表达式
- ?:三目操作符,如果?前的表达式为真,则执行:前的表达式;如果为假,则执行:后的表达式
- 逗号表达式,必须从左到右依次执行。
操作符描述用法示例结果类型结合性是否控制求值顺序
()()里优先算(表达式)与()中表达式相同N/A
()函数()rexp(rexp,…rexp)rexpL-R
[]下标引用rexp[rexp]lexpL-R
.访问结构成员lexp.namelexpL-R
->访问结构指针成员rexp->namelexpL-R
++变量后++lexp ++rexpL-R
变量后–lexp –rexpL-R
!逻辑反! rexprexpR-L
~按位取反~ rexprexpR-L
+单目,表示正值+ rexprexpR-L
-单目,表示负值- rexprexpR-L
++前置++++ lexprexpR-L
前置–– lexprexpR-L
*间接访问* rexplexpR-L
&取地址& lexprexpR-L
sizeof取其长度sizeof rexprexpR-L
(类型)类型转换(类型) rexprexpR-L
*乘法rexp * rexprexpL-R
/除法rexp / rexprexpL-R
%整数取余rexp % rexprexpL-R
+加法rexp + rexprexpL-R
-减法rexp - rexprexpL-R
<<左移位rexp << rexprexpL-R
>>右移位rexp >> rexprexpL-R
>大于rexp > rexprexpL-R
>=大于等于rexp >= rexprexpL-R
<小于rexp < rexprexpL-R
<=小于等于rexp <= rexprexpL-R
==相等rexp == rexprexpL-R
!=不相等rexp != rexprexpL-R
&按位与rexp & rexprexpL-R
^按位异或rexp ^ rexprexpL-R
|按位或rexp | rexprexpL-R
&&逻辑与rexp && rexprexpL-R
||逻辑或rexp || rexprexpL-R
?:条件操作符rexp ? rexp : rexprexpN/A
=赋值rexp = rexprexpR-L
+=加等rexp += rexprexpR-L
-=减等rexp -= rexprexpR-L
*=乘等rexp *= rexprexpR-L
/=除等rexp /= rexprexpR-L
%=模等rexp %= rexprexpR-L
<<=左移等rexp <<= rexprexpR-L
>>=右移等rexp >>= rexprexpR-L
&=按位与等rexp &= rexprexpR-L
^=按位异或等rexp ^= rexprexpR-L
|=按位或等rexp |= rexprexpR-L
,逗号rexp,rexprexpL-R
  • 问题表达式

    • 表达式1

      a*b + c*d + e*f;
      
      //*比+的优先级高,所以只能保证*计算的比a+早,并不能决定第三个*比第一个+早执行
      //可能的计算顺序:
      //顺序一:计算a*b、c*d、e*f,然后计算他们的和
      //顺序二:计算a*b、c*d,然后计算a*b + c*d,然后再计算e*f,然后将前两个的和与e*f的结果加起来
      
    • 表达式2

      c + --c;
      
      //操作符优先级只能决定--的运算在+运算的后面,但是我们并不知道c是在--c之后才获取值,还是获取了c的值之后,再--c,所以结果是不可测的。
      //如c=3。
      //如果是--c之前获取值:3+2=5
      //如果时计算了--c才获取c的值:2+2=4
      
    • 表达式3

      //这段代码在不同的编译器下,运行结果都不同
      #include <stdio.h>
      
      int main()
      {
          int i = 10;
          i = i-- - --i * ( i = -3) * i++ + ++i;
          printf("i=%d \n",i); //36
          return 0;
      }
      
    • 表达式4

      //虽然这个表达式在大多数编译器上求得结果都相同,但是函数调用的先后顺序并不能通过操作符优先级决定
      #include <stdio.h>
      
      int fun()
      {
          static int count = 1;
          return ++count;
      }
      int main()
      {
          int answer;
          //根据操作符优先级,我们可以得知先算乘法,再算加法。但函数调用的先后顺序,我们无法通过操作符优先级确定。
          //顺序一:先调用fun()得到count=2, 再调用乘法里的fun(),得到count=3和count=4。然后2-3*4=-10。
          //顺序二:先调用乘法里的fun(),得到count=2和count=3,再调用-前面的fun(),得到count=4。然后4-2*3=-2
          answer = fun() - fun() * fun();
      
          printf("%d\n",answer);//-10
          return 0;
      }
      
    • 表达式5

      //有的编译器运行结果就是12,有的是10
      #include <stdio.h>
      
      int main()
      {
          int i = 1;
          //为什么会出现12?
          //计算机计算数的时候,是通过寄存器来计算的。i刚开始是1,计算机将1放到寄存器中,然后++i,就是为寄存器中存储的数+1,数值变成2
          //然后再次++i,此时寄存器中存储的是3,再次++i,此时存储的是4。此时++已经计算完了,将寄存器中的值给i。
          //此时执行加法:将i的值放到寄存器中,然后+i,再+i,就是3个4相加,就算出12。然后放到ret中。
          //如果是这个编译器下进行(++i) + (++i) + (++i) + (++i) + (++i),则结果是30。
          //因为是先把所有的++i计算完之后,才计算加法。i=1,5次++之后,i=6。5个6相加=30。
          //也即是说,如果是a+b+c+d+e,这里的a、b、c、d、e都是表达式。
          //先计算出所有表达式的值(计算出a、b、c、d、e),然后再做加法。
          //如果有更多的表达式,则依次类推。最后同一加
      
          //为什么会是10呢?
          //先计算前两个++i,计算了之后i=3,然后3+3就是6,再用6+(++i),就是6+4=10
          //可以理解为:a+b+c,a与b与c都是表达式。先计算a与b的值,然后再计算a+b,然后计算c的值,再用a+b的值+c的值
          //int ret = (++i) + (++i) + (++i);
          //printf("%d\n",ret); //10
          //printf("%d\n",i);   //4
      
          //所以如果是5个表达式相加,就是a+b+c+d+e,先算a与b的值,然后加起来,算c表达式的值,然后用a+b的值加c的值
          //然后算d表达式的值,然后用a+b+c的值+d,然后算e表达式的值,然后用a+b+c+d的值+e。
          //这里就是先算++i与++i,i变成3,然后3+3=6,此时计算6+(++i)+(++i)+(++i)中的6+(++i),6的结果是6,++i变成4
          //6+4=10,再计算10+(++i)+(++i)中的10+(++i),10的结果是10,++i之后i变成5。10+5=15。
          //此时就是计算15+(++i),15的结果就是15,++i之后i变成了6,然后15+6=21。所以这里输出21。
          int ret = (++i) + (++i) + (++i) + (++i) + (++i);
          printf("%d\n",ret); //21
          //printf("%d\n",i);   //6
      
          //如果是更多的表达式相加,则按照这个编译器相加原理,就是:先计算前两个表达式的值,然后相加,用这个结果去加第三个表达式的结果
          //然后用前三个相加的结果+第四个表达式的结果,然后用前四个相加的结果+第五个....以此类推。
          
          return 0;
      }
      

    结论

    虽然操作符有优先级,但有的表达式,其具体的计算顺序还是无法确定。
    
    也就是说,我们写出的表达式,有可能计算1结果并不是唯一的。即使我们知道了操作符的优先级,也无法确定这个表达式的运算结果是唯一的,那么这个就是问题表达式。我们在编程中一定要避免这种情况。
    

练习

  1. 编写函数,求一个数其二进制中1的个数

    /*
     * n&(n-1)   n =15
     * 15的二进制数1111 与 15-1的二进制数1110,按位与运算得到:1110,最右边的1被取了出来
     * 继续循环:
     * - 1110与1101取余,得到:1100,最右边的1被取了出来。
     * - 1100与1010取余,得到:1000,最右边的1被取了出来。
     * - 1000与0111取余,得到:0000,最右边的1被取了出来
     *
     * 扩展:求一个二进制数是不是2的n次方(仅考虑正数,负数不考虑)。
     * 思路:n&(n-1) == 0
     * 解释:一个数如果是2的倍数,则换算成二进制数,所有数种就只有一个1,用n&(n-1)取出来之后,就只剩下0了
     *      所以当n&(n-1) == 0 的时候,这个数是2的倍数。1是2的0次方,不排除在外。
     */
    #include <stdio.h>
    
    int NumberOf1(int num)
    {
        int count = 0;
        while(num)
        {
            //每次取最右侧的1,直到全部取出,num变为0,循环停止。
            num = num&(num-1);
            //每取出一次1,次数+1。
            count++;
        }
        return count;
    }
    
    int main()
    {
        int num = -1;
        printf("其中1的个数:%d",NumberOf1(num));
        return 0;
    }
    
  2. 求两个数的二进制数中不同的位数

    #include <stdio.h>
    
    int main()
    {
        int m = 0;
        int n = 0;
        scanf("%d %d",&m,&n);
    
        int i;
        int count = 0;
        for(i=0 ; i<32 ; i++)
        {
            //右移1位,然后与1取余,如果得到的是1和0,或0和1,则表示这两个数的二进制位不同,此时统计+1。
            if(((m>>i)&1) != ((n>>i)&1))
            {
                count++;
            }
        }
        printf("%d\n",count);
        return 0;
    }
    

    异或的方式

    #include <stdio.h>
    
    int NumberOf1(int num)
    {
        int count = 0;
        while(num)
        {
            num = num&(num-1);
            count++;
        }
        return count;
    }
    
    int main()
    {
        int m = 0;
        int n = 0;
        scanf("%d %d",&m,&n);
        //按位异或,相同位0,相异为1。
        int compare = m^n;
        //其中有几个1,就有几位不同,放入函数中取1的位数。
        int count = NumberOf1(compare);
        printf("%d\n",count);
        return 0;
    }
    
  3. 统计二进制中1的个数

    #include <stdio.h>
    //11111111 11111111 11111111 11111111
    int main()
    {
        int n = 0;
        scanf("%d",&n);
        int i = 0;
    
        //倒着打印偶数位
        printf("偶数位:");
        for(i=31 ; i>=1 ; i-=2)
        {
            printf("%d   ",(n>>i)&1);
        }
    
        //倒着打印奇数位
        printf("\n奇数位:");
        for(i=30 ; i>=0 ; i-=2)
        {
            printf("  %d ",(n>>i)&1);
        }
        return 0;
    }
    
  4. 不使用中间变量,交换两个数。

    //不使用第三个变量,交换两个变量。
    
    #include <stdio.h>
    
    int main()
    {
        int a =5;
        int b =3;
        printf("交换前:a=%d   b=%d\n",a,b);//交换前:a=5   b=3
        //异或的结果,与原来的其中一个值异或,可以得到另外一个值。
        //111 与011进行异或,得到:110
        a = a ^ b;
        //110与011进行异或,得到101,也就是5,因为要交换,所以赋给b
        b = a ^ b;
        //110与101进行异或,得到011,也就是3,赋给a
        a = a ^ b;
        printf("交换后:a=%d   b=%d\n",a,b);//交换后:a=3   b=5
    
        int c = 4;
        printf("%d %d\n",c^c,c^0);//c^c=0 c^0=4
    
        /*
         * 解析:
         * - 一个数与自己进行异或,则会返回0;如果与0进行异或,则会返回这个数。
         *   也就是说a与b异或返回给a,此时a=a^b,而(a^b)^b就是a的值,因为a的值求出来了,此时b的值就是(a^b)^a
         * - 结论:两个数异或得到第三个数,其中两个异或可以得到零一个。
         *   比如:a^b=c ,那么a^c可以得到b的值 ,b^c 就可以得到a的值。
         */
        //将以上a^b的值定义为一个变量会更好理解:
        int d,e,f;
        d = 10;
        e = 20;
    
        //根据异或原理可以得到:
        f = d^e;
        d = f^e;
        e = f^d;
        printf("交换前:d=%d e=%d\n",d,e);
    
        //但是我们要交换值,并且不能借助第三个变量,就是:
        d = d^e; //此时d中存储的是d与e异或的结果,现在d已经不是原来的值了,但是e还是原来的值。
        //也就是说,现在的d与e异或,得到的是d的值,因为是要完成变量的交换,我们将这个值赋给e
        e = d^e; //此时,e中存储的是原来d的值,d中存储的d^e,
        //现在的d与e异或,得到的就是原来e的值,因为是要完成交换,所以将这个值赋给d
        d = d^e;
        printf("交换后:d=%d e=%d\n",d,e);
        return 0;
    }
    
    //缺陷:当a和b都很大时,a+b可能就会溢出int存储范围。
    //int main()
    //{
    //    int a =5;
    //    int b =3;
    //    printf("交换前:a=%d   b=%d\n",a,b);//交换前:a=5   b=3
    //    a = a+b;
    //    b = a-b;
    //    a = a-b;
    //    printf("交换后:a=%d   b=%d\n",a,b);//交换后:a=3   b=5
    //    return 0;
    //}
    
  5. 以下程序输出:0 0 3 4 5

    #include <stdio.h>
    
    int main()
    {
        int arr[] = {1,2,3,4,5};
        //内存中是以16进制存储的,并且是倒着存。
        //arr数组:01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
        //每个十六进制数就是4位二进制,每两个16进制数就占1个字节。
    
        short* p = (short*)arr;
        int i = 0;
        for(i=0 ; i<4 ; i++)
        {
            //short类型指针每次跳过一个short类型大小,所以就是每次跳两个字节。将其改为0。
            //第一次将01 00变为00 00 第二次将 00 00变为00 00 第三次将02 00变为00 00 第四次将00 00 变为00 00
            *(p+i) = 0;
            //arr数组变为:00 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
        }
        for(i=0 ; i<5 ; i++)
        {
            //也就是将1、2变为了0,所以输出0 0 3 4 5
            printf("%d ",arr[i]);  //0 0 3 4 5
        }
        return 0;
    }
    
  6. 指针变量与++

    #include <stdio.h>
    
    int main()
    {
        int year = 1009,*p = &year;
    
        //*p += 1;    //1010   //因为*的优先级更高,所以*p先解引用,再+=1,就是1010
        //(*p)++;     //1010   //前置++和后置++的优先级都比*更高,加个()先解引用再++。
        //++(*p);     //1010   //加个()先解引用再++。
    
        printf("%p\n",p);//000000daa27ff6d4
        *p++;         //++优先级高,所以先p++,再*解引用。因为p是一个指针变量,所以++是+了一个int类型大小,就是4个字节。//year并没有变化
        printf("%p\n",p);//000000daa27ff6d8
        printf("%d\n",year);
    
        return 0;
    }
    
  7. 表达式a*b+d-c的类型为

    char a;
    int b;
    float c;
    double d;
    
    //类型精度从高到低:
    long double > double > float > unsigned long int > long int > unsigned int > int
    //如果某个操作数的类型在上面中排名较低,那么就要首先转换为另一个操作数的类型后,才能进行运算。
    
    所以a*b+d-c的类型为:double类型
    
  8. sizeof结果类型的考察

    #include <stdio.h>
    int i;//i是全局变量,不初始化,默认是0
    int main()
    {
        i--;
        if(i > sizeof(i))
        {
            printf(">\n");
        }
        else
        {
            printf("<\n");
        }
    
        //为什么输出 > 呢?
        printf("%d",i);//-1
        printf("%d",sizeof(i));//4
    
        //-1怎么比4大的?
        //sizeof返回的结果是unsigned int 无符号整型的。有符号整型与无符号整型比较大小的收,会把有符号整型转换为无符号整型,然后进行比较
        //-1转换为无符号数后,32个1是一个很大的数。肯定是大于4的。
    
        return 0;
    }
    
  9. 由一个数组成的前n项和

    //求 Sn = a + aa + aaa + aaaa + aaaaa的前五项之和。
    //例如:2+22+222+2222+22222
    #include <stdio.h>
    int main()
    {
        //求出a组成的前n项和,不考虑溢出。
        int a = 0;
        int n = 0;
        scanf("%d %d",&a,&n);
        int i;
        int sum =0;
        //每一项都等于前一项*10+这个数。第一项就是0*10+数字本身。
        int ret = 0;
        for(i=0 ; i<n ; i++)
        {
            ret = ret*10 + a;
            sum += ret;
        }
        printf("sum=%d\n",sum);
        return 0;
    }
    
  10. 表达式求值的正确说法

    /*
     * - 表达式求值先看是否存在整型提升或算术转换,再进行计算。
     * - 表达式真正计算的时候,看相邻操作符的优先级,来决定先计算谁。
     * - 相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序。
     *
     * 错误的:只要有了优先级和结合性,表达式就能求出唯一值。
    */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值