一 、算术操作符
+、-、*、/、%(取余,只适用于整型运算)
PS:其中除法分为整数除法和浮点数除法,整数除法得到的是整数的商,浮点数除法可以得到小数。浮点数除法只需要除数和被除数任一为浮点数就行;
注意:类似于7.0这样的小数,编译器会默认为是double类型,如果要定义为float类型需要自己加f;
二 、移位操作符和位操作符
>>、<< 移位操作符:操作二进制位
&(按位与)、^(按位异或,不同为1,相同为0)、| (按位或) 位操作符:操作二进制位
注意:1)移位操作符的操作数只能是整数;2)在内存中整型存储的形式是补码,因此位操作符是对补码进行操作。3)移位操作负数(a>>-1)是标准未定义行为,结果未知;
// 右移操作符>>
int a = 15;
int b = a >> 1; /此处将a的内存空间的中的bit大小的数据往右移动1个bit距离
//整数的二进制表示有三种码:原码、反码、补码
//正整数的原码==反码==补码
//15二进制:0(符号位0为正数,1为负数) 000 0000 0000 0000 0000 0000 0000 1111
//b : 0000 0000 0000 0000 0000 0000 0000 0111 (7)
//算数右移:右边丢弃,左边补原来的符号位;
//逻辑右移:右边丢弃,左边补0; 一般编译器都是算数右移
int a = -15;
int b = a >> 1; /此处将a的内存空间的中的bit大小的数据往右移动1个bit距离
//负整数的原码、反码和补码需要计算
//-15二进制原码:1000 0000 0000 0000 0000 0000 0000 1111
//-15二进制反码:1111 1111 1111 1111 1111 1111 1111 0000 (符号位不变,其它位按位取反)
//-15二进制补码:1111 1111 1111 1111 1111 1111 1111 0001 (反码+1)
//右移1补码:1111 1111 1111 1111 1111 1111 1111 1000
//右移1反码:1111 1111 1111 1111 1111 1111 1111 0111
//b:右移1原码:1000 0000 0000 0000 0000 0000 0000 1000 (-8)
// 左移操作符<<
左边丢弃,右边补零
// 位操作符的功能例子(按位异或)
// 不创建临时变量,交换两个变量,且不对两个变量相加使得有可能发生的溢出产生
int main()
{
a = a ^ b;
b = a ^ b;
a = a ^ b;
return 0; // 讲解:a^a = 0,a^0 = a,异或有交换律;
}
三、 赋值操作符
=、+=、-=、*=、/=、&=、^=、|=、>>=、<<=
例子:a = a + 5;等价于a += 5;
四、单目操作符
单目操作符的意思是只有一个操作数,即对一个数进行操作;
!(逻辑反操作)、-(负值)、+(正值)、&(取地址)、sizeof(如果对象是变量可以不用括号,如果是类型就一定需要括号)、(~(按位取反)、--、++、*(间接访问操作符)、(类型)类型强制转换操作符
拓展:int arr[10]的类型为“int [10]”;
五、 关系操作符
==、!=、>、>=、<、<=
注意:只能应用到适合的类型之间,比如结构体和字符串就无法比较;
六、逻辑操作符
&&、||
注意:&&操作符从左往右,有一个为假后面就不会算了;||操作符从左往右,有一个为真,后面就不会算了;
七、条件操作符(三目操作符)
exp1 ? exp2 : exp3 exp1位真,执行exp2,exp1为假,执行exp3
八、逗号表达式
exp1,exp2,...,expn 逗号表达式是从左向右依次执行的,最后返回的是最后一个表达式的结果
九、下标引用、函数调用和结构体成员
[](下标引用)、()函数调用、结构体调用:.(结构体名.结构体成员名)、->(结构体指针->结构体成员名)
说明:1)arr[2]中arr、2是[]的两个操作数;2)strlen("abc")中strlen、"abc"是()的两个操作数;
printf("%s %s %.1f \n", s1.name, s1.author, s1.price);
//%.1f是只打印小数点后一位的float
printf("%s %s %.1f \n", (*ptr).name, (*ptr).author, (*ptr).price);
printf("%s %s %.1f \n", ptr->name, ptr->author, ptr->price);
十、表达式求值
- 表达式求值顺序是由操作符的优先级和结合性决定的;
- 表达式求值过程中操作数可能需要转换成其它类型;
10.1 隐式类型转换
C语言的整型计算是以默认的普通整型(缺省整型)精度进行的,因此如果是短整型或者字符进行整型计算时,短整型或者字符会被转换成普通的整型,这种现象被称为整型提升。有符号数提升按照符号位补全,无符号数提升全补零;
整型提升的意义:CPU中通用寄存器长度是4个字节,因此无法直接实现对1个字节的计算,因此小于4个字节的变量都要先转换成int或者unsigned int才被传到CPU中计算。
char a = 5;
\\ 5补码:0000 0000 0000 0000 0000 0000 0000 0101
\\ a存:0000 0101
char b = 127;
\\ 127补码:0000 0000 0000 0000 0000 0000 0111 1111
\\ b存:0111 1111
char c = a + b;
\\ a + b:0000 0000 0000 0000 0000 0000 1000 0100
\\ c存:1000 0100
printf("%d\n", c);
\\ %d —— 十进制的形式打印有符号的整型;%u —— 十进制的形式打印无符号的整型;
\\ c提升补码:1111 1111 1111 1111 1111 1111 1000 0100
\\ c提升反码:1111 1111 1111 1111 1111 1111 1000 0011
\\ c提升原码:1000 0000 0000 0000 0000 0000 0111 1100 (-124)
10.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非一个操作数转换为另一个操作数的类型,否则操作就无法进行。
转换等级:long double、double、float、unsigned long int、long int、unsigned int、int
右边会转换为左边
注意:转换是临时的,变量本身的属性不变。
10.3 操作符属性
操作符有优先级,比如*优先于+-等,此处不一一列举。需要注意的是,就算按照操作符的优先级写出了相应的代码也可能会出现意想不到的问题。
\\ 例子1
a + ++a;
\\ 操作符++的优先级高于+毋庸置疑会先执行++a
\\ 但是第一个a是何时准备好的,就要看编译器的设置
\\ 有可能是++a执行之后也可能是执行之前准备好
\\ 因此写代码的时候要尽量避免这种情况的发生
\\ 例子2
int test()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = test() - test() * test();
return 0;
}
\\ 此代码一定是乘号先计算,但先调用哪个test()就无法确定,answer的值在不同编译器也不同
总结:写表达式的时候,要确定有唯一的计算路径,尽量简化代码;