C语言操作符详解

目录

一.操作符的分类:

1.算术操作符 

2.移位操作符

 3.位操作符

 4.赋值操作符

5.单目操作符 

 6.关系操作符

7.逻辑操作符 

8.条件操作符 

9.逗号表达式 

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

二.表达式求值

1.隐式类型转换 

 2.算术转换

 3.操作符的属性

4.一些问题表达式


一.操作符的分类:

1.算术操作符
2.移位操作符
3.位操作符
4.赋值操作符
5.单目操作符
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号表达式
10.下标引用、函数调用和结构成员

1.算术操作符 

+  -   *  /   %

%(取余)         结果是余数。

%操作符的两个操作数必须是整数,则最后返回的数是余数。

其余的操作符可以是整数也可以是浮点数。

对应   /  操作符如果两个都是整数,则执行整数除法。

2.移位操作符

<< 左移操作符
>> 右移操作符
这个移位是指在二进制的基础上进行的。
而整数的内存的存储是补码的二进制数

如:

int   a=5;

 这个5是十进制数,转化二进制就是101,又因为a是整数,占4个字节,32个bit位,则:

原码:00000000 00000000 00000000 00000101

反码:00000000 00000000 00000000 00000101

补码:00000000 00000000 00000000 00000101

(正整数的原码,反码,补码都是一致的)

若:

int a=-5;

原码:10000000 00000000 00000000 00000101(把5的原码首位改成1)

反码:111111111 111111111 111111111  11111010(-5原码首位不变,其余的取反)

补码:111111111 111111111 111111111  11111011  (在反码的基础上+1)

(负数的原码,反码,补码各不相同)

(原码,补码,反码的首位称为符号位 ,符号位为0 -代表正数,1-代表负数)

int a = 5;

int b <<1 ; 

b的值是a的补码向左移动一格,即:左边去掉一个数,右边补一个0;

b的补码:00000000 00000000 00000000 00001010

转化二进制数就是10

同理:

int a = 5 ;

int c >> 1 ;

c的值是a的补码向右移动一格,即:右边去掉一个数,左边补一个0;

c的补码:00000000 00000000 00000000 00000010

转化二级制就是2

从算术上说:左/右边丢弃,右/左边补原符号位

从逻辑上说:左/右边丢弃,右/左边补0

移位不会改变操作数的本身数值

注:移位操作符的操作数只能是整数。
对应于移位操作符,不要移动负正数。

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

 3.位操作符

&    //按(二进制)位与      
|     //按(二进制)位或
^    //按(二进制)位异或
#include<stdio.h>
int main()
{
	int a = 3;
	int b =-5;
	int c = a & b;
	
	printf("%d\n", c);
	return 0;
}

 先分别求出a和b的补码,&是与,二级制数一一对应,两个同为1则为1

 先分别求出a和b的补码,|是或,二级制数一一对应,两者其中为1则为1

 先分别求出a和b的补码,^是异或,二级制数一一对应,两者相同则为0,相异为1

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

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	printf("%d %d\n", a, b);
    //这是有第三个变量的
	//int tmp = a;
	//a = b;
	//b = tmp;
	//printf("%d %d\n", a, b);
    //这是没有第三方变量

	//a = a + b;        //把a和b的和赋值给a
	//b = a - b;        //此时a=8;b=a-b;b=3
	//a = a - b;        //此时a=8,b=3;a=a-b;a=5
	//printf("%d %d\n", a, b);
    //但这种有缺陷,当a,b的数值过大,导致溢出,就会出现问题
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("%d %d\n", a, b);
    //a=3;  二进制是011
    //b=5;  二级制是101
    //进行异或后就成立了
	return 0;
}

注意:异或操作符只能运用整数里。

 4.赋值操作符

=   赋值操作符可以连续使用,从右向左赋值,但不能在定义变量时对变量进行连续赋值。

复合赋值:

  1. +=

  2. -=

  3. *=

  4. /=

  5. %=

  6. >>=

  7. <<=

  8. &=

  9. |=

  10. ^=

5.单目操作符 

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

sizeof对于变量名可以省略后面的括号,但是对于数据类型不能省。

sizeof括号中放的表达式不参与运算。

EOF-->-1,则~EOF是0

前置++是先++再使用

后置++是先使用再++

 

 6.关系操作符

>
>=
<
<=
!=       用于测试“不相等”
==      用于测试“相等

7.逻辑操作符 

&&       逻辑与
||          逻辑或

逻辑与,逻辑或只用来关心真假,按位与,按位或是用来二进制的计算。

逻辑与是两者都为真才行,逻辑或是有一个成立就可以。

#include <stdio.h>
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	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++是后置,先使用,a++是0,所以为假,后面的不用看,最后打印出a=1,b=2,c=3,d=4。

8.条件操作符 

exp1 ? exp2 : exp3

exp1如果为真,exp2执行

exp1如果为假,exp3执行

