• C语言详解系列——操作符详解


    算术操作符

    首先我们为大家介绍一下算术操作符

    + - * / %

    他们的用法与数学中的加减乘除取余运算一样,值得注意的是,除了%操作符之外,其他几个操作符都可以用于整数和浮点数,%操作符的两个操作数必须位整数。返回的是整除之后的余数。对于/操作符如果两个操作数都为整数,执行整数除法,而只要有浮点数执行的就是浮点数除法

    移位操作符

    << 左移操作符
    >> 右移操作符
    移位操作符的操作数也只能是整数

    要了解移位操作符的化,我们需要向大家介绍一下有关原码,反码,和补码的知识。一个整数在内存中存储的是二进制的补码,移位操作符,移动的是存储在内存当中的补码
    什么是原码,反码和补码呢?

    1.正整数的原码反码补码相同,都是他们自身的二进制表示
    2.负整数的原码是他自身的二进制表示,反码是原码的符号位不变,其他位按位取反,补码是反码+1

    例如1
    原码,补码,反码都是:00000000000000000000000000000001因为整数存放在int类型的变量中,int类型占四个字节,32个比特位。所以二进制有32位,其中最左边的一位是符号位,0表示为正整数,1表示为负整数
    例如-1
    原码:10000000000000000000000000000001
    反码:111111111111111111111111111111111110
    补码:111111111111111111111111111111111111

    移位操作符移动的就是他们在内存当中存储的补码

    例如int num = -1;
    num = num <<1;
    原码:10000000000000000000000000000001
    反码:111111111111111111111111111111111110
    补码:111111111111111111111111111111111111
    将num向左移动移位,丢弃左边,在右边补0
    补码变成:111111111111111111111111111111111110
    反码:111111111111111111111111111111111101
    原码:10000000000000000000000000000010
    num =num >> 1;
    原码:10000000000000000000000000000001
    反码:111111111111111111111111111111111110
    补码:111111111111111111111111111111111111
    将num向右移一位,两种规则1.逻辑位移,左边加0,右边丢弃2.算术位移:左边加原符号位,右边丢弃,大部分编译器使用的是算术位移
    补码:111111111111111111111111111111111111
    反码:111111111111111111111111111111111110
    原码:10000000000000000000000000000001

    注意:对于移位操作符,不要移动负数位,这个没有定义

    位操作符

    & 按位与
    | 按位或
    ^ 按位异或
    他们的操作数也必须是整数

    他们也是对整数在内存当中存储的二进制补码进行操作

    3 & -5
    00000000000000000000000000000011 3的补码
    10000000000000000000000000000101 -5的原码
    11111111111111111111111111111010 -5的反码
    11111111111111111111111111111011 -5的补码
    将他们的补码进行&,同为1取1,有0则0
    00000000000000000000000000000011
    11111111111111111111111111111011
    00000000000000000000000000000011
    -3 | -5
    00000000000000000000000000000011
    11111111111111111111111111111011
    11111111111111111111111111111011
    将他们的补码进行|,有1则1,同0取0
    3 ^ -5
    00000000000000000000000000000011
    11111111111111111111111111111011
    11111111111111111111111111111000
    ==将他们的补码进行^,相同为0,不同为1

    赋值操作符

    赋值操作符=,可以连续使用,用于给变量赋值

    int main()
    {
    	int x = 10;
    	int y = 20;
    	int z = 30;
    	x = y = z = 40;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    但是这样写逻辑不够清晰我们还是建议大家分开写

    int main()
    {
    	int x = 10;
    	int y = 20;
    	int z = 30;
    	y = z;
    	x = y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    符合赋值符

    +=
    -=
    *=
    /=
    %=
    >>=
    <<=
    &=
    |=
    ^=

    这些运算都有复合的效果,例如:

    int main()
    {
        int x =10;
        x = x+10;
        x += 10;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的x = x+10;x += 10;有同的效果。

    单目操作符

    ! 逻辑反操作符
    - 负值
    + 正值
    & 取地址
    sizeof 操作数的类型长度(以字节为单位)
    ~ 对一个数的二进制位按位取反
    -- 前置,后置–
    ++ 前置后置++
    * 间接访问操作符(解引用操作符)
    (类型) 强制类型转换

    我与大家分析一下sizeof这个操作符

    int main()
    {
    	int x = 10;
    	char a = 'a';
    	printf("%d\n", sizeof(x));
    	printf("%d\n", sizeof(a));
    	printf("%d\n", sizeof x);
    	printf("%d\n", sizeof a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    如上图我们知道了sizeof的使用可以不用加括号,所以sizeof不是函数而是操作符,他的作用就是求变量所占的空间大小。

    void test1(int arr[])
    {
    	printf("%d \n",sizeof arr);
    }
    
    void test2(char arr[])
    {
    	printf("%d \n", sizeof arr);
    }
    
    int main()
    {
    	int arr[10] = { 0 };
    	char arr1[10] = { 0 };
    
    	printf("%d \n", sizeof arr);
    	printf("%d \n", sizeof arr1);
    	test1(arr);
    	test2(arr1);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    sizeof与数组,因为sizeof是求变量所占空间大小所以,int类型的数组每个元素占4个字节,有十个元素,所以是40,char类型的数组每个元素占1个字节,所以是10,而数组在传参的过程中,传递的是首元素地址,地址在32位环境下占4个字节,所以都是4。

    前置-- 和 ++是先自减和自加,在调用,后置-- ++是先调用在自减和自加

    关系操作符

    >=
    <=
    <
    >
    !=
    ==

    他们的用法跟他们在数学当中的用法一样!=用于测试不等于,==用于测试等于

    逻辑操作符

    && 逻辑与
    || 逻辑或

    逻辑与和或是判断真假的,他们特点:

    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

    输出的结果为 1 2 3 4,因为&&判断真假,遇假则假,当a++进行判断时,先调用a++,调用时a = 0;所以后面不需要在进行判断,直接返回为假。这是&& ||的短路特点,至于下一个是多少大家可以自己思考一下。

    条件操作符

    expq ? expe2 : exp3;

    条件操作符又称三目操作符,作用与if基本相同

    int main()
    {
    	int a = 10;
    	int b = 20;
    	
    	printf("%d",b > a ? b : a);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    int main()
    {
    	int a = 10;
    	int b = 20;
    	if (b > a)
    	{
    		printf("%d", b);
    	}
    	else
    	{
    		printf("%d", a);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如上图这两个代码所表达的逻辑一样。

    逗号表达式

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

    int main()
    {
    	int a = 0;
    	int b = 10;
    	int c = (a=a + b,b = a * b, b);
    	printf("%d %d %d", a, b, c);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

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

    下标引用

    []下标引用操作符,操作数:一个数组名,一个索引值

    int main()
    {
    	int arr[10] = { 0 };
    	arr[9] = 10;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里的[]两个操作时arr和9。

    函数调用操作符

    接收一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递个给函数的参数。

    int add(int a, int b)
    {
    	return (a + b);
    }
    
    int main()
    {
    	int a = 10;
    	int b = 10;
    	int c = add(a, b);
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    访问结构成员

    . 结构体成员名
    -> 结构体指针名

    struct student
    {
    	char name[20];
    	int age;
    };
    
    print(struct student stu)
    {
    	stu.age = 20;
    }
    
    print2(struct student* stu)
    {
    	stu->age = 20;
    }
    int main()
    {
    	struct student stu = { "lll",18 };
    	print(stu);
    	print(stu);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    表达式求值

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

    隐式类型转换

    在C语言中,整形算数运算总是至少以缺省整形类型的精度进行的。为了获得这个精度,表达式中的字符和短整型在使用前被转换为普通整型,这种转换称为整型提升
    整形提升的意义:

    表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
    一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
    因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
    度。
    通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
    中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
    换为int或unsigned int,然后才能送入CPU去执行运算。

    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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    因为变量a与b在进行判断时,进行整形提升, 变为的负数,所以表达式 a == 0xb6,b == 0bb600的结果为假。
    那么整形提升时如何进行的呢?

    正数的整型提升:高位补充符号位,即为1。
    负数的整形提升:高位补充符号位,即为0。
    无符号的整形提升,高位补0。

    算术转换

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

    long double
    double
    float
    unsigned long int
    long int
    unsigned int
    int

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

    int main()
    {
       float f = 3.14;
       int num = f;//隐式转换,会有精度的丢失。
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    操作符的属性

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

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

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

  • 相关阅读:
    展商企业【广东伟创科技开发有限公司】| 2024水科技大会暨技术装备成果展
    c# 反射专题研究
    基于 java+springboot+vue 的酒店⺠宿⽹站250910
    Java api中文在线版
    37.解数独(内含回溯算法重要思路)
    编程随笔:控制复杂度是计算机编程的本质。
    基于ssm框架的高校班级管理系统设计与实现
    Ansys Zemax | 使用 OpticStudio 进行闪光激光雷达系统建模(上)
    2.1 八大类50条小红书爆款标题文案【玩赚小红书】
    linux进程的调度
  • 原文地址:https://blog.csdn.net/weixin_64182409/article/details/126098097