操作符详解

1.操作符的分类

• 算术操作符: + , -  ,  *  ,  / , %

• 移位操作符: << , >>

• 位操作符: & , | ,  ^ ,~

• 赋值操作符: = /  , %  ,  +=  , -= ,  *=  , /= , %= , <<= , >>= ,  &= , |= , ^=

• 单⽬操作符: /= 、 %= 、 >= 、 !、 ++ 、- 、 & 、 * 、 + 、 、 ~ 、 sizeof 、 ( 类型 )

• 关系操作符: >  , >=  , <  , <= , == , !=

• 逻辑操作符:&& , ||

• 条件操作符: ? , :

• 逗号表达式: ,

• 下标引⽤: []

• 函数调⽤: ()

• 结构成员访问: .   ,  ->

2.原码,反码和补码

整数的2进制表示方法有三种,即原码、反码和补码 有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。 符号位都是用表示“正”,用1表示“负”。

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

负整数的三种表示方法各不相同。

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

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码+1就得到补码。

反码通过取反,+1的操作得到其对应的原码;补码符号位不变,其它位取反+1得到原码。补码-1之后再取反(符号位不变)也可以得到原码。

对于整形来说:数据存放内存中其实存放的是补码。

3.移位操作符

<< 左移操作符

>> 右移操作符

 移位操作符的操作数只能是整数。

3.1左移操作符

移位规则:左边抛弃、右边补0

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int n = 10;
	int num = n << 1;
	printf("n = %d\n", n);
	printf("num = %d\n", num);//输出20
	return 0;
}

3.2右移操作符

移位规则:首先右移运算分两种:

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

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

对于移位运算符,不要移动负数位,这个是标准未定义的

4.位操作符:& | ^ ~

位操作符操作的是二进制的位

&  按位与               &&  逻辑与
|  按位或               ||  逻辑或  
^  按位异或  相同位0相异为1  
~  按位取反

一个数与它自己异或得到0;一个数与0异或还是这个数 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int num1 = 3;
	int num2 = -5;
	printf("%d\n", num1 & num2);//3
    //000000000000000000000011  3的补码
    //000000000000000000000101  -5的原码
    //111111111111111111111011  -5的补码
    //000000000000000000000011  补码 因为是正数 同时也是原码
	printf("%d\n", num1 | num2);//-5
    //100000000000000000000011  3的补码
    //111111111111111111111011  -5的补码
    //111111111111111111111011  补码
    //100000000000000000000101  原码
	printf("%d\n", num1 ^ num2);//-8
	printf("%d\n", ~0);//-1

	return 0;

}

5.逗号表达式 

 逗号表达式,就是⽤逗号隔开的多个表达式。逗号表达式从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。

#include <stdio.h>

代码1
int main()
{
	int a = 1;
	int b = 10;
	int c = (a > b, a = b + 1, b = a + 1);
	printf("%d", c);//12
	return 0;
}

代码2
if (a = b + 1, c = a / 2, d > 0)
{
	//
}

代码3
a = get_val();
count_val(a);
while ( a > 0 )
{
	a = get_val();
	count_val(a);
}
//可改写为
while (a = get_val(), count_val(a), a > 0)
{
	//
}

6.结构成员访问操作符 

C语⾔已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类 型还是不够的,假设我想描述学⽣,描述⼀本书,这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要 名字、年龄、学号、⾝⾼、体重等;描述⼀本书需要作者、出版社、定价等。C语⾔为了解决这个问 题,增加了结构体这种⾃定义的数据类型,让程序员可以⾃⼰创造适合的类型。

结构体的关键字:struct

结构体是用来描述一个复杂对象的,它里面可以包含多个对象。

(1)结构的声明

struct tag
 {
 member-list;
 }variable-list;

(2)结构体变量的定义和初始化

#include <stdio.h>
结构体
struct student
{
	char name[20];
	int age;
	int height;
	double weight;
	char id[16];
}s1{"wang",80}, s2, s3;//是结构体变量(全局的)  //可在声明类型的同时定义和初始化变量
struct student s7; //(全局的);

int main()
{
	struct student s4;
	struct student s5 = { "张三",18,180,150,"2310250"};
	struct student s6 = { .age = 15, .height = 170, .id = "2301242"};	可通过”.“点操作符指定初始化变量
    //三个结构体变量(局部的)
    printf("%d %s\n", s5.height, s5.name);
	printf("%d %s\n", s6.height, s6.name);
	printf("%s %d\n", s1.name, s1.age);
	return 0;

}

(3)嵌套访问

#include <stdio.h>
struct s
{
	char a;
	int n;
};
struct b
{
	struct s c;
	char d[10];
	float e;
};
int main()
{
	struct b  b1 = { {'w',10},"hehe",5.2f};
	printf("%c %d %s %.2lf", b1.c.a, b1.c.n, b1.d, b1.e);
	return 0;
}

7.操作符的属性:优先级,结合性

C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。

(1)优先级

优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是 不⼀样的。

(2)结合性

如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执行顺序。⼤部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符( = )。

运算符的优先级顺序很多,下面是部分运算符的优先级顺序(按照优先级从高到低排列),建议大概记住这些操作符的优先级就行,其他操作符在使用的时候查看下⾯表格就可以了。

 圆括号( () ) 自增运算符( ++ )自减运算符(- ) 单⽬目运算符( + 和 ) 乘法( * )除法( / )  加法( + )减法( ) 关系运算符( < 、 > 等)  赋值运算符( = )

由于圆括号的优先级最高,可以使用它改变其他运算符的优先级

参考:https://zh.cppreference.com/w/c/language/operator_precedence 

8.表达式求值

(1)整型提升

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

整型提升的意义:

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

//实例1
#include <stdio.h>
int main()
{
	char a = 3;
	char b = 97;
	char c = a + b;
	printf("%d\n", c);//100
	return 0;
}

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

如何进行整体提升

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

2. 无符号整数提升,高位补0 

 

//常见的编译器中,char就是signed char
//负数的整形提升
 
char c1 = -1;
变量c1的⼆进制位(补码)中只有8个⽐特位:
11111111因为char为有符号的char
所以整形提升的时候,⾼位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111

//正数的整形提升
char c2 = 1;
变量c2的⼆进制位(补码)中只有8个⽐特位:00000001
因为char 为有符号的char
所以整形提升的时候,⾼位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//⽆符号整形提升,⾼位补0 

(2)算术转换 

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

 // long double
 // double
 // float
 // unsigned long int
 // long int
 // unsigned int
 // int

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

(3) 问题表达式解析

//表达式的求值部分由操作符的优先级决定。
 
//表达式1 
a*b + c*d + e*f

表达式1在计算的时候,由于 * 比 + 的优先级高,只能保证, * 的计算是比 + 早,但是优先级并不 能决定第三个 *比第⼀个 + 早执行。所以该表达式就有两种计算顺序。 

//表达式2 
c + --c;

同上,操作符的优先级只能决定自减- 的运算在 + 的运算的前⾯,但是我们并没有办法得知,+操 作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。 

 //表达式3 
int main()
 {
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0;
 }

表达式3在不同编译器中测试结果:非法表达式程序的结果 。

以上表达式在不同编译器中可能会得到不同的计算结果,即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的 计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别负责的表达式。

 

 

 

 

 

 

 

 

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值