操作符
算术操作符
+ - * / %
- 除了
%
操作符,其他几个操作符都可以作用于整数和浮点数 - 对于
/
操作符,如果两个操作数都为整数,执行整数除法,只要有浮点数,则执行浮点数除法 %
操作符的两个操作数必须为整数,返回的是整除之后的余数
位操作符
<< 左移操作符
>> 右移操作符 例:num << 2; num >> 2;
~ 按位取反
& 按位与
| 按位或
^ 按位异或
注意:参与运算的数均以 补码 的形式参与运算; 只适用于整型变量和字符型变量。
- 左移操作符:
<<
(需要移动的数字 << 移动的位数)- 按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。
- 数学意义:在数字没有溢出的前提下,对于正数和负数,左移一位都相当于乘以2的1次方,左移n位就相当于乘以2的n次方。
-
右移操作符:
>>
(需要移位的数字 >> 移动的位数)-
算术右移(低位丢弃,高位补原符号位)
- 按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1。
- 数学意义:右移一位相当于除2,右移n位相当于除以2的n次方。(注意是整除)
-
-
逻辑右移(低位丢弃,高位补0)
- 按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补0。
-
正数的算术右移和逻辑右移一致。
-
注意:移位操作符的操作数只能是整数;移动的位数是正整数。
-
按位取反
~
对操作数按二进制的各位取反,即0变1,1变0。
-
按位与
&
(均为1,结果为1)- 参与运算的两个数各对应的二进制位相与,只有对应的两个二进制位均为1时,结果位才为1 ,否则为0。参与运算的数以补码方式出现。
- 按位与可以用于设置字节中的某位为0,其余位为1。
-
按位或
|
(出现1,结果为1)- 参与运算的两个数各对应的二进制位相或,只要对应的一个二进制位有一个为1时,结果位就为1。参与运算的两个数均以补码出现。
- 按位或可以用于设置字节中的某位为1,其余位为0。
-
按位异或
^
(不相同,结果为1)-
参与运算的两个数各对应的二进制位相异或,当两个数对应的二进制位不相同时,结果位就为1;否则结果位为1。参与运算的两个数均以补码出现。
-
按位异或可以用于翻转字节中的相应位,设置字节中的某位为1,其余位为0。
-
例:交换两个变量的值(不能临时创建一个新变量)
int main() { int a = 2; int b = 3; //交换 a = a ^ b; b = a ^ b; a = a ^ b; return 0; //优解 }
int main() { int a = 2; int b = 3; //交换 a = a + b; b = a - b; a = a - b; return 0; //此方法可行,但存在弊端,若a,b数值太大,可能会导致溢出 }
-
赋值操作符
赋值操作符 =
复合赋值操作符 += -= *= /= %= >>= <<= &= |= ^=
复合赋值操作符:对于复合赋值操作符,可以理解为先操作(+ - * / % >> << & | ^)再赋值。
int a = 3;
int b = 2;
a = a + b;
a += b;
//这两种写法表达的意思相同。
注意:=
表示赋值;==
表示判断相等。
单目操作符
! 逻辑非运算符
~ 按位取反
- 负号运算符
+ 正号运算符
++ 自增运算符
-- 自减运算符
& 取地址
* 指针运算符(间接访问操作符(解引用操作符))
(类型) 类型转换运算符
sizeof 长度运算符(操作数的类型长度(以字节为单位))
-
!
操作符-
单目运算符,只有右操作数,结果与右操作数相反。
a !a 0 1 非0 0
-
-
++
和--
运算符- 在使用变量的同时使变量的值增加1或者减少1;
- 前缀表示法(先变再用):++i ,–j 意义是先修改变量的值后再用修改后的值。
- 后缀表示法(先变再用):i++ ,j-- 意义是先使用变量的值后再修改变量的值。
- 在使用变量的同时使变量的值增加1或者减少1;
-
sizeof
操作符-
测试出数据类型或表达式所占用的内存字节的大小;
-
sizeof 在测试表达式的时候,不会真正计算表达式,而是仅仅给出该表达式结果的类型所占用的内存大小
short s = 5; int i = 3; printf("%d", sizeof(s = i + 2)); //编译结果为2
-
sizeof 的括号内如果为变量,则括号可以省略——sizeof 是操作符,而不是函数。
-
-
-
&
操作符和*
操作符- 这两个操作符在指针章节详细说明。
-
(类型)强制类型转换
-
把变量从一种类型转换为另一种数据类型;
-
int a = (int)3.14; //强制将浮点型数据转换为整型数据
-
关系操作符
> >= < <=
== 等于运算符
!= 不等于运算符
运算符 | 含义 | 作用 |
---|---|---|
< | 小于 | 左操作数小于右操作数,返回1;否则返回0 |
<= | 小于等于 | 左操作数小于等于右操作数,返回1;否则返回0 |
> | 大于 | 左操作数大于右操作数,返回1;否则返回0 |
>= | 大于等于 | 左操作数大于等于右操作数,返回1;否则返回0 |
== | 等于 | 左操作数等于右操作数,返回1;否则返回0 |
!= | 不等于 | 左操作数不等于右操作数,返回1;否则返回0 |
逻辑操作符
&& 逻辑与
|| 逻辑或
操作符 | 含义 | 作用 |
---|---|---|
&& | 逻辑与 | 只有左右操作数都为非0(逻辑真),返回1(逻辑真) |
|| | 逻辑或 | 只有左右操作数都为0(逻辑假),返回0(逻辑假) |
注意:在逻辑表达式中,并不是所有的逻辑运算符都会被执行。只要在计算过程中,能明确得出最终结果的值,那么求值的过程立即结束,这种现象称为逻辑运算的短路。例:
#include <stdio.h>
int main() //逻辑运算的短路
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;//当a++的值在表达式中为0,由于后面是逻辑与,整个表达式的值一定为0,则后面的表达式不需要计算。
// i = c++ || ++b || d++;//当c++的值在表达式中为3,由于后面是逻辑或,整个表达式的值一定为1,则后面的表达式不需要计算。
printf("%d %d %d %d", a, b, c, d);
return 0;
} //编译结果:逻辑与:1 2 3 4 ;逻辑或:0 2 4 4
条件操作符
exp1 ? exp2 : exp3
- 条件操作符是C语言中唯一一个三目操作符;
- 运算过程:如果 exp1 的值为真(非0),那么整个条件表达式的值为 exp2 的值,否则为 exp3 的值。
逗号表达式
,
- 逗号表达式按自左向右的顺序依次计算各表达式的值**(一定要计算),并以最右端的表达式的类型与值作为逗号表达式的类型和值。**
下标引用、函数调用、结构成员
[] 下标引用
() 函数调用
-> 结构体指针运算符
. 结构体成员运算符
-
[]
下标引用操作符-
操作数:一个数组名+一个索引值
-
int arr[10];//创建数组 arr[9] = 10;//使用下标引用操作符;[]的两个操作数是arr和9
-
-
()
函数调用操作符-
接受一个或多个操作数:第一个操作数是函数名,剩余的操作数是传递给函数的参数。
-
int ADD(int x, int y)//函数定义 { return x + y; } int main() { int a = 10, int b = 20; int ret = ADD(a,b);//函数调用操作符 return 0; }
-
-
.
和->
操作符- 这两个操作符在结构体章节详细说明。
表达式求值
- 表达式求值的顺序一部分是由操作符的优先级和结合性决定的,同样,有些表达式的操作数在求值过程中肯需要转换为其他类型。
显示类型转换
-
需要使用
()
运算符 -
格式:
(类型说明符) 表达式
;括号内的类型说明符就是转换后的数据类型 -
值域小的类型转换为值域大的类型是安全的,而值域大的类型转换为值域小的类型需要注意,可能会舍弃部分数据
隐式类型转化
整型提升
-
C语言的整型算数运算总是至少以缺省整型类型的精度来进行的
-
整型提升:为了获取上述精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型。
-
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8bit字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
-
如何进行整型提升?
整型提升分为有符号和无符号两种。
**有符号的:**整型提升时是按照变量的补码被截断时的最高位是什么进行补位的,如果截断后最高位即最左面的一位数为 1 则在最高位前补 1 ,如果最高位是 0 则在前面补 0 ,补够32位即int类型即可。
**无符号的:**直接在被截断的前面补 0 即可。
-
例:
-
//例一 #include <stdio.h> int main() { char a = 3;//a的补码:00000000 00000000 00000000 00000011 //a为char类型,发生截断,a以00000011存入内存 //发生整型提升:00000000 00000000 00000000 00000011 char b = 127;//b的补码:00000000 00000000 00000000 01111111 //b为char类型,发生截断,b以01111111存入内存 //发生整型提升:00000000 00000000 00000000 01111111 char c = a + b; //a+b:00000000 00000000 00000000 10000010 //赋值给c后,结果被截断:10000010 printf("%d", c); //c发生整型提升:11111111 11111111 11111111 10000010 补码 //将c的补码转化为原码:10000000 0 0000000 00000000 01111110 原码 得到结果:-126 return 0; }//编译结果:-126 //例二 #include <stdio.h> int main() { char c = 1; printf("%u\n", sizeof(c)); printf("%u\n", sizeof(+c)); printf("%u\n", sizeof(-c)); return 0; }//编译结果:1 4 4 //sizeof(+c)和sizeof(-c)中,c参与运算,发生了整型提升,所以是4个字节
-
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的类型转换为另一个操作数的类型,否则操作就无法进行。
也就是说,在包含两种数据类型的任何运算中,值域小的类型会隐式转换为值域大的类型
下列数据类型的值域为向下由大到小
long double;
double;
float;
unsigned long int;
long int;
unsigned int;
int;
警告: 算术转换要合理,要不然会有一些潜在的问题。
函数传参时的隐式转换
在函数调用传参时,char
和short
会被隐式转换为int
,float
会被隐式转换为double
操作符的属性
复杂表达式的求值有三个影响因素:操作符的优先级、操作符的结合性、是否控制求值顺序
-
操作符的优先级
-
操作符的结合性