操作符详解

操作符详解

1.操作符分类

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

2.算术操作符

+ - * / %

  1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
  2. 对于 / 操作符,如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
  3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

加减乘都属于常规运算,这里特意讲解一下除法和取模运算

2.1除法运算

    int a1 = 7 / 2;
    printf("a1 = %d\n",a1);
    double a2 = 7 / 2;
    printf("a2 = %d\n",a2);
    double a3 = 7.0 / 2;
    printf("a3 = %d\n",a3);

在这里插入图片描述

可以发现除法的结果不在意指定的类型,只要有一个数字是小数,结果执行的就是小数除法,除法中除数不可以为0.

2.2 取模操作

取模意思就是取余,取模操作符的两个操作符都必须是整数,其操作符符号为%

比如3 / 2 = 1.5(1余5),所以3 % 2 = 1

3.移位操作符

3.1二进制的原反补码

想要弄懂移位操作符,首先先明白二进制的原反补码。

整数的二进制表示形式有三种表示形式:原码、反码、补码。

1.正整数的原码、反码、补码是相同的

2.负整数的原码、反码、补码是要计算的,计算规则如下:

​ 原码不变

​ 原码除符号位之外,取余部分全部按位取反(0变1,1变0),得到的就是反码。

​ 反码加1,得到的就是补码。

首先1个整数是4个字节,一个字节有8个bit位。所以,一个整数就有32个bit位。其中第一个比特位0/1(也叫符号位)表示的是数字的正负,其中0表示正,1表示负,

例如整数15,在二进制的表示中14 = [1 * 2^3] + [1 * 2^2] +[1 * 2^1] + [0 * 2^0],所以14的二进制就是可以表示为1110.

所以:整数15的原码 —>> 00000000000000000000000000001111

​ 反码—>> 00000000000000000000000000001111

​ 补码—>> 00000000000000000000000000001111

​ 负整数15的原码 —>> 10000000000000000000000000001111

​ 反码—>> 11111111111111111111111111110000(原码除符号位之外,预取按位取反)

​ 补码—>> 11111111111111111111111111110001 (反码加1)(二进制满2进1)

其中,整数在内存中存储的是补码,计算的时候也是使用补码计算的。

3.2右移移位操作

移位本质上移动的其实就是补码的二进制序列。

右移分为算术右移和逻辑右移。c语言中没有明确规定是算术右移还是逻辑右移,一般编译器上采用的是算术右移

逻辑移位:左边用0填充,右边丢弃

算术移位:左边用原该值的符号位填充,右边丢弃

举例:

#include <stdio.h>
int main() 
{
    int a = -15;
    int b = a >> 1;
    printf("%d\n",b);
    return 0;
}

上述代码可知,a的补码为 11111111111111111111111111110001(32位),经过右移一位,原来的补码就变为了 1111111111111111111111111111000(31位,最右边的1丢失了),此时发现对比原来的补码,符号位缺失了,根据右移规则,符号位用原该值的符号位补充,补充后的结果为: 11111111111111111111111111111000,该二进制表示的数值为 【1 * 2^3】+【0 * 2^2】+【0 * 2^1】+【0 * 2^0】 = 8,又因为符号位为1,所以数值为负数。由此可得该二进制表示的结果为-8.

在这里插入图片描述

从运行结果来看,我们的分析是正确的。为了更加清晰的看出bit位的变化,下面贴上数值的变化前后对比:

在这里插入图片描述

注意:移位不可以移动负数位,比如 a >> -2不能代表左移2位,移动负数位的这种写法c语言本身并未定义

3.3左移操作符

移动和计算规则与右移方法基本一致。

移位规则:

左边抛弃、右边补0

在这里插入图片描述

4.位操作符

& 按位与

| 按位或

^ 按位异或

注:他们的操作数必须是整数。

4.1按位与&

举例:

#include <stdio.h>

