跳到主要内容

运算符、分支与循环

数值溢出

如果 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 注意事项

  1. 不要随意在 scanf() 函数中添加 '\n'。如果添加了换行符,会被 scanf 当成格式来约束用户输入。

    • printf() 中的 '\n' 用来向屏幕输出换行。
    • scanf() 函数中的 '\n' 不能用来输出,因为这是一个输入函数。
  2. 解决 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.
  3. 解决方法:在 .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;
}

运算符

算数运算符

  1. + - * /:先乘除取余,后加减。
  2. 除法运算后,得到的结果赋值给整型变量,取整数部分。int c = 10/20; ——> 0
  3. 除 0:错误操作,不允许。printf("%d\n", 10/0);
  4. 对 0 取余:错误操作,不允许。printf("%d\n", 123 % 0);
  5. 不允许对小数取模。35 % 3.4;
  6. 对负数取余,结果为余数的绝对值。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;
}

赋值运算符

  1. = 在计算机中只能完成"赋值"操作,一定是右边赋值给左边,也叫单向赋值符。
  2. a += 10; 等价于 a = a + 10;
  3. a -= 30; 等价于 a = a - 30;
  4. 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;
}

类型转换

隐式类型转换

  1. 编译器自动完成(小类型转大类型,或同类型大小之间转换)。
  2. 由赋值产生。

规律:

  • 小类型 ——> 大类型:没问题。
  • 大类型 ——> 小类型:可能丢失数据。

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。

强制类型转换

语法:

  1. 强转变量:(目标类型)变量
  2. 强转表达式:(目标类型)表达式
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;
}