1.算术操作符
2.移位操作符
3.位操作符
4.赋值操作符
5.复合赋值操作符
6.单目操作符
7.关系操作符
8.逻辑操作符
9.条件操作符
10.逗号操作符
11.下标引用、函数调用和结构成员
+ | - | * | / | % |
---|
+
、-
、*
这三个操作符与平常数学中运算规则相同,除法操作符/
代表取整运算, %
代表取模运算。%
运算符之外,其他的操作符都可以用于整数和浮点数,而%
运算符只能操作整数。/
操作符而言,如果操作符的操作数都为整数,则执行整数除法。而只要有浮点数执行的就是浮点数除法。正整数
对于正整数而言,**正整数的原码、反码、补码是相同的。**例如:int a = 5
; 一个整形占4个字节、32个比特位,换算为二进制(原码)为 : 00000000000000000000000000000101
,则5的原码、反码、补码相同,都为: 00000000000000000000000000000101
,开头的第一个数字表示正负, 0表示正数 ,1表示负数。
负整数
对于负整数而言,负整数原码、反码、补码是要计算的。例如:int a=-5;
换算为二进制(原码)为 10000000000000000000000000000101
。反码:原码的符号位不变,其他数字按位取反: 11111111111111111111111111111010 反码加1就是补码: 11111111111111111111111111111011。
移位操作符
操作符 | 功能 |
---|---|
<< | 左移操作符 |
>> | 右移操作符 |
<< 左移操作符
移位规则:左边丢弃, 右边补0。 例如: 5<<1
,相当于补码整体向左移动一个字符。
>> 右移操作符
右移运算分两种,分别是逻辑移位和算术移位。
1.逻辑移位:左边用0填充,右边丢弃
还是用-5举例:
11111111111111111111111111111011
逻辑右移后:
01111111111111111111111111111101
2.算术移位:左边用原该值的符号位填充,右边丢弃
11111111111111111111111111111011
算术右移后:
11111111111111111111111111111101
vs系列编译器在右移的时候,采用的是算术右移,到底是算术右移还是逻辑右移取决于编译器。
操作符 | 功能 |
---|---|
& | 按位与 |
丨 | 按位或 |
^ | 按位异或 |
位操作符的操作数必须是整数,且操作的位是二进制位补码。
&
操作符,全1为1,否则为0。|
操作符,有1为1,否则为0。^
操作符,相同为0,不同为1。对于任意整数a, a^a=0
;0^a=a
。& 按位与
| 按位或
^ 按位异或
不能创建临时变量,实现两个数的交换
#include
int main()
{
int a = 5; //101
int b = 3; //011
a = a ^ b; //a=110
b = a ^ b; //b=101 原理:b=a^b^b=a^0=a
a = a ^ b; //a=011 原理:a=a^b^a=b
printf("a = %d b = %d\n", a, b);
//局限性——异或操作符只能用于整数,这个方法不适用于字符操作。
return 0;
}
求一个整数存储在内存中的二进制中1的个数
//方法一:
#include
int main()
{
int num = 0;
scanf("%d", &num);
int count = 0;
int i = 0;
for ( i = 0; i < 32; i++)
{
if (1 == ((num >> i) & 1)) //一个数字&1,如果得到1,说明这个数字的最后一位是1
{
count++;
}
}
printf("%d", count);
return 0;
}
//方法二:
#include
int main()
{
int num = -1;
int count = 0; //计数
while (num)
{
count++;
num = num & (num - 1);
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
赋值操作符 =
int weight = 120; //体重
weight = 89; //不满意就赋值
复合赋值符:
+= | -= | *= | /= | %= | >>= | <<= | &= | |= | ^= |
---|
int x = 10;
x = x+10;
x += 10; //复合赋值,//其他运算符一样的道理。这样写更加简洁。
单目操作符也就是只接受一个操作数的操作符。
C99中引入了布尔类型,这样和0、1相比可读性更高。使用!
逻辑反操作符可以得到相反的布尔类型。
sizeof
是一个操作符,不是函数,可以求变量(类型)所占空间的大小,单位是字节。sizeof(long)
在32位下为4,而在64位下为8, 这个取决于编译器的实现,C语言只是规定sizeof(long)>=sizeof(int)
。#include
void test1(int arr[]) //本质上传过来是int* arr
{
printf("%d\n", sizeof(arr));// 4/8
//数组首元素的地址用指针变量来存储,指针变量的大小是4/8,取决于计算机
}
void test2(char ch[]) //本质是传过来是 char* ch
{
printf("%d\n", sizeof(ch));//4/8
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr)); // 40 ——数组的大小10*4
printf("%d\n", sizeof(ch)); //10 ——数组的大小10*1
test1(arr);
test2(ch);
return 0;
}
~
操作符表示对一个数的二进制按位取反 ,即二进制每一位取反。
~ -1
对-1二进制位取反得到0;while(~scanf("%d",&n)
可以终止循环。++和–运算符
++a
, 前置++,先++后使用。a++
,后置++,先使用,后++,前置后置-- 同理。
强制类型转换
int a =(int)3.14; //3
操作符 | 功能 |
---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
!= | 用于测试“不相等” |
== | 用于测试“相等” |
判断字符串相等的时候,if ('abc' == 'afg')
是错误的,这样比较的是字符串首字符的地址。正确的做法是用 strcmp
函数来比较字符串的大小,自左向右逐个按照ASCII码值进行比较,直到出现不同的字符或遇’\0’为止。
操作符 | 名称 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
&&
操作符,如果左边为假,就不用算右边了||
操作符遇到表达式为真,直接输出结果就不执行后面的操作了。条件表达式的一般形式:表达式1 ? 表达式2:表达式3
。此操作符有三个操作数,所以是三目操作符。
int a = 3;
int b = 6;
int c = 0;
c = a > b ? a : b;
//a > b 为表达式1,a为表达式2,b为表达式3
//该语句的意思为:如果a>b,则将a的值赋给c,否则将b的值赋给c
exp1, exp2, exp3, …expN
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
printf("%d", c); //13
return 0;
}
[] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[]的两个操作数是arr和9
( ) 函数调用操作符
表达式求值的顺序一部分是由操作符的优先级和结合性决定,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
隐式类型转换
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU是难以直接实现两个8比特位直接相加运算所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
//实例
char a,b,c;
a = b + c;
//b和c的值被提升为普通整型,然后再执行加法运算。
//加法运算完成之后,结果将被截断,然后再存储于a中。
如何进行整体提升
整形提升是按照变量的数据类型的符号位来提升的。
int main()
{
char a = 0xb6;//10110110
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)//1011011000000000
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
/*实例1中的a,b要进行整形提升,但是c不需要整形提升
a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真.*/
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
long double
double
float
unsigned long int
long int
unsigned int
int
//但是算术转换要合理,要不然会有一些潜在的问题
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
操作符的属性
复杂表达式的求值有三个影响的因素:
操作符的优先级。
操作符的结合性。
是否控制求值顺序。
两个相邻的操作符先执行哪个,取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
如果写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f
//注释:代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行
//表达式2
c + --c;
/*注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得
知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。*/
//代码3-非法表达式
int main()
{
int i = 10;
i = i-- - --i * (i = -3) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
//表达式3在不同编译器中测试结果都不同
//代码4
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);//输出多少?
return 0;
}
/*虽然在大多数的编译器上求得结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。*/
//代码5
#include
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);//VS:12 Linux:10
printf("%d\n", i);
return 0;
}