C语言操作符详解

一、操作符分类

1.算数操作符

算数操作符
+
*
/
%

关于除法,分为整数除法和浮点数除法:
整数 / 整数 = 整数
浮点数 / 整数 = 浮点数
浮点数 / 浮点数 = 浮点数
% (取模操作符):例如 7 % 3,求的是 7 / 3 之后的余数,这是整数除法,所以 7 / 3 = 2 … 1,余数为 1 ,得出结果 7 % 3 = 1
注意:取模操作符只能针对整数进行运算,浮点数不可用

2.移位操作符

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

注意:左移操作符和右移操作符只能针对整型运算。

(1)左移操作符

#include <stdio.h>
int main()
{
	int a = 10;
	int num = a << 1;
	printf("%d",num);

	return 0;
}

例如上述代码,我们定义了 a ,并进行左移一位。
在这里插入图片描述
左移规则:左边丢弃,右边补 0
第一行是 a 的二进制,第二行是 a << 1的结果,向左移一位,左边会丢失一位,右边空一位,空的位补 0
我们来看一下代码运行的结果
在这里插入图片描述
由此可见,左移一位有乘2的效果。

(2)右移操作符

①算数移位

规则:右移后左边用原符号位补充,右边丢弃。(负数的符号位为 1 ,整数的符号位为 0,符号位是该数换算二进制后最左边的一位)

#include <stdio.h>
int main()
{
	int i = -10;
	int num = i >> 1;
	printf("%d",num);

	return 0;
}

在这里插入图片描述
我们定义了 i = -10,符号位为 1 ,然后进行右移一位。由程序运行结果可知,我们编译器默认是算数右移(最左边补原符号位)。

②逻辑移位

规则:右移后左边用 0 补充,右边丢弃。

注意:移位操作符不要移动负数位,这种操作是未定义的!

3.位操作符

原码、反码、补码

在学习位操作符之前,我们先来了解一下原码、反码和补码。
正数:

int a = 3;
//00000000000000000000000000000011   -原码
//00000000000000000000000000000011   -补码
//00000000000000000000000000000011   -反码

将 3 直接转换为二进制的表示形式,叫做原码。
对于一个正数而言,原码、反码和补码相同。
负数:

int b = -5;
//10000000000000000000000000000101   -原码
//11111111111111111111111111111010   -反码
//11111111111111111111111111111011   -补码

将 -5 直接转换为二进制的表示形式,叫做原码。
原符号位不变,其他位取反,0 变为 1, 1 变为 0。得到的二进制表示结果叫做反码。
反码表示二进制的值 + 1 得到补码。

注意:在内存中,保存的是补码

位操作符讲解

位操作符
& 按位与
| 按位或
^ 按位异或

注:以上操作符的操作数必须是整数
(1)按位与操作符(&)
规则:有 0 为 0 ,两个同为 1 才为 1。

int a = 3;
int b = -5;
int c = a & b;
//00000000000000000000000000000011   3的补码
//11111111111111111111111111111011   -5的补码
//00000000000000000000000000000011    a & b

结果输出 3
在这里插入图片描述

注:位操作符是对补码进行操作的。

(2)按位或操作符(|)
规则:有 1 为 1,同时为 0 才为 0

int a = 3;
int b = -5;
int c = a | b;
//00000000000000000000000000000011   3的补码
//11111111111111111111111111111011   -5的补码
//11111111111111111111111111111011   a | b

结果输出 -5
在这里插入图片描述
(3)按位异或操作符(^)
规则:相同为 0 ,相异为 1

int a = 3;
int b = -5;
int c = a ^ b;
//00000000000000000000000000000011   3的补码
//11111111111111111111111111111011   -5的补码
//11111111111111111111111111111000   a ^ b

上述代码中求得的是补码,我们要求原码。所以先 -1得到反码,然后取反就是原码。

//11111111111111111111111111110111    -1操作得到反码
//10000000000000000000000000001000     取反得到原码

最终我们得到结果 -8
在这里插入图片描述

有这么一道变态的面试题:
不能创建临时变量(第三个变量),实现两个数的交换。

#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;
}

在这里插入图片描述
这道题巧妙的利用按位异或操作符实现了两个整数的交换。

4.赋值操作符

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

int a = 10;
int b = 20;
int c = a = b + 1;    //连续赋值
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
int a = 10;
int b += a;
//b += a; 可拆分如下
//b = b + a;

