操作符Part

只要你愿意 开始总比放弃好。 Roman.

愿我们都有自己的目标并正在为其不懈努力。

---------------------------------------------------------------------------------------

一、操作符分类

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

***************************************************

二、算术操作符

1.包括:

   +   -   *   /   %

 2.对于 % 操作符而言,两边都必须是整数

 3.除了 % 操作符以外, 其他操作符均可作用于整数和浮点数

***************************************************

三、移位操作符

一) <<左移操作符

 1.规则: 左边抛弃,右边补0

 2.其实质:是将二进制补码拿起来向左移动,框不变

 3.原来的数不会改变

二) >>右移操作符

 1.两种右移:

  •  算术右移:右边丢弃,左边补原位符号
  • 逻辑右移:右边丢弃,左边补0

 2.到底是 算术右移 还是 逻辑右移 取决于编译器

 3.注:VS2019是算术右移

三)注意/补充

 1. 移位操作符的操作数只能是整数

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

 3. 注意:移位操作符操作的是二进制位!

  1)补充:

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

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

      负整数的原码、反码、补码是要通过计算获得的

 2)如果整数是有符号数,最高位是符号位(0表示正数 1表示负数)

 3)负整数:

      原码:最高位为1

      反码:原码符号位不变!! 其他位按位取反

      补码:反码+1

 4)整数在内存中存储是以二进制补码形式

 5)补码 ---> 原码:

      ① (补码 - 1)  得到 (反码)--->(反码符号位不变,其他位按位取反) 得到 (原码)

      ② (补码取反)-- 符号位不变,其他位按位取反  --->  再 (+ 1)

 6)打印的是原码,故需要进行转换

 7)注:整型数有4字节,即:32比特位

***************************************************

四、位操作符

 1. 包括有:

   按位与&     按位或|     按位异或^

 2. 注:位操作符的操作数必须是整数

 3. 规则:按二进制位进行,又内存中是以二进制补码进行存储的,所以以二进制补码进行运算

 4.  区分: 按位与& 是双目操作符, 取地址& 是单目操作符

 5. 按位与&: 操作数均为1 才是1

    按位或| : 操作数只要存在1 就是1 

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

   (此处: 1 表示真, 0 表示假)

 6. 实例:不创建临时变量,实现两个数交换

//不创建临时变量,实现两个数交换

#include<stdio.h>

void Swap1(int* a, int* b)
{
	//方法一:使用加法:需要使用地址改变内容
	//该方法会存在一个问题:如果加数过大,相加后会存在溢出现象
	*a = *a + *b;
	*b = *a - *b;
	*a = *a - *b;

}

void Swap2(int a, int b)
{
	//方法二:异或^实现:相同为0 相异为1
    //根据性质:a^a=0   a^0=a

	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
}

int main()
{
	// 定两个数
	int a = 0;
	int b = 0;
	printf("请任意输入两个数:\n");
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(&a, &b);
	printf("交换后(加法传地址):a=%d b=%d\n", a, b);
	Swap2(a, b);
	printf("交换后(异或传原数):a=%d b=%d\n", a, b);
	return 0;
}

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

//求一个整数存储在内存中的二进制中 1 的个数

#include<stdio.h>

int Num1(int a)
{
	
	int i = 0;
	int count = 0;
	for (i = 0; i < 32; i++)
	{
		//错误!!!
		//与0异或(针对二进制):每一位都进行异或--32次 左移操作-针对二进制
		/*if (1 == (a ^ 0))
		{
			count++;
		}
		a <<= 1;*/

		//正确
		//与1(只有最右边是1)按位与(针对二进制):每一位都进行异或--32次 右移操作-针对二进制
		if (1 == ((a >> i) & 1))
		{
			count++;
		}
	}
	return count;
}

int main()
{
	int a = 0;
	printf("请任意输入一个整数:\n");
    scanf("%d", &a);
	int count = Num1(a);
	printf("%d的二进制中1的个数为:\n", a);
	printf("%d\n", count);
	return 0;
}

***************************************************

五、赋值操作符

 1. 复合赋值符:

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

 2.赋值操作符= 可以连续使用, 但是代码可读性较差  

    其顺序为: 从左至右

***************************************************

六、单目操作符

 1. 包括有: 

!逻辑反操作符(一般针对真假)

- 负值          + 正值          &取地址

sizeof  操作数的类型长度(以字节为单位)

