操作符详解


操作符的分类:
算术操作符:+ 、- 、* 、/ 、%
移位操作符: << >>
位操作符: & | ^ ~
赋值操作符: = 、+= 、-= 、 = 、/= 、%= 、<<= 、>>= 、&=、|=、^=
单⽬操作符:!、++、–、&、
、+、、~ 、sizeof(类型)
关系操作符: > 、>= 、< 、<= 、== 、 !=
逻辑操作符:&& 、||
条件操作符:? :
逗号表达式:,
下标引⽤:[]
函数调用:()

一.进制转换

我们常说的有二进制,八进制,十进制,十六进制,它们其实都是数值的不同表示形式而已,本篇文章着重介绍二进制数

15的二进制:1111
15的八进制:17
15的十进制:15
15的十六进制:F

首先介绍十进制数:

  1. 每一位都是0~9
  2. 满10进1

因此二进制数:

  1. 每一位都是0~1
  2. 满2进1

八进制数:

  1. 每一位都是0~7
  2. 满8进1

十六进制数:

  1. 每一位都是0~F(0 ~ 9 ,10用A表示,11用B表示,直到15用F表示)
  2. 满16进1

问:十进制数123为什么等于123呢?
其实十进制数每一位都是有权重的,十进制数从右向左依次是个位,十位,百位…依分别对应的权重是100,101,102
在这里插入图片描述

1.二进制与十进制的互换

1.1十进制数转换为二进制数

第一种方法就是除2取余法(最后得到的余数从下往上取)
如:10的二进制为1010在这里插入图片描述

第二种方法
把数拆分成几个2的次方数的和,如下图:权重23就在第四个位置放1(从右往左数),权重21就在第二个位置放1,其余放0在这里插入图片描述

2.2二进制数转换成十进制数
在这里插入图片描述

2.二进制与八进制和十六进制的转换

2.1二进制转换成十六进制
在这里插入图片描述

2.2十六进制转换成二进制
在这里插入图片描述

2.2二进制转换成八进制
在这里插入图片描述

二.原码,反码,补码

整数的二进制表示方法有3种:原码,反码,补码
有符号整数的表示方法由符号位和数值位构成,二进制中最高位是符号位,其余都是数值位
若是正数,符号位用0表示;若是负数,符号位用1表示

正数的原码,反码,补码都相同
负数的反码由原码按位取反(若是0,换成1;若是1,换成0)得到
补码由反码加1得到
补码也可以取反,加1得到原码

byte是字节,1字节等于8比特位,也就是8个二进制位
1byte = 8bit
1kb = 1024byte
1mb = 1024kb

这里就不介绍正数的原,反,补了

在这里插入图片描述

三.移位操作符

计算机内存中存放的是补码,也是用补码用来计算的

移位操作符的操作数必须是整数

1.左移操作符

左边丢弃,右边补0

在这里插入图片描述
在这里插入图片描述

这里得到了num左移1位后的补码,然后在把它转换成原码(这里我就不写原码了)就得到了结果r = 20

2.右移操作符

右移操作符有2种移位规则:
1.逻辑右移:左边补0,右边丢弃
2.算术右移:左边用原值的符号位填充,右边丢弃

这里讲一下算术右移(我用的是vs2022,执行的是算术右移)在这里插入图片描述
在这里插入图片描述

四.位操作符

位操作符的操作数必须是整数

位操作符有:

  1. &:按位与 全1则1
  2. |:按位或 有1则1
  3. ^:按位异或 相同为0,相异位1
  4. ~:按位取反

1.按位与&

计算 3 & -5
在这里插入图片描述

两个都是1的时候则是1
在这里插入图片描述

2.按位或|

计算3 | -5
在这里插入图片描述

两个中有1 则是1在这里插入图片描述

3.按位异或^

在这里插入图片描述

两个相同则为0,不同则为1在这里插入图片描述

4.按位取反~

