7. 关系操作符
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
注意:在编程的过程中==和=不小心写错,导致的错误
== 两个等于在C语言中才是数学中的相等(判断常量和变量相等,好的习惯是把常量写在右边)。
= 一个相等在C语言中是赋值(注:赋值是左边为变量)
代码实例:
//关系操作符 //== 用于测试“相等” #include<stdio.h> int main() { int a = 0; if (0 == a)//判断常量与变量是否相等,好的习惯把变量放在右边 { printf("%d\n", a); } if (a = 0)//如写在左边,少些一个等号不报错,一个等号是赋值 { printf("%d\n", a); } return 0; }
8. 逻辑操作符
逻辑操作符
&& 逻辑与(并且:参与运算的两个逻辑值都为真时,结果为真)
|| 逻辑或(或者:参与运算的两个逻辑值都为假时,结果为假)
注意区分逻辑与(或)和按位与(或):
逻辑与(或)----->只关注真假
按位与(或)----->通过二进制计算得到
代码实例:
//逻辑操作符 //判断闰年的条件 //1、能被4整除,并且不能被100整除 //2、能被400整除 #include<stdio.h> int main() { int y = 2048; if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) { printf("%d是闰年\n", y); } else { printf("%d不是闰年\n", y); } return 0; }
补充:逻辑操作符的短路特性
&&操作符,左边为假,右边无需计算
||操作符,左边为真,右边就无需计算
代码实例:
//逻辑操作符的短路特性 #include<stdio.h> int main() { int a = 0; int i = 0; int b = 2; int c = 3; int d = 4; //代码1 //&&操作符,左边为假,右边无需计算 //i = a++ && ++b && d++; //代码2 // ||操作符,左边为真,右边就无需计算 i= a++ || ++b || d++; printf(" a=%d\n b=%d\n c=%d\n d=%d\n", a, b, c, d); return 0; }
运行结果:
代码1:
代码2:
9. 条件操作符(三目操作符)
代码实例:
//条件操作符 #include<stdio.h> int main() { int a = 10; int b = 20; int max = 0; //代码1:使用选择语句,找两个数的较大值 //if (a > b) //{ // max = a; //} //else //{ // max = b; //} //printf("max=%d\n", max); //代码2 //转换为条件表达式,是怎么样的? //改用条件表达式实现找两个数的较大值 max = (a > b ? a : b); printf("max=%d\n", max); return 0; }
运行结果:
10. 逗号表达式
exp1,exp2,exp3,...expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
代码实例:
//逗号表达式 #include<stdio.h> int main() { int a = 1; int b = 2; int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式 printf("%d\n", c);//13 return 0; }
11. 下标引用、函数调用和结构成员
1. []下标引用操作符
操作数:一个数组名+一个索引值(下标)
代码实例:
//1、[]—下标引用操作符 //操作数:一个数组名+一个索引值(下标) #include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5 }; printf("%d\n", arr[4]);// []—下标引用操作符,操作数是arr,4 return 0; }
2. ()函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。(注:一个函数调用操作符至少有一个操作数即函数名)
代码实例:
//2、()函数调用操作符 //接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数 #include<stdio.h> #include<string.h>//预处理,对strlen的声明 int main() { int len = strlen("abcdef");//()就是函数调用操作符,操作数:strlen,“abcdef” printf("%d\n", len);//strlen函数是计算字符串长度的,len=6 return 0; }
3. 访问一个结构的成员
. 结构体变量.成员名
-> 结构体指针->成员名
代码实例:
//结构体 //类型:内置类型和自定义类型 //内置类型:我们常用的int char short long ,long long float double //自定义类型(聚合类型):结构体,枚举,联合体 //为什么要有自定义类型? //生活中有些(复杂)对象要被描述的话,不能简单使用单个内置类型 //如书:书名,作者,定价……(用{}聚合在一起) #include<stdio.h> //结构体类型 struct Book { char name[20];//书名 char author[20];//作者 double price;//定价 }; void print1(struct Book* p) { printf(" %s %s %.2lf\n", (*p).name, (*p).author, (*p).price); printf(" %s %s %.2lf\n", p->name, p->author, p->price); //结构体指针->成员名 } int main() { struct Book b1 = { "数学","张三",66 }; struct Book b2 = { "英语","李四",88 }; //那结构体成员是怎么访问的呢? //结构体变量.成员名 printf(" %s %s %.2lf\n", b1.name, b1.author, b1.price); printf(" %s %s %.2lf\n", b2.name, b2.author, b2.price); print1(&b1);//传址调用 return 0; }
运行结果:
12. 表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
12.1 隐式类型转换
C的整型算术运算总是至少以缺省(缺省就是默认的意思)整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整形提升。
整形提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
代码实例:
代码1:
//隐式类型转换 //C的整形算术运算总是至少以缺省整形类型的精度进行的 //为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整形提升 //整形提升是针对于类型小于整形的 //char short int long…… // 1 2 4 //代码1 #include<stdio.h> int main() { char a = 3; char b = 127; char c = a + b; //b和c的值被提升为普通整型,然后再执行加法运算。 //加法运算完成之后,结果将被截断,然后再存储于a中 printf("c=%d\n", c); return 0; }
整形提升是针对于类型小于整形的,如char short类型操作数在使用之前转换为int型
代码2:
如何进行整形提升呢?
整形提升是按照变量的数据类型的符号位来提升的
//代码2 //如何进行整形提升? //整形提升是按照变量的数据类型的符号位来提升的 //负数的整形提升 char c1 = -1; //变量c1的二进制位(补码)中只有8个比特位: //1111111 //因为 char 为有符号的 char //所以整形提升的时候,高位补充符号位,即为1 //提升之后的结果是: //11111111111111111111111111111111 //正数的整形提升 char c2 = 1; //变量c2的二进制位(补码)中只有8个比特位: //00000001 //因为 char 为有符号的 char //所以整形提升的时候,高位补充符号位,即为0 //提升之后的结果是: //00000000000000000000000000000001 //无符号整形提升,高位补0
代码3:
//代码3 #include<stdio.h> int main() { //当前VS编译器char---->signed char char a = 3; //整形的二进制:00000000000000000000000000000011 // 截断 //char存储:00000011 char b = 127; //整形的二进制:00000000000000000000000001111111 //截断 //char存储:01111111 char c = a + b; //a-00000011 //b-01111111 //整形提升 //a-00000000000000000000000000000011 //b-00000000000000000000000001111111 //a+b:00000000000000000000000010000010 //截断 //c-10000010 printf("c=%d\n", c); //%d是打印十进制的整数 //c-10000010 //整形提升 //补码:11111111111111111111111110000010 //过程:11111111111111111111111110000001 //原码:10000000000000000000000001111110 return 0; }
代码运行C是多少呢?
C=-126
为什么呢?那我们了解一下char的取值范围吧。
代码4:
//整形提升的例子 //整形提升是针对于类型小于整形的,如char short类型操作数在使用之前转换为int型 //代码4 #include<stdio.h> int main() { char a = 0xb6; short b = 0xb600; int c = 0xb6000000; if (a == 0xb6)//a-10110110要整形提升,变成负数,假 { printf("a"); } if (b == 0xb600)//b-1011011000000000要整形提升,变成负数,假 { printf("b"); } if (c == 0xb6000000)//c不整形提升,真 { printf("c"); } return 0; }
代码5:
//代码5 #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; }
运行结果:
C只要参与表达式运算,就会发生整形提升。
12.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long
intlong int
unsigned int
int
由低到高转换(从下向上)
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
注意:算术转换讨论的类型都是大于或等于int型的
警告:
但是算术转换要合理,要不然会有一些潜在的问题。(就高不就低,否则可能精度丢失)
例:
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
总结:
低于int型的char,short为整形提升,等于或大于int型的为算术转换。(从低到高)
12.3 操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
操作符 描述 用法示例 结合类型 结合性 是否控制求值顺序
() 聚组 (表达式) 与表达式同 N/A 否
() 函数调用 rexp(rexp,...,rexp) rexp L-R 否
[] 下标引用 rexp[rexp] lexp L-R 否
. 访问结构成员 lexp.member_name lexp L-R 否
-> 访问结构指针成员 rexp->member_name lexp L-R 否
++ 后缀++ lexp++ rexp L-R 否
-- 后缀-- lexp-- rexp L-R 否
! 逻辑反 !rexp rexp R-L 否
~ 按位取反 ~rexp rexp R-L 否
+ 单目,表示正值 +rexp rexp R-L 否
- 单目,表示负值 -rexp rexp R-L 否
++ 前缀自增 ++lexp rexp R-L 否
-- 前缀自减 --lexp rexp R-L 否
* 间接访问 *rexp lexp R-L 否
& 取地址 &lexp rexp R-L 否
sizeof 取其长度,以字节表示 sizeof rexp sizeo(类型) rexp R-L 否
(类型 ) 类型转换 (类型)rexp rexp R-L 否
* 乘法 rexp*rexp rexp L-R 否
/ 除法 rexp/rexp rexp L-R 否
% 整数取余 rexp%rexp rexp L-R 否
+ 加法 rexp+rexp rexp L-R 否
- 减法 rexp-rexp rexp L-R 否
<< 左移位 rexp<<rexp rexp L-R 否
>> 右移位 rexp>>rexp rexp L-R 否
> 大于 rexp>rexp rexp L-R 否
>= 大于等于 rexp>=rexp rexp L-R 否
< 小于 rexp<rexp rexp L-R 否
<= 小于等于 rexp<=rexp rexp L-R 否
== 等于 rexp==rexp rexp L-R 否
!= 不等于 rexp!=rexp rexp L-R 否
& 位于 rexp&rexp rexp L-R 否
^ 位异或 rexp^rexp rexp L-R 否
| 位或 rexp|rexp rexp L-R 否
&& 逻辑与 rexp&&rexp rexp L-R 是
|| 逻辑或 rexp||rexp rexp L-R 是
?: 条件操作符 rexp?rexp:rexp rexp N/A 是
= 赋值 lexp=rexp rexp R-L 否
+= 以...加 lexp+=rexp rexp R-L 否
-= 以...减 lexp-=rexp rexp R-L 否
*= 以...乘 lexp*=rexp rexp R-L 否
/= 以...除 lexp/=rexp rexp R-L 否
%= 以...取模 lexp%=rexp rexp R-L 否
<<= 以...左移 lexp<<=rexp rexp R-L 否
>>= 以...右移 lexp>>=rexp rexp R-L 否
&= 以...与 lexp&=rexp rexp R-L 否
^= 以...异或 lexp^=rexp rexp R-L 否
|= 以...或 lexp|=rexp rexp R-L 否
, 逗号 rexp,rexp rexp L-R 真
说明:N/A是没有的意思,R-L是从右向左
从上到下优先级变低。
小结:
操作符的属性
1.首先确定优先级,相邻操作符按照优先级高低计算(相邻操作符才讨论优先级)
2.优先级相同的情况下,结合性才起作用。
3.注意是否控制求值顺序(只有四个:&& || (?:) ,)
那知道了操作符的属性是否就能确定计算的唯一路径呢?
一些问题表达式:
1.
2.
注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
3.
这个代码是有问题的。
虽然在大多数的编译器上求得结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
4.
看看同样的代码产生了不同的结果,这是为什么?
简单看一下汇编代码.就可以分析清楚.
这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。
总结:
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。