~ 对一个数的二进制按位取反

-- 前置、后置              ++ 前置、后置

* 间接访问操作符(解引用操作符  一般与取地址操作符相关时用)

(类型)强制类型转换

 2. 补充:

 C语言中 C99之前没有表示真假的类型, C99中引入了布尔类型

 #include<stdbool.h>

其中库函数定义如下:

#define  bool  _Bool

#define  false  0

#define  true  1

所以  可以在引入头文件后使用如下:

_Bool  flag1 = false;

bool  flag2 = true;

(_Bool  等价于 bool)

 3. 注:

  如有: int arr[10] = {0} ;

  & arr ;   //取出的是数组地址,数组地址应该放到 数组指针 中去

  (注意:此处要记忆 数组名不表示首元素地址的两种情况 -- sizeof(数组名)  &数组名 )

 4. sizeof 是操作符(运算符),不是函数

  • sizeof 变量 , 变量的括号可以省略

     sizeof(类型),类型的括号不可以省略

  • sizeof 是计算类型创建的变量所占内存的大小, 单位是 字节
  • 举例:

 int a = 10;

 short b = 0;

printf (" %d\n" , sizeof (b = a + b));  //此时会发生截断   //输出2(是b 的类型)

printf (" %d\n" , b);  //输出0

说明: sizeof(表达式)中表达式不参与运算

           sizeof 是在编译期间处理的 (test.c -->  编译 --> 链接 --> test.exe)

 5.  ~ 按位取反:所有位(包括符号位!!)都取反, 但是针对的是补码 , 打印的是 原码

 6. 前置 ++ -- : 先++ -- ,后使用

     后置 ++ -- : 先使用,后++ --

如:

int a = 2;

int b = a++;  // 后置:先使用,后++ : b = a ; a =a ++;

int c = ++a; // 前置: 先++, 后使用: a = a++; b = a;

***************************************************

七、关系操作符

 1. 包括有:

>     >=     <      <=    !=     ==

 2. 注意:

  •  == 不能比较两字符串的大小, 比较的是首元素地址
  •  strcmp()专门用来比较字符串的大小: 对应位置上字符的大小,不是比较长度! 逐位比较

 3. 区分:  = 赋值         == 等于

***************************************************

八、逻辑操作符

 1. 包括有:

逻辑与&&         逻辑或||

 2. 注意:

  •  逻辑操作符 只关心真假:非0即真 0即假
  • 逻辑与&& : 两个均为真才真
  • 逻辑或|| : 只要一个真则真
  • 逻辑操作符适用于: 均满足 or 只满足一个条件即可
  • 逻辑与&& :只要左边为0 右边就不进行计算
  • 逻辑或|| : 只要左边为真 右边就不进行计算

 3. 补充:

 不是有字符串就创建存储空间

 如:

char arr [ ] = " abcdef " ;  // 没有创建存储空间

char* p = " abcdef " ;  // 创建了存储空间 指针指向该字符串

***************************************************

九、条件操作符

 exp1 ? exp2 : exp3 ;

 含义: 表达式1 是否满足? 满足则执行表达式2, 否则执行表达式3

***************************************************

十、逗号表达式

 1. 形式:

exp1,exp2,exp3,... ,expN

即:用逗号表达式隔开的多个表达式

 2. 注意:

  逗号表达式 从左至右 执行,整个表达式的结果就是最后一个表达式的结果

(注意:逗号表达式前面表达式会影响后面表达式结果)

***************************************************

十一、下标引用、函数调用和结构成员

一)下标引用操作符[ ]

 1. 用于数组:

     如: arr[4] = 5;

    其操作数:arr (数组名)、 4(下标,索引值)

 2. 注:

arr[4] == *(arr+4) == *(4+arr) == 4[arr]

二)函数调用操作符( )

 1.  函数调用操作符( ): 至少有一个操作数(即:函数名--在不需要传参的情况下)

 2. 如:

int ret = Add(3,5);

// 函数调用操作符,其操作数是: Add(函数名)、 3、5(传递的值)

三)访问一个结构的成员

 1. 类型:

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

 2. 补充:

  • 结构体定义 如下:

struct  Stu 

{

  char name [20];

  int age;

  float score;

}

修改数组内容:

如:

struct Stu s = { "张三" , 20 ,90.5f }

修改: strcpy (s.name, "张三丰" };  // 需要使用头文件 string.h

