运算符、分支与循环
数值溢出
如果 int 类型变量已经取到最大值(2147483647),再给这个变量 +1,就发生了溢出。
- 上溢出:最大值 + 1
- 下溢出:最小值 - 1
无符号数
取值范围 0 ~ 2³² - 1。
- 最大值 + 1 ——> 0
- 0 - 1 ——> 最大值
// 上溢出
int main(void)
{
unsigned int a = UINT_MAX; // 取出无符号最大值
printf("最大值+1 = %d\n", a + 1); // 0
printf("最大值+2 = %d\n", a + 2); // 1
return 0;
}
有符号数
- 上溢出:最大值 + 1,上溢出 == 最小值。
- 下溢出:最小值 - 1,下溢出 == 最大值。
// 下溢出
int main(void)
{
int a = INT_MAX; // 取出有符号最大值
printf("最大值+1 = %d, 最小值 = %d\n", a + 1, INT_MIN);
return 0;
}
小结
大部分编译器对于数值溢出采用上述处理方式,但个别编译器有可能不同。
受数值溢出影响,进行数据存储时,要选择恰当的数据类型。例如 389482390483209 就不适合用 int 存储。
不常用关键字(了解)
extern:表示声明。声明没有内存空间,不能提升为定义。const:限制一个变量为只读,即常量。volatile:防止编译优化。register:定义一个寄存器变量,没有内存地址。
输入输出函数
输出函数
printf()
常用格式匹配符:%d、%c、%x、%u、%s 等。
putchar() 函数
输出一个字符到屏幕。'abcZ' 是错误的定义,既不是 char 也不是字符串。
putchar(97); // 97 == 'a'
putchar('\n'); // 输出换行符
putchar('b');
putchar('\n'); // 使用频率较高,等价于 printf("\n");
putchar('abcZ'); // 'abcZ' 是错误的定义,既不是 char 也不是字符串
输入函数
scanf 函数
按照指定的格式匹配符,获取指定类型数据。
获取整数:
int a; // 可以定义 a 变量(有内存空间),也可以声明(自动提升成定义,有内存空间)
scanf("%d", &a); // &: 取变量 a 的地址 ——> 拿到 a 的内存空间
// scanf 输入
int main(void)
{
//int a = 10;
int a; // 在 scanf 执行时,会提示未定义
int ret = scanf("%d", &a); // scanf 可以从键盘获取用户输入,用户根据 %d 输入整数
printf("获取的a为:%d\n", a);
return EXIT_SUCCESS;
}
一次性获取多个整数:
常见的错误书写方法:
int a, b, c; // 一次性创建多个 int 类型变量
// scanf 可以从键盘获取用户输入,用户根据 多个 %d 输入整数
int ret = scanf("%d%d%d", &a, &b, &c);
// 上述写法编译器无法识别,输入的 123 应该怎样分配给 3 个 %d
正确的书写方式(推荐):
int main(void)
{
int a, b, c; // 一次性创建多个 int 类型变量
// scanf 可以从键盘获取用户输入,用户根据 多个 %d 输入整数
int ret = scanf("%d %d %d", &a, &b, &c);
printf("获取的a为:%d\n", a);
printf("获取的b为:%d\n", b);
printf("获取的c为:%d\n", c);
return EXIT_SUCCESS;
}
获取字符:
// scanf 输入,获取字符
int main(void)
{
char ch1, ch2, ch3; // 一次性创建多个 char 变量
printf("请输入3个字符,用空格隔分:");
scanf("%c %c %c", &ch1, &ch2, &ch3);
printf("ch1 = %c\n", ch1);
printf("ch2 = %c\n", ch2);
printf("ch3 = %c\n", ch3);
return EXIT_SUCCESS;
}
scanf 注意事项
-
不要随意在
scanf()函数中添加'\n'。如果添加了换行符,会被 scanf 当成格式来约束用户输入。printf()中的'\n'用来向屏幕输出换行。scanf()函数中的'\n'不能用来输出,因为这是一个输入函数。
-
解决 VS 中使用 scanf 函数报 C4996 错误。以下是错误描述:
C4996 'scanf': This function or variable may be unsafe. Consider using scanf_s instead.To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. -
解决方法:在
.c文件的第一行添加#define _CRT_SECURE_NO_WARNINGS。
getchar 函数
直接从键盘接收一个字符,并将得到的字符对应的 ASCII 返回。
int main(void)
{
char ch = getchar(); // 定义 char 变量 ch,接收 getchar() 函数返回值作为初值
printf("ch的数值:%d\n", ch); // %hhd %hd
printf("ch的字符:%c\n", ch);
return EXIT_SUCCESS;
}
运算符
算数运算符
+ - * /:先乘除取余,后加减。- 除法运算后,得到的结果赋值给整型变量,取整数部分。
int c = 10/20;——>0 - 除 0:错误操作,不允许。
printf("%d\n", 10/0); - 对 0 取余:错误操作,不允许。
printf("%d\n", 123 % 0); - 不允许对小数取模。
35 % 3.4; - 对负数取余,结果为余数的绝对值。
printf("%d\n", 10 % -3);——>1
自增自减运算符
- 前缀自增(
++)、自减(--):先自增、自减,再取值。 - 后缀自增(
++)、自减(--):先取值,再自增、自减。
int a = 10;
++a; // 等价于 a = a+1;
// ++ / -- 运算符
int main(void)
{
// 前缀
int a = 10;
printf("%d\n", ++a); // ++a 等价于 a = a+1;
// 后缀
int b = 10;
printf("%d\n", b++); // b++ 等价于 b = b+1;
// 不管前缀、后缀,含有变量的 ++/-- 表达式执行完后,变量均发生了自增、自减
printf("b = %d\n", b);
return EXIT_SUCCESS;
}
赋值运算符
=在计算机中只能完成"赋值"操作,一定是右边赋值给左边,也叫单向赋值符。a += 10;等价于a = a + 10;a -= 30;等价于a = a - 30;a %= 5;等价于a = a % 5;
比较运算符
真:1(非 0),假:0。
| 运算符 | 含义 |
|---|---|
== | 判等符(= 不是用来判等) |
!= | 不等 |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
【强调】:数学运算中的 13 < var < 16 判断,在计算机中要写成:var > 13 && var < 16;
逻辑运算符
0 为假,非 0 为真(非 0:1、27、-9)。
- 逻辑非(
!):非真为假,非假为真。 - 逻辑与(
&&):同真为真,其余为假。 - 逻辑或(
||):有真为真,同假为假。
// 逻辑运算符
int main(void)
{
// 逻辑非
int a = 34; // 34 是非 0,默认 a 为真
int b = 0;
printf("a = %d\n", !a); // a 为真,非 a 为假 ---> 0
printf("b = %d\n", !b); // b=0, b 为假,非 b 为真 ---> 1
return EXIT_SUCCESS;
}
printf("======%d\n", a && !b); // a 为真,!b 为真,真 && 真 -- 真 ---> 1
printf("------%d\n", a || !b); // a 为真,!b 为真,真 || 真 -- 真 ---> 1
printf("------%d\n", a || b); // a 为真,b 为假,真 || 假 -- 真 ---> 1
printf("------%d\n", !a || b); // !a 为假,b 为假,假 || 假 -- 假 ---> 0
运算符优先级
[] () > ++/--(后缀高于前缀) > (强转) ! (逻辑非) sizeof
> 算数运算符(先乘除取余,后加减)
> 比较运算符 > 逻辑运算符 > 三目运算(条件运算)
> 赋值运算符 > 逗号运算符
建议用一周左右的时间记忆运算符优先级。
逗号运算符
含有 , 运算符的表达式,其运算结果是最后一个子表达式的结果。
int x, y, z;
int a = (x=1, y=2, z=3); // x=1, y=2, z=3 是一个逗号运算符表达式,运算结果为 a = 3;
int a = (x=1, z=3, y=27); // 运算结果为 a = 27;
三目运算符
语法:表达式1 ? 表达式2 : 表达式3
- 表达式1 必须是一个判断表达式。
- 结果为真:整个三目运算返回表达式2。
- 结果为假:整个三目运算返回表达式3。
- 如果不使用
(),三目运算默认的结合性是自右向左。
// 三目运算
int main(void)
{
int a = 40;
int b = 4;
//int m = a > b ? 69 : 10;
//printf("m = %d\n", m); // m = 69;
//int m = a < b ? 69 : 10;
//printf("m = %d\n", m); // m = 10;
// 三目运算支持嵌套
int m = a < b ? 69 : (a < b ? 3 : 5); // 先算表达式3,取5,整个三目运算取表达式3 --> 5
printf("m = %d\n", m); // m = 5;
int m = a < b ? 69 : a < b ? 3 : 5;
printf("m = %d\n", m); // m = 5;
return EXIT_SUCCESS;
}
类型转换
隐式类型转换
- 编译器自动完成(小类型转大类型,或同类型大小之间转换)。
- 由赋值产生。
规律:
- 小类型 ——> 大类型:没问题。
- 大类型 ——> 小类型:可能丢失数据。
VS 中
Ctrl + F7只编译,检查语法错误,不运行。
以 321 赋值给 char 为例:
- 321:256 有,128 没有,64 有,32/16/8/4/2 没有,有 1。
0000 0000 0000 0000 0000 0001 0100 0001—— 321 的二进制表现形式。char只有一个字节,赋值后 char 值为:0100 0001——> 1 + 64 = 65。
int main(void)
{
int a = 321;
char ch = a; // 用值为 321 的 a 变量给 char 类型赋值
printf("ch = %d\n", ch); // 运行输出 65;
return EXIT_SUCCESS;
}
int r = 3;
float s = 3.14 * r * r;
// 3.14 默认类型 double,r 为 int 类型。运算过程中转换为 double 类型运算。
// 运算结束,赋值给 s 时,转换为 float。
强制类型转换
语法:
- 强转变量:
(目标类型)变量 - 强转表达式:
(目标类型)表达式
int main(void)
{
float price = 3.6; // 单价
int weight = 4; // 斤数
//int sum = weight * (int)price; // 强转变量
//printf("sum = %d\n", sum); // --- 12
int sum = (int)(weight * price); // 强转表达式
printf("sum = %d\n", sum); // --- 14
return EXIT_SUCCESS;
}
if 分支语句
if ... else 分支语句实现一种模糊匹配,匹配一个范围。
if ... else 分支
if (判断表达式) {
// 判别表达式为真,执行的代码
}
else {
// 判别表达式为假,执行的代码
}
示例:
int a;
printf("请输入一个数:");
int ret = scanf("%d", &a);
if (a > 0) {
printf("a > 0\n");
}
else {
printf("a ≤ 0\n");
}
多个分支逻辑
if (判断表达式1) {
// 判别表达式1为真,执行的代码
}
else if (判断表达式2) {
// 判别表达式1为假,判断表达式2为真,执行的代码
}
else if (判断表达式3) {
// 判别表达式1为假,判断表达式2为假,判断表达式3为真,执行的代码
}
// ...
else {
// 以上所有判别表达式都为假,执行的代码
}
示例:
int score;
printf("请输入学生成绩:");
scanf("%d", &score);
if (score >= 90) // 优秀
{
printf("优秀\n");
}
else if (score >= 70 && score < 90) // 70 < score < 90 是错误写法
{
printf("良好\n");
}
else if (score >= 60 && score < 70)
{
printf("及格\n");
}
else
{
printf("差劲\n");
}
分支嵌套练习
编写程序,实现 3 只小猪称体重:屏幕输入 3 只小猪的重量,借助 if 分支找出最重的那只小猪。
- 分支语句中可以嵌套其他分支语句。
else总是与它前面最近的、未配对的if组合使用。
int main(void)
{
int pig1, pig2, pig3;
printf("请输入3只小猪的重量:");
scanf("%d %d %d", &pig1, &pig2, &pig3);
if (pig1 > pig2) // pig1 重
{
if (pig1 > pig3) // 1. 这行不能写分号。 2. 缩进使用 tab 实现
{
printf("第一只小猪最重,体重为:%d\n", pig1);
}
else
{
printf("第3只小猪最重,体重为:%d\n", pig3);
}
}
else // pig2 重
{
if (pig2 > pig3)
{
printf("第2只小猪最重,体重为:%d\n", pig2);
}
else
{
printf("第3只小猪最重,体重为:%d\n", pig3);
}
}
return EXIT_SUCCESS;
}
// 其他实现方法:
// 使用逻辑运算 if (pig1 > pig2 && pig1 > pig3) ---> pig1。
// 使用三目运算 pig1 > pig2 ? pig1 : pig2;
switch 分支语句
精确匹配,结构较清晰,较 if 语句执行效率略高。
缺点:不能直接判断区间,需要借助表达式运算。
switch (表达式)
{
case 1:
执行语句;
break; // 表示一个分支执行结束,跳出 switch
case 2:
执行语句;
break;
// ......
case N:
执行语句;
break;
default:
其他情况,执行语句; // 上述所有的 case 都不满足
break;
}
练习:获取学生成绩,给出优良及格差。
int main(void)
{
int score;
printf("请输入学生成绩:");
scanf("%d", &score);
// 将学生成绩,通过运算,得出可以放入 switch case 分支的表达式
switch (score / 10)
{
case 10:
printf("优秀\n");
break; // 表示当前分支结束
case 9:
printf("优秀\n");
break;
case 8:
printf("良好\n");
break;
case 7:
printf("良好\n");
break;
case 6:
printf("及格\n");
break;
default: // 所有 case 都不满足的其他情况
printf("不及格");
break;
}
return EXIT_SUCCESS;
}
case 穿透
一个 case 分支如果没有 break;,它执行完本 case 的代码后,会继续向下执行下一个 case 分支的代码,这称之为 case 穿透。
大多数情况下,一个 case 分支应该对应一个 break;。
利用 case 穿透可以简化代码:
switch (score / 10)
{
case 10: // 故意让 switch 发生 case 穿透
case 9:
printf("优秀\n");
break;
case 8: // 故意让 switch 发生 case 穿透
case 7:
printf("良好\n");
break;
case 6:
printf("及格\n");
break;
default: // 所有 case 都不满足的其他情况
printf("不及格");
break;
}
while 循环语句
语法
while (判别表达式) // 如果为真,执行循环体;如果为假,跳出循环
{
循环体
}
练习:敲 7
从 1 ~ 100 数数,逢 7 和 7 的倍数敲桌子。
分析:
- 7 的倍数:
num % 7 == 0 - 个位含 7:
num % 10 == 7 - 十位含 7:
num / 10 == 7
int main(void)
{
//7的倍数: num % 7 == 0
//个位含7: num % 10 == 7
//十位含7: num / 10 == 7
int num = 1;
while (num ≤ 100)
{
// 判断敲桌子的时机
// if ((num % 7 == 0) || (num % 10 == 7) || (num / 10 == 7))
if (num % 7 == 0 || num % 10 == 7 || num / 10 == 7)
{
printf("敲桌子!\n");
}
else
{
printf("%d\n", num);
}
num++;
}
return EXIT_SUCCESS;
}
do while 循环语句
无论如何先执行一次循环体,然后再判断循环是否应该继续。
语法:
do {
循环体
} while (判断表达式);
练习:求水仙花数
求水仙花数:一个三位数(100 ~ 999),各个位上数字的立方和等于本数字。
分析:
- 3 位数:100 ~ 999,如 234、861。
- 个位数:
int a = num % 10; - 十位数:
int b = num / 10 % 10; - 百位数:
int c = num / 100;
int main(void)
{
//-个位数:int a = num % 10;
//-十位数:int b = num / 10 % 10;
//-百位数:int c = num / 100;
int num = 100; // 数数从 100 开始
int a, b, c; // 定义存储个位、十位、百位数的变量
do {
a = num % 10; // 求个位数
b = num / 10 % 10;
c = num / 100;
// 判断这个数字是否是"水仙花数"
if (a * a * a + b * b * b + c * c * c == num)
{
printf("水仙花数:%d\n", num);
}
num++; // 不断向后数数
} while (num ≤ 999);
return EXIT_SUCCESS;
}