+ - * / %
/ 分子分母如果都是整数 那么结果必然是整数 ,如果分子分母至少有一个浮点数 那么结果就是浮点数 这里面涉及到的算术转换在下面会提到
% 取模操作符,取余数,这个操作符只能作用与整数 下面我用代码阐述一下我介绍的上述两点
#include<stdio.h>
int main() {
int a = 3;
int b = 2;
float c = 0.2;
int d = a / b;
int e = a % b;
printf("%d %d ", d, e);
return 0;
} 这段代码输出的应该是 1 1
这样写程序运行的很良好 但是如果这样

程序虽然不报错 但是会出现警告 实际上算出的浮点数 因为上述代码是非要把给结果f设置成int类型 这样如果数字大了 可能会造成精度丢失 这是一种不好的操作习惯 我们要避免
但如果把取模操作符中某一个数字换成浮点数 那么程序就会报错
在讲下列操作符之前,我们得先知道一个概念,每个数字都有三个码——原码,反码,补码,数字在电脑中储存的二进制位都是该数字的补码 但是电脑上面显示的却是数字的原码
三者关系如下
以int类型整数为例,因为int是四个字节 32个比特位 所以由32位组成,最前面的一位是符号位,0为正,1为负
如果正数 eg:1 它的原码,反码,补码都是00000000000000000000000000000001
负数不一样 eg -2 原码:10000000000000000000000000000010
反码: 111111111111111111111111111111111101 除符号其他全部取反
补码: 111111111111111111111111111111111110 反码+1等于补码
下述操作符若无明显提示 那么电脑操作的就是补码
2.移位操作符
<<
左移操作符
>>
右移操作符
注:移位操作符的操作数只能是整数。
操作的对象都是二进制位
eg:int a = 4;
它的二进制位:00000000000000000000000000000100(补码);
移位规则:
左边抛弃、右边补0
上述的4可能不明显 我们换成15
15的二进制位:
00000000000000000000000000001111
我们把这个左移两位:变成了00000000000000000000000000111100
现在这个数字是
我们看看代码具体是怎么操作的

再看一个图加深一下理解