*(s.name)= "张三丰" ;  // 错误 数组名表示首元素地址,解引用后只是一个字符大小,会溢出

输入:scanf("%s", s.name) ;   // name 是数组名,表示首元素地址,所以不用再取地址

***************************************************

十二、表达式求值

 表达式求值的顺序一部分是由操作符的优先级和结合性决定的

 同样,有些表达式的操作数在求值过程中可能需要转换为其他类型

一)隐式类型转换

 1. C的整型算术运算总是至少以缺省整型类型的精度来进行的

 2. 为了获得这个精度, 表达式中的 字符和短整型操作数 在使用之前被转换为普通整型,这种转换称为 整型提升

 3. 整型提升的意义:

 表达式的整型运算要在CPU相应运算器内执行, CPU 内整型运算器(ALU)的操作数的字节长度一般就是 int 的字节长度,同时也是 CPU 的通用寄存器长度。

 因此:即使两个 char 类型的相加, 在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。

通用 CPU 是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。

所以: 表达式中各种长度可能小于 int 长度的整型值,都必须先转换为 int 或 unsigned int , 然后才能送入 CPU 去执行运算。

 4. 如何进行整型提升?

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

  •  正数、负数的整型提升:

 如:

 char c ;

 变量 c 的二进制位(补码)中只有 8 个比特位

 又 char 为有符号的 char

 所以,整型提升(提升到32位)的时候,高位补充符号位(即:8个比特位的最高位

 (正数0   负数1)

  • 无符号的整型提升:高位补0

 5. 注意:

  •  char 型 也是整型
  • char 到底是 signed char 还是 unsigned char 取决于编译器, 不过绝大多数是 signed char
  • 其余, 如 short 、int 是 signed ,即:有符号

 6. 实例:

 1) 整型存入字符型, 放不下就会发生截断

//截断

#include<stdio.h>
int main()
{
	//整型放入字符型:截断

	char c1 = 3; 
	// 二进制:原反补码:00000000000000000000000000000011
	// char 类型只有8个比特,发生截断:00000011--补码

	char c2 = 127;
	// 二进制:原反补码:00000000000000000000000001111111
	// char 类型只有8个比特,发生截断:01111111--补码

	char c3 = c1 + c2;
	//先进行整型提升: 有符号--按最高位进行提升
	// c1: 00000000000000000000000000000011 (补码)
	// c2: 00000000000000000000000001111111 (补码)
	// c3: 00000000000000000000000010000010 (补码)
	// 截断: c3-- 10000010 (补码)

	printf("%d\n", c3);  //整型提升
	//整型提升:c3 --11111111111111111111111110000010 (补码)
	// c3 反码(补码-1): 11111111111111111111111110000001
	// c3 原码: 100000000000000000000000001111110
	//所以:打印原码:c3-- -126

	return 0;
}

  2)整型提升实例1

#include<stdio.h>
int main()
{
	char a = 0xb6;   
	//十六进制,但是 char 是8比特:a: 1011 0110 

	short b = 0xb600;
	// 十六进制,但是 short 是16比特:b:1011 0110 0000 0000 

	int c = 0xb6000000;
	// int 是 32比特 : 1011 0110 0000 0000 0000 0000 0000 0000 

	if (a == 0xb6) 
		//注意:此时a 是char型, 0xb6是十六进制整型
		// a 进行整型提升:按符号位 11111111 11111111 11111111 1011 0110
		printf("a");

	if (b == 0xb600)
		//注意:此时b 是short型, 0xb6是十六进制整型
		// b 进行整型提升:按符号位 11111111 11111111 1011 0110 0000 0000 
		printf("b");

	if (c == 0xb6000000) 
		//二者均是整型:无需整型提升,相等
		//输出
		printf("c");

	return 0;
}

   3)整型提升实例2

#include<stdio.h>
int main()
{
	char c = 1;
	//截断:00000001
	
	printf("%u\n", sizeof(c));
	//char型 1字节

	printf("%u\n", sizeof(+c));
	//正负运算后: 4字节--进行整型提升

	printf("%u\n", sizeof(-c));
	//正负运算后: 4字节--进行整型提升

	return 0;
}

二)算术转换

 1. 如果某个操作符的各个操作数属于不同类型, 那么除非其中一个操作数转换为另一个操作数类型, 否则操作就无法进行。

下面的层次体系称为寻常算术转换。

long  double

double

float

unsigned long int

long int

unsigned int

