1、什么是操作符
操作符也称运算符,是一种表示对数据进行某种运算处理的符号。 C语言的运算符按完成的运算操作性质 :
可以分为算术运算符、关系运算符、逻辑运算符、赋值运算符和其他运算符。
2、操作符分类
算术操作符移位操作符位操作符赋值操作符单目操作符关系操作符逻辑操作符条件操作符逗号表达式下标引用、函数调用和结构成员
2.1算术操作符
+ - * / %
+ - * / 都是我们熟知的,较为陌生的是 %(取余)
例如:
10 / 3 = 3 (我们得到的是商)
10 % 3 = 1 (我们得到的是余数)
2.2移位操作符
移位操作符移动的是二进制的位
移位操作符又分为:左移操作符 右移操作符
<< 左移操作符>> 右移操作符注:移位操作符的操作数只能是整数。
既然要移位我们就要知道 原码、反码、补码。
正整数的原反补码相同。
负整数
原码:把一个数按照正负直接翻译成二进制
反码:原码取反
补码:反码+1
2.2.1左移操作符
左边抛弃、右边补0
通俗讲就是需要左移几位就移几位,右边空缺的地方补0
2.2.2右移操作符
1. 逻辑移位左边用 0 填充,右边丢弃2. 算术移位左边用原该值的符号位填充,右边丢弃
采用逻辑移位还是算术移位取决于编译器
举个例子:
举个例子:
int num = 10 ;num >>- 1 ; //error
2.3位操作符
& // 按位与 (同1为1,有0为0)| // 按位或 (有1为1,同0为0)^ // 按位异或(相同为0,相异为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;
}
也可以利用两数的和,差来计算,但是如果两个数很大相加超过了int范围就会出现问题;利用^不会出现溢出。
2.4赋值操作符
赋值操作符简单的说就是给你的变量赋值,或者修改变量的值。
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 ; // 连续赋值这样的代码感觉怎么样?那同样的语义,你看看:x = y + 1 ;a = x ;这样的写法是不是更加清晰爽朗而且易于调试。
复合赋值符
+=-=*=/=%=>>=<<=&=|=^=
int x = 10 ;x = x + 10 ;x += 10 ; // 复合赋值//其他运算符一样的道理。这样写更加简洁。int y = 5;y = y-10;y -= 10;
2.5单目操作符
2.5.1单目操作符分类
! 逻辑反操作- 负值+ 正值& 取地址sizeof 操作数的类型长度(以字节为单位)~ 对一个数的二进制按位取反-- 前置、后置 --++ 前置、后置 ++* 间接访问操作符 ( 解引用操作符 )( 类型 ) 强制类型转换
光说不练假把式
int main()
{
int a = -10;
int* p = NULL;
printf("%d\n", !2);
printf("%d\n", !0);
a = -a;
p = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//这样写行不行?
printf("%d\n", sizeof int);//这样写行不行?
return 0;
}
2.5.2sizeof 和 数组
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
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));//(3)
test1(arr);
test2(ch);
return 0;
}
结果如下图:
#include <stdio.h>int main (){int a = 10 ;int x = ++ a ;// 先对 a 进行自增,然后对使用a ,也就是表达式的值是 a 自增之后的值。 x 为 11 。int y = -- a ;// 先对 a 进行自减,然后对使用 a ,也就是表达式的值是 a 自减之后的值。 y 为 10;return 0 ;}// 后置 ++ 和 --#include <stdio.h>int main (){int a = 10 ;int x = a ++ ;// 先对 a 先使用,再增加,这样 x 的值是 10 ;之后 a 变成 11 ;int y = a -- ;// 先对 a 先使用,再自减,这样 y 的值是 11 ;之后 a 变成 10 ;return 0 ;}
前置
后置
2.6关系操作符
>>=<<=!= 用于测试 “ 不相等 ”== 用于测试 “ 相等 ”
关系操作符大多数用于条件的判断
if(x <= y)
return 1;
else if(x == y)
return 2;
else if(x >=y)
return 3;
类似于这样的代码
2.7逻辑操作符
&& 逻辑与|| 逻辑或
1 & 2 -----> 01 && 2 ----> 1 (左边大于0为真,右边大于0为真,结果为1)1 | 2 -----> 31 || 2 ----> 1(左边大于0为真,或者右边大于0为真,结果为1)
我们看一道题就能清楚的知道为什么了
#include <stdio.h>int main (){int i = 0 , a = 0 , b = 2 , c = 3 , d = 4 ;i = a ++ && ++ b && d ++ ;//i = a++||++b||d++; // a = 1 , b = 3 , c = 3, d = 4printf ( "a = %d\n b = %d\n c = %d\nd = %d\n" , a , b , c , d );return 0 ;}
由此我们可以得出结论
&& 操作符左边为假,右边不再计算
|| 操作符左边为真,右边不再计算
2.8条件操作符
exp1 ? exp2 : exp3
举个栗子:
if ( a > 5 )b = 3 ;elseb = - 3 ;转换成条件表达式,是什么样?b=( (a > 5) ? 3 : -3)
2.9逗号表达式 和 下标引用、函数调用和结构成员
exp1 , exp2 , exp3,..... ,expn
int a = 1 ;int b = 2 ;int c = ( a > b , a = b + 10 , a , b = a + 1 ); // 逗号表达式
int arr [ 10 ]; // 创建数组arr [ 9 ] = 10 ; // 实用下标引用操作符。[ ] 的两个操作数是 arr 和 9 。
#include <stdio.h>void test1 (){printf ( "hehe\n" );}void test2 ( const char * str ){printf ( "%s\n" , str );}int main (){test1 (); // 实用()作为函数调用操作符。est2 ( "hello bit." ); // 实用()作为函数调用操作符。return 0 ;}
#include <stdio.h>struct Stu{char name [ 10 ];int age ;char sex [ 5 ];double score ;} ;void set_age1 ( struct Stu stu ){stu . age = 18 ;}void set_age2 ( struct Stu * pStu ){pStu -> age = 18 ; // 结构成员访问}int main (){struct Stu stu ;struct Stu * pStu = & stu ; // 结构成员访问stu . age = 20 ; // 结构成员访问set_age1 ( stu );pStu -> age = 20 ; // 结构成员访问set_age2 ( pStu );return 0 ;}
3、表达式求值
3.1隐式类型转换
表达式的整型运算要在 CPU 的相应运算器件内执行, CPU 内整型运算器 (ALU) 的操作数的字节长度一般就是 int 的字节长度,同时也是 CPU 的通用寄存器的长度。因此,即使两个 char 类型的相加,在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。通用 CPU ( general-purpose CPU )是难以直接实现两个 8 比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于 int 长度的整型值,都必须先转换为 int 或 unsigned int ,然后才能送入 CPU 去执行运算。
// 实例 1char a , b , c ;...a = b + c ;b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于 a中。
如何进行整体提升呢?
整形提升是按照变量的数据类型的符号位来提升的
// 负数的整形提升char c1 = - 1 ;变量 c1 的二进制位 ( 补码 ) 中只有 8 个比特位:1111111因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为 1提升之后的结果是:11111111111111111111111111111111// 正数的整形提升char c2 = 1 ;变量 c2 的二进制位 ( 补码 ) 中只有 8 个比特位:00000001因为 char 为有符号的 char所以整形提升的时候,高位补充符号位,即为 0提升之后的结果是:00000000000000000000000000000001// 无符号整形提升,高位补 0
举个例子:
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
3.2算术转换
long doubledoublefloatunsigned long intlong intunsigned intint
float f = 3.14 ;int num = f ; // 隐式转换,会有精度丢失
3.3操作符的属性
操作符 | 描述 | 用法示例 | 结果类型 | 结合性 | 是否控制求值顺序 |
() | 聚组 | (表达式) | 与表达 式同 | N/A | 否 |
() | 函数调用 | rexp(rexp,...,rexp) | rexp | L-R | 否 |
[ ] | 下标引用 | rexp[rexp] | lexp | L-R | 否 |
. | 访问结构成员 | lexp.member_name | lexp | L-R | 否 |
-> | 访问结构指针成员 | rexp->member_name | lexp | L-R | 否 |
++ | 后缀自增 | lexp ++ | rexp | L-R | 否 |
-- | 后缀自减 | lexp -- | rexp | L-R | 否 |
! | 逻辑反 | ! rexp | rexp | R-L | 否 |
~ | 按位取反 | ~ rexp | rexp | R-L | 否 |
+ | 单目,表示正值 | + rexp | rexp | R-L | 否 |
- | 单目,表示负值 | - rexp | rexp | R-L | 否 |
++ | 前缀自增 | ++ lexp | rexp | R-L | 否 |
-- | 前缀自减 | -- lexp | rexp | R-L | 否 |
* | 间接访问 | * rexp | lexp | R-L | 否 |
& | 取地址 | & lexp | rexp | R-L | 否 |
sizeof | 取其长度,以字节 表示 | sizeof rexp sizeof(类 型) | rexp | R-L | 否 |
(类 型) | 类型转换 | (类型) rexp | rexp | R-L | 否 |
* | 乘法 | rexp * rexp | rexp | L-R | 否 |
/ | 除法 | rexp / rexp | rexp | L-R | 否 |
% | 整数取余 | rexp % rexp | rexp | L-R | 否 |
+ | 加法 | rexp + rexp | rexp | L-R | 否 |
- | 减法 | rexp - rexp | rexp | L-R | 否 |
<< | 左移位 | rexp << rexp | rexp | L-R | 否 |
>> | 右移位 | rexp >> rexp | rexp | L-R | 否 |
> | 大于 | rexp > rexp | rexp | L-R | 否 |
>= | 大于等于 | rexp >= rexp | rexp | L-R | 否 |
< | 小于 | rexp < rexp | rexp | L-R | 否 |
<= | 小于等于 | rexp <= rexp | rexp | L-R | 否 |
== | 等于 | rexp == rexp | rexp | L-R | 否 |
!= | 不等于 | rexp != rexp | rexp | L-R | 否 |
& | 位与 | rexp & rexp | rexp | L-R | 否 |
^ | 位异或 | rexp ^ rexp | rexp | L-R | 否 |
| | 位或 | rexp | rexp | rexp | L-R | 否 |
&& | 逻辑与 | rexp && rexp | rexp | L-R | 是 |
|| | 逻辑或 | rexp || rexp | rexp | L-R | 是 |
? : | 条件操作符 | rexp ? rexp : rexp | rexp | N/A | 是 |
= | 赋值 | lexp = rexp | rexp | R-L | 否 |
+= | 以...加 | lexp += rexp | rexp | R-L | 否 |
-= | 以...减 | lexp -= rexp | rexp | R-L | 否 |
*= | 以...乘 | lexp *= rexp | rexp | R-L | 否 |
/= | 以...除 | lexp /= rexp | rexp | R-L | 否 |
%= | 以...取模 | lexp %= rexp | rexp | R-L | 否 |
<<= | 以...左移 | lexp <<= rexp | rexp | R-L | 否 |
>>= | 以...右移 | lexp >>= rexp | rexp | R-L | 否 |
&= | 以...与 | lexp &= rexp | rexp | R-L | 否 |
^= | 以...异或 | lexp ^= rexp | rexp | R-L | 否 |
|= | 以...或 | lexp |= rexp | rexp | R-L | 否 |
, | 逗号 | rexp,rexp | rexp | L-R | 是 |
下面我们来看一些问题表达式
//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f
注释:代码在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不
能决定第三个*比第一个+早执行。
所以表达式的计算机顺序就可能是
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
或者:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
//代码-非法表达式
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
表达式在不同编译器中测试结果:非法表达式程序的结果
值 | 编译器 |
—128 | Tandy 6000 Xenix 3.2 |
—95 | Think C 5.02(Macintosh) |
—86 | IBM PowerPC AIX 3.2.5 |
—85 | Sun Sparc cc(K&C编译器) |
—63 | gcc,HP_UX 9.0,Power C 2.0.0 |
4 | Sun Sparc acc(K&C编译器) |
21 | Turbo C/C++ 4.5 |
22 | FreeBSD 2.1 R |
30 | Dec Alpha OSF1 2.0 |
36 | Dec VAX/VMS |
42 | Microsoft C 5.1 |
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
以上就是对操作符的理解,如果有不对不妥之处欢迎大家留言交流!!!