【C语言】操作符汇总&详解

目录

前言

1. 知识点补充 — 原码、反码、补码

2. 算术操作符

3. 赋值操作符

4. 移位操作符

5. 位操作符 

6. 单目操作符

7. 关系操作符

8. 逻辑操作符

9. 逗号操作符

10. 下标引用、函数调用

11. 结构成员访问

12. 操作符的优先级和结合性

结语


前言

操作符又称 “运算符”,它是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言中定义了丰富的运算符,熟练地运用操作符能让我们敲代码更加得心应手

1. 知识点补充 — 原码、反码、补码

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,在计算机用机器数的最高位的1位是被当作符号位,用来存放符号,剩余的都是数值位

在符号位上,用 0 表示正数,用 1 表示负数

正整数的原、反、补码都相同

负整数的原、反、补码不尽相同

原码:直接将数值按照正负数形式翻译成二进制得到的就是原码

反码:原码的符号位不变,其他位次全部取反得到的就是补码

补码:反码 + 1 得到的就是补码

注意:对于整型来说,数据在内存中是以补码形式存放的

举个 “栗子” :(整型为 4 个字节,共 32 个 bit 位)

        int a = 1;  // 原、反、补码一样
        // 原码:0000 0000 0000 0000 0000 0000 0000 0001
        // 反码:0000 0000 0000 0000 0000 0000 0000 0001
        // 补码:0000 0000 0000 0000 0000 0000 0000 0001
        int b = -1;
        // 原码:1000 0000 0000 0000 0000 0000 0000 0001 // 符号位为 1
        // 反码:1111 0000 0000 0000 0000 0000 0000 1110 // 符号位不变,其余位按位取反
        // 补码:1111 0000 0000 0000 0000 0000 0000 1111 // 反码 + 1

那我们要怎么从补码得到原码呢?有人可能会说把上面的方法逆推回去不就行了嘛。哎,这种想法肯定是没有问题滴

但实际上我们也可以通过 — 补码取反,+ 1 来得到原码 ,如图

以上就是简单的原码、反码、补码的知识普及,为的是让我们更好地理解后面的操作符知识。好,那么正片开始

2. 算术操作符

算数操作符功能描述
+加法
-减法
*乘法
/除法(整除)
%取余(取模)

注意:

  1.  +、-、*、/ 这四个操作符都可以用与整型和浮点型地运算
  2. 取余 % 操作符只能用在两个整型相除,此时返回的是余数,故称 取余
  3. 如果除号 / 的两个操作数都为整型,则运算后得到数也会是整型,如果想得到浮点型,两个操作数必须至少有一个为浮点数

演示:当 a = 20;b = 10;c = 20.0

	#include <stdio.h>

	int main()
	{
		int a = 20;
		int b = 10;
		float c = 20.0;
		printf("%d\n", a + b);
		printf("%d\n", a - b);
		printf("%d\n", a * b);
		printf("%d\n", a / b);//整型
		printf("%f\n", c / b);//浮点型
		printf("%d\n", a % b);
		return 0;
	}

运行结果为:

 

3. 赋值操作符

赋值操作符有两种: = 和 复合赋值

在创建变量时,给一个初始化,在变量创建好后,再给一个值,这就叫赋值

        int a = 10; // 初始化
        a = 20;     // 赋值,这里使用的 = 就是赋值操作符

赋值操作符也可以连续赋值 (一般不这样写)

        int a = 10;
        int b = 10;
        int c = 0;
        c = b = a + 10; // 连续赋值,从左到右依次赋值

 而当我们需要对一个数进行自增、自减等操作时,可能会这样写:

        int a = 10;
        a = a + 3;
        a = a - 3;

看起来是不是有些麻烦。事实上,我们可以用复合赋值来让代码看起来更加舒服,如:

        int a = 10;
        a += 3;  =====> a = a + 3
        a -= 3;   =====> a = a - 3

下面是一些常见的复合赋值操作符:

加等减等乘等除等取模等
+=-=*=/=%=

4. 移位操作符

左移操作符        <<       
右移操作符 >>

注意:移位操作符的操作数只能是整数,并且移位是在二进制层面进行的 

左移操作符的移位规则为:左边丢弃,右边补 0

右移操作符的移位规则有两种,分别为:

  • 逻辑右移:左边补 0,右边丢弃
  • 算术右移:左边用符号位的原值补充,右边丢弃
  • 具体是哪种取决于编译器,但大部分编译器是算术右移