5.单目操作符

(1)单目操作符介绍

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

逻辑反操作符:

if (!3)
{
    代码块
}

上述代码中判断条件为 !3, 3为真,!3则为假,逻辑反操作。

#include <stdio.h>
int main()
{
 int a = -10;
 int *p = NULL;
 printf("%d\n", !2);
 printf("%d\n", !0);
 a = -a;
 p = &a;
 printf("%d\n", sizeof(a));       //①
 printf("%d\n", sizeof(int));     //②
 printf("%d\n", sizeof a);        //③
 printf("%d\n", sizeof int);      //④
 return 0;
}

例如上述代码,利用到了sizeof操作符,但是④处代码是错误的,求类型的长度时要加括号。

前置、后置++、- -操作符:

//前置++和--
#include <stdio.h>
int main()
{
    int a = 10;
    int x = ++a;
        //先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
        int y = --a;
    //先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
    return 0;
}
//后置++和--
#include <stdio.h>
int main()
{
    int a = 10;
    int x = a++;
    //先对a先使用,再增加,这样x的值是10;之后a变成11;
    int y = a--;
    //先对a先使用,再自减,这样y的值是11;之后a变成10;
    return 0;
}

解引用操作符:

int a = 10;
int* b = &a;
printf("%d\n",*b);

在这里插入图片描述
将 10 放入变量 a 中,指针变量 b 存放 a 的地址,利用解引用操作符,是通过地址找到位置,从而进行对 a 的操作。

(2)sizeof 和数组

#include <stdio.h>
void test1(int arr[])
{
 printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
 printf("%d\n", sizeof(ch));//(4)
}
int main()
{
 int arr[10] = {0};
 char ch[10] = {0};
 printf("%d\n", sizeof(arr));//(1)
 printf("%d\n", sizeof(ch));//(3)
 test1(arr);
 test2(ch);
 return 0;
}

我们看上述代码,思考(1)(2)(3)(4)位置各输出多少
(1)处和(3)处位置都是计算的整个数组大小,(1)处为int 类型,有10个元素,所以输出 40;(3)处为char类型,10个元素,所以输出 10。
(2)处和(4)处位置都是传的首元素地址,首元素地址是int类型,所以在X86环境下都是输出 4 ,在X64 环境下都是输出 8。
X64:
在这里插入图片描述
X86:
在这里插入图片描述

6.关系操作符

关系操作符简介
>
>=
<
<=
!=用于测试“不相等”
==用于测试“相等”
注意:在编程中 = 和 == 不要写错!

7.逻辑操作符

逻辑操作符简介
&&逻辑与
| |逻辑或
注意:按位与、按位或和逻辑与、逻辑或不同,要注意区分!
关于逻辑操作符的特点,我们来看一道360面试题:
一下程序的输出结果是什么?
#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;
}

由于初始化时 a = 0,所以第二行代码从左向右执行,先执行 a++,因为是后置++,逻辑与操作符,所以 i = 0,不继续读取后面的内容了。
在这里插入图片描述

#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;
}

在这里插入图片描述

8.条件操作符

exp1 ? exp2 : exp3
if (a > 5)
        b = 3;
else
        b = -3;
a > 5 ? 3 : -3;

上述两段代码表达的意思相同。

9.逗号表达式

exp1, exp2, exp3, …expN
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);

此段代码中,逗号表达式从左向右依次执行,输出最后一个表达式的结果。

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

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

int arr[10];//创建数组
 arr[9] = 10;//实用下标引用操作符。
 [ ]的两个操作数是arr和9。

( )函数调用操作符:
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

#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.运算操作符优先级表

在这里插入图片描述

二、表达式求值

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

1.隐式类型转换

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

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

char a,b,c;
...
a = b + c;

这时会先将char类型的变量进行整型提升,提升为int类型,然后进行运算。
那么如何进行提升呢?
整形提升是按照变量的数据类型的符号位来提升的

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

2.算数转换

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

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

float n = 3.14;
int a = n;

这时隐式转换则会有精度丢失的问题。

3.操作符属性

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

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

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。我们写出的表达式若不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的!

注:本次学习就暂时结束啦,文章中有错误、不足之处欢迎大佬指正,让我们共同学习,共同进步,一切成功的秘诀,都在于我们不懈的追求,加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值