十一:C语言-操作符详解

1.了解二进制

其实二进制;八进制;十进制和十六进制都是数值的不同表示形式而已

  • 二进制:基数为2,由0和1两个数字组成,逢2进1。
  • 八进制:基数为8,由0~7八个数字组成,逢8进1。
  • 十进制:基数为10,由0~9十个数字组成,逢10进1。
  • 十六进制:基数为16,由0~9十个数字和A/a(10);B/b(11);C/c(12);D/d(13);E/e(14);F/f(15)六个数字共同组成,逢16进1。

(1)二进制转十进制

位权:以十进制为例

百位十位个位
十进制的位123
10^210^110^0
按权展开100101
求值1*100+2*10+3*1

注意:

  • 基数的多少次幂,这样的数被称为位权
  • 任何数的1次方都等于其本身;任何数的0次方都等于1

因此,二进制转十进制的做法如下:以二进制的111为例

二进制的位111
2^22^12^0
按权展开421
求值1*4+1*2+1*1

(2)十进制转二进制

口诀:转2除2,倒取余,从下往上依次排序

如:将十进制的10转为二进制

在这里插入图片描述

注意: 当里面的数小于外面的基数时,也就是除不开的时候,里面的数就是最后的余数

(3)二进制转八进制

方法:将3个二进制数划为一组,按421这个上标的顺序标上标(分组是从后往前分,标上标是从前往后标),不足3位的在最前边补0(0标上标后依旧是0,不参与运算)其余的每组上标相加得出结果,最后每个组的结果合并就是转换完成的八进制数

如:将二进制数1101111转换为八进制的数

二进制数:1101111
分组后001 101 111
上标421 421 421
八进制数结果1 + 5 + 7 = 157

注意: 以0开头的数字会被当做八进制

(4)二进制转十六进制

方法:将4个二进制数划为一组,按8421这个上标的顺序标上标(分组是从后往前分,标上标是从前往后标),不足4位的在最前边补0(0标上标后依旧是0,不参与运算)其余的每组上标相加得出结果,最后每个组的结果合并就是转换完成的十六进制数

如:将二进制数110110111转换为十六进制的数

二进制数:0001 1011 0111
分组后0001 1011 0111
上标8421 8421 8421
十六进制数结果1 + b + 7 = 0x1b7

注意:

  • 转换成十六进制时,10~15这些数字必须用字母表示
  • 表示十六进制的时候前面要加0x
2.原码、反码和补码

整数的二进制表示方法有3种:原码、反码和补码

这三种表示方法均有符号位和数值位两部分,符号位都是用0表示正、用1表示负;而数值位最高位的一位是被当做符号位,剩余的都是数值位。

正整数的原码、反码和补码都相同,但负整数的三种表示方法各不相同:

  • 原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码
  • 反码:将原码的符号位(从左往右数第一位就是符号位)不变,数值位(除去符号位的其它位)依次按位取反就可以得到反码
  • 补码:反码+1就得到补码

如:int类型 5 的原码;反码和补码

在这里插入图片描述

如:int类型 -5 的原码;反码和补码

在这里插入图片描述

为什么表示5的是32位二进制数?

因为1字节等于8比特,而1个int类型占4个字节,也就是32比特位,所以用32个二进制数来表示

对于整型来说,数据存放内存中其实存放的是补码。 因为在计算机系统中,数值一律用补码来表示和存储。原因在于使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器);此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路

示例:

int main()
{
	int a = 1;
	int b = -1;
	// 1 + (-1)
	
    // 0000000000 0000000000 000000000001 -- 1原码/补码
    
	// 1000000000 0000000000 000000000001 -- -1原码
	// 1111111111 1111111111 111111111110 -- -1反码
	// 1111111111 1111111111 111111111111 -- -1补码
}
3.移位操作符
  • << 左移操作符
  • >> 右移操作符

(1)左移操作符

移位规则:左边丢弃,右边补上0

代码示例:将num左移一位

#include <stdio.h>
int main()
{
    int num = 10;
    // num的二进制表示:0000000000 0000000000 0000001010
    int ret = num << 1;
    // num << 1的移位结果:000000000 0000000000 00000010100
    printf("num = %d\n",num);
    printf("ret = %d\n",ret);
    return 0;
}

注意:

  • 整个过程num本身的值是不会发生改变的,只有将移位过程赋值给num本身的时候值才会发生改变
  • 无论是正数还是负数,在内存中移位运算的都是补码

(2)右移操作符

  1. 逻辑右移:左边用0填充,右边丢弃
  2. 算术右移:左边用原该值的符号位填充,右边丢弃

注意:

右移所采用的是逻辑右移,还是算术右移是不确定的,取决于编译器。但是大部分的编译器是采用算术右移的

代码示例:将num右移一位(逻辑右移)