>> 右移操作符
移位规则:
首先右移运算分两种:
1.
逻辑移位
左边用
0
填充,右边丢弃
2.
算术移位
左边用原该值的符号位填充,右边丢弃
至于为什么有两种?
因为整数分为有符号整形和无符号整形
有符号整型右操作符实行的是算术移位 就是当把数字的二进制位补码向右移动是 最左边(最高位)的数字是0 那么左边全补0 最左边的数字(最高位)是1,那么左边全补1
而无符号整形右移补的都是0.
什么叫有无符号整形?
eg:-1 的二进制位 11111111111111111111111111111111补码
下面代码我列举了-1分别是有符号整型和无符号整型在都向右移动两位的区别
第一个-1我用的是有符号整型 int
第二个-1我用的是无符号整型unsigned 区别很明显啦
4. 位操作符
&
//
按位与
|
//
按位或
^
//
按位异或
注:他们的操作数必须是整数。
&
//
按位与:
两个数的二进制位只有同时为1时候才为1,不同的话就为0
|
//按位或:
两个数的二进制位只有同时为0时候才为0,不同的话就为1,同时为1也是1
^
//
按位异或:
两个数相同为0,相异为1
题目:
不能创建临时变量(第三个变量),实现两个数的交换。
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
思考一下这两个题目 我会在下一篇博客阐述这两题的具体解法
5.复合赋值符
其实就是赋值操作符的延申
eg: a = a+1;可以写成a+=1;a = a*1写成a*=1;
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^= 了解即可
!
逻辑反操作
-
负值
+
正值
&
取地址
sizeof
操作数的类型长度(以字节为单位)
~
对一个数的二进制按位取反
--
前置、后置
--
++
前置、后置
++
*
间接访问操作符
(
解引用操作符
)
(
类型
)
强制类型转换
先阐述!与~
在c语言中,!和~均表示取反,这两个的区别在于:
- ! :代表逻辑取反,即:把非0的数值变为0,0变为1;
- ~ :表示按位取反,即在数值的二进制表示方式上,将0变为1,将1变为0;
至于取地址& 与sizeof 我在这就不过多阐述了 在我之前的博客都有详解
还有一个就是前置++与后置++的问题了
个人理解:
前置++:先++后使用
后置++:先使用后++
减法也是一样的
eg:
//
后置
++
和
--
#include <stdio.h>
int
main
()
{
int
a
=
10
;
int
x
=
a
++
;
int z = ++a;//先对a增加1变成11,然后赋值给z
//
先对
a
先使用,再增加,这样
x
的值是
10
;之后
a
变成
11
;
int
y
=
a
--
;
//
先对
a
先使用,再自减,这样
y
的值是
11
;之后
a
变成
10
;
return
0
;
}
* 间接访问操作符(解引用操作符)
这个我目前理解为是跟指针有关的
eg:
#include<stdio.h>
int main() {
int a = 0;
int* p = &a;//那么这边p就是a的地址 *p代表的就是a
*p = 20;//*存在告诉编译器p是一个指针变量
printf("%d\n", a);
return 0;
}
还有二级指针 三级指针 我在后续会介绍
(类型) 强制类型转换:强制类型转换是把变量从一种类型转换为另一种数据类型。
强制类型转换算是C语言中常见常考的一项内容,如对于类型处理不好,将会产生错误结果。对于某些类型的转换编译器可隐式地自动进行,不需人工干预,称这种转换为自动类型转换;而有些类型转换需要编程者显式指定,通常,把这种类型转换称为强制类型转换
计算机硬件进行算术操作时,要求各操作数的类型具有相同的大小(存储位数)及存储方式。
eg;#include<stdio.h>
int main() {
double a = 6.7;
int b = 2;
int c = b + (int)a;
printf("%d", c);
return 0;
}
如果不加(int)那么这个b会先转换成浮点型 然后在复制给整型c 这样的话可能会出现某些误差或者警告,而加了(int)后计算机会将a识别成整型 不会产生什么误差
而eg:int (x+y)表示整型
(double) x表示浮点型
至于在计算机中数字是如何计算的 这里面涉及到
表达式求值
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
C
的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为
整型
提升
。
整型提升的意义
:
表达式的整型运算要在
CPU
的相应运算器件内执行,
CPU
内整型运算器
(ALU)
的操作数的字节长度
一般就是
int
的字节长度,同时也是
CPU
的通用寄存器的长度。
因此,即使两个
char
类型的相加,在
CPU
执行时实际上也要先转换为
CPU
内整型操作数的标准长
度。
通用
CPU
(
general-purpose CPU
)是难以直接实现两个
8
比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于
int
长度的整型值,都必须先转
换为
int
或
unsigned int
,然后才能送入
CPU
去执行运算
总结:如果是在计算中小于int的类型计算机会先将其转换成int类型 在进行下面的计算,
//
实例
1
char
a
,
b
,
c
;(换成short也是一样)
...
a
=
b
+
c
;
b
和
c
的值被提升为普通整型,然后再执行加法运算。
那么整型如何提升 怎么证明整型提升存在呢?
整形提升是按照变量的数据类型的符号位来提升的
//
负数的整形提升
char
c1
= -
1
;
变量
c1
的二进制位
(
补码
)
中只有
8
个比特位:
1111111
因为
char
为有符号的
char
所以整形提升的时候,高位补充符号位,即为
1
提升之后的结果是:
11111111111111111111111111111111
//
正数的整形提升
char
c2
=
1
;
变量
c2
的二进制位
(
补码
)
中只有
8
个比特位:
00000001
因为
char
为有符号的
char
所以整形提升的时候,高位补充符号位,即为
0
提升之后的结果是:
00000000000000000000000000000001
//
无符号整形提升,高位补
0
证明整型提升:
#include<stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
输出:c
因为a和b发生了整型提升 且a b是有符号类型 所以整型提升后变成的是负数
a在内存中二进制是
1100 0110 通过上述对整型提升的阐述可以看出提升后a
的第一位是1 是负数 所以才这样讲
但c不用发生任何变化 因此可证明
再举个例子:
int
main
()
{
char
c
=
1
;
printf
(
"%u\n"
,
sizeof
(
c
)); 输出:1 4 4
printf
(
"%u\n"
,
sizeof
(
+
c
));
printf
(
"%u\n"
,
sizeof
(
-
c
));
return
0
;
}
sizeof()括号内部不会进行计算eg:
#include <stdio.h>
int main()
{
int i;
i = 10;
printf("%d\n", i);
printf("%d\n", sizeof(++i));
printf("%d\n", sizeof(i++));
printf("%d\n", i);
return 0;
}
输出:10 4 4 10
如果sizeof的操作数是一个表达式的话,这个表达式时不会被计算的。
sizeof当预处理看就行了,它后面括号里的东西,根本不求值,只根据C的一堆规则判断结果类型,然后返回结果类型的大小
上述我们了解到char类型和short类型需要转换成int 那么之后呢?——算数转换
long double
double
float
unsigned long int
long int
unsigned int
int
如果没有特别说明 在两种不同类型(类型大小大于等于int) 遵循下面的自动转化成上面的类型
如:
int a = 9;
double c = 6.7;
b = a+c;//这边b的类型就是double 就理解为double的精度更高吧
赋值中的类型转换
当赋值运算符两边的运算对象类型不同时,将要发生类型转换, 转换的规则是:把赋值运算符右侧表达式的类型转换为左侧变量的类型。
如
double a = 2.5;
int b = a;//b的值为2 计算机自动省略小数点后面的数 但是这会造成精度丢失 不建议使用
现在我们拿到了数字类型都知道了 但是计算是还应该考虑一个东西
复杂表达式的求值有三个影响的因素。
1.
操作符的优先级
2.
操作符的结合性
3.
是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级

记忆方法:
记住一个最高的:构造类型的元素或成员以及小括号。
记住一个最低的:逗号运算符。
剩余的是一、二、三、赋值。
意思是单目、双目、三目和赋值运算符。
在诸多运算符中,又分为:
算术、关系、逻辑。
两种位操作运算符中,移位运算符在算术运算符后边,逻辑位运算符在逻辑运算符的前面。再细分如下:
算术运算符分 * , / , % 高于 + , - 。
关系运算符中,〉,〉 = , < , <= 高于 == ,! = 。
逻辑运算符中,除了逻辑求反(!)是单目外,逻辑与( && )高于逻辑或( || )。
逻辑位运算符中,除了逻辑按位求反(~)外,按位与(&)高于按位半加(^),高于按位或( | )。
这样就将15种优先级都记住了,再将记忆方法总结如下:
去掉一个最高的,去掉一个最低的,剩下的是一、二、三、赋值。双目运算符中,顺序为算术、关系和逻辑,移位和逻辑位插入其中。(其实我直接也不能完全记住 所以在写代码的过程中还是加小括号吧 hh)
知道操作符的结合性以及优先级 计算机的结果未必·一定
eg:
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
eg:c + --c;
注释:同上,操作符的优先级只能决定自减
--
的运算在
+
的运算的前面,但是我们并没有办法得
知,
+
操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义
的。
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0; }
再举个例子
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0; }
在不同编译器中测试结果:非法表达式程序的结果

总结
:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题 的(所以遇到这种表达式我们不必纠结 它本身就是错的 hh)