在这里插入图片描述

按位取反就是0与1互换在这里插入图片描述

五.位操作符的实践

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

法一:
在这里插入图片描述

然而这种方法是有风险的,如果a和b的值很大,a+b就可能会溢出

法二:

a ^ a = 0;
a ^ 0 = a;
3 ^ 5 ^ 3 = 3 ^ 3 ^ 5 = 5   //异或支持交换律

因此代码如下:
在这里插入图片描述

在这里插入图片描述

题目二:找出单身狗问题,有一组数,只有一个数字出现1次,其它数字都是出现2次,请找出出现1次的数字

我们也可以用^来实现,假如这组数字为:1 2 3 4 5 1 2 3 4

把所有的数字都异或就能得到5了
即 1^2^3^4^5^1^2^3^4

题目三:求⼀个整数存储在内存中的⼆进制中1的个数。(即补码中1的个数)

int main()
{
	int num = 0;
	scanf("%d", &num);
	int i = 0;
	int count = 0; //计数
	for (i = 0; i < 32; i++) //有32位,所以要执行32次
	{
		if (((num >> i) & 1) == 1)
		{
			count++;
		}
	}
	printf("%d\n", count);
	return 0;
}

在这里插入图片描述

我们采用 num&1 来实现,若num的最低位是1,count++
如果num的最低位是1,num&1的结果就是1;如果num的最低位是0,num&1的结果就是0,因此结果是1的话我们的count就加1,然后右移1位,把第二位移到最低位,再次&1,判断结果是否等于1,然后右移2位,把第三位移到最低位

上述代码虽能实现,但每次都要执行32次,效率比较低

优化后的代码:(有几个1就执行几次)

int main()
{
	int num = 0;
	scanf("%d", &num);
	int count = 0; //计数
	while (num)
	{
		num = num & (num - 1);
		count++;
	}
	printf("%d\n", count);
	return 0;
}

在这里插入图片描述

根据图中可以看到,每执行一次n = n & (n-1) 就去掉了低位的1,去掉后我们就count++

六.逗号表达式

exp1,exp2,exp3,.....expN

逗号表达式就是用逗号隔开的多个表达式
计算规则是:从左往右依次执行,最后一个表达式的结果就是整个表达式的结果

int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1); //c=13

c的值就是b=a+1的结果,从左往右依次计算,其中a=b+10使a的值为12了,所以最后的表达式的结果就为13

七.下标访问[]与函数调用()

1.下标引用操作符[]

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

2.函数调用操作符()

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

八.操作符的属性:优先级和结合性

1.优先级

a + b * c;

上述示例中,有加法和乘法两个操作符,根据优先级乘法高于加法,因此先计算b * c

2.结合性

当优先级相同时就需要考虑结合性来决定计算的先后顺序了,操作符的结合性又分为左结合(从左往右执行)和右结合(从右往左执行),大部分都是左结合。

a * b / c;

上述示例中,乘法和除法优先级相同,两者都是左结合操作符,因此先计算a * b

我们可以把想要先计算的放在()里面,就像数学那样有括号的先计算括号里面的。

下面附一张图:
在这里插入图片描述

九.表达式求值

1.整型提升

计算机中执行运算的操作数至少是int,凡是小于int类型的都要转换,这就是整型提升。如:char,short类型

char a = -10;
char b = 120;
char c = a + b;
printf("%d",c);

上述示例中,你想的是打印出a+b的值,结果是110,对吧?

因为a和b都是char类型,所以需要整型提升 (1个字节提升为4个字节)
那又如何实现呢?
1.有符号整数的提升是按照其变量的数据类型的符号位来提升的,符号位是0,高位就补0;符号位是1,高位就补1
2.无符号整数的提升,高位补0

下面用图来说明上述打印的结果:
在这里插入图片描述
整型提升后的a+b:
在这里插入图片描述
最后打印:
在这里插入图片描述