#include <stdio.h>
int main()
{
    int num = -1;
    // num的二进制表示:1111111111 1111111111 1111111111
    int ret = num >> 1;
    // num >> 1逻辑右移的移位结果:01111111111 1111111111 111111111
    printf("num = %d\n", num);
    printf("ret = %d\n", ret);
    return 0;
}

代码示例:将num右移一位(算术右移)

#include <stdio.h>
int main()
{
    int num = -1;
    // num的二进制表示:1111111111 1111111111 1111111111
    int ret = num >> 1;
    // num >> 1算术右移的移位结果:1111111111 1111111111 1111111111
    printf("num = %d\n", num);
    printf("ret = %d\n", ret);
    return 0;
}

注意:

  • 移位操作符的操作数只能是整数,不能是浮点数
  • 对于移位操作符,不要移动负数位,这个标准是未定义的
//如:
int num = 10;
num >> -1; //移动负数位是错误的
4.位操作符

位操作符有:

&  // 按位与
|  // 按位或
^  // 按位异或
~  // 按位取反操作符

注意:

  • 以上位操作符的操作数只能是整数
  • 位指的是二进制位

多说无益,代码示例:

(1)按位与的运算规则:对应的二进制位上,有0则为0,两个同时为1才为1

#include <stdio.h>
int main()
{
    // &
    int a = 5;
    int b = -6;
    int c = a & b; // 按位与
    // a的二进制表示:0000000000 0000000000 0000000101
    // b的二进制表示:1111111111 1111111111 1111111010
    printf("%d\n",c);
    // a & b:0000000000 0000000000 0000000000
    return 0;
}

(2)按位或的运算规则:对应的二进制位上,有1则为1,两个同时为0才为0

#include <stdio.h>
int main()
{
    // |
    int a = 5;
    int b = -6;
    int c = a | b; // 按位或
    // a的二进制表示:0000000000 0000000000 0000000101
    // b的二进制表示:1111111111 1111111111 1111111010
    printf("%d\n",c);
    // a | b:1111111111 1111111111 1111111111
    return 0;
}

(3)按位异或的运算规则:对应的二进制位上,相同则为0,相异则为1

#include <stdio.h>
int main()
{
    // ^
    int a = 5;
    int b = -6;
    int c = a ^ b; // 按位异或
    // a的二进制表示:0000000000 0000000000 0000000101
    // b的二进制表示:1111111111 1111111111 1111111010
    printf("%d\n",c);
    // a ^ b:1111111111 1111111111 1111111111
    return 0;
}

(4)按位取反的运算规则:二进制是0的变成1,是1的变成0

#include <stdio.h>
int main()
{
    int n = 0; // 0000000000 0000000000 000000000000
    int a = ~n; //按位取反 1111111111 1111111111 111111111111
    printf("%d\n",a);
    return 0;
}

练习1:实现两个数的交换

#include <stdio.h>
int main()
{
    int a = 3;
    int b = 5;
    int tmp = 0;
    printf("交换前:a = %d b = %d\n",a,b);
    tmp = a;
    a = b;
    b = tmp;
    printf("交换后:a = %d b = %d\n",a,b);
    return 0;
}

练习2:不能创建临时变量(第三个变量),实现两个数的交换

// 第一种方法
#include <stdio.h>
int main()
{
    int a = 3;
    int b = 5;
    printf("交换前:a = %d b = %d\n",a,b);
    a = a + b;
    b = a - b;
    a = a - b;
    printf("交换后:a = %d b = %d\n",a,b);
    return 0;
}

注意: 第一种的这个方法有缺陷,当a和b的数特别大的时候会导致范围越界

// 第二种方法
#include <stdio.h>
int main()
{
    int a = 3;
    int b = 5;
    printf("交换前:a = %d b = %d\n",a,b);
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    printf("交换后:a = %d b = %d\n",a,b);
    return 0;
}

1. 异或运算符的特点:

  • a ^ a = 0
  • 0 ^ a = a
  • a ^ a ^ b = b
  • a ^ b ^ a = b

通过以上的特点分析得出一个结论:异或是支持交换律的

2. 异或运算的局限性:

  • 异或只能用于整数交换
  • 代码的可读性较差
  • 代码的执行效率也是低于使用第3变量的方法的

练习3:求一个整数存储在内存中的二进制中1的个数

// 第一种方法
#include <stdio.h>
int main()
{
    int a = 15;
    int count = 0;
    while(a)
    {
        if(a % 2 == 1)
        {
            count++;
        }
        a = a / 2;
    }
    printf("count = %d\n",count);
    return 0;
}
// 第二种方法
#include <stdio.h>
int main()
{
    int a = 0;
    scanf("%d",&a);
    int i = 0;
    int count = 0;
    for(i<0; i<32; i++)
    {
        if(((a>>i) & 1) == 1)
        {
            count++;
        }
    }
    printf("count = %d\n",count);
    return 0;
}
// 第三种方法
#include <stdio.h>
int main()
{
    int n = 0;
    int count = 0;
    scanf("%d",&n);
    while(n)
    {
        n = n & (n-1);
        count++;
    }
    printf("count = %d\n",count);
    return 0;
}

