栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

<C>.操作符&&数据类型

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

<C>.操作符&&数据类型

 1.算术操作符:+ - * / %

一.
’/‘ 两端都是整数的话,执行的都是整数除法
所以float a=3/2,结果也是1
必须写成float a=3/2.0,才是1.5
必须保证/两边要有一个是浮点数,才会计算小数部分

二.
’%‘取模,得到的是余数。
取模操作符无论是左边还是右边的操作数都必须是整形
当9%10,他的余数的范围就是0-9,因为余数是不可能大于被除数的。 


整数的二进制表示方式有3种形式:原码,反码,补码

在有符号的整数下,最高位是符号位,0为正数,1为负数
正数的原码,反码,补码相同
负数的反码是:符号位不变,其他的按位取反
负数的补码是:反码+1
计算机在内存中,是以补码储存的,使用时会自己转成原码。

原码和补码直接如何快速转换?

法一:直接补码-1,然后符号位不变,其他按位取反
法二:补码再次取反,然后+1,得到原码

 2.移位操作符

<< 左移操作符
>> 右移操作符
操作的是补码,且,移位操作符的操作数只能是整数。

一.左移操作符

左移操作符,直接整体左移,多出去的直接丢掉,右边剩下的补0
当数是负数时,需要转成补码进行操作,输出时要变回原码进行输出
观察可得左移操作符有一种*2的效果。

二.右移操作符

算数右移:右边丢弃,左边空出的补原符号位
逻辑右移:右边丢弃,左边一律补0

对于正数而言,左边补啥都无所谓,负数稍微有点区别。
左边一般来说都是补符号位,当然不排除补0的,主要取决于编译器。

对于移位运算符,不要移动负数位,这个是标准未定义的,例如:num>>-1; 就是错的


位操作符

&
|
^
//按(二进制)位与:只要对应的二进制位,只要有0,那么与的结果就是0。两个同时为1的时候,才为1
//按(二进制)位或;只要对应的二进制位,只要有1,那么就是1。只有两个同时为0,那么才是0
//按(二进制)位异或:只要对应的二进制位,相同为0,相异为1

注:他们的操作数必须是整数。

注意:
    &a 是取地址a,是单目操作符
    a&b是 a按位与b,是双目操作符

1.&

int main()
{
	int a = 3;
	int b = -5;
	int c = a & b;//只有对应的二进制位,只有有0,那么与的结果就是0。两个同时为1的时候,才为1,
//	00000000000000000000000000000011 3的补码
//	10000000000000000000000000000101 -5的原码
//	11111111111111111111111111111010 -5的反码
//	11111111111111111111111111111011 -5的补码

	//	00000000000000000000000000000011 3的补码
	//	11111111111111111111111111111011 -5的补码
	//	00000000000000000000000000000011 -a&b的结果(补码)
	//最高位是0,0为正数,所以源码和补码一致,结果就是3

	return 0;
}

2.|

int main()
{
	int a = 3;
	int b = -5;
	int c = a | b;//只要对应的二进制位,只要有1,那么就是1。只有两个同时为0,那么才是0
//	00000000000000000000000000000011 3的补码
//	10000000000000000000000000000101 -5的原码
//	11111111111111111111111111111010 -5的反码
//	11111111111111111111111111111011 -5的补码

	//	00000000000000000000000000000011 3的补码
	//	11111111111111111111111111111011 -5的补码
	//	11111111111111111111111111111011 -a|b的结果(补码)
	
	//所以结果是-5

	return 0;
}

3.^ 异或操作符

int main()
{
	int a = 3;
	int b = -5;
	int c = a ^ b;//只要对应的二进制位,相同为0,相异为1
//	00000000000000000000000000000011 3的补码
//	10000000000000000000000000000101 -5的原码
//	11111111111111111111111111111010 -5的反码
//	11111111111111111111111111111011 -5的补码

	//	00000000000000000000000000000011 3的补码
	//	11111111111111111111111111111011 -5的补码
	//	11111111111111111111111111111000 -a^b的结果(补码)
	//	11111111111111111111111111110111
	//	10000000000000000000000000001000  原码 答案是-8

	return 0;
}

a^a 是什么?对应的二进制数完全相同,所以a^a=0

0^a的结果是什么?其实就是它本身,所以0^a=a

比如说在我一堆成对出现的数字,让我找出只出现一个数字的题的情况下,我就可以用^操作符

例如1 1 3 3 2 2 5 4 4 6 6:我只要把这些数字全部^在一起, 只出现一次的数就找到了

 例2:运用^实现两个数的交换

a=a^b相当于一把钥匙, a^b再^b,我就拿到了a。a^b^a,我就拿到了b。

