文章目录
第四章 运算符,表达式和语句
运算符
赋值运算符:=
在C语言中,=并不意味着“相等”,而是一个赋值运算符。下面的赋值表达式语句:
bmw=2002;
把值 2002赋给变量bmw。也就是说,=号左侧是一个变量名,右侧是赋给该变量的值。符号=被称为赋值运算符。另外,上面的语句不读作“bmw 等于 2002”,而读作“把值 2002赋给变量 bmw”。赋值行为从右往左进行。
也许变量名和变量值的区别看上去微乎其微,但是,考虑下面这条常用的语句:
i=i + 1;
对数学而言,这完全行不通。如果给一个有限的数加上1,它不可能“等于”原来的数。但是,在计算机赋值表达式语句中,这很合理。该语句的意思是:找出变量i的值,把该值加1,然后把新值赋值变量i。
在C语言中,类似这样的语句没有意义(实际上是无效的):
2002 =bmw;
因此,在编写代码时要记住,=号左侧的项必须是一个变量名。实际上,赋值运算符左侧必须引用一个存储位置。最简单的方法就是使用变量名。
常见示例
int ex;
int why;
int zee;
int Two=2;
why =42;
zee = why;
ex=TWO *(why + zee);
说明
/*定义整形变量 ex why zee Two,其中Two赋值为2*/
int ex;
int why;
int zee;
int Two=2;
/*
将42 赋值给 变量why
*/
why =42;
/*
将why中的值(42) 赋值给 变量zee
*/
zee = why;
/*
将TWO *(why + zee)中的值 2 *(42 + 42) 赋值给 变量ex
*/
ex=TWO *(why + zee);
加法运算符:+
加法运算符用于加法运算,使其两侧的值相加。
int i;
/* 将 35(12+23)的值赋值给变量i */
i = 12+23
减法运算符: -
减法运算符用于减法运算,使其左侧的数减去右侧的数。
int i;
/* 将 20(21-1)的值赋值给变量i */
i = 21 - 1
+和-运算符可以都被称为二元运算符,即这些运算符需要两个运算对象才能完成操作。
符号运算符:-和+
减号还可用于标明或改变一个值的代数符号
int i = -1;
int a = -23;
int s = -12312;
以这种方式使用的负号被称为一元运算符。一元运算符只需要一个运算对象
正号也可以写上去,但是默认如果不是 负号,则该数为正
乘法运算符:*
符号*表示乘法
/*下面的语句用2.54乘以inch,并将结果赋给cm:*/
int cm,inch = 2;
cm=2.54*inch;
除法运算符:/
C使用符号/来表示除法。/左侧的值是被除数,右侧的值是除数。
整数除法和浮点数除法不同。
浮点数除法的结果是浮点数
而整数除法的结果是整数
整数是没有小数部分的数。这使得5除以3很让人头痛,因为实际结果有小数部分。在语言中,整数除法结果的小数部分被丢弃,这一过程被称为截断。
#include <stdio.h>
int main(void)
{
printf("integer division: 5/4 is %d \n", 5/4);
printf("integer division: 6/3 is %d \n", 6/3);
printf("integer division: 7/4 is %d \n", 7/4);
printf("floating division: 7./4. is %1.2f \n", 7./4.);
printf("mixed division: 7./4 is %1.2f \n", 7./4);
return 0;
}
则会出现以下结果
/*整数*/
integer division: 5/4 is 1
integer division: 6/3 is 2
integer division: 7/4 is 1
/*浮点数*/
floating division: 7./4. is 1.75
/*混合类型*/
mixed division: 7./4 is 1.75
注意
整数除法会截断计算结果的小数部分(丢弃整个小数部分),不会四舍五入结果。混合整数和浮点数计算的结果是浮点数。实际上,计算机不能真正用浮点数除以整数,编译器会把两个运算对象转换成相同的类型。本例中,在进行除法运算前,整数会被转换成浮点数。
size_t类型
C语言规定,sizeof返回size_t类型的值。这是一个无符号整数类型,但它不是新类型。
size_t作为 unsigned int 或unsigned long的别名。这样,在使用sizet类型时,编译器会根据不同的系统替换标准类型。
求模运算符:%
求模运算符只能用于整数,不能用于浮点数。
求模运算符给出其左侧整数除以右侧整数的余数。
例如,13%5(读作“13求模5”)得3,因为13比5的两倍多 3,即 13除以5的余数是3。
负数求模如何进行?
C99 规定“趋零截断”之前,该问题的处理方法很多。但自从有了这条规则之后,如果第1个运算对象是负数,那么求模的结果为负数:如果第1个运算对象是正数,那么求模的结果也是正数:
递增运算符:++
递增运算符执行简单的任务,将其运算对象递增1。
该运算符以两种方式出现。
第1种方式,++出现在其作用的变量前面,这是前缀模式;
第2种方式,++出现在其作用的变量后面,这是后缀模式。
两种模式的区别在于递增行为发生的时间不同。
#include <stdio.h>
int main(void)
{
int a = 1, b = 1;
int a_post, pre_b;
a_post = a++;
pre_b = ++b;
printf("a a_post b pre_b \n");
printf("%1d %5d %5d %5d\n", a, a_post, b, pre_b);
return 0;
}
a和b都递增了 1,但是,a_post是a递增之前的值,而b pre 是b递增之后的值。这就是++的前缀形式和后缀形式的区别。
递减运算符:–
每种形式的递增运算符都有一个递减运算符与之对应,用–代替++即可:
--count;//前缀形式的递减运算符
count–;//后缀形式的递减运算符
#include <stdio.h>
int main(void)
{
int a = 5, b = 5;
int a_post, pre_b;
a_post = a--;
pre_b = --b;
printf("a a_post b pre_b \n");
printf("%1d %5d %5d %5d\n", a, a_post, b, pre_b);
return 0;
}
方式与递增运算符同理
移位运算符
注意:移位操作符不能移动负数位
左移操作符:<<
移位规则:左边抛弃、右边补零。
右移操作符:>>
移位规则:右边抛弃,左边用该值的原符号位填充
右移操作符同理也是对操作数的二进制位向右进行移动,如果是整数,符号位为0,则左面填充0,如果为负数,符号位为1,则左面填充1
位操作符
&(按位与)
|(按位或)
^(按位异或)
这些操作符的操作数必须为整数
按位与:&
基础概念
按位与是指:参加运算的两个数据,按二进制位进行“与”运算。
如果两个相应的二进制位都为1,则该位的结果值为1;否则为0。
这里的1可以理解为逻辑中的true,0可以理解为逻辑中的false。
按位与其实与逻辑上“与”的运算规则一致。逻辑上的“与”,要求运算数全真,结果才为真。
若,A=true,B=true,则A∩B=true
例如:3&5
3的二进制编码是11(2)。
将11(2)补足成一个字节,则是00000011(2)。
5的二进制编码是101(2),
将101(2)其补足成一个字节,则是00000101(2)
注:
(为了区分十进制和其他进制,本文规定,凡是非十进制的数据均在数据后面加上括号,括号中注明其进制,二进制则标记为2)
内存储存数据的基本单位是字节(Byte),一个字节由8个位(bit)所组成。位是用以描述电脑数据量的最小单位。二进制系统中,每个0或1就是一个位。
3&5:
00000011(3的二进制) | & | 00000101(5的二进制) | =00000001(最终与的结果) |
---|
具体操作
0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
---|---|---|---|---|---|---|---|
& | & | & | & | & | & | & | & |
0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
= | = | = | = | = | = | = | = |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
注意:可以将&(与) 想象成数学中的乘法
C语言中 | 数学中 |
---|---|
0 & 1 = 0 | 0 * 1 =0 |
1 & 1 = 1 | 1 * 1 =1 |
1 & 0 = 0 | 1 * 0 =0 |
0 & 0 = 0 | 0 * 0 =0 |
作用:
清零
若想对一个存储单元清零,即使其全部二进制位为0,只要找一个二进制数,其中各个位符合一下条件:
原来的数中为1的位,新数中相应位为0。然后使二者进行&运算,即可达到清零目的。
例:原数为43,即00101011,另找一个数,设它为148,即10010100,将两者按位与运算:
00101011&10010100=00000000
#include <stdio.h>
int main()
{
int a=43;
int b = 148;
printf("%d\n",a&b);
}
清除高位/低位
若有一个整数a,想要取其中的低字节,只需要将a与8个1按位与即可。
a & b = c
a | 00101100 | 10101100 |
---|---|---|
b | 00000000 | 11111111 |
c | 00000000 | 10101100 |
这样,高位就清零了,低位则保存下来了
保留指定位:
与一个数进行“按位与”运算,此数在该位取1.
例如:有一数84,即01010100,想把其中从左边算起的第3,4,5,7,8位保留下来,运算如下:
01010100 |
---|
& |
00111011 |
= |
00010000 |
#include <stdio.h>
void main()
{
int a=84;
int b = 59;
printf("%d",a&b);
}
按位或:|
两个相应的二进制位中只要有一个为1,该位的结果值为1。借用逻辑学中或运算的话来说就是,一真为真,全假才假
0 | 0 = 0 |
---|
0 | 1 = 1 |
1 | 1 = 1 |
1 | 1= 1 |
作用:
置位
通常我们可把按位“与”操作|作为置位(即将该位置1)的手段,例如我们想要将a数中的第0位和1位置1,而又不影响其它位的现状,可以用一个数0x03,即二进制数00000011去与a数作按位“或”运算:
十六进制 | 二进制 |
---|---|
0x88 | 10001000 |
| | | |
0x03 | 00000011 |
= | = |
0x8B | 10001011 |
注意,这个数除第0、1位为1外,其它各位均为0,操作的结果只会将a数中的第0、1位置0,而a数的其它位不受影响。也就是说,若需要某个数的第n位置1,只需要将该数与另一个数按位相“或”,另一个数除了相应的第n位为1外,其它各位都为0,以起到对其它各位的屏蔽作用。
按位异或:^
按位“异或”运算符^的作用是对运算符两侧以二进制表达的操作数按位分别进行“异或”运算,而这一运算是以数中相同的位(bit)为单位的。异或运算操作的规则是:仅当两个操作数不同时,相应的输出结果才为1,否则为0。
1 | 0 | 1 | 1 | 1 | 0 | 0 | 1 |
---|---|---|---|---|---|---|---|
^ | ^ | ^ | ^ | ^ | ^ | ^ | ^ |
1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
再例如:
0x88 10001000 a数
^
0x81 10000001 异或数
=00001001
交换两个值,不用临时变量
例如:a=3,即11(2);b=4,即100(2)。
想将a和b的值互换,可以用以下赋值语句实现:
a=a^b;
b=b^a;
a=a^b;
a=011; b =100
/*a=a^b;*/
a = a ^ b = 011 ^ 100 = 111 = 7
此时 a = 7 b =4
b = b ^ a = 100 ^ 111 = 011 = 3
此时 a = 7 b =3
a = a ^ b = 011 ^ 111 = 100 = 4
综上,完成交换
使特定的位取反
a = 0x88
例如:我们想让a数中的最低位和最高位取反,只要用0x81,即二进制数10000001去与它作按位“异或”运算,其运算结果同上式。经过操作后,最高位的值已经由1变0,而最低位的值也已经由0变1,起到了使这两位翻转的效果。其它位的状态保持不变。
可以看到,这个数除最低位、最高位为1外,其它各位均为0,操作的结果只会将a数中的第0、7位取反,而a数的其它位不受影响。也就是说,若需要某个数的第n位取反,只需要将该数与另一个数按位相“异或”,另一个数除了相应的第n位为1外,其它各位都为0,以起到对其它各位的屏蔽作用。上面的运算可以用a=a^ (0x81)来表示,也可以用a^=(0x81)来表达。
取反运算符:~
他是一元运算符,用于求整数的二进制反码,即分别将操作数各二进制位上的1变为0,0变为1。
例如:
10010110取反后为01101001。
将1取反为254
~00000001 = 11111110
小注意事项
- & 按位与 如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
- *| 按位或 两个相应的二进制位中只要有一个为1,该位的结果值为1
- ^ 按位异或 若参加运算的两个二进制位值相同则为0,否则为1
- ~ 取反 ~是一元运算符,用来对一个二进制数按位取反,即将0变1,将1变0
- << 左移 用来将一个数的各二进制位全部左移N位,右补0
- >> 右移 将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0
(1)对于+、-、*、/来说,如果操作符的两个操作数其中一个操作数是小数,则执行小数运算,如果都为整数,则进行整数运算。
(2)%的两个操作数必须都为整数,否则会报错,其他四个操作符则都可以。
(3)的语言/、%,0不能当除数或者模数 (这应该是个数学问题)。
优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | – |
() | 圆括号 | (表达式)/函数名(形参表) | – | ||
. | 成员选择(对象) | 对象.成员名 | – | ||
-> | 成员选择(指针) | 对象指针->成员名 | – | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
– | 自减运算符 | –变量名/变量名– | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | – | ||
sizeof | 长度运算符 | sizeof(表达式) | – | ||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 余数(取模) | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | |||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1?表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | – |
/= | 除后赋值 | 变量/=表达式 | – | ||
*= | 乘后赋值 | 变量*=表达式 | – | ||
%= | 取模后赋值 | 变量%=表达式 | – | ||
+= | 加后赋值 | 变量+=表达式 | – | ||
-= | 减后赋值 | 变量-=表达式 | – | ||
<<= | 左移后赋值 | 变量<<=表达式 | – | ||
>>= | 右移后赋值 | 变量>>=表达式 | – | ||
&= | 按位与后赋值 | 变量&=表达式 | – | ||
^= | 按位异或后赋值 | 变量^=表达式 | – | ||
|= | 按位或后赋值 | 变量|=表达式 | – | ||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | – |
逻辑运算符
与或非介绍
& 和 &&
相同点:
这两个逻辑运算符都表示:“与”,一个为False,全为False。
区别:
&:表达式a & 表达式b 在运算时,表达式a和b都会被执行,进行True和False的判断。
&&:表达式a && 表达式b 在运算时,如果表达式a为False,则表达式b不进行运算,直接返回结果,计算效率高。
| 和 ||
相同点:
这两个逻辑运算符都表示:“或”,一个为True,全为True。
区别:
|:表达式a | 表达式b 在运算时,表达式a和b都会被执行,进行True和False的判断。
||:表达式a || 表达式b 在运算时,如果表达式a为True,则表达式b不进行运算,直接返回结果,计算效率高。
!
逻辑非:取相反结果
三目运算符
三目运算符,又称条件运算符,是计算机语言(C,C++,Java等)的重要组成部分。它是唯一有 3 个操作数的运算符,所以有时又称为三元运算符,其实三目运算符和 if / else 条件判断类似。
三目运算符的书写:**
<表达式1> ? <表达式2> : <表达式3>;
返回值:先求表达式 1 的值,如果为真,则执行表达式 2,并返回表达式 2 的结果;如果表达式 1 的值为假,则执行表达式 3,并返回表达式 3 的结果。
举个例子:
// 对于条件表达式
b ? x : y
q先判断条件 b 真假,如果 b 的值为 true ,那么返回表达式 x 的计算结果;否则,计算 y 的值,返回表达式 y 的计算结果。
一个条件表达式绝不会既计算 x,又计算 y(就好比在 if / else 条件判断中,不可能既执行 if 后的代码又执行 else 之后的代码)。
例子:
#include "stdafx.h"
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 0;
//三目运算符返回整数
c = a ? (a + b) : (a - b);//因为表达式1(a=1),条件为真,所以执行表达式2,返回a+b的结果
printf("整数c = %d\n",c);
//三目运算符返回字符串
char *s = 0 ? "条件成立" : "条件不成立";//因为表达式1(0),条件为假,所以执行表达式3,返回字符串"c条件不成立"
printf("字符串s = %s\n", s);
printf("main函数结束!\n");
return 0;
}
/*
输出结果:
整数c = 3
字符串s = 条件不成立
main函数结束!
请按任意键继续. . .
*/
类型转换**
通常,在语句和表达式中应当使用的是类型相同的变量和常量。但是如果是混合类型的,C会采用一套自动类型转化
类型转化规则如下:
1:当类型转换出现在表达式时,无论了unsigned还是signed的char和short都会自动转化成int,float会被自动转化成double。由于都是从小类型转化大类型,所以这些欣慰叫做/*升级*/
2:涉及两种类型的运算,两个值会被分别转化成两种类型的更高级别。
3:类型的级别类型是(由高到低)
long double、double、float、float、unsigned long long、long long、unsigned long、long、unsigned int、int
特殊情况:当long和int的大小相同时,unsigned int 比long级别高
4:在赋值表达式语句中,计算的最终结果都会被转换成被赋值变量的类型,这个过程可能导致类型升级或者降级,所谓/*降级*/、是指把一种类型转化成更低的类别
类型的升级一般都不会有什么问题,但是降级会导致真正的麻烦
原因:较低类型可能放不下高类型的值
如果待赋值得值与目标类型不匹配得话得规则:
1:目标类型是无符号得整形,且待赋值得值是整数得时,额外的位将会被忽略
例如:如果目标是8位unsigned char,待赋值是原始值求模256
2:如果目标类型是一个有符号的整数,且待赋值的值是整数,结果因实现而异。
3:如果目标类型是一个整形,且待赋值的值是浮点数,这种行为未定义
4:如果把一个浮点值转化位整形,原来的浮点值将被截断
例程:
#include <stdio.h>
int main(void)
{
char ch;
int i;
float fl;
fl = i = ch = 'C'; /* line 9 */
printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); /* line 10 */
ch = ch + 1; /* line 11 */
i = fl + 2 * ch; /* line 12 */
fl = 2.0 * ch + i; /* line 13 */
printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl); /* line 14 */
ch = 1107; /* line 15 */
printf("Now ch = %c\n", ch); /* line 16 */
ch = 80.89; /* line 17 */
printf("Now ch = %c\n", ch); /* line 18 */
return 0;
}
强制转化
通常,应该避免自动类型转换,尤其是类型降级。但是如果能小心使用,类型转换也很方便。我们前面讨论的类型转换都是自动完成的。然而,有时需要进行精确的类型转换,或者在程序中表明类型转换的意图。这种情况下要用到强制类型转换,即在某个量的前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型。圆括号和它括起来的类型名构成了强制类型转换运算符(castoperalor),其通用形式是:
//强制转化成 int
mice =(int)1.6(int)1.7;
//强制转化成 float
mice =(float)1.61111111(float)1.7111111;
//强制转化成 char
mice =(char)1.6(char)1.7;
一般而言,不应该混合使用类型(因此有些语言直接不允许这样做),但是偶尔这样做也是有用的。C语言的原则是避免给程序员设置障碍,但是程序员必须承担使用的风险和责任。
带参数的函数
#include <stdio.h>
void pound(int n); // ANSI function prototype declaration
int main(void)
{
int times = 5;
char ch = '!'; // ASCII code is 33
float f = 6.0f;
pound(times); // int argument
pound(ch); // same as pound((int)ch);
pound(f); // same as pound((int)f);
return 0;
}
void pound(int n) // ANSI-style function header
{ // says takes one int argument
while (n-- > 0)
printf("#");
printf("\n");
}
如果函数不接受任何参数,函数头的圆括号中应该写上关键字 void。
由于该函数接受一个int 类型的参数,所以圆括号中包含一个int类型变量n的声明。参数名应遵循C语言的命名规则。
声明参数就创建了被称为形式参数的变量。
该例中形式参数是int 类型的变量n。像pound(10)这样的函数调用会把10赋给n。在该程序中,调用pound(times)就是把 times 的值(5)赋给 n。我们称函数调用传递的值为实际参数,简称实参。所以,函数调用 pound(10)把实际参数10传递给函数,然后该函数把10赋给形式参数(变量 n)。也就是说,main()中的变量 times 的值被拷贝给 pound()中的新变量n。