练习4:判断一个数是否是2^n次方

#include <stdio.h>
int main()
{
    int n = 0;
    scanf("%d",&n);
    
    if(n & (n - 1) == 0)
    {
        printf("yes\n");
    }
    else
    {
        printf("no\n");
    }
    return 0;
}

练习5:将a = 13的二进制位中第五位数字改为1,并且其它位保持不变

#include <stdio.h>
int main()
{
    int a = 13;
    // 0000000000 0000000000 000000001101
    // 0000000000 0000000000 000000010000
    // 1 << 4
    // 0000000000 0000000000 000000011101
    
    a = a | (1 << 4);
    printf("a = %d\n",a);
    return 0;
}

练习6:将a = 29的二进制位中第五位数字改为0,并且其它位保持不变

#include <stdio.h>
int main()
{
    int a = 29;
    // 0000000000 0000000000 000000011101
    a = a & ~(1 << 4);
    // 1111111111 1111111111 111111101111
    printf("a = %d\n",a);
    // 0000000000 0000000000 000000001101
    return 0;
}
5.逗号表达式
  • 逗号表达式就是用逗号隔开的多个表达式
  • 逗号表达式从左向右依次执行,整个表达式的结果是最后一个表达式的结果
  • 逗号操作符(,)的优先级是最低级的

代码示例:

#include <stdio.h>
int main()
{
    int a = 1;
    int b = 2;
    int c = (a > b, a = b + 10, b = a + 1);
    printf("%d\n",c);
    return 0;
}
6.下标访问和函数调用

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

操作数:一个数组名 + 一个索引值

#include <stdio.h>
int main()
{
	int arr[5] = {1,2,3,4,5}; // 创建一个数组
	printf("%d\n",arr[4]); // 使用下标引用操作符
    // 下标引用操作符( [] )的两个操作数就是 arr 和 4
	return 0;   
}

在上述的代码中,下标引用操作符([])的两个操作数就是 a 和 9

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

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

#include <stdio.h>
int Add(int x, int y)
{
    return x+y;
}

int main()
{
    //函数的调用
    int ret = Add(1,2); // 使用函数调用操作符
    // 这个函数调用操作符:( () )的操作数就是 Add, 1, 2
    printf("%d\n",ret);
    return 0;
}

注意:

  1. 函数调用操作符至少要有一个操作数

  2. sizeof()不是函数,是操作符,因为sizeof后面的括号是可以省略掉的,而函数后面的括号是坚决不可以省略的。

// 代码示例:
#include <stdio.h>
int main()
{
    int a = 10;
    int n = sizeof a;
    printf("%d\n", n);
    return 0;
}
7.操作符的属性

C语言的操作符有两个重要的属性:优先级和结合性。这两个属性一定程度上决定了表达式求值的计算顺序。

(1)优先级

优先级指的是如果一个表达式包含多个运算符,那么哪个运算符应该优先执行。每种运算符的优先级是不一样的

常用运算符的优先级顺序(从上到下,优先级是由高到低的):

  1. 圆括号:()
  2. 自增运算符:++ ;自减运算符:--
  3. 单目运算符:+-
  4. 乘法:* ;除法:/
  5. 加法:+ ;减法:-
  6. 关系运算符:<>
  7. 赋值运算符:=

注意:

  1. 关于优先级,我们一般讨论的是两个相邻运算符的优先级

  2. 由于圆括号的优先级最高,因此可以使用它来改变其它运算符的优先级

(2)结合性

如果两个运算符的优先级相同,没办法确定先计算哪个,这时候就要看结合性了。根据运算符是左结合;还是右结合,决定执行顺序。大部分的运算符都是左结合(从左到右执行);只有少数运算符是右结合(从右到左执行)。

参考链接: C 运算符优先级 - cppreference.com

级和结合性。这两个属性一定程度上决定了表达式求值的计算顺序。

(1)优先级

优先级指的是如果一个表达式包含多个运算符,那么哪个运算符应该优先执行。每种运算符的优先级是不一样的

常用运算符的优先级顺序(从上到下,优先级是由高到低的):

  1. 圆括号:()
  2. 自增运算符:++ ;自减运算符:--
  3. 单目运算符:+-
  4. 乘法:* ;除法:/
  5. 加法:+ ;减法:-
  6. 关系运算符:<>
  7. 赋值运算符:=

注意:

  1. 关于优先级,我们一般讨论的是两个相邻运算符的优先级

  2. 由于圆括号的优先级最高,因此可以使用它来改变其它运算符的优先级

(2)结合性

如果两个运算符的优先级相同,没办法确定先计算哪个,这时候就要看结合性了。根据运算符是左结合;还是右结合,决定执行顺序。大部分的运算符都是左结合(从左到右执行);只有少数运算符是右结合(从右到左执行)。

参考链接: C 运算符优先级 - cppreference.com

在这里插入图片描述

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温轻舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值