操作符详解(上)
- 1.操作符分类
- 2.算数操作符
- 3.移位操作符
- 3.1 右移
- 3.2 左移
- 4.位操作符
- 4.1位操作符发的应用
- 5.赋值操作符
- 6.单目操作符
- 7.关系操作符
- 8.逻辑操作符
1.操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
2.算数操作符
+ - * / %
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
- 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
- % 操作符的两个操作数==必须为整数。==返回的是整除之后的余数。
具体的代码实操这里就不做过多的讲解,都是一些比较简单的操作。
3.移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。
注:移位操作符操作的是二进制位。
这里我们先了解一下整数的二进制的表达形式,主要有三种:原码,反码,补码。
- 正整数的原码,反码,补码是相同的。
- 负的整数原码,反码,补码是要进行计算的。
首先不管是负整数还是正整数都是可以写出他们的二进制原码的。
- 根据正负号直接写出来的二进制序列就是原码。
比如我们的 int a = 1;
我们知道一个int类型的大小时4个字节,一个字节时8个bit位,所以我们表示a的二进制就是:
0 0000000 00000000 00000000 00000001
其中最高位表示符号位(就是黄色代表的那个),我们C语言规定最高位0代表正数,1代表负数
如果要表示int a = -1;就是:
1 0000000 00000000 00000000 00000001
这个个时候就表示-1了。
现在我们来讨论一下他们的原码,反码,补码。
int a = 1;
我们说了正整数的原码,反码,补码是相同的。
00000000 00000000 00000000 00000001 ——原码
00000000 00000000 00000000 00000001 ——反码
00000000 00000000 00000000 00000001 ——补码
但是负数的原码,反码,补码是要计算的。
int a = -1;
10000000 00000000 00000000 00000001 ——原码
111111111 111111111 111111111 111111110 ——反码 (原码的符号位不变,其他按位取反得到的就是反码。按位取反的意思就是把是1的换成0,把是0的换成1)。
111111111 111111111 111111111 111111111 ——补码 (反码+1得到的就是补码)
至于为什么是这样的这个是C语言规定的。
注:整数在内存中存储的是补码,所以在进行计算的时候也是用补码进行计算的。所以我们移动的二进制位也是补码。
我们分析下面一串代码:
#include <stdio.h>
int main()
{
int a = 1;
int b = a >> 1;
int d = a << 1;
printf("%d\n", b);
printf("%d\n", d);
return 0;
}
3.1 右移
所以对于我们的右移不回去的那个位置有两个规定:
- 如果是算术右移:那么右边丢掉的,左边补原来的符号位。
- 如果是逻辑右移:那么右边丢掉的,左边直接补0。
注:以上的位移操作都是对二进制的补码进行的,只不过上面的a是正整数,它的原反补是相同的。
是负数的话,就要要将其转换成补码来移动,二我们显示屏上显示的是二进制位的原码,所以就得把补码转换成原码,着个转化也很简单,就是补码-1得到反码,在按位取反就得到了原码。
注:C语言没有明确规定使用哪一种方法,但是一般编译器采用的是算术右移
3.2 左移
二对于我们的左移就没那么复杂:
左移的话,左边少的,直接在右边补0就行了。
所以上面的代码的展示就是:0 和 2
注:我们可能写除a>>-1,也就是说一移动负数个位,这是个错误的写法。
4.位操作符
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数。
注:这些也是操作的二进制位的补码。
例如:
#include <stdio.h>
int main()
{
int num1 = 1;
int num2 = -2;
int a = num1& num2;
int b = num1 | num2;
int c = num1^ num2;
printf("%d %d %d", a, b, c);
return 0;
}
-
我们的&的规则:对应的二进制位有0则为0,同时为1才是1,两个同时为0也是0。
我们的num1=1。
他的原码和补码是相同的的:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
而num1=-2,他们的:
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0——原码(得到这个,但是我们屏幕上展示的是原码,所以要转换成原码的形式)
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1——反码
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——补码
num1&num2:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
得到:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0——补码
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1——反码
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0——原码
所以num1&num2=0; -
我们的|对应的规则是:对应的二进制位有1则是1,同时是1则是1,同时是0则是0;
同样的:
num1|num2:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
得到:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1——补码
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——反码
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1——原码
所以num1|num2=-1; -
我们的^的规则是:两个二进制位相同则是0,不相同则是1.
num1^num2:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
得到:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1——补码
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——反码
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1——原码
所以num1^num2=-1;
代码展示:
4.1位操作符发的应用
我们的直接上代码来解释:
说这有个题目,交换两个数,但是不能创建临时变量。
于是我们就可以这样写;
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a=a+b;
b=a-b;
a=a-b;
printf("a = %d b = %d\n", a, b);
return 0;
}
但是这种写法也是有缺缺陷的,那就是当a=a+b,这个a+b的值超出了int型所能存储的范围,这个时候就有可能会丢失一些数据。就得不到我们想要的结果。
于是我们改进:
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}
-
a的二进制位:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
b的二进制位:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 -
a^b得到的二进制位:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 -
这个时候a=a^b==0;
然后b=a^b:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
得到:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
这个时候b就等于1; -
再就是:a=a^b;
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
得到:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
这个时候a=2 -
经过上面的过程,我们我们成功的交换了a,b的值。
这也就说明了我们的异或是支持交换律的。 -
上面的写法就有效的避免了数据溢出的可能,因为异或是不会进位的,只会改变二级制位。
5.赋值操作符
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋
值。
int weight = 100;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
见这些赋值操作符组合一下就可以得到复合赋值符:
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁。
6.单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
- 这里讲一下!(逻辑反操作),就是把真的看成假的,把假的看成是真的。
#include <stdio.h>
int main()
{
int a = 1;
if (!a)
{
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}
就比如这个,本来我们if语句里面的判断是非0为真,为0是假,所以上面打印的因该是hehe。
但是我们加上了!(逻辑反操作)就不一样了,if语句变成了是0则执行,非0不执行,所以上面的执行结果因该是haha。
-
+正号,-符号这个就不用多讲了,就是我们数学上的那样了
-
& *有两个不同的作用。
第一种:应用于指针。一个是取地址,和解引用操作。
第二种:&按位与,和指针类型。
int* pa;//pa是个指针。
int b=20;
*pa=10;//解引用操作。
pa=&b;//取地址操作。
int a,b,c;
c=a&b;//按位与。
- sizeof
sizeof不是函数,是操作符。计算的是类型创建变量的大小,单位字节。
用法也很简单:
int a=10;
printf("%d",sizeof(int));
printf("%d",sizeof(a));
同时sizeof也可以用来计算数组的大小。
int arr[10]={0};
printf("%d",sizeof(arr));//计算的整个数组的大小时40个字节。
- ~按位取反
比如:
int main()
{
int a = 0;
printf("%d", ~a);
return 0;
}
这个结果是什么呢?我们来分析一下:
a的补码:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ——补码
按位取反之后:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ——补码(取反后还是补码)
我们显示出来时以原码的形式展示的
反码:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——反码(-1得到反码)
原码:
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1——原码(符号位不变,其他取反)
这个时候我们按位取反得到的 数就是-1了。
我们结合一下上面的知识点:
这里我们给出int a = 13.
此时a的二进制位是:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1
这个时候我希望把上面画黄色的0改成1,这么办。
我们就想到让
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1按位或上
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
这个时候及改成了
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1
于是我们就写成:
a | = (1<<4) 就得到我们想要的结果了。
而这个时候我们又想把那个一给改回去,怎么办。我们用到了按位且
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1按位且上
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
就可以得到
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1
而1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1则是1<<4后取反
于是就可以写成:a &= (~(1<<4))
- ++和–
++和–分为;–前置,后置–,++前置,后置++。
int a=1;
int b=a++;//后置++
后置++的特点是先执行后++,也就是上面的执行顺序是先让b=a,然后a=a+1;
这时候a=2,b=1
我们看一下效果:
而如果改成前置++呢?
int a=1;
int b=++a;//前置++
前置++的特点是,先++后使用
也就是说它的执行顺序是:a=a+1,b=a;
这个时候b=2,a=2;
而我们–也是一样的。
- (类型)强制类型转换
int a=(int)3.1415926
就是把3.1415926强制类型转换成int,此时a就等于3
这里做一个练习:
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(3)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(2)
test1(arr);
test2(ch);
return 0;
}
这个时候(1)(2)(3)(4)分别是多少呢?
(1)和(2)我们可以轻易的得到(1)==40,(2)==10
问题就在于(3)和(4)是多少呢
我们test1和test2都是传的数组过去的,也就是数组的首元素地址,所以我们形参接收的本质上是指针所以我们计算的是指针的大小,所以(3)和(4)的值是4或者8
展示:
我的电脑是64位的所以指针大小是8。
7.关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
这些关系运算符比较简单,但是我们要注意一些运算符使用时候的陷阱。
警告:在编程的过程中 = = 和=不小心写错,导致的错误
8.逻辑操作符
&& 逻辑与——并且
|| 逻辑或——或者
区分逻辑与和按位与
区分逻辑或和按位或
1&2----->0
1&&2---->1
1|2----->3
1||2---->1
做一个笔试题:
#include <stdio.h>
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;(1)
//i = a++||++b||d++;(2)
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
这个程序的结果是什么呢?
-
我们来分析一下(1):
a=0,而a++是先赋值后++的所以在语句没有结束之前a都是等于0的,0为假所以表达式为假,后面的表达式就不会在执行了。而结束后a加加了。
所以最终结果就是:1 2 3 4。
所以我们知道了==&&左边为假后面的就不计算了==。
-
我们来分析一下(2):
首先是a++||++b,a等于0,b是前置++所以b=3,这个时候是逻辑或,只要出现真后面就不计算。
所以到我们计算的++b的时候表达式为真,跳出,后面的d++就不计算了。
所以最后的答案是1 3 3 4