操作符共12中,分别是:
算术操作符:+ ,-,*,/,
移位操作符:>>,<<;
位操作符:&,|,^;
赋值操作符:=,+=,-= , /= , %= , >>= , <<= , &= , |= , ^= ;
单目操作符:!,- , + , & , sizeof , ~ , -- , ++ , * , (类型);
关系操作符:> , >= , < , <= , != , == ;
逻辑操作符: &&, || ,
条件操作符: 表达式1?表达式2:表达式3 ;
逗号操作符: exp1,exp2 ,exp3 ,exp4 ,exp5 ,…………
下标引用操作符: [ ];
函数调用操作符: ( );
结构体操作符: . , ->;
以上就是全部的操作符了,接下来我们细细讲解每一种操作符的作用以及一些特殊的列子。
1:算术操作符
+,-,*,/,%
+,-,*,/,都如字面上表示,分别是加减乘除的作用,优先率和数学上相同,但是除号有一点不同,它的计算分为整数相除和浮点数相除;
整数相除就是所得结果是整数,若是小数点后面有数字,就直接归零,比如3/2 = 1;
而浮点数相除所得结果就是浮点数,需要除号两边至少有一边是浮点数,比如3/2 = 1.5;
然后有一个特殊的算术操作符,%操作符,这个操作符被叫做取模操作符,作用是计算%左边的数除以%右边的数的余数,如5%2 = 3;
2:移位操作符
>>,<<
在讲这个操作符之前,我们需要了解数据在计算机中是如何存储的,首先,任何一个数据都能用二进制表示,这里我们用整数型int的数据来说明。
整数型int用sizeof计算可以直到int类型有4个字节,而一个字节又有八个比特位,这里每一个比特位就用来存储二进制中的0和1,因此一个整数型的数据有32个0/1组成的二进制序列,而整数又分为正数与负数,在计算机中,有符号整数的二进制序列中,最高位是符号位,0表示正数,1表示负数;
而计算机对于数据的存储并不是简单的将数据化为二进制存储,在这其中又细分为原码,反码,补码,我们先来结束一下三种码是怎么一回事吧;
1:原码:
原码实际上就是将一个数据转化为二进制序列时产生的;
2:反码
反码就如字面意义,是原码按除符号位以外的数按位取反,1变为0,0变为1;
3:补码
补码实际上就是反码+1;
而正数负数的原码,反码,补码也有不同,正数的原反补三码相同,负数就按照上面所说,分别转化而来,而在计算机中,一般存储的都是数据的补码,在使用的时候再转换为原码来使用。
而移位操作符就是对这个补码进行一些操作,我们先讲讲左移操作符。
1:左移操作符
<<
左移操作符实际上就是将二进制序列的补码左移一位,舍弃最左边的那一位,然后右边空出的一位补上一个0;
但是这个实际上并不会让数据变化,需要用赋值操作符才能保存下来;
比如我们这样写代码:
#include<stdio.h>
int main()
{
int a = 5;
int b = 0;
b = a << 1;
printf("a = %d\n", a);
printf("b = %d", b);
return 0;
}
结果是这样:
这里我们简单计算一下:
a = 5,a的二进制序列补码是:
00000000000000000000000000000101
左移一位后变为
00000000000000000000000000001010
根据二进制转换为十进制,可得b = 10;
然后简单讲一下右移操作符:
2:右移操作符
>>
右移操作符分为两种移位:
算术移位:右边丢弃,左边补原符号位
逻辑移位:右边丢弃,左边补0
整数一般是逻辑移位,负数是算术移位;
我们来看下右移实际操作吧;
我们看看正负两数的操作吧
#include<stdio.h>
int main()
{
int a = 5;
int b = 0;
int c = -5;
int d = 0;
b = a >> 1;
d = c >> 1;
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("d = %d\n", d);
return 0;
}
来简单计算一下;
5的二进制序列补码是:
00000000000000000000000000000101
右移一位后变为:
00000000000000000000000000000010
计算得2;
-5的二进制序列补码是:
111111111111111111111111111111111010
右移一位后变为
111111111111111111111111111111111101
然后变为反码得:
111111111111111111111111111111111100;
再转化为原码
1000000000000000000000000000011;
通过计算得-3;
3 位操作符
&,|,^
在上面将了移位操作符再来了解改操作符就简单多了
先讲讲三个操作符的作用吧
&:按位与,有0为0,全1为1;
|:按位或,有1为1,全0为0;
^:按位异或,相同为0,不同为1;
这三个操作符也对符号位有作用,所以若是正数和负数相&,得到的一定为正,正负相 | ,一定为负数,正数负数 ^ 也一定是负数。
这几个操作符有很大的作用,这里有一个问题:求出一个数中有几个1(除符号位)
思路是这样,我们可以计算一下,发现,一个数若是&这个数-1的数,那么就会发现数据会少一个1;
比如我们设a = 15,
它的二进制序列
00000000000000000000000000001111;
而14的二进制序列为
00000000000000000000000000001110;
二者&得
00000000000000000000000000001110;
不断重复直到这个数为0,就能得到该数有几个1了,代码如下:
#include<stdio.h>
int main()
{
int a;
scanf("%d", &a);
int count = 0;
while (a)
{
a &= (a - 1);
count++;
}
printf("%d", count);
return 0;
}
结果如下:
4:赋值操作符
=,%=,+=,-=,/=,>>=,<<=,&=,|=,^=;
赋值操作符有以上,具体作用就是将值赋给变量,并且会根据=左边的符号对数据变量进行操作并且将结果赋给变量。
5:单目操作符
! 逻辑反
- 负值
+ 正值
& 取地址
sizeof() 求操作符类型长度(字节为单位)
~ 按位取反(对补码操作)、
-- 前置--,后置--;
++ 前置++,后置++;
* 间接访问操作符 (解引用操作符)
(类型) 强制类型转换
这些相信大家基本都遇过,我就讲两个特殊点的吧;
首先是~操作符
它的作用是将1变为0,0变为1;
当然它也有妙用;
可以限制将变量第n位的数据取反,
代码如下:
#include<stdio.h>
int main()
{
int a;
scanf("%d", &a);
int n = 0;
scanf("%d", &n);
a &= (~(1 << (n - 1)));
printf("a = %d", a);
return 0;
}
我输入15,并且让它将第一位的1变为0,可以得到14,结果如下:
然后是sizeof这个操作符:
sizeof在计算变量字节大小的时候,可以省略括号,比如
int a = 0;
sizeof a;
这里是可以计算出a的字节大小的,但不能这样
sizeof int
计算变量大小的时候,需要加上括号才能进行。
顺带一提(sizeof具有返回值,返回一个无符号整形)
6:关系操作符
==,>=,<=,>,<,!=
这些操作符唯一一个要注意的是==,在判断的时候不要写成
还有一点是关系操作符是不能比较字符串的大小的,字符串的大小比较只能用strcmp函数来比较。
7:逻辑操作符
&& 全真则真,有假则假
|| 全假为假,有真则真
在c语言中,0表示为假,非0表示为真
这两个需要注意的只有一点,
当&&操作符左边有一个为假,那么右边就不会计算了,而 || 操作符左边有一个为真,那么右边就不会计算了,证明可以看下面的代码:
#include<stdio.h>
int main()
{
int a = 0, b = 1, c = 2;
int d = (a++ && b++ && ++c);
printf("a = %d,b = %d,c = %d", a, b, c);
return 0;
}
上面的a++是后置++,因此是先使用再++,因此在判断的时候,a依旧 = 0,然后我们可以看到后面的b和c都没有变化
而 || 也是同样的:
#include<stdio.h>
int main()
{
int a = 0, b = 1, c = 2;
int d = (++a || b++ || ++c);
printf("a = %d,b = %d,c = %d", a, b, c);
return 0;
}
这里的a是前置++,因此在使用的时候是先++,由0变为1,因此此处第一个就是真,所以后面的值都没变。
8:条件操作符
exp1?exp2 :exp3 ;
这个很简单,若是exp1为假,就会使用exp3,exp1为真,就用exp2;
实际运用如下:
#include<stdio.h>
int main()
{
int a = 0, b = 1, c = 2;
a = b > c ? b : c;
printf("a = %d", a);
return 0;
}
9:逗号操作符
逗号操作符会将最后一个逗号赋给其他变量,当然前面的表达式也会运行,比如:
#include<stdio.h>
int main()
{
int a = 0;
int b = (a++, a++, ++a, ++a);
printf("a = %d\n", a);
printf("b = %d", b);
return 0;
}
这里可以看到,b最后的值变成了最后一个++a所计算出的4,而a的值也有变化。
但是若是b没有括号,那么就会将第一个逗号前的表达式的值给b,比如上面的代码去掉括号后会这样:
10:下标引用操作符
[ ]
这个就是数组使用的时候,会用来寻找数组内的元素,这里给大家看一个大家应该没看过的操作:
#include<stdio.h>
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
printf("a[7] = %d\n", 7[a]);
return 0;
}
这里就能说明 [ ] 是一个操作符,但是希望各位看到这个操作知道就行,最好不写,不然会导致代码可读性下降;
11:函数调用
()
函数操作符不用说,就是调用函数时用的。
12:结构体操作符
.
->
这两个操作符是用来引用结构体变量的内部成员的
不同点是 第一个操作符不能用于结构体指针,而第二个用于引用结构体指针的成员
接下我们讲一下表达式求值
表达式求值
计算机在进行表达式求值的时候,会有一个隐式类型转换,会将字符类型或者short数值的二进制补码序列提升到普通整形的长度,然后再切断成字符类型或者short类型的长度再计算
整型提升的原因如下:
我们这里拿char类型来说明,char类型只有8个bit位,但是int有32个比特位,若是直接转换为整形给char类型的变量,无法存储,因此编译器会截取后8位放入变量中,使用时,再根据第8位是1还是0来补充,看是补充1还是补充0;当然这里char类型变量在计算机中也是存储的补码,需要根据第8位是1还是0来还原成原码。
这里给出两个简单的实列来简单的说明一下什么是整型提升
char c1 = 1;
char c2 = -1;
1是一个正数,它的补码是00000000 00000000 00000000 00000001;
然后由于c1是char类型的,只能存储后八位数据,因此c1中存储的是00000001,只有在使用的时候就会换原成00000000 00000000 00000000 00000001
而-1补码是111111111 111111111 111111111 111111111
c2中存储的是后八位数据也就是 111111111,在使用的时候会补1然后成111111111 111111111 111111111 111111111,再还原成原码。
表达式求值还有一种转换是算术转换,计算机会根据这个列表的进行算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
当有两个列表中的类型的数据在计算时,计算机会将层次低的类型提升至层次高的类型后计算,但是算术转换应合理,否则会出现bug。