但是这种算法,只能用于整数,小数就不行了。所以推荐使用第一种


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

int main()
{
	int input = 0;
	scanf("%d", &input);
	int i = 0;
	int count = 0;
	for (i = 0; i < 32; i++)
	{
		if (((input >> i) & 1)== 1)
		{
			count++;
		}
	}
	printf("%d", count);
	return 0;
}

赋值操作符

‘=’ 赋值操作符

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。

当然,赋值操作符是可以连等的。

赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值

他是先执行y+1,把值赋给x,再把x的值赋给a;他的可读性是非常低的,不建议这样写。最好能拆就拆了写

x=y+1;
a=x;

复合赋值符

类似于 a += 2; →a=a+2
a=a>>1;→ a>>=1;
a=a&4;→ a&=4
…………


单目操作符

!
-
+
&
sizeof
~
--
++

*

(类型)

逻辑反操作
负值
正值
取地址
操作数的类型长度(以字节为单位)
对一个数的二进制按位取反
前置、后置--
前置、后置++

间接访问操作符(解引用操作符)

强制类型转换

 !逻辑反操作符:真变成假,假变成真

0表示假,非0表示真

把一个假的事情,当作真的,要做什么事情

C语言中0表示假,非0表示真
	int num = 10;
	if (num)
	{

	}
	if (!num)//num为假,做事情
	{

	}

在C99的标准中加入了布尔类型

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

#include 