举个 “栗子”:

        int a = 10;
        int b = a << 1; // 左移一位
        int c = a >> 1; // 右移一位

分析和结果:

            b =  a << 1 = 10 << 1 = 20;                                                               c = a >> 1 = 10 >> 1 = 5;

注意:

  • 位移操作后不改变原值,a 的值仍为 10
  • 移位是不能移负数位

5. 位操作符 

按位与     &     
按位或|
按位异或^
按位取反~

注意:

  • 位操作符的操作数必须为整数
  • 是在二进制层面对操作数进行运算
  • 在计算机中,数据存的都是二进制的补码

符号说明:

 & ————> 有 0 就 0,同 1 则 1

 | ————>  有 1 就 1,同 0 则 0 

 ^ ————> 相同为 0,不同为 1

 ~ ————> 取反(符号位也要取反)

1. & (按位与:有 0 就 0,同 1 则 1)

举个 “栗子”:

        int a = -3 ;
        int b = 5 ;
        int c =  a & b ;

因为在计算机中,数据在内存中都是以二进制的补码存储的,所以在计算前我们需要把操作数都化为补码形式

在开头,我们一起学习了原码、反码、补码之间相互转化的规则,现在就让我们来实践一下吧,如图:

计算结果为:int c = a & b = 5 

2. | (按位或:有 1 就 1,同 0 则 0)

再举个 “栗子”:

        int a = -3 ;
        int b = 5 ;
        int d = a | b ;

 如图:

计算结果为:int d = a | b = -3 

3. ^ (按位异或:相同为 0,不同为 1) 

再再举个 “栗子”:

        int a = -3 ;
        int b = 5 ;
        int e = a ^ b ;

如图:

计算结果为:int e = a ^ b = -8 

4. ~ (取反:全部取反,符号位也不例外)

最后举个 “栗子”:

        int a = 0 ;
        int f = ~ a ;

如图:

计算结果为:int f = ~a = -1 

知识补充:

  1. 对于按位异或操作符 ^ :a ^ a = 0 ; 0 ^ a = a(支持交换律)
  2. 对于按位与操作符 & :若 a & 1 == 1,说明 a 的二进制中最低为是 1;若 a & 1 == 0,说明 a 的二进制中最低位是 0
  3. 公式(用于去掉位次最右边的 1): a = a & ( a - 1 )

例题一:不能创建临时变量,实现两个数的交换(知识点1)

	#include <stdio.h>

	int main()
	{
		int a = 10;
		int b = 20;
		printf("a = %d  b = %d\n", a, b);
		a = a ^ b;
		b = a ^ b; // b = (a ^ b) ^ b
		a = a ^ b; // a = (a ^ b) ^ a
		printf("a = %d  b = %d\n", a, b);
		return 0;
	}

例题二:求一个整数存储在内存中的二进制中 1 的个数(知识点2) 

(提示:负数在内存中是以补码形式存储的)

	#include <stdio.h>

	int main()
	{
		int n = 0;
		scanf("%d", &n);
		int count = 0;
		int i = 0;
		for (i = 0; i < 32; i++)
		{
			if ((n & 1) == 1)
			{
				count++;
			}
		}
		printf("二进制中 1 的个数:%d", count);
		return 0;
	}