int 

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

 2. 算术转换 用于 大于等于 int型

 3. 警告: 算术转换要合理,否则会存在潜在问题

    如: float 类型 (高)转换为 int 型 (低),会丢失精度

三)操作符的属性

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

 ① 操作符的优先级(相邻!!的两个操作符之间)

  附上 :

  操作符优先级表格(从上到下优先级逐渐降低     N/A表示无结合性)

操作符优先级
操作符描述用法实例结果类型结合性是否控制求值顺序
( )聚组(表达式)与表达式相同N/A          否
( )函数调用rexp(rexp,... ,rexp)rexpL-R          否
  [ ]下标引用rexp [rexp]lexpL-R          否
  .
访问结构成员
lexp.member_name
lexp
L-R          否
->
访问结构指针成员
rexp->member_name
lexp
L-R          否
++
后缀自增
lexp ++
rexpL-R          否
--后缀自减lexp --rexpL-R          否
逻辑反
! rexp
rexpR-L          否
~按位取反~rexprexpR-L          否
+单目,表示正值+rexprexpR-L          否
-单目,表示负值-rexprexpR-L          否
++前缀自增++lexprexpR-L          否
--前缀自减--lexprexpR-L          否
*间接访问*rexprexpR-L          否
&取地址&lexprexpR-L          否
sizeof取其长度,以字节表示sizeof rexp sizeof(类型)rexpR-L          否
(类型)类型转换(类型)rexprexpR-L          否
*乘法rexp * rexprexpL-R          否
/除法rexp / rexprexpL-R          否
%
整数取余rexp % rexprexpL-R          否
+加法rexp + rexprexpL-R          否
-减法rexp - rexprexpL-R          否
<<左移位rexp << rexprexpL-R          否
>>右移位rexp >> rexprexpL-R          否
>大于rexp > rexprexpL-R          否
>=大于等于rexp >= rexprexpL-R          否
<小于rexp < rexprexpL-R          否
<=小于等于rexp <= rexprexpL-R          否
==等于rexp == rexprexpL-R          否
!=不等于rexp != rexprexpL-R          否
&位与rexp & rexprexpL-R          否
^位异或rexp ^ rexprexpL-R          否
|位或rexp | rexprexpL-R          否
&&逻辑与rexp && rexprexpL-R          是
||逻辑或rexp || rexprexpL-R          是
? :条件操作符
rexp ? rexp : rexp
rexpN/A          是
=赋值
lexp = rexp
rexpR-L          否
+=以...加
lexp += rexp
rexpR-L          否
-=以...减
lexp -= rexp
rexpR-L          否
*=以...乘
lexp *= rexp
rexpR-L          否
/=以...除
lexp /= rexp
rexpR-L          否
%=以...取模
lexp %= rexp
rexpR-L          否
<<=以...左移
lexp <<= rexp
rexpR-L          否
>>=以...右移
lexp >>= rexp
rexpR-L          否
&=以...与
lexp &= rexp
rexpR-L          否
^=以...异或
lexp ^= rexp
rexpR-L          否
|=以...或
lexp |= rexp
rexpR-L          否
,逗号
rexp rexp
rexpL-R          是

 ②操作符的结合性:优先级相同的情况下

 ③是否控制求值顺序: 逻辑与&&   逻辑或||     三目操作符? :      逗号表达式,  (控制)

 2. 注:

表达式的运算顺序无法唯一确定

若无法唯一确定计算表路径,错误

如1:

// 表达式的求值部分由操作符的优先级(相邻两个操作符)决定。
a * b + c * d + e * f
// 注释:代码 在计算的时候,由于 * + 的优先级高,只能保证, * 的计算是比 + 早,但是优先级并不能决定第三个* 比第一个 + 早执行。
如2:
int fun ()
{
    static int count = 1 ;  // 静态变量不销毁,记忆性
    return ++ count ;
}
int main ()
{
    int answer ;
    answer = fun () - fun () * fun (); //调用顺序未知
    printf ( "%d\n" , answer ); // 输出多少?
    return 0 ;
}
//注释:该代码同样有问题:
// 静态变量不销毁,存在记忆性,而函数的调用顺序未知
 3. 总结:
 写出的表达式如果不能 通过操作符的属性确定唯一计算路径, 那该表达式就是存在问题的。
-------------------------一个人所有的愤怒都来源于对自己无能的痛苦。----------------------------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

'Dream是普通小孩耶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值