文章目录
所学视频及PPT截图来源: 爱课程资源共享课
选择控制结构与if语句
1.编程人员的基本素养: 全面,缜密
2.程序的组成: 数据,数据存储,算法
3.算法的描述: (flow-chart:流程图)
4.顺序结构
5.算法应该满足的条件:
6.if选择语句:
[表达式非0为真,0为假] (同样适用于while,for等语句)
无论if里的语句有多少,都应该使用大括号的原因:
(1)避免在程序维护时,需要向if中添加新语句时忘记打上花括号的情况
(2)用于严格限制if-else对齐,以免混淆出错:
7.条件运算符(C唯一的三目运算符)
条件运算符结合性: 从右到左
(多个条件运算符并列,先满足最右边的条件运算符的组成需要,然后再从右到左依次满足)
举例:
res = a>b?c:d<=e?e+10:sizeof(f)>4?10:g;
等同于:
res = (((a>b)? c:d<=e)? e+10:sizeof(f)>4)? 10:g;
补充 : C语言_Bool关键字及与布尔类型相关的类型转换
1._Bool关键字:
- C语言使用 _Bool 来定义布尔型变量。
- _ Bool类型长度为1(sizeof),取值范围为0或1。
- 将任意非零值赋值给 _ Bool 类型,都会先转换为1,表示真 ; 将零值赋值给 _ Bool类型,结果为0,表示假。
2.头文件stdbool.h:
头文件源代码主要内容:
#define bool _Bool
#define true 1
#define false 0
#define __bool_true_false_are_defined 1
也就是说,引入了stdbool.h之后,我们可以用bool代替_Bool,用true/false代替1/0.更符合其他大多数编程语言的习惯.
//测试:布尔类型的内存大小和赋值
_Bool a = 3;
_Bool b = 0;
printf("sizeof _Bool a : %d
value of _Bool a : %d
",sizeof(a),a);
printf("sizeof _Bool b : %d
value of_Bool b : %d
",sizeof(b),b);
// 打印:
// sizeof _Bool a : 1
// value of _Bool a : 1
// sizeof _Bool b : 1
// value of _Bool b : 0
关系运算符(relational operator)
注:笔者渣翻,原文请见 WG14 N1570
6.5 关系运算符(>,>=,<,<=)
约束条件:
2.必须满足下列情况之一:
(1)两操作数都是实数类型
(2)两操作数都是指向合格或不合格兼容对象类型的指针
语义:
3.如果操作数都是算数类型,就会执行常规数值转换(usual arithmetic conversion)
4.为了尽可能地实现关系运算符,一个指向非数组元素的指针和一个指向单元素数组中唯一元素并且以该元素的类型为数组类型的指针是等价的.
5.当两个指针相比较时, 其结果取决于两指针所指向的目标在地址空间的相对位置(而非目标所存储的值).
- 如果两个指针都同时指向同一个目标,或者都同时指向某一数组末尾元素之后的一个位置,这两个指针比较相等.
- 如果被指针指向的目标是同一个聚合类型(数组/结构类型),结构中后声明的成员的指针比先声明的成员的指针大;且在指向数组元素的指针中,目标元素下标较大的指针大于目标元素下标较小的指针.
- 指向同一联合类型的指针一律相等.
- 如果语句P指向某数组的一个元素且语句Q指向同一数组的最后一个元素,那么语句Q+1比P的值大.
- 在上述之外的任何情况下,两指针的大小比较都是未定义的.
6.关系运算符(>,>=,<,<=)所进行的比较结果为真,整个比较语句返回1;结果为假,返回0.返回类型为int类型.
switch开关语句
1.要点:
(1)判断表达式为int/char类型,不接受其他类型
(2)case与其后紧跟的值间有一个空格
(3)case后的值必须为常量,不可以是表达式
2.注意事项:
(1)default分支禁止省略
(2)如果case对应语句不用break语句结束,会发生case穿透,程序继续执行下一条case语句,直到遇到break语句或者整个代码块结束
(3)有时候,我们可以利用case穿透优化我们的代码; 其他情况下,每个case之后,必须用break语句结束.
//测试1:case穿透的利用--计算器程序(*,x,X统一判断为乘法运算符的实现)
switch(operator){
case '+': //此处实现加法运算;
break;
case '-': //此处实现减法运算
break;
case '*':
case 'x':
case 'X': //此处实现乘法的计算
break;
case '/': //此处实现除法运算
break;
default: //例外情况
break;}
//测试02:开关语句case穿透与语句顺序
int a = 10;
double b = 1.32;
_Bool c = 0;
//test01:无case穿透下的default语句顺序的影响
switch(sizeof(c)){
case 4 : printf("it's an int
");break;
case 8 : printf("it's a double
");break;
default:printf("nothing here
");break;
case 1 : printf("it's a _Bool
");break;
}
// print:
// it's a _Bool
// conclusion:
// 无case穿透,default的位置顺序无影响
//test02:有case穿透时default语句的顺序的影响
switch(sizeof(b)){
case 4 : printf("it's an int
");break;
case 8 : printf("it's a double
");
default:printf("nothing here
");break;
case 1 : printf("it's a _Bool
");break;
}
// print:
// it's a double
// nothing here
// conclusion:有case穿透时,default的位置顺序有影响
浮点数的相等比较
== 对浮点数"貌似"失效的原因
//测试:浮点数的大小比较
float f1 = 0.1;
double f2 = 0.1;
printf("%f
",f1);
printf("%f
",f2);
if(f1-f2){
printf("f1 不等于 f2
");
}else{
printf("f1 等于 f2
");
}
if(f1==f2){
printf("f1 等于 f2
");
}else{
printf("f1 不等于 f2
");
}
运行结果:
0.100000
0.100000
f1 不等于 f2
f1 不等于 f2
原因:
我们知道,C语言的浮点数是采用"阶码+尾数"形式存储的,而目前已知的所有的C/C++编译器都是按照IEEE(国际电子电器工程师协会)制定的IEEE 浮点数表示法来进行运算的。这种结构是一种科学表示法,用符号(正或负)、指数和尾数来表示,底数被确定为2,也就是说是把一个浮点数表示为尾数乘以2的指数次方再加上符号.
也就说,我们需要把要存储的数按照如下形式表示: (X为要表示的小数,An为系数且取值仅为0或1)
X = An×2n + An-1×2n-1 + …+ A1×21 + A0×20 + A-1×2-1 + A-2×2-2 + …+ A-(n-1)×2-(n-1) + A-n×2-n
上面的式子,当 系数下标 >= 0 时,为X的整数部分;当 系数下标 < 0 时,为X的小数部分.问题就出在小数部分.
- 无论整数部分为何值,总能用有限的位数将它表示并存储下来
- 小数部分则不同: 举例如下
0.5(10) = 0.1(2)
0.1(10) = 0.00011001100110011001100110011001100…(2),其系数是1100的循环.
当系数为无限数或循环时,我们为了存储,必须忍痛割爱,只截取有限的位数.而因为float类型和double类型的内存大小不同,截取的位数肯定不同(flaot较短而double较多).
而当我们用0.1对float和double赋值,然后两者进行计算和比较时,两者由于截取的位数不同,数值就不同,才有了上面代码的运行结果.
举例:(假设float截取8位,而double截取16位.注意!是假设)
0.1(10) = 0.00011001100110011001100110011001100…(2)
f1 = 0.00011001(2)
f2 = 0.0001100110011001(2)
当执行f1-f2或者f1==f2时,由于隐式转换:
f1’ = 0.0001100100000000(2)
f2 = 0.0001100110011001(2)
f1’的后8位都是0,当然和f2不相等了.
浮点数相等比较的解决办法
逻辑运算符与逻辑表达式
1.逻辑运算符(&&,||,!)
2.逻辑运算符的短路特性
3.逻辑运算符应用举例
C语言所有运算符优先级与结合性
2.分析与简记
(1) 优先级
最高优先级的是选择数组元素,选择成员和括号(调整优先级/执行函数)的运算符,我们不妨将之归类为"选择运算符",由**[],(), . ,->**组成.
我们还可以用一张图片来描述:
没错,就是一辆"坦克",炮管尾部的一坨就是点号.
- 优先级排序: 选择 > (单目 > 双目 > 三目) > 赋值 > 逗号
- 选择运算符: 用于找到操作数
- 单双三目运算符: 利用操作数进行各种运算
- 赋值运算符: 将运算的结果保存起来
- 并且 单目和三目,赋值和逗号 以及我们的"选择运算符"都是各自出于同一优先级,而双目运算符一类横跨了多个优先级,需要单独拿出来分析:
- 双目运算符中,各个小类的优先级: 乘性(乘除余) > 加性(加减) > 移位(左右) > 关系 > 相等 > 位操作 > 逻辑操作
- 其中,位操作和逻辑操作,每一个运算符都独享一个优先级,顺序为:
- 位操作: 与&& > 或|| (注意: 非!是单目运算符,所以优先级高得多,不在此列)
- 逻辑操作: 与& > 异或^ (注意: 位取反~是单目运算符,所以优先级高得多,不在此列)
(2)结合性:
- 单目,三目,赋值为从右到左;其余为从左到右
- 什么是结合性?
(从左向右/左结合)当同优先级的运算符并列出现时,最先满足最左边运算符的构成需要,来进行分组,然后对剩余部分重复前面的步骤,从左至右依次满足.
基于C语言特性,我们应该养成哪些好的编程习惯来避免失误
- 不要想当然,使用数据类型时,先sizeof确定类型的大小,尽量避免使用int和long double类型
- if的判断式虽然不局限于关系和相等表达式,但我们应该一律化为 关系表达式.
- if的判断式使用相等表达式时,常量在左,变量在右.
- if中的语句,禁止省略大括号
- switch语句,不利用case穿透时,一律禁止省略结尾的break
- switch语句,一律禁止省略default分支
- 不要把自增,自减运算放到复杂的表达式中