【知识点1】运算符与表达式
1 运算符
用来进行某种运算的符号
几目运算符:这个运算符需要几个操作数
单目运算符:该运算符带一个操作数
双目运算符:该运算符带两个操作数
三目运算符:该运算符带三个操作数
结合性:这个式子是从右往左还是从左往右运算的
a + b 和 b + a在数学上是一样的 但是在c语言里面含义是不一样的
例如:假设 i = 5
(i++) + i; //12
i + (i++); //11
优先级:谁先运算谁后运算的问题
单目运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 条件运算符 >
赋值运算符 > 逗号运算符
有括号先算扩号,没有括号再按优先级走
(1)算术运算符:进行算术的一个运算符
++ --:单目运算符
+ - * / %:双目运算符,结合性是从左算到右
当然乘除大于加减
3 + 5
5 - 2
3 * 4
6 / 4 -> /在这里有两个意思:当操作数都是整数的时候是取整的意思
当操作数是小数的时候就是除以
6.0 / 4 ->这个时候就是除以 结果就是1.5
不一定结果是1.5我们就可以去拿这个1.5
当我们的结果是一个整形的变量去保存的那么直接丢弃小数部分
int a = 6.0 / 4; -> 这个时候只会保留整数部分
%:只有两个操作数都是整数的时候才有意义
取余 -> 求模
上述的运算符:不一定非得全部写整数才可以
(int)6.2 % (int)4.1 ->实际上用的都是整数
c语言里面如果在一个数(变量也可以)前面打一个括号,然后写一个类型
表示将后面的这个数强制转换成这个类型 -> 强转
强转性的问题在后面会看的越来越多,所以现在一定要开始适应
有的时候强制转换可能会和我们想的不一样
float a = 3.1;
int b = (int)a;//如果类型不同是不能进行赋值
b = 3;
int * p = (int *)&a;//还没有讲到指针 这里不需要你们掌握
//我们只需要知道和我们想的可能不一样
*p != 3; ->这个玩意就是一个很奇怪的值
(2)单目运算符:++ --
++ -- 我们有前++ -- 也有后++ --
他们的语义是不一样的
前++ --:算了这个结果之后直接改变这个变量的值
后++ --:算了这个结果之后是先将没有算结果的时候的值返回然后再改这个变量的值 //(重点!!)
int a = 3;
int b = a++; -> 先将a的值返回3 然后再将a的值+1
这个式子之后 b的值为3 a的值为4
int c = ++a; -> 先将a的值自加1,然后再取a的值赋值给c
这个c的值为5,a的值为5
为了让你们更清楚的认识这个++ --;再这里我超一下纲,按c++里面的实现方式给你们讲
c++里面的这个算符运算非常的直接,实际上运算符是用函数实现的
int &function(int &a)//这个相当于是前++ 里面的a就相当于传进来的那个a
{
a += 1;
return a;
}
int function(int &a,int)//这个相当于是后++
{
int b = a;
a += 1;
return b;
}
根据代码显示:前++与后++是存在代码上面的区别的
一个是返回自加之后自己本身的值
一个是返回保存原先的那个值,没有自加之前的那个值
并且前++的效率要高于后++
上述的代码操作c语言里面是有问题的,所以不要尝试
void function(int a,int b,int c,int d,int e)
{
printf("%d %d %d %d %d\n",a,b,c,d,e);
}
int main()
{
int i = 10;
function(i++,i--,i,++i,--i);//函数调用也是一个运算符
//结合性是从右往左
//--i ++i都是拿i的值
//i本身也是拿i的值
//i++,i--先拿i的值在运算
//上面的函数调用后面的给实参实际上要等全部的东西处理完之后才会一次性的给过去
//那么i,++i,--i这三个玩意儿就会一次性的都拿i的值
//就是最后i的值
i--的时候首先拿算这个式子的时候i的值 因此是10
i++的时候首先拿算这个式子的时候i的值 因此是9
}
上述的代码很神奇,可以做为脑筋急转弯,但是不要写在代码里面去了
你看不懂,别人也看不懂,但是不代表它是错的
别人看不懂那么就会认为你这个人写的代码有问题
那么你的工作就很难保障,因此不到万不得已不要用
(3)关系运算符:
用来判断两个东西的关系的运算符
< <= > >= == !=
双目运算符,从左到右结合
关系表达式会有两种值:关系成立(真值 1/非0) 关系不成立(假值 0)
如:
3 > 4 值为0
3 <= 4 值为1
5 > 4 > 3 值为0
5 > 4 > 3是合法的,也就是c语言里面是可以使用的
但是和我们数学上面的意思不一样,数学上面没有问题
(5 > 4) > 3 -> 5 > 4就会有一个值为1
再次判断 1 > 3 是假的值为0
因此这种形式也尽量不要写
说明一下:
有很多同学在写==的时候很有可能会少一个=号
如:
if(a == 4)
{
}
但是很有可能会写成
if(a = 4)//将4的值赋值给a并且判断a = 4是否是真的
//a = 4的值是右值
{
}
语义完全改了,因此建议写成
if(4 == a)//如果你少写=号了会直接报错
{
}
(4)逻辑运算符:表示并且 或者 不是这种意思的
! 逻辑非 单目运算符 真变假 假变真
&& 逻辑与 双目运算符 从左到右 并且的意思,两者都是真的那么才是真的
|| 逻辑或 双目运算符 从左到右 或者的意思,两者都是假的才是假
eg:
a = 4;b = 5;
a && b -> 1
a || b -> 1
!a || !b -> 0
!a && b -> 0
练习:
int a,b,c,d,m,n;
a = 1,b = 2,c = 3,d = 4;
m = 1,n = 1;
(m = a > b) && (n = c > d);
printf("%d %d\n",m,n);
上面的代码最后的打印是 0 1
也就是发现(n = c > d)好像没有走
为神马?
我们的思维已经知道最后的结果了,那么我还有必要往下面走吗?
(m = a > b) && (n = c > d);
(m = a > b)是假的 因此值为0
而0 && x是不是都是假的
因此前面的这个玩意儿如果为假了,那么我可就可以确定最后的结果一定是假的
因此后面的这些东西就没有必然往下了 -> 惰性运算
c语言里面的运算符是惰性运算 -> 知道结果之后就不会往后了
1 a && b && c
只有a为真的时候才会去继续往后面走
只有a b 都是真的那么才会去判断c
如果a为假了 后面的全部都不会判断了
2 a || b || c
只有a为假才会去判断b
只有ab都是假的时候才会去判断c
练习:
闰年的定义:能被4整除不能被100整除,或者能被400整除
那么这个年份就是闰年
输入一个年份,判断是否为闰年
是打印yes 否则打印no
使用惰性运算
int y;
scanf("%d",&y);//在终端键入年份 如1990 回车就会输入到y里面去了
if((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0))
{
yes
}
else
{
no
}
(5)位运算:
位运算是按bit位来进行的运算,运算符有如下
& 按位与
| 按位或
^ 按位异或
~ 按位取反 单目
<<按位左移
>>按位右移
除了~取反以外都是双目的
结合性都是从左往右结合
位运算的操作符里面的操作数必须是整形
~ 单目运算符
规则:底层实现 0变1 1变0的操作
每一个bit都还要进行
int a = ~(-3);
printf("%d %u\n",a,a);
1 我们需要看-3的存储形式
-3 -> 取绝对值为3
00000000 00000000 00000000 00000011
~ 11111111 11111111 11111111 11111100
+1 11111111 11111111 11111111 11111101
2 在对-3进行取反
~ 11111111 11111111 11111111 11111101
00000000 00000000 00000000 00000010
3 将结果存入到a里面 因此a的值为2
int a = ~(3);
printf("%d %u\n",a,a);
1 我们需要看3的存储形式
00000000 00000000 00000000 00000011
2 在对-3进行取反
~ 00000000 00000000 00000000 00000011
11111111 11111111 11111111 11111100
3 将结果存入到a里面 因此a的值为2
11111111 11111111 11111111 11111100
%d ->前面是负号 因此我们需要按照负数的形式进行处理
-1 11111111 11111111 11111111 11111011
~ 00000000 00000000 00000000 00000100
加一个负号就可以得到最终的值
-4
%u 11111111 11111111 11111111 11111100
全是值
2 ^ 32 - 4
& 按位与:双目运算符
规则:全都是1才是1
a b a & b
0 0 0
0 1 0
1 0 0
1 1 1
int a = 3;
int b = 4;
a & b = ?
0000 0011
& 0000 0100
-------------
0000 0000
在寄存器操作里面有两个非常重要的操作
置位 -> 打开某一个功能,寄存器某一个bit变成1
复位 -> 将某一个功能关闭,实际上就是将这个寄存器的某一个bit变成0
假设有一个变量a,我想要将3bit复位,其他的bit不要动
假设这个a是8bit的
如何操作
a xxxx xxxx
& 1111 0111 -> ~0000 1000 -> ~(1 << 3)
--------------
xxxx 0xxx
a = a & 0xf7
=》 a = a & (~(1 << 3))
x & 1 = x;
x & 0 = 0;
我有一个需求,我希望知道这个3bit上面这个功能是否打开了
也就是3bit上面是1还是0,请问代码应该如果操作,按8bit算
xxxx xxxx
我们如果希望判断3bit上面的 我们就需要排除其他的干扰
那么我就需要让其他的上面变成0
0000 x000
如果x为1 那么就是真
x为0 那么就是假的
xxxx xxxx
& 0000 1000
-------------
0000 x000
if(a & (1 << 3))
{
//x为1 功能打开
}
else
{
//x为0 功能没有打开
}
在考虑效率的情况下,实现对一个unsigned int a里面的1进行计数
也就是如果高效的去判断a里面有多少个1
不需要你写代码
b = a - 1;
c = a & b; ->一直循环到与出0为止
| :按位或:双目运算符 结合性是从左到右
规则:全部为0才是1 只要有一个1就是1
a b a | b
0 0 0
0 1 1
1 0 1
1 1 1
假设有一个变量a,我想要将3bit置位,其他的bit不要动
假设这个a是8bit的
如何操作
xxxx xxxx
| 0000 1000
-------------
xxxx 1xxx
a = a | (1 << 3)
x | 0 -> x
x | 1 -> 1
|上0为保留自己本身,|上1对这个bit进行置位操作
^ :异或运算 双目运算符 操作数也是两个
规则:相同为0 不同为1
a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0
假设有一个变量a,我想要将3bit进行取反操作,其他的bit不要动
假设这个a是8bit的
如何操作
xxxx xxxx
^ 0000 1000
-------------
xxxx (~x)xxx
a = a ^ (1 << 3)
x ^ 0 = x
x ^ 1 = ~x
保留自己本身异或0 取反操作异或1
练习:
交换两个变量的值
int a = 3;
int b = 5;
//何种操作之后
a = 5 b = 3;
请写出至少两种以上的方法
方法1:有中间值
int t;
t = a;
a = b;
b = t;
方法2:你们可能感觉是正确的
a = a + b;//可能溢出
b = a - b;
a = a - b;
方法3:连做三次异或运算
a = a ^ b;
b = a ^ b;
a = a ^ b;
就可以实现换位置?
位运算是当位运算,只管当前的那个bit,不会出现进位与借位
那么我们如果让bit位全部互换位置 那么这两个数是不是就换了位置?
a b a=a^b b=a^b a=a^b
0 0 a=0 b=0 a=0
1 1 a=0 b=1 a=1
0 1 a=1 b=0 a=1
1 0 a=1 b=1 a=0
上面真值表显示 a与b在这个bit位上面全部互换的位置
那么如果是全部的bit呢?是不是也是一样
<< :按位左移 双目运算符 让这个数据整体往左边移动n个bit
a << n; ->让这个a整体往左边移动nbit
左移之后高位移出去了直接舍弃就可以了,你也没有办法对他进行保存
低位就会留下空缺,补0就可以了,直接在低位补0
1 << 5 -> 00000000 00000000 00000000 00000001
<<5 00000000 00000000 00000000 00100000
假设这个值高位是有1的
01000000 00000000 00000000 00000001
<<1 10000000 00000000 00000000 00000010
<<1 00000000 00000000 00000000 00000100 //前面出去这个1我们就管不了了 直接不要了
>> :按位右移运算 双目运算符 让这个数据整体往右边移动n个bit
a >> n; 让这个a整体往右边移动nbit
规则跟左移有点不一样
有符号的:低位直接舍弃不要,高位空缺补符号位
无符号的:低位直接舍弃不要,高位空缺补0
int a = -1;
a = a >> 31;
|-1| 00000000 00000000 00000000 00000001
~ 11111111 11111111 11111111 11111110
+1 11111111 11111111 11111111 11111111
>>31 11111111 11111111 11111111 11111111
unsigned int a = -1;
a = a >> 31;
11111111 11111111 11111111 11111111
>>31 00000000 00000000 00000000 00000001
右移过后这个a就变成了1
unsigned int a =xxxxx;
小练习:求这个a里面有多少个1
1 常规做法
int i;
int count = 0;
for(i = 0;i < 32;i++)
{
//判断每一个bit上面是否是1就可以了
if(a & (1 << i))
{
count++;
}
}
printf("%d\n",count);
2 做减一运算
见 3.c
软件很有可能会在某一个地方加一些字符 但是看不出来
编译的时候很有可能会出问题
我们去linux里面去查看就可以了
gedit 2.c
(6)赋值运算符:双目运算 从右往左,优先级是很低的,只比逗号运算符高一点
a = 5 + 3; ->首先计算 5 + 3 = 8然后再讲8这个值赋值给a代表的这个可写的地址
5 = 5;//不行 有一个地址非常特殊 0地址 ->也就是NULL
b = a = 6;//a b最后的值都是6
//赋值运算符连接在一起的这个式子我们赋值表达式
//赋值表达式自己本身有一个值(右值) 这个值我们就可以判断真假
//非0就是真的 0才是假的
赋值运算符和和其他的一切运算符组合起来使用
我们复合赋值运算符
+=
-=
*=
/=
.....
<<=
>>=
|=
&=
^=
上述的意思就是将左边的那边地址拿到右边来参与复合的那个运算符的运算
a += 1; -> a = a + 1;
a |= 0x01; -> a = a | 0x01;
(7)条件运算符
?: 三目运算符
使用形式:表达式1 ? 表达式2 :表达式3;
表达式1如果成立则去表达式2的值
如果不成立则去表达式3的值
sum = a > b ? a : b ; //取ab之间大的值
//我们可以将上面的式子转换成if的形式去做
注意:少写那些看起来就很难的式子
sum = j++ > i++ ? j++ : i++;//这种式子少写 你表达的意思很不明确
因此尽量不要用
(8)逗号表达式:双目运算符 优先级是最低的,结合性从左到右
使用形式:表达式1,表达式2,表达式3......
先求表达式1的值,在求表达式2的值,再求表达式3的值
这个逗号表达式的整体值是表达式3的值
sum = (a = 5,b = 3,a = a + b); ->最后sum的值就是 a = a + b的值 也就是8
int fd = ("/hehe",O_RDWR);
#define ->宏定义 只做替换不做运算
#define Max(a,b) ((a) > (b) ? (a) : (b)) //带参数的宏 了解即可
(9)指针运算符(* &)
(10)求字节运算符(sizeof)
int a;
num = sizeof(a);
sizeof(1);
sizeof(1.1);
(11)分量运算符:取一个整体里面的某一个成员,在结构体里面使用
.
->
(12)下标运算符
取下标 在数组里面使用
[]
int a[3];
a[0] a[1] a[2] a[3](数组越界了,不要操作)
%s去打印一个字符串的时候,它一定要找到\0,如果没有找到
它会无限制的找下去,直到找到\0或者程序出错
(13)强制类型转换运算符
强转
float f = 3.5;
(int)f + 3.4;->6.4
(int)(f + 3.4); -> 6
(14)函数调用运算符
函数调用
【知识点2】表达式是什么?
表达某一个意思的式子
c语言里面用运算符连接起来的式子我们就叫表达式
每一个合法的表达式都会有一个值
表达式都是可以判断真假的
真值是非0
假值为0
注意事项:我们在写代码的时候有的时候搞不清优先级
记得加上万能的括号,那么怎么都不会错
((a > b) || (a < c)) && (d > e)
uns