9.逗号表达式 

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

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

1.   [ ]    下标引用操作符
操作数:一个数组名 + 一个索引值
 int arr[10];
 arr[9] = 10;
2. ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
int add(int x,int y)
{
  return x+y;
}
int main()
{
int c=add(2,3);
printf("%d",c);
return 0;
}
3. 访问一个结构的成员
   结构体 . 成员名
结构体指针 -> 成员名
 
#include <stdio.h>
struct stu
{
	char name[20];
    char id[20];
	int age;
};
int main()
{
	struct stu b = {"xiaohong","C202115311103",55};
	printf("%s %s %d",b.name,b.id,b.age);
	struct stu * pb=&b;
	printf("%s %s %d",(*pb).name,(*pb).id,(*pb).age); 
	//printf("%s %s %d", pb->name, pb->id, pb->age);
}

二.表达式求值

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

1.隐式类型转换 

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

 怎么整型提升呢?

// 负数的整形提升
char c1 = - 1 ;
变量 c1 的二进制位 ( 补码 ) 中只有 8 个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为 1
提升之后的结果是:
11111111111111111111111111111111
// 正数的整形提升
char c2 = 1 ;
变量 c2 的二进制位 ( 补码 ) 中只有 8 个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为 0
提升之后的结果是:
00000000000000000000000000000001
// 无符号整形提升,高位补 0
#include<stdio.h>
int main()
{
	char a = 3;
	//3的二进制补码是00000000000000000000000000000011
	//提升后00000011
	char b = 127;
	//127的二进制补码00000000000000000000000001111111
	//提升后01111111
	char c = a + b;
	//00000000000000000000000000000011
	//00000000000000000000000001111111
	//00000000000000000000000010000010
	//c提升后10000010
	//因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为1提升之后的结果是:
	//11111111111111111111111110000010
	//11111111111111111111111110000001
	//10000000000000000000000001111110
	//即-126
	printf("%d\n", c);
	return 0;
}

 2.算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为 寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
:但是算术转换要合理,要不然会有一些潜在的问题。

 3.操作符的属性

1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
*两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

优先级: (优先级从上到下依次变小)

操作
名称
用法示例
结合
是否控制求值 顺序
()
聚组
(表达式)
()
函数调用
rexp rexp ...,rexp
从左到右
[ ]
下标引用
rexp[rexp]
.
访问结构成员
lexp.member_name
->
访问结构指针成员
rexp->member_name
++
后缀自增
lexp ++
--
后缀自减
lexp --
!
逻辑反
! rexp
从右向左
~
按位取反
~ rexp
+
单目,表示正值
+ rexp
-
单目,表示负值
- rexp
++
前缀自增
++ lexp
--
前缀自减
-- lexp
*
间接访问
* rexp
&
取地址
&lexp
sizeof
取其长度,以字节表示
sizeof rexp sizeof()
(类 型)
( 类型)
(类型 ) rexp
*
乘法
xp * rexp
从左向右
/
除法
rexp / rexp
%
取余rexp % rexp
+
加法rexp + rexp
-减法rexp - rexp
<<
左移位
rexp << rexp
>>
右移位
rexp >> rexp
>
大于
rexp > rexp
>=
大于等于
rexp >= rexp
<
小于
rexp < rexp
<=
小于等于
rexp <= rexp
==
等于rexp == rexp
!=
不等于rexp != rexp
&
位与
rexp & rexp
^
位异或
rexp ^ rexp
|
位或
rexp | rexp
&&
逻辑与
rexp && rexp
||
逻辑或
rexp || rexp
? :
条件操作符
rexp ? rexp : rexp
=
赋值
lexp = rexp
从右向左
+=
...
lexp += rexp
-=
...
lexp -= rexp
*=
...
lexp *= rexp
/=
...
lexp /= rexp
%=
... 取模
lexp %= rexp
<<=
... 左移
lexp <<= rexp
>>=
... 右移
lexp >>= rexp
&=
...
lexp &= rexp
^=
... 异或
lexp ^= rexp
|=
...
lexp |= rexp
逗号lexp,rexp从左向右

4.一些问题表达式

示例1:

a*b + c*d + e*f
代码 1 在计算的时候,由于 * + 的优先级高,只能保证, * 的计算是比 + 早,但是优先级并不
能决定第三个 * 比第一个 + 早执行。
a*b 
c*d 
a*b + c*d 
e*f 
a*b + c*d + e*f
或者:
a*b 
c*d 
e*f 
a*b + c*d 
a*b + c*d + e*f

 示例2:

c + --c;
同上,操作符的优先级只能决定自减 -- 的运算在 + 的运算的前面,但是我们并没有办法得
知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
如:如果已知c的值为2      先--c得1      2+1=3
如果c的值还没准备好,先--c 得出值    (--c的值)+  --c     比如1+1=2
所以:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

J 2

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

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

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

打赏作者

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

抵扣说明:

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

余额充值