int main()
{
    int a = 3;
    //00000000000000000000000000000011 - 补码
    int b = -5;
    //10000000000000000000000000000101 - 原码
    //11111111111111111111111111111010 - 反码
    //11111111111111111111111111111011 - 补码
    int c = a & b;
    //a:00000000000000000000000000000011
    //b:11111111111111111111111111111011
    //c:00000000000000000000000000000011  ---> 计算值为 [1 * 2^1]+[1 * 2^0] = 3
    printf("c = %d\n",c);
    return 0;
}

&符号的运算规则就是对应二进制位有0则为0,两个同时为1才是1,所以上述代码运行结果就是为3.

在这里插入图片描述

4.2按位或 |

按位或| 的运算规则与按位与正好相反:对应二进制位有1则为1,两个同时为0才是0

举例:

#include <stdio.h>
int main()
{
    int a = 3;
    //00000000000000000000000000000011 - 补码
    int b = -5;
    //10000000000000000000000000000101 - 原码
    //11111111111111111111111111111010 - 反码
    //11111111111111111111111111111011 - 补码
    int c = a | b;
    //a:00000000000000000000000000000011
    //b:11111111111111111111111111111011
    //c:11111111111111111111111111111011 --补码
    //  11111111111111111111111111111010 --反码
    //  10000000000000000000000000000101 --原码  ---> 计算值为 [1 * 2^2]+[0 * 2^1]+[1 * 2^0] = -5
    printf("c = %d\n",c);
    return 0;
}

结果发现c的值就是-5.

在这里插入图片描述

这时可能会有小伙伴产生了疑问,为什么按位与是直接计算,而按位或则是算出原码才计算呢?

当然,至于为什么也是很简单的,一开始就提及到正整数的原反补码是一样的,所以按位与直接获得的补码就等于原码,可以直接计算,而按位或得到的补码是负整数,负整数的原反补码并不相同,所以需要额外计算出原码,在进行计算求值。

4.3按位异或^

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

#include <stdio.h>
int main()
{
    int a = 3;
    //00000000000000000000000000000011 - 补码
    int b = -5;
    //10000000000000000000000000000101 - 原码
    //11111111111111111111111111111010 - 反码
    //11111111111111111111111111111011 - 补码
    int c = a ^ b;
    //a:00000000000000000000000000000011
    //b:11111111111111111111111111111011
    //c:11111111111111111111111111111000 --补码
    //  11111111111111111111111111110111 --反码
    //  10000000000000000000000000001000 --原码  ---> 计算值为 [1 * 2^3]+[0 * 2^2]+[0 * 2^1]+[0 * 2^0] = -8
    printf("c = %d\n",c);
    return 0;
}

在这里插入图片描述

从运行结果可以发现,c的值的确为-8.

4.4 练习

看到这里,可能会有小伙伴疑惑,这个操作符有什么用呢?接下来,让我们来看一道题:

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

解答:

#include <stdio.h>
int main()
{
 	int a = 10;
 	int b = 20;
 	a = a^b;
	b = a^b;
 	a = a^b;
 	printf("a = %d b = %d\n", a, b);
 	return 0;
}

运行结果如下:

在这里插入图片描述

5.赋值操作符

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

例如:

#include <stdio.h>
int main()
{
    int w = 12;
    printf("Before w is: %d\n",w);
    w = 89;
    printf("After w is: %d\n",w);
    double s = 1000.0;
    printf("Before w is: %lf\n",s);
    s = 20000.0;
    printf("Before s is: %lf\n",s);
    return 0;
}

运行结果如下:

在这里插入图片描述

除了单一赋值符外,还有复合赋值符

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

例举其中一个,其他赋值符均一样。比如a+=b,其运行过程就是a = a + b:

#include <stdio.h>
int main()
{
    int a = 3;
    int b = 6;
    a+=b;
    printf("%d\n",a)
    return 0;
}

在这里插入图片描述

6.单目操作符