在上面的代码中,因为一个整数有 32 个比特位,所以我们必须循环 32 次才能完成统计,有没有什么优化办法呢(知识点3

	#include <stdio.h>

	int main()
	{
		int n = 0;
		scanf("%d", &n);
		int count = 0;
		while (n)
		{
			count++;
			n = n & (n - 1);
		}
		printf("二进制中 1 的个数:%d", count);
		return 0;
	}

例题三: 将 13 二进制序列的第 5 位修改为 1,然后再改为 0

    13 的二进制序列:0000 0000 0000 0000 0000 0000 0000 1101
   将第5位改为 1 后:0000 0000 0000 0000 0000 0000 0001 1101
   将第5位改回 0 后:0000 0000 0000 0000 0000 0000 0000 1101

思路:灵活运用移位操作符和位操作符

	#include <stdio.h>

	int main()
	{
		int n = 13;
		n = n | (1 << 4);
		printf("n = %d\n", n);
		n = n & ~(1 << 4);
		printf("n = %d\n", n);
		return 0;
	}

6. 单目操作符

逻辑取反取地址&
前置、后置+++解引用*
前置、后置---二进制取反~
正号+求字节长度sizeof
负号-强制类型转换( )

举例说明:

         !(逻辑反)

        int a = 10;
        if (a != 10) // 逻辑反
        {
                ……
        }

        前置++、后置++

    int a = 10;
    int b = ++a; //此时 b = 11; a = 11 ———— 前置++:先+1,后使用
    a = 10;      //重置 a 的值
    int c = a++; //此时 c = 10; a = 11 ———— 后置++:先使用,后+1

    前置--与后置--同理,因此不再赘述

         (正号)、 - (负号)

    int a = 10;
    int b = -10;

         & (取地址操作符)

		int a = 10;
		printf("a 的地址为:%p\n", &a);//取出 a 的地址
        // 打印地址要用 %p 来打印

         * (解引用操作符) 

	int a = 10;
	int* p = &a;
	*p = 20; //此时 a 的值就会变成 20

         ~ (二进制取反)

    int a = 0;
    int b = ~a; // 对一个数的二进制按位取反
    // b = -1

         sizeof (求字节长度)

	int a = 10;
	size_t b = sizeof(a); // b = 4
    //size_t 等同于无符号整型,专门用于接收sizeof

         ( ) (强制类型转换)

		int a = (int)1.5;
		printf("%d\n", a);
        //打印结果为 1

注意:

  1.  sizeof 是一个操作符,并不是函数,求的是操作数的类型长度(单位是字节)
  2.  sizeof 使用时不要省略括号,求变量长度时除外
  3.  !操作符时对一个数逻辑取反, ~ 操作符是对一个数的二级制序列按位取反,两者不一样

7. 关系操作符

   大于      小于   大于等于小于等于   不等于      等于   
><>=<=!===

注意: 要把赋值符号 = 和关系等符号 == 区分开

8. 逻辑操作符

逻辑与逻辑或
&&| |
一假全假一真就真
  •  要注意按位与和逻辑与,按位或和逻辑或的区别
  • 逻辑与中,只要有一个表达式为假,便不再执行后面的表达式,直接返回假
  • 逻辑或中,只要有一个表达式为真,便不再执行后面的表达式,直接返回真

9. 逗号操作符

        表达式1,表达式2,表达式3……表达式n

  •  逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
	a = test1();
	test2(a);
	while (a > 0)
	{
		……;
		a = test1();
		test2(a);
	}

我们可以用逗号表达式优化上述代码:

	while (a = test1(),test2(a),a > 0)
	{
		……;
	}

10. 下标引用、函数调用

1.  [ ] (下标引用操作符)

下标引用出现在数组中,操作数为一个数组名+一个索引值

	int arr[10] = { 0 };
	arr[0] = 10;
    // [ ] 的操作数是 ar r和 0

2.  ( ) (函数调用操作符) 

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

    test1();     //1 个操作数
    test2(a, b); //3 个操作数

11. 结构成员访问

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

                            .

->
	#include <stdio.h>
	#include <string.h>
	struct Stu
	{
		char name[10]; //名字
		int age;       //年龄
	};

	void set_stu(struct Stu* ps)
	{
		strcpy(ps->name, "李四");  // 结构体成员访问
		ps->age = 25;			   // 结构体成员访问
	}

	int main()
	{
		struct Stu s = { "张三",20 };
		printf("%s %d\n", s.name, s.age);  // 结构体成员访问
		set_stu(&s);
		printf("%s %d\n", s.name, s.age);  // 结构体成员访问
		return 0;
	}

12. 操作符的优先级和结合性

  1. 优先级:如果在一个表达式中包含多个操作符,哪个操作符一个最先执行
  2. 结合性:如果两个操作符优先级相同,那么就得看结合性。操作符都有自己的结合性,一些是左结合,一些是右结合,得根据实际操作符来判断。大部分操作符都是左结合(从左到右执行),少数是右结合(从右到左执行),例如赋值操作符

下面的表格展示了大部分操作符的优先级顺序和它们各自的结合性

                                        (表格来源于网络,如有侵权,联系删除)

结语

今天我们一起学习了各类操作符和它们的相关知识,有总结不到位的地方还请多多谅解,若有出现纰漏,希望大佬们看到错误之后能够在私信或评论区指正,博主会及时改正,共同进步!

欢迎各位在评论区友好讨论。如果觉得不错的话,麻烦您点个赞吧,十分感谢!

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值