最后打印的时候是以%d的形式打印的,而c是char类型,因此还要发生一次整型提升,最终打印出110。不要认为是直接相加的值,看下面再举个例子

注:下图把a的值改为10,结果就不是130了

在这里插入图片描述

2.算术转换

某个操作符的操作数属于不同类型,那么除非其中一个操作数的类型转换成另一个操作数的类型,否则无法进行计算。

举个简单的例子:
int a = 1;
float b = 2.1;
a+b;
//a会转换成float类型的在进行计算

以下是转换的优先级 从高到低 低类型转换成高类型
long double
double
float
unsigned long int
long int
unsigned int
int

十.问题表达式

a*b + c*d + e*f

我们想到的肯定都是先计算乘法,然后从左往右计算加法。但计算机的执行顺序就不唯一了
第一种顺序:
在这里插入图片描述
第二种顺序:
在这里插入图片描述

c + --c;

上述示例中,第一个c是有歧义的。- -的优先级高于+,但是我们不知道+的左操作数是–c之后的c,还是没有–后的c。

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

在这里插入图片描述

 #include <stdio.h>
 int fun()
 {
 	static int count = 1;
 	return ++count;
 }
 int main()
 {
 	int answer;
 	answer = fun() - fun() * fun();
 	printf( "%d\n", answer);//输出多少?
	return 0;
}

上述示例中,我们只能得知操作符*和-的运算顺序,但是3个fun()的执行顺序是不清楚的。

#include <stdio.h>
 int main()
 {
 	int i = 1;
 	int ret = (++i) + (++i) + (++i);
 	printf("%d\n", ret);
 	printf("%d\n", i);
 	return 0;
 }

上述示例和前面第一个列举的类似,第一个+与第三个++的执行顺序不清楚。
在这里插入图片描述

执行第一步,++i后i等于2,执行第二步,++i后i等于3,执行第三步,即3+3,执行第四步,++i后i等于4,执行第五步,3+3+4,结果为10

在这里插入图片描述

执行第一步,++i后i等于2,执行第二步,++i后i等于3,执行第三步,++i后i等于4,执行第四步,4+4,执行第五步,4+4+4,结果为12

建议:不要写太冗长的表达式

资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 在ASP.NET开发中,定时任务是一种常见功能,用于在固定时间间隔内执行特定操作,比如数据同步、清理缓存或发送通知等。以下是实现ASP.NET定时任务的详细步骤和关键要点: ASP.NET定时任务通常通过System.Threading.Timer或System.Timers.Timer实现,二者都能周期性触发事件。在ASP.NET中,可以利用后台线程或HttpApplication生命周期事件来启动定时器。 System.Threading.Timer:适合在独立线程上运行任务,避免阻塞主线程,适合轻量级任务。 System.Timers.Timer:在多线程环境下,它会自动管理线程,更适合服务器端复杂任务。 创建定时器对象,设置Interval属性为10000毫秒(10秒),并注册Elapsed事件。该事件会在每个时间间隔结束时触发。 在Elapsed事件中编写要执行的代码,确保代码执行效率高,避免阻塞,因为长时间运行的任务可能影响其他请求。 通过Timer.Start()启动定时器,Timer.Stop()停止定时器。在ASP.NET中,可以在Application_Start和Application_End事件中控制定时器的启动和停止,确保服务器启动时定时器开始运行,关闭时停止。 在多用户环境下,如果定时任务会修改共享状态,必须考虑线程安全问题,可以使用锁或其他同步机制来确保数据一致性。 将应用程序部署到IIS时,需设置应用程序池的回收策略,避免定时任务因应用程序回收而中断。同时,确保IIS配置支持长时间运行的请求。 为定时任务添加日志记录非常重要,可以帮助排查问题并监控任务执行情况。 定时任务过于频繁可能会影响服务器性能,进而影响其他请求的响应时间。可根据需求调整时间间隔,或
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值