6.1单目操作符的介绍

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

单目操作符顾名思义就是只有一个操作数,其中关于sizeof其实我们之前已经见过,可以求变量(类型)所占空间的大小。

6.2前后置的++和–

这里用代码演示一下前置和后置的++与–,以下对前后置+±-进行举例:

//++和--运算符
//前置++和--
#include <stdio.h>
int main()
{
    int a = 10;
    int x = ++a;
    //先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
    printf("x = %d\n",x);
    printf("a = %d\n",a);
    int y = --a;
    //先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
    printf("y = %d\n",y);
    printf("a = %d\n",a);
    return 0;
}

在这里插入图片描述

//后置++和--
#include <stdio.h>
int main()
{
    int a = 10;
    int x = a++;
    //先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
    printf("x = %d\n",x);
    printf("a = %d\n",a);
    int y = a--;
    //先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
    printf("y = %d\n",y);
    printf("a = %d\n",a);
    return 0;
}

在这里插入图片描述

7.关系操作符

举例:

> >= < <=

!= 用于测试“不相等”

== 用于测试“相等”

这些关系运算符比较简单,就不一一举例了,但是我们要注意一些运算符使用时候的陷阱。

警告:

在编程的过程中== 和=不小心写错,导致的错误

8.逻辑操作符

逻辑操作符有哪些:

&& 逻辑与

|| 逻辑或

区分逻辑与和按位与

1&2 -----> 0

1&&2 ----> 1

区分逻辑或和按位或

1|2 -----> 3

1||2 ----> 1

下面来看一道习题:

#include <stdio.h>
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;
}

运行结果如下:(看看自己算对了吗)

在这里插入图片描述

9.条件操作符

exp1 ? exp2 : exp3

语法意思就是exp1为真吗,如果为真,结果为exp2;如果为假,结果为exp3.

举例:

正常的表达式书写:

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

利用条件操作符书写:

#include <stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int m = 0;
	scanf("%d %d",&a,&b);
	m = (a > b ? a : b);
	printf("m = %d\n",m);
    return 0;
}

在这里插入图片描述

10.逗号表达式

exp1, exp2, exp3, ……epN
其结果就是expN

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

举一个简单的例子:

#include <stdio.h>
int main()
{
    int a = 1;
    int b = 2;
    int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);
    return 0;
}

在这里插入图片描述

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

11.1下标引用操作符

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

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5};
    printf("%d\n",arr[2]);  // [ ]的两个操作数是arr和2
    return 0;
}

11.2 函数调用操作符

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

#include <stdio.h>
void test1()
{
    printf("hehe\n");
}

void test2(const char *str)
{
    printf("%s\n", str);
}

int main()
{
    test1(); //实用()作为函数调用操作符。
    test2("hello bit.");
    return 0;
}

11.3 访问一个结构的成员

. 结构体.成员名

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

举例:

#include <stdio.h>
struct Stu
{
    char name[10];
    int age;
    char sex[5];
    double score;
};
int set_age1(struct Stu stu)
{
    stu.age = 18;
    return stu.age;
}
void set_age2(struct Stu* pStu)
{
    pStu->age = 18;//结构成员访问
}
int main()
{
    struct Stu stu;
    struct Stu* pStu = &stu;//结构成员访问

    int a = set_age1(stu);
    printf("stu = %d\n",a); //打印结果就为18

    pStu->age = 20;//结构成员访问
    set_age2(pStu);
    return 0;
}

12 表达式求值

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

12.1隐式类型转换

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

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

//实例1
char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果将被截断,然后再存储于a中。

如何进行整体提升呢?

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

//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0

12.2算术转换

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

long double

double

float

unsigned long int

long int

unsigned int

int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

警告:

但是算术转换要合理,要不然会有一些潜在的问题

float f = 3.14;
int num = f;//隐式转换,会有精度丢失

12.3操作符的属性

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

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

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

操作符优先级:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值