• 操作符详解



    前言

    1. 各种操作符的介绍。
    2. 表达式求值

    操作符分类

    算术操作符
    移位操作符
    位操作符
    赋值操作符
    单目操作符
    关系操作符
    逻辑操作符
    条件操作符
    逗号表达式
    下标引用、函数调用和结构成员

    一、算术操作符

    算术操作符有哪些呢?
    +、-、*、/、%
    优先级和结合性如何?
    在这里插入图片描述
    都是双目操作符;
    注意:
    1、%两边的操作数只能为整数,不能为浮点数;
    2、=、-、 * 、/ 两边两个操作数都为整数都为整数,才执行整数的运算法则,只要其中有一个浮点数,则执行浮点数运算;

    二、移位操作符

    移位操作符分为两类:
    <<:左移操作符;
    >>:右移操作符;
    首先我们来了解一下左移操作符;
    使用方法:
    左移多少位,右边就补多少位0;
    操作代码:

    int main()
    {
    	int a = -4;
    	printf("%d\n",a<<4);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    那我们就得知道,所有的整数在内存中都是以补码进行存储的,我们的左右移操作符都是对补码进行直接操作的;
    于是我们的了解-4的补码:
    在这里插入图片描述
    既然有了补码,那我们直接对其进行操作:
    在这里插入图片描述
    我们向左移动了4个位,那右边有缺失啊,补啥?无脑补0;
    在这里插入图片描述
    我们现在不就得到一串新的补码,我们在对其进行原码转换:
    补码的补码就是原码:
    补码:1111111111111111111111111000000
    反码:1000000000000000000000111111
    补码:1000000000000000000001000000
    既:-64;
    我们来看看结果对不对呢?
    在这里插入图片描述
    的确是这样:
    我们接下来再来看看右移操作符:
    使用方法:
    对于有符号整型:右移对少位,左边就补多少个原符号位;(算术右移)
    对于无符号整型:右移对少位,左边就补多少个0;(逻辑右移)
    我们来看段代码:

    int main()
    {
    	int a = -4;
    	unsigned int c = -4;
    	int b = a >> 4;
    	int d = c >> 4;
    	printf("b=%d\n", b);
    	printf("d=%d\n",d);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这是-4的补码:
    在这里插入图片描述
    在a,c中都是一样的序列:
    a>>4;
    在这里插入图片描述
    左边补原符号位:
    补码:11111111111111111111111111111111
    反码:1000000000000000000000000000
    补码:1000000000000000000000000001
    既-1,故b=-1;
    再来看c
    在这里插入图片描述
    补码:00001111111111111111111111111111
    既:268435455,故d=268435455;
    我们来看看是不是
    在这里插入图片描述
    显然是的;
    警告⚠ :
    对于移位运算符,不要移动负数位,这个是标准未定义的。
    例如:

    int num = 10;
    num>>-1;//error
    
    • 1
    • 2

    三、 位操作符

    主要有:
    & :按位与
    | :按位或
    ^ :按位异或
    ~:按位取反
    注:他们的操作数必须是整数。
    &运算规则:在相同比特位下:1&0=0;0&0=0;1&1=1;
    比如
    3:011
    4:100
    则3&4=000=0;
    在这里插入图片描述
    的确是这样;
    |运算规则:在相同比特位下:1|0=1;0|0=0;1|1=1;
    3:011
    4:100
    则3|4=111=7
    在这里插入图片描述
    的确如此:
    再来看看^异或运算规则:相同比特位下,相同为0,相异为1;
    3:011
    4:100
    则3^4=111=7;
    在这里插入图片描述
    当然根据这个操作符的规则,我们可以得出这一个公式:
    a^a=0;
    a^0=a;
    根据这个公式我们可以出一道题:
    不能创建临时变量(第三个变量),实现两个数的交换:
    法一:

    int main()
    {
    	int a = 2;
    	int b = 3;
    	printf("\nbefore:a=%d b=%d", a, b);
    	a = a + b;
    	b = a - b;
    	a = a - b;
    	printf("\nafter:a=%d b=%d",a,b);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这方法有个弊端就是容易造成超出范围,即a,b很大,但是没有超出int范围,但是a+b就超出了,后面一系列操作就会出错;
    为了解决这个问题我们可以用^来帮助我们:
    法2:

    int main()
    {
    	int a = 2;
    	int b = 3;
    	printf("\nbefore:a=%d b=%d", a, b);
    	a = a ^ b;
    	b = a ^ b;
    	a = a ^ b;
    	printf("\nafter:a=%d b=%d",a,b);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这样不会产生进位,也就不会出现超出范围的说法;

    ~按位取反运算规则:按比特位逐个取反:1->0;0->1;
    为了方便演识,我们写个打印二进制的函数来看看:

    void print_bin(int n)//大家可以下来自行研究一下
    {
    	int i = 0;
    	int num = sizeof(int) * 8;
    	i = num - 1;
    	while ((i--)>=0)
    	{		if (n & (1 << i))
    			printf("1");
    		else
    			printf("0");
    }
    	putchar('\n');
    }
    
    int main()
    {
    	int a = -1;
    	printf("按位取反之前:");
    	print_bin(a);
    	printf("按位取反之后:");
    	print_bin(~a);
    	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

    在这里插入图片描述

    四、 赋值操作符

    赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

    int weight = 120;//体重
    weight = 89;//不满意就赋值
    double salary = 10000.0;
    salary = 20000.0;//使用赋值操作符赋值。
    
    • 1
    • 2
    • 3
    • 4
    赋值操作符可以连续使用,比如:
    int a = 10;
    int x = 0;
    int y = 20; a = x = y+1;//连续赋值
    这样的代码感觉怎么样?
    那同样的语义,你看看:
    x = y+1; a = x;
    这样的写法是不是更加清晰爽朗而且易于调试。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当然还有升级版赋值操作符:
    复合赋值符
    +=
    -=
    *=
    /=
    %=
    >>=
    <<=
    &=
    |=
    ^=
    这些运算符都可以写成复合形式;

    int x = 10; 
    x = x+10;
     x += 10;//复合赋值
    //其他运算符一样的道理。这样写更加简洁
    
    • 1
    • 2
    • 3
    • 4

    五、 单目操作符

    ! 逻辑反操作
    - 负值
    + 正值
    & 取地址
    sizeof 操作数的类型长度(以字节为单位)
    – 前置、后置–
    ++ 前置、后置++

    •         *     间接访问操作符(解引用操作符) 
      
      • 1
    • (类型) 强制类型转换

    逻辑取反:
    在这里插入图片描述
    相同的代码我们可以这么改:
    在这里插入图片描述
    就是在逻辑上是真,!一下就是逻辑上是假了;同理逻辑上是假,!一下逻辑上就是真了;

    • – 、 +这两就不用多做解释了吧;
      &:取地址,取出的是所有地址中最低的一个地址(对所有类型都是这样)
      在这里插入图片描述
      sizeof经常被认为是函数的关键字;求一个类型在内存中占的空间大小;
      虽然用法上与函数并无差异,但是它确实不是一个函数,下面前我们通过例子来证明:
      在这里插入图片描述
      我们可以发现sizeof 变量名是可以编译运行的,也是可以不用带括号的,也就是括号可以被省略,但是函数的括号确实不能被省略,从这一点我们证明了sizeof不是函数,是一个关键字,但是在对sizeof(类型)必须加括号,不带编译器会报错;
      – 前置、后置–
      ++ 前置、后置++
      这个的却别就是,后置- - 、++先使用后自增,如果有接收方的话,则会被先接受,在自增:
      在这里插入图片描述
      前置- -、++先自增在使用,有接受方,接收方接收的是自增过后的值,如没有,直接自增后置也是如此;
      在这里插入图片描述

    *解引用操作符:


    在这里插入图片描述

    通过指针的方式间接访问变量;
    (强制类型转换):
    只是改变编译器看待数据的角度,并不会该变其内存中的二进制序列;
    int a=(int)3.13;
    其实最终a=3;,但是3.13在内存中二进制布局并没有改变;

    六. 关系操作符

    =
    <
    <=
    != 用于测试“不相等”
    == 用于测试“相等”
    这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。
    警告:
    在编程的过程中== 和=不小心写错,导致的错误

    七、逻辑操作符

    逻辑操作符有哪些:&&逻辑与 ||逻辑或
    区分逻辑与和按位与
    区分逻辑或和按位或
    1&2----->0
    1&&2---->1
    1|2----->3
    1||2---->1
    对于&&来说,如果左边为假,编译器就不会再去执行右边了,在编译器看来这没必要;
    对于||来说,如果左边为真,编译器就不会再去执行右边了,在编译器看来这没必要;

    //360笔试题
    #include 
    int main()
    {
        int i = 0,a=0,b=2,c =3,d=4;
        i = a++ && ++b && d++;
        //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
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    在这里插入图片描述
    归最终结果就是上式;

    八、 条件操作符

    在这里插入图片描述

    1.
    if (a > 5)
            b = 3;
    else
            b = -3;
    转换成条件表达式,是什么样?
    (a>5)?3:-3;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    九、 逗号表达式

    逗号表达式,就是用逗号隔开的多个表达式。
    逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果

    //代码1
    int a = 1;
    int b = 2;
    int c = (a>b, a=b+10, a, b=a+1);/
    
    • 1
    • 2
    • 3
    • 4

    c=13;

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

    1. [ ] 下标引用操作符
      操作数:一个数组名 + 一个索引值
      在这里插入图片描述
    2. ( ) 函数调用操作符
      接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
    #include 
     void test1()
     {
     printf("hehe\n");
     }
     void test2(const char *str)
     {
     printf("%s\n", str);
     }
     int main()
     {
     test1();            //实用()作为函数调用操作符。
     test2("hello bit.");//实用()作为函数调用操作符。
     return 0;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 访问一个结构的成员
      . 结构体.成员名
      -> 结构体指针->成员名
    #include 
    struct Stu
    {
     char name[10];
     int age;
     char sex[5];
     double score;
     }void set_age1(struct Stu stu) {
     stu.age = 18; }
    void set_age2(struct Stu* pStu) {
     pStu->age = 18;//结构成员访问
    }
    int main()
    {
     struct Stu stu;
     struct Stu* pStu = &stu;//结构成员访问
     
     stu.age = 20;//结构成员访问
     set_age1(stu);
     
     pStu->age = 20;//结构成员访问
     set_age2(pStu);
     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

    十一、 表达式求值

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

    整型提升?

    C的整型算术运算总是至少以默认整型类型的精度来进行的。
    为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

    整型提升的意义

    表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU,C的整型算术运算总是至少以默认整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这个转换称为整型提升。整型提升的意义:去执行运算。
    总的来说,只要是小于int型的整型数据在进行整数运算时,都会先提升称为int类型在进行运算;(比如char,unsigned char,short,unsigned short在进行整数运算时都会发生整型提升)

    如何进行整型提升?

    对于有符号整数来说:
    往最高位补原符号位;
    比如char a=-1;
    在char中:11111111
    发生整型提升就是,补符号位,符号位为1,则补1;
    11111111111111111111111111111111;
    对于无符号来说,直接补0
    比如unsigned char a=-1;
    11111111:
    发生整型提升:
    00000000000000000000000011111111;
    整型提升的例子;

    int main()
    {
     char a = 0xb6;//10110110
     short b = 0xb600;//1011011000000000
     int c = 0xb6000000;
     if(a==0xb6)//会发生整型提升:
     //补码:11111111111111111111111110110110
     //反码:10000000000000000000000001001001
     //补码:10000000000000000000000001001010//-4A
     printf("a");
     if(b==0xb600)//会发生整型提升
     //补码:1111111111111111101101100000000
     //反码:1000000000000000010010011111111
     //补码:1000000000000000010010100000000//-2500
     printf("b");
     if(c==0xb6000000)//不会发生整型提升
     printf("c");
     return 0; }//最终结果只会打印c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    int main()
    {
     char c = 1;
     printf("%u\n", sizeof(c));
     printf("%u\n", sizeof(+c));
     printf("%u\n", sizeof(-c));
     return 0; }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    十二、算术转换

    如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换

    long double
    double
    float
    unsigned long int
    long int
    unsigned int
    int
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
    警告:
    但是算术转换要合理,要不然会有一些潜在的问题。

    float f = 3.14;
    int num = f;//隐式转换,会有精度丢失
    
    • 1
    • 2
  • 相关阅读:
    2023.10.02
    <sa8650> sa8650介绍
    应急响应-分析(windows)
    LinkedHashSet创建Integer类型的数据
    数学建模准备知识
    2023NOIP A层联测10-子序列
    IDEA 不推荐使用 @Autowired 注解的原因
    Linux--gcc与make
    计算机网络 - 网络层
    xx技术2023前端开发卷A
  • 原文地址:https://blog.csdn.net/qq_62106937/article/details/126076545