int main()
{
	_Bool flag1 = false;
	bool flag2 = true;

	if (flag2)
	{
		printf("hehen");
	}

int main()
{
	//& 取地址操作符
	//* 解引用操作符(间接访问操作符)
	
	int a = 10;
	int* pa = &a;
	*pa = 20;//* - 解引用操作符
	//
	//*&a ==> a;
	//
	int arr[10] = {0};
	&arr;//取出数组的地址,数组的地址应该放到【数组指针】中去

	struct S s = {0};
	struct S* ps = &s;
	
	return 0;
}

sizeof操作符,是计算类型创建的变量所占的大小,单位是字节

sizeof是一个操作符,他不是函数。

	printf("%dn", sizeof a);//括号是可以去掉的,所以不是函数
	printf("%dn", sizeof(a));
	printf("%dn", sizeof(int));  //当是类型时,括号不能去掉

接下来看这道题

int main()
{

	int a = 10;
	short s = 0;
	printf("%dn", sizeof(s = a + 2));
	printf("%dn", s);                
	return 0;
}
	

结果是 2和0,为什么不是2和12呢 

因为sizeof括号里的表达式是不参与计算的,sizeof在编译期间,就已经把表达式给处理掉了

 

 sizeof与数组

#include 

void test1(int arr[])
{
printf("%dn", sizeof(arr));
}

void test2(char ch[])
{
printf("%dn", sizeof(ch));
}

int main()
{
    int arr[10] = {0};
    char ch[10] = {0};

    printf("%dn", sizeof(arr));//40
    printf("%dn", sizeof(ch));//10

    test1(arr);
    test2(ch);
return 0;
}
问:
(1)、(2)两个地方分别输出多少?
(3)、(4)两个地方分别输出多少?

我传过去的test 1 test 2 的结果是什么呢?

4和1吗?错

其实都是4/8 和4/8

我数组传参传的都是地址,只要是地址,那么大小就是4/8,可不能看贬了


~按位取反操作符

int main()
{
	int a = 0;
	a = ~a;
	printf("%d", a);
	return 0;
}

 结果是什么? 是-1

00000000000000000000000000000000
11111111111111111111111111111111 - 补码
11111111111111111111111111111110
10000000000000000000000000000001 -> -1

按位或 | 操作符;只要对应的二进制位,只要有1,那么就是1。只有两个同时为0,那么才是0

我们来看这道题:

int a=11
11的二进制数是:00000000000000000000000001011
我如何把倒数第三位 0 变成1呢?             ↑
我直接在                               这个位上或一个1,就是1了,其他位全部或0
就相当于按位或上个000000000000000000000000100
怎么得到这个数呢?
直接1 向左移动两位其实就是了,然后再或上去
a |=(1<<2)   //结果是15


如何改回来呢?
在这个位与上一个0就可以了,其他位必须与1
所以与上11111111111111111111111111111011这样的数字
如何得到呢?
就是刚刚那数取反就可以了
a &=(~(1<<2)) //结果是11

为什么while(~scanf("%d“,&a))可以终止循环?

因为scanf在读取失败时,返回EOF,EOF其实就是-1,对-1进行按位取反时,就是0


"++" "--"操作符

int a=3;
int b=++a  //前置++  →先++,后使用 → a=a+1 b=a  

int b=a++   //后置++ →先使用,后+ →b=a,a=a+1

-- 如出一辙

强制类型转换()

eg:int a=(int)3.14;


关系操作符:
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”

注意 ==不能比较字符串。语法写成if(“abcde”==“aee”)是没有问题,但是比较的其实是两个字符串首个字符的地址。地址绝对不可能相等的,所以没啥用。要想比较两个字符串得用strcmp

strcmp是比较两个字符串的大小,对应位置上字符(ASCII)的大小,不是比较长度


如果写成 char arr[ ]="abcdef" →是拿这样的常量字符串去初始化了我这个数组,这个常量字符串实际上是不存在的。

但是写成 char*p=”abcdef“ →这样的话这个常量字符串是存在的,我这个p变量是指向了他


逻辑操作符

&&逻辑与

||逻辑或

我们来看这道题:

#include 
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;

//i = a++||++b||d++;

printf("a = %dn b = %dn c = %dnd = %dn", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
&&→结果是1 2 3 4
||→结果是

对于&&操作符而言,左边如果是假,我右边就不用算了
所以,a++是后置++,我先使用再++,a本来是0,i=a++的结果就是0,所以左边是假,右边不用算了。所以后面都没有动,唯一只有a用完了后自增了一下,所以结果是1234

 对于||操作符也一样,如果左边是真,那么右边就不重要了

倘若把a变成1,&&的结果就是2 3 3 5

 如果是||操作符,||的结果就是2234

 如果a又变成0呢?结果就是1334


条件操作符:表达式1?表达式2:表达式3
eg: a>b?a:b


逗号表达式:用逗号隔开的多个表达式,从左到右依次执行,整个表达式的结果是最后一个表达式的结果

我们来看这道题:

int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少

结果是13

 这道题呢

if (a =b + 1, c=a / 2, d > 0)

其实最后只不过是拿d>0这个表达式进行判断

用逗号表达式我也可以给代码进行一些优化

a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}

如果使用逗号表达式,改写:

while (a = get_val(), count_val(a), a>0)
{
//业务处理
}

这样写就不会太冗余

1. [ ] 下标引用操作符

arr和[ ]里面的数就是两个操作数而已,[ ]就是一个操作符,类似于+-这样的操作符
所以arr[4]其实也可以写成 4[arr],完全没有任何问题
编译器碰到arr[4]也会编译成→*(arr+4)

 2. ( ) 函数调用操作符

Add(a,b),操作数是Add,a,b,但是这里的ab和add就不会换位置。函数调用操作符的操作数至少是一个。


3. 访问一个结构的成员

.
->
结构体.成员名
结构体指针->成员名
#include 

struct Stu
{
	char name[20];
	int age;
	float score;
};
void print1(struct Stu ss)
{
	printf("%s %d %fn", ss.name, ss.age, ss.score);
}

//结构体变量.成员名

void print2(struct Stu* ps)
{
	//printf("%s %d %fn", (*ps).name, (*ps).age, (*ps).score);
	printf("%s %d %fn", ps->name, ps->age, ps->score);
}
//结构体指针->成员名

int main()
{
	struct Stu s = {"张三", 20, 90.5f};
	strcpy(s.name, "张三丰");
	//scanf("%s", s.name);
 // *(s.name)  = "张三丰"// err 只能访问一个字符 因为他的类型是char*
 //   
 //
	//print1(s);
	print2(&s);
	return 0;
}

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

12.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

来看这道题:

int main()
{
	char c1 = 3;
	char c2 = 127;
	char c3 = c1 + c2;
    printf("%d",c3);
    return 0;
}

结果是什么?

3→00000000000000000000000000000011
放到char c1里就要截断:00000011

127→00000000000000000000000001111111
放到char c2里就要截断:01111111

当c1+c2时,他们是char类型,但是cpu难以直接实现两个8比特字节直接相加运算,所以必须整型提升为int类型

00000000000000000000000000000011
00000000000000000000000001111111
结果→00000000000000000000000010000010

c3里存着就是10000010这个值。

最后打印又是以%d的形式打印,所以又要进行整型提升

11111111111111111111111110000010→补码
10000000000000000000000001111110→源码

所以结果最后是-126

由此也可以看出整型提升是存在的,只要他参与表达式计算,就会整形提升:


算术转换

当类型>=Int时,就会进行算术转换
long double         ↑
double                 ↑
float                     ↑
unsigned long  ↑
long                 ↑
unsigned int        ↑
int                        ↑

int见到unsigned int类型进行计算时,int会变成unsigned int,这样向上转换的(隐式)

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。但是算术转换要合理,要不然会有一些潜在的问题。
例如:
float f = 3.14;
int num = f; //隐式转换,会有精度丢失


 操作符的属性

复杂表达式的求值有三个影响的因素。
1. 操作符的优先级:相邻的两个操作符才有优先级
2. 操作符的结合性:相邻的两个操作符优先级一样,先算哪个?
3. 是否控制求值顺序:例如&&操作符,左边为假,右边不算了
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
优先级由高到低增长,结合性L-R→从左到右结合,R-L同理

操作
描述用法示例结果类 型结合 性是否控制求值 顺序
()聚组(表达式)与表达
式同
N/A
()函数调用rexp(rexp,...,rexp)rexpL-R
 [ ]下标引用rexp[rexp]lexpL-R
.访问结构成员lexp.member_namelexpL-R
->访问结构指针成员rexp->member_namelexpL-R
++后缀自增lexp ++rexpL-R
--后缀自减lexp --rexpL-R
!逻辑反! rexprexpR-L
~按位取反~ rexprexpR-L
+单目,表示正值+ rexprexpR-L
-单目,表示负值- rexprexpR-L
++前缀自增++ lexprexpR-L
--前缀自减-- lexprexpR-L
*间接访问* rexplexpR-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 : rexprexpN/A
=赋值lexp = rexprexpR-L
+=以...加lexp += rexprexpR-L
-=以...减lexp -= rexprexpR-L
*=以...乘lexp *= rexprexpR-L
/=以...除lexp /= rexprexpR-L
%=以...取模lexp %= rexprexpR-L
<<=以...左移lexp <<= rexprexpR-L
>>=以...右移lexp >>= rexprexpR-L
&=以...与lexp &= rexprexpR-L
^=以...异或lexp ^= rexprexpR-L
|=以...或lexp |= rexprexpR-L
逗号rexp,rexprexpL-R

计算机最重要的是调用顺序,而不是计算顺序

我们来看这个式子,有什么问题?

1.a*b + c*d + e*f

我们唯一能确定的是先乘后加,但是我们并不能确定,先a*b,再c*d,然后两个相加,再算e*f,
还是我们三个都先乘,然后再一起加,这个顺序我们无法确定

倘若我们只是计算值,这些顺序对于我没什么影响
要是这里的abcdef,分别代表的是表达式的话,那我的执行顺序,调用顺序,就会对我最后的结果造成非常大的影响。

我们再来看这个式子

2. c + --c 

这个式子我们从优先级判断肯定可以看出是先--,然后再+

但是我们无法确定寄存器先储存的是什么,+号前面的c什么时候准备好的,我们无法确定。如果c=2,寄存器先处理了c,再处理--c,再相加,结果就是2+1=3

如果我先处理--c,再处理c,再相加,结果就是1+1=2。我们无法确定计算机先调用什么,所以这种代码就是问题代码

 3.我们再来看这个式子:

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

以下是不同编译器下的结果:

编译器
—128Tandy 6000 Xenix 3.2
—95Think C 5.02(Macintosh)
—86IBM PowerPC AIX 3.2.5
—85Sun Sparc cc(K&C编译器)
—63gcc,HP_UX 9.0,Power C 2.0.0
4Sun Sparc acc(K&C编译器)
21Turbo C/C++ 4.5
22FreeBSD 2.1 R
30Dec Alpha OSF1 2.0
36Dec VAX/VMS
42Microsoft C 5.1

连编译器都无法给出确定的值,这就是错误代码

4.我们再来看最后一个代码

int fun()
{
    static int count = 1;
    return ++count;
}
int main()
{
    int answer;
    answer = fun() - fun() * fun();
    printf( "%dn", answer);//输出多少?
    return 0;
}

倘若我先算后面乘法,再算前面,就是4-2*3=-2

要是我先准备前面的,再准备后面,就是2-3*4=-10

我们唯一能确定的是先乘后减,但是我们根本无法确定我们先调用哪一个函数。所以这样的代码都是错误的,调用顺序是最重要的,而不是计算顺序。在Vs2019的编译器运行的代码,结果就是-10
所以我们要避免写出这样的代码

看看这道题什么问题?

#include 
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%dn", ret);
printf("%dn", i);
return 0;
}

可知,在两个编译器底下的结果截然不同。在Vs底下,是先算i++,三个i++过后,等于i=4,再后3个4相加等于12

但在VC底下,是先算两个++,i=3,相加=6,再算最后一个++,再相加,结果=10

所以这是个错误代码

总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/784445.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号