C语言基础内容(三)——第03章_运算符与流程控制

本文详细介绍了C语言中的运算符和流程控制结构,包括算术、赋值、比较、逻辑、位运算符,以及if-else、switch-case、for、while、do-while循环结构。讲解了运算符的使用、优先级和位运算的常见应用,以及流程控制的关键字如break和continue。此外,还讨论了goto语句在特殊情况下的应用。

第03章_运算符与流程控制

本章专题脉络

在这里插入图片描述

1. 运算符(Operator)

运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等。

运算符的分类:

  • 按照功能分为:算术运算符、赋值运算符、比较(或关系)运算符、逻辑运算符、位运算符、条件运算符、sizeof运算符
分类运算符
算术运算符+、-、+、-、*、/、%、++、–
赋值运算符=、+=、-=、*=、/=、%=等
比较(或关系)运算符>、>=、<、<=、==、!=
逻辑运算符&&、||、!
位运算符&、|、^、~、<<、>>
条件运算符(条件表达式)?结果1:结果2
sizeof运算符sizeof()
  • 按照操作数个数分为:一元运算符(单目运算符)、二元运算符(双目运算符)、三元运算符 (三目运算符)
分类运算符
一元运算符(单目运算符)正号(+)、负号(-)、++、–、!、~
二元运算符(双目运算符)除了一元和三元运算符剩下的都是二元运算符
三元运算符 (三目运算符)(条件表达式)?结果1:结果2

1.1 算术运算符

算术运算符专门用于算术运算,主要有下面几种。
在这里插入图片描述举例1:

float x = 6 / 4;
printf("%f\n", x); // 输出 1.000000

float x = 6.0 / 4; // 或者写成 6 / 4.0
printf("%f\n", x); // 输出 1.500000

举例2:%,运算结果的符号与被模数相同

int x1 = 6 % 4; // 2
int x2 = -6 % 4; // -2
int x3 = 6 % -4; // 2
int x4 = -6 % -4; // -2

举例3:自加自减运算

理解:++ 运算,表示自增1。同理,-- 运算,表示自减1,用法与++ 一致。

1、单独使用

  • 变量在单独运算的时候,变量前++和变量后++,是没有区别的。
  • 变量前++ :例如 ++a
  • 变量后++ :例如 a++

2、复合使用

  • 其他变量放在一起使用或者和输出语句放在一起使用前++后++就产生了不同。
  • 变量前++ :变量先自增1,然后再运算。
  • 变量后++ :变量先运算,然后再自增1。
int main() {
    // 其他变量放在一起使用
    int x = 3;
    //int y = ++x; // y的值是4,x的值是4,
    int y = x++; // y的值是3,x的值是4

    printf("%d\n",x);
    printf("%d\n",y);
    printf("==========\n");

    // 和输出语句一起
    int z = 5;
    //printf("%d\n",++z);// 输出结果是6,z的值也是6
    printf("%d\n",z++);// 输出结果是5,z的值是6
    printf("%d\n",z);

    return 0;
}

与此对应的:

  • 变量前-- :变量先自减1,然后再运算。
  • 变量后-- :变量先运算,然后再自减1。

1.2 赋值运算符

  • 符号 =
    • 当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理。
    • 支持连续赋值
  • 扩展赋值运算符: +=、 -=、*=、 /=、%=
运算符名称实例展开形式
+=复合加赋值a+=ba=a+b
-=复合减赋值a-=ba=a-b
*=复合乘赋值a*=ba=a*b
/=复合除赋值a/=ba=a/b
%=复合模赋值a%=ba=a%b

1.3 比较运算符(或关系运算符)

常用的比较运算符:

关系运算符含义举例
>大于num > 10
>=大于等于num >= 10
<小于num < 10
<=小于等于num <= 10
==等于num == 10
!=不等于num != 10
  • 比较运算的结果只有两个取值,要么是真(非0 表示,默认使用1),要么是假(0 表示)。
    • 比如, 20 > 12 返回 1 , 12 > 20 返回 0 。
  • 比较运算符“==”不能误写成“=

举例:多个关系运算符不宜连用。

i < j < k   //期望判断j是否大于i,且小于k

这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量 j 的值在 i 和 k 之间。比如:

//i < j < k
int j = 10;
if(15 < j < 20){
    printf("j大于15,且小于20"); //输出此语句
}else{
    printf("j不在15到20之间");
}

因为关系运算符是从左到右计算,所以实际执行的是:

(i < j) < k; //i < j 返回 0 或 1 ,所以最终是 0 或 1 与变量 k 进行比较

期望的效果应该写为:

//i < j < k
int j = 10;
if(15 < j && j < 20){
    printf("j大于15,且小于20"); 
}else{
    printf("j不在15到20之间"); //输出此语句
}

1.4 逻辑运算符

主要有下面三个运算符:

逻辑运算符描述功能举例
&&与运算符两个条件都要满足num1 >= 10 && num2 >= 20
||或运算符两个条件只需满足其一num1 >= 10 || num2 >= 20
!非运算符否定条件!(num1 >= 10)(等价于 num1 < 10)

逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式。

举例:

aba && ba || b!a
1(真)1(真)1(真)1(真)0(假)
1(真)0(假)0(假)1(真)0(假)
0(假)1(真)0(假)1(真)1(真)
0(假)0(假)0(假)0(假)1(真)

对于逻辑运算符来说,任何非零值都表示真,零值表示伪。比如, 5 || 0 会返回 1 , 5 && 0 会返回0 。

举例1:短路现象

  • &&:a && b
    • 当 a 为假(或0)时,因为a && b 结果必定为 0,所以不再执行表达式 b
    • 当 a 为真(非0)时,因为a && b 结果不确定,所以会继续求解表达式b
int i = 0;
int j = 10;
if(i && j++ > 0){
    printf("床前明月光");
}else{
    printf("我叫郭德纲");
}

printf("%d\n",j); //10

  • || :a || b
    • 当 a 为真(非0)时,因为a || b 结果必定为 1,所以不再执行表达式 b
    • 当 a 为假(或0)时,因为a || b 结果不确定,所以会继续求解表达式b
int i = 1;
int j = 10;
if(i || j++ > 0){
    printf("床前明月光");
}else{
    printf("我叫郭德纲");
}

printf("%d\n",j); //10

1.5 位运算符

  • C 语言提供一些位运算符,用来操作二进制位(bit)。
  • 位运算符的运算过程都是基于二进制的补码运算。
运算符描述运算规则
<<二进制左移将一个数的各二进制位全部左移指定的位数,左边的二进制位丢弃,右边补0。
>>二进制右移将一个数的各二进制位全部右移指定的位数,正数左补0,负数左补1,右边丢弃。
&按位与两个二进制位都为 1,结果为1,否则为0。
|按位或两个二进制位只要有一个为1(包含两个都为 1 的情况),结果为1,否则为0。
^按位异或两个二进制位一个为0,一个为1,结果为1,否则为0。
~按位取反将每一个二进制位变成相反值,即 0 变成 1 , 1 变成 0 。
  • 结合赋值运算符的经验,这里有:<<= 、 >>= 、 &= 、 ^= 等

举例1:

(1)左移:<<

运算规则:在一定范围内,数据每向左移动一位,相当于原数据*2。(正数、负数都适用)

【注意】当左移的位数n超过该数据类型的总位数时,相当于左移(n-总位数)位

3<<4  类似于  3*24次幂 => 3*16 => 48

在这里插入图片描述

-3<<4  类似于  -3*24次幂 => -3*16 => -48

在这里插入图片描述

(2)右移:>>

运算规则:在一定范围内,数据每向右移动一位,相当于原数据/2。(正数、负数都适用)

【注意】

1、如果不能整除,向下取整

2、右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。

69>>4  类似于  69/24次幂 = 69/16 =4

在这里插入图片描述

-69>>4  类似于  -69/24次幂 = -69/16 = -5

在这里插入图片描述

练习:高效的方式计算2 * 8的值(经典面试题)

答案:2 << 3 、  8  << 1

举例2:

(1)按位与:&

运算规则:对应位都是1才为1,否则为0。

  • 1 & 1 结果为1
  • 1 & 0 结果为0
  • 0 & 1 结果为0
  • 0 & 0 结果为0
9 & 7 = 1

在这里插入图片描述

-9 & 7 = 7

在这里插入图片描述
(2)按位或:|

运算规则:对应位只要有1即为1,否则为0。

  • 1 | 1 结果为1
  • 1 | 0 结果为1
  • 0 | 1 结果为1
  • 0 & 0 结果为0
9 | 7  //结果: 15

在这里插入图片描述

-9 | 7 //结果: -9

在这里插入图片描述(3)按位异或:^

运算规则:对应位一个为1一个为0,才为1,否则为0。

  • 1 ^ 1 结果为0
  • 1 ^ 0 结果为1
  • 0 ^ 1 结果为1
  • 0 ^ 0 结果为0
9 ^ 7  //结果为14

在这里插入图片描述

-9 ^ 7 //结果为-16

在这里插入图片描述(4)按位取反:~

运算规则:对应位为1,则结果为0;对应位为0,则结果为1。

  • ~0就是1
  • ~1就是0
~9  //结果:-10

在这里插入图片描述

~-9  //结果:8

在这里插入图片描述练习:特定位清零

技巧:待清零的位与0,其它位与1

示例:设字符型 x 的当前值为 53,将其最低两位清 0,其余位保持不变

分析:与二进制的0b11111100数值求&运算即可。

int main() {
    char x = 53; // 0b00110101
    x = x & 252; // 0b11111100
    printf("%d", x); //0b00110100
    return 0;
}

举例:判断特定位是否为零

技巧:待判定位与 1,其它位与 0;判与运算结果是否为 0

示例:设字符型 x 的当前值为 53,判定其最高位是否为 0

分析:与二进制的0b10000000求&运算,若结果为 0,则最高位为 0,否则为 1

int main() {
    char x = 53; // 0b00110101
    x = x & 128; // 0b10000000
    if(x == 0)
        printf("最高位为0");
    else
        printf("最高位不为0");
    
    return 0;

}

举例:保留特定位

技巧:待保留位全部与 1,其余位与 0

示例:设字符型 x 的当前值为 53,保留其最低 4 位,其余位清零

分析:与二进制的0b00001111求&运算即可。

int main() {
    char x = 53; // 0b00110101
    x = x & 15;  // 0b00001111

    printf("%d\n",x); //0b00000101

    return 0;

}

举例:待置 1 或 1,保持位或 0

示例:设字符型 x 的当前值为 53,将其最低两位置 1,其余位保持不变

分析:与二进制0b00000011求或运算即可

int main() {
    char x = 53; // 0b00110101
    x = x | 3;   // 0b00000011

    printf("%d\n",x); //0b00110111

    return 0;

}

举例:特定位取反

待取反位异或 1,保持位异或 0

示例:设字符型 x 的当前值为 53,将其最低两位取反,其余位保持不变

分析:与二进制0b00000011求异或运算即可

int main() {
    char x = 53; // 0b00110101
    x = x ^ 3;   // 0b00000011

    printf("%d\n",x); //0b00110110

    return 0;

}

1.6 条件运算符

  • 条件运算符格式:
(条件表达式)? 表达式1:表达式2
  • 说明:条件表达式是如果为 true (非0值),就执行表达式1,否则执行表达式2。
    在这里插入图片描述

  • 如果运算后的结果赋给新的变量,要求表达式1和表达式2为同种或兼容的类型

**举例:**获取两个数中的较大值

int main() {
    //获取两个数的较大值
    int m1 = 10;
    int m2 = 20;

    int max1 = (m1 > m2)? m1 : m2;
    printf("m1和m2中的较大值为%d\n",max1);

    retun 0;

}

1.7 sizeof 运算符

sizeof 运算符:sizeof(参数)

  • 参数可以是数据类型的关键字,也可以是变量名或某个具体的值
  • 返回某种数据类型或某个值占用的字节数量。

举例1:参数为数据类型

int x = sizeof(int);  //通常是 4 或 8

举例2:参数为变量

int i;
sizeof(i); //通常是 4 或 8

// 参数为数值
sizeof(3.14); //浮点数的字面量一律存储为double类型,故返回 8

sizeof返回值的类型说明

sizeof 运算符的返回值,C 语言只规定是无符号整数,并没有规定具体的类型,留给系统自己去决定sizeof 到底返回什么类型。不同的系统中,返回值的类型有可能是 unsigned int ,也有可能是unsigned long ,甚至是 unsigned long long ,对应的 printf() 占位符分别是 %u 、 %lu和 %llu 。这样不利于程序的可移植性。

C 语言提供了一个解决方法,创造了一个类型别名 size_t ,用来统一表示 sizeof 的返回值类型。该别名定义在 stddef.h 头文件里面,对应当前系统的 sizeof 的返回值类型,可能是 unsigned int ,也可能是 unsigned long 。

printf() 有专门的占位符 %zd%zu ,用来处理 size_t 类型的值。

printf("%zd\n", sizeof(int));

上面代码中,不管 sizeof 返回值的类型是什么, %zd 占位符(或 %zu )都可以正确输出。

如果当前系统不支持 %zd 或 %zu ,可使用 %u (unsigned int)或 %lu (unsigned long int)代替。

1.8 运算符的优先级

运算符有不同的优先级,所谓优先级就是在表达式运算中的运算符顺序。

上一行中的运算符总是优先于下一行的。
在这里插入图片描述

开发建议:

  1. 不要过多的依赖运算的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用()来控制表达式的执行顺序。
  2. 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成。例如:
    ​ (num1 + num2) * 2 > num3 && num2 > num3 ? num3 : num1 + num2;

2. 流程控制结构

  • 流程控制结构是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。
  • 程序设计中规定的三种流程结构,即:
    • 顺序结构
      • 程序从上到下逐行地执行,中间没有任何判断和跳转。
    • 分支结构
      • 根据条件,选择性地执行某段代码。
      • if…elseswitch-case两种分支语句。
    • 循环结构
      • 根据循环条件,重复性的执行某段代码。
      • forwhiledo-while三种循环语句。

    2.1 顺序结构

程序从上到下逐行地执行。表达式语句都是顺序执行的。并且上一行对某个变量的修改对下一行会产生影响。

在这里插入图片描述

int main() {
    int x = 1;
    int y = 2;
    printf("x = %d\n", x);
    printf("y = %d\n", y);
    //对x、y的值进行修改
    x++;
    y = 2 * x + y;
    x = x * 10;
    printf("x = %d\n", x);
    printf("y = %d\n", y);

    return 0;

}

c语言中定义变量时采用合法的前向引用。如:

int main() {
	int num1 = 12;
	int num2 = num1 + 2;
    
    return 0;
}

2.2 分支结构1:if-else

2.2.1 基本语法

结构1:单分支

if(条件表达式){
  	语句块;

执行流程:条件表达式为真(值不为 0 )时,就执行语句块。

图示:
在这里插入图片描述

举例:成年人心率的正常范围是每分钟60-100次。体检时,如果心率不在此范围内,则提示需要做进一步的检查。

int main(){
    int heartBeats = 89;

    if(heartBeats < 60 || heartBeats > 100){
        printf("你需要做进一步的检查");
    }

    printf("体检结束");
    return 0;
}

结构2:双分支

if(条件表达式) { 
  	语句块1;
}else {
  	语句块2;
}

执行流程:

  1. 首先判断条件表达式看其结果是为真(值不为 0 )还是假(值为0)
  2. 如果是真,就执行语句块1
  3. 如果是假,就执行语句块2

在这里插入图片描述

举例:定义一个整数,判定是偶数还是奇数

int main() {
    int a = 10;

    if (a % 2 == 0) {
        printf("%d是偶数\n", a);
    } else {
        printf("%d是奇数\n", a);
    }
    
    return 0;
}

结构3:多重分支

if (条件表达式1) {
  	语句块1;
} else if (条件表达式2) {
  	语句块2;
}
...
}else if (条件表达式n) {
 	语句块n;
} else {
  	语句块n+1;
}


执行流程:

  1. 首先判断关系表达式1看其结果是真(值不为0)还是假(值为0)
  2. 如果是真,就执行语句块1,然后结束当前多分支
  3. 如果是假,就继续判断条件表达式2,看其结果是真还是假
  4. 如果是真,就执行语句块2,然后结束当前多分支
  5. 如果是假,就继续判断条件表达式…看其结果是真还是假

​ …

n. 如果没有任何关系表达式为真,就执行语句块n+1,然后结束当前多分支。

在这里插入图片描述

当条件表达式之间是“互斥”关系时(即彼此没有交集),条件判断语句及执行语句间顺序无所谓。

当条件表达式之间是“包含”关系时,“小上大下 / 子上父下”,否则范围小的条件表达式将不可能被执行。

当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略

语句块只有一条执行语句时,一对{}可以省略,但建议保留

练习:判断输出结果

int main() {
    int number = 20;
    if (number > 6)
        if (number < 12)
            printf("The number >= 6,且 <= 12.\n");
    else
        printf("判断不成功.\n");

    return 0;
}

说明:

如果有多个 if 和 else ,没有{}的情况下, else 总是跟最接近的 if 匹配。

为了提供代码的可读性,建议使用大括号,明确 else 匹配哪一个 if 。

如果希望else与外层的if匹配,改写为如下:

int main() {
    int number = 20;
    if (number > 6) {
        if (number < 12) {
            printf("The number >= 6,且 <= 12.\n");
        }
    } else {
        printf("判断不成功.\n");
    }

    return 0;
}


2.2.2 举例

举例1:

#include <stdio.h>

int main() {

    int age = 0;
    scanf("%d", &age);
    if (age < 18) {
        printf("未成年\n");
    } else {
        printf("成年\n");
    }

    return 0;
}


举例2:

编写一个程序,判定某个年份是否为闰年。年份满足如下条件之一,即是闰年:

⑴ year 是 400 的整倍数: year%400==0

(2) 能被4整除,但不能被100整除:year % 4 == 0 && year % 100 != 0

#include<stdio.h>

int main() {
	int year;
    printf("输入年份: ");
    scanf("%d", &year);
    if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))
        printf("%d 是闰年\n", year);
    else
        printf("%d 不是闰年\n", year);
    
    return 0;
}

举例3:解方程

设计求解一元二次方程 a x 2 + b x + c = 0 ax^2+bx+c=0 ax2+bx+c=0 (a≠0)的程序

#include<stdio.h>
#include <math.h>

int main() {
    float a, b, c;
    float x1, x2, d;
    printf("输入方程中的系数与常量 a,b,c: ");
    scanf("%f%f%f", &a, &b, &c);
    d = b * b - 4 * a * c;

    if (d >= 0) {
        x1 = (-b + sqrt(d)) / (2 * a);
        x2 = (-b - sqrt(d)) / (2 * a);
        printf("x1=%f,x2=%f\n", x1, x2);
    } else
        printf("方程没有根\n");

    return 0;
}


2.3 分支结构2:switch-case

2.3.1 基本语法

switch 语句用于判断条件有多个常量结果的情况。它把多重的 else if 改成更易用、可读性更好的形式。

switch(表达式){
	case 常量值1: 
        语句块1;
        //break;
	case 常量值2: 
        语句块2;
        //break;
	┇ ┇
	case 常量值n: 
        语句块n; 
        //break;
	[default: 
        语句块n+1;
    ]
}

执行流程图:
在这里插入图片描述
执行过程:

第1步:根据switch中表达式的值,依次匹配各个case。如果表达式的值等于某个case中的常量值,则执行对应case中的执行语句。

第2步:执行完此case的执行语句以后,
​ 情况1:如果遇到break,则执行break并跳出当前的switch-case结构
​ 情况2:如果没有遇到break,则会继续执行当前case之后的其它case中的执行语句。—>case穿透
​ …
​ 直到遇到break关键字或执行完所有的case及default的执行语句,跳出当前的switch-case结构

使用注意点:

  • case子句中的值必须是常量,不能是变量名或不确定的表达式值或范围。
  • 同一个switch语句,所有case子句中的常量值互不相同。
  • 如果没有break,程序会顺序执行到switch结尾;从使用频率说,一般switch-case结构中,都需要编写break。
  • default子句是可选的。同时,位置也是灵活的。当没有匹配的case时,执行default语句。
2.3.2 举例

举例1:

int main() {
    int grade = 1;
    switch (grade) {
        case 0:
            printf("zero\n");
            break;
        case 1:
            printf("one\n");
            break;
        case 2:
            printf("two\n");
            break;
        case 3:
            printf("three\n");
            break;
        default:
            printf("other\n");
    }
    return 0;
}

与如下代码对比:

int main() {
    int grade = 1;
    switch (grade) {
        case 0:
            printf("zero\n");
            //break;
        case 1:
            printf("one\n");
            //break;
        case 2:
            printf("two\n");
            //break;
        case 3:
            printf("three\n");
            //break;
        default:
            printf("other\n");
    }
    return 0;
}

举例2:使用switch-case实现:对学生成绩大于60分的,输出“合格”。低于60分的,输出“不合格”。

如果多个 case 分支对应同样的语句体,可以改写如下:

int main() {
    int score = 83;

    //体会case穿透
    switch(score / 10){
        case 0:
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            printf("不及格");
            break;
        case 6:
        case 7:
        case 8:
        case 9:
        case 10:
            printf("及格");
            break;
        default:
            printf("成绩输入有误");
            break;
    }

    return 0;
}

从算法层面,还可以优化下代码,改为如下:

int main() {
    int score = 83;

    //方式3:算法层面优化
    switch(score / 60){
        case 0:
            printf("不及格");
            break;
        case 1:
            printf("及格");
            break;
        default:
            printf("成绩输入有误");
            break;
    }

    return 0;
}

2.3.3 if-else与switch-case比较
  • 结论:凡是使用switch-case的结构都可以转换为if-else结构。反之,不成立。
  • 开发经验:如果既可以使用switch-case,又可以使用if-else,建议使用switch-case。因为效率稍高。
  • 细节对比:
    • if-else语句优势
      • if语句的条件可以用于范围的判断,也可以用于等值的判断,使用范围更广
      • switch语句的条件是一个常量值,只能判断某个变量或表达式的结果是否等于某个常量值,使用场景较狭窄
    • switch语句优势
      • 当条件是判断用穿透性,同时执行多个分支,而if…else没有穿透性。

2.4 循环结构之1:for循环

  • 循环结构的理解:循环语句具有在某些条件满足的情况下,反复执行特定代码的功能。
  • 循环结构分类:
    • for 循环
    • while 循环
    • do-while 循环
  • 循环结构四要素
    • 初始化部分
    • 循环条件部分
    • 循环体部分
    • 迭代部分
    2.4.1 基本语法

语法格式:

for (①初始化部分; ②循环条件部分; ④迭代部分){
         	③循环体部分;

**执行过程:**①-②-③-④-②-③-④-②-③-④-…-②

图示:
在这里插入图片描述说明:

  • for(;;)中的两个;不能多也不能少
  • ①初始化部分,用于初始化循环变量,只执行一次。可以声明多个变量,但必须是同一个类型,用逗号分隔
  • ②循环条件部分,只要为 true ,就会不断执行循环体;当值为false时,退出循环
  • ④迭代部分,每轮循环结束后执行,使得循环变量发生变化。可以有多个变量更新,用逗号分隔
2.4.2 举例

案例1:使用for循环重复执行某些语句

题目:输出5行HelloWorld

int main() {

    for (int i = 1; i <= 5; i++) {
        printf("Hello World!\n");
    }

    return 0;
}

案例2:格式的多样性

题目:写出输出的结果

int main() {

    int num = 1;
    for(printf("a");num < 3;printf("c"),num++){
        printf("b");

    }
    return 0;
}

int main() {

    int i, j;
    for (i = 0, j = 999; i < 10; i++, j--) {
        printf("%d, %d\n", i, j);
    }

    return 0;
}

案例3:累加的思想

题目:遍历1-100以内的偶数,并获取偶数的个数,获取所有的偶数的和

int main() {

    int count = 0;//记录偶数的个数
    int sum = 0;//记录偶数的和

    for (int i = 1; i <= 100; i++) {

        if (i % 2 == 0) {
            printf("%d\n", i);
            count++;
            sum += i;
        }

        //printf("偶数的个数为:" + count);
    }

    printf("偶数的个数为:%d\n", count);
    printf("偶数的总和为:%d\n", sum);
    
    return 0;
}

案例4:结合分支结构使用

题目:输出所有的水仙花数,所谓水仙花数是指一个3位数,其各个位上数字立方和等于其本身。例如: 153 = 1*1*1 + 3*3*3 + 5*5*5

int main() {

    //定义统计变量,初始化值是0
    int count = 0;

    //获取三位数,用for循环实现
    for (int x = 100; x < 1000; x++) {
        //获取三位数的个位,十位,百位
        int ge = x % 10;
        int shi = x / 10 % 10;
        int bai = x / 100;

        //判断这个三位数是否是水仙花数,如果是,统计变量++
        if ((ge * ge * ge + shi * shi * shi + bai * bai * bai) == x) {
            printf("水仙花数:%d\n", x);
            count++;
        }
    }

    //输出统计结果就可以了
    printf("水仙花数共有%d个", count);

    return 0;
}


拓展:

打印出四位数字中“个位+百位”等于“十位+千位”并且个位数为偶数,千位数为奇数的数字,并打印符合条件的数字的个数。

案例5:结合break的使用

说明:输入两个正整数m和n,求其最大公约数和最小公倍数。

比如:12和20的最大公约数是4,最小公倍数是60。

int main() {

    //需求1:最大公约数
    int m = 12, n = 20;
    //取出两个数中的较小值
    int min = (m < n) ? m : n;

    for (int i = min; i >= 1; i--) {//for(int i = 1;i <= min;i++){

        if (m % i == 0 && n % i == 0) {
            printf("最大公约数是:%d\n", i); //公约数

            break; //跳出当前循环结构
        }
    }


    //需求2:最小公倍数
    //取出两个数中的较大值
    int max = (m > n) ? m : n;

    for (int i = max; i <= m * n; i++) {

        if (i % m == 0 && i % n == 0) {

            printf("最小公倍数是:%d\n", i);//公倍数

            break;
        }
    }

    return 0;
}


说明:

1、我们可以在循环中使用break。一旦执行break,就跳出当前循环结构。

2、小结:如何结束一个循环结构?

​ 结束情况1:循环结构中的循环条件部分返回false

​ 结束情况2:循环结构中执行了break。

3、如果一个循环结构不能结束,那就是一个死循环!我们开发中要避免出现死循环。

2.5 循环结构之2:while循环

2.5.1 基本语法

语法格式:

①初始化部分
while(②循环条件部分){
    ③循环体部分;
    ④迭代部分;
}

**执行过程:**①-②-③-④-②-③-④-②-③-④-…-②

图示:

在这里插入图片描述

说明:

  • while(循环条件部分)中循环条件为非零值,表示true、真;为零值,表示false、伪。
  • 注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
  • for循环和while循环可以相互转换。二者没有性能上的差别。实际开发中,根据具体结构的情况,选择哪个格式更合适、美观。
  • for循环与while循环的区别:初始化条件部分的作用域不同
2.5.2 举例

**案例1:**输出5行HelloWorld!

int main() {
    int i = 1;
    while (i <= 5) {
        printf("Hello World!\n");
        i++;
    }
    return 0;
}

**案例2:**遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)

int main() {
    //遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)
    int num = 1;

    int sum = 0;//记录1-100所有的偶数的和
    int count = 0;//记录1-100之间偶数的个数

    while (num <= 100) {

        if (num % 2 == 0) {
            printf("%d\n", num);
            sum += num;
            count++;
        }

        //迭代条件
        num++;
    }

    printf("偶数的总和为:%d\n", sum);
    printf("偶数的个数为:%d\n", count);
    return 0;
}


案例3:折纸珠穆朗玛峰

世界最高山峰是珠穆朗玛峰,它的高度是8848.86米,假如我有一张足够大的纸,它的厚度是0.1毫米。
请问,我折叠多少次,可以折成珠穆朗玛峰的高度?

int main() {
    //定义一个计数器,初始值为0
    int count = 0;

    //定义珠穆朗玛峰的高度
    int zf = 8848860;//单位:毫米

    double paper = 0.1;//单位:毫米

    while (paper < zf) {
        //在循环中执行累加,对应折叠了多少次
        count++;
        paper *= 2;//循环的执行过程中每次纸张折叠,纸张的厚度要加倍
    }

    //打印计数器的值
    printf("需要折叠:%d次\n", count);
    printf("折纸的高度为%f米,超过了珠峰的高度", paper / 1000);

    return 0;
}

2.6 循环结构之3:do-while循环

do-while 结构是 while 的变体,它会先执行一次循环体,然后再判断是否满足条件。如果满足的话,就继续执行循环体,否则跳出循环。

2.6.1 基本语法

语法格式:

①初始化部分;
do{
	③循环体部分
	④迭代部分
}while(②循环条件部分); 

**执行过程:**①-③-④-②-③-④-②-③-④-…-②

图示:
在这里插入图片描述

说明:

  • do{}while();最后有一个分号
  • do-while结构的循环体语句是至少会执行一次,这个和for和while是不一样的
  • 循环的三个结构for、while、do-while三者是可以相互转换的。
2.6.2 举例

**案例1:**遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)

int main() {
    //遍历1-100的偶数,并计算所有偶数的和、偶数的个数(累加的思想)
    //初始化部分
    int num = 1;

    int sum = 0;//记录1-100所有的偶数的和
    int count = 0;//记录1-100之间偶数的个数

    do {
        //循环体部分
        if (num % 2 == 0) {
            printf("%d\n", num);
            sum += num;
            count++;
        }

        num++;//迭代部分


    } while (num <= 100); //循环条件部分


    printf("偶数的总和为:%d\n", sum);
    printf("偶数的个数为:%d\n", count);

    return 0;
}

**案例2:**体会do-while至少会执行一次循环体

int main() {
    //while循环:
    int num1 = 10;
    while (num1 > 10) {
        printf("hello:while\n");
        num1--;
    }

    //do-while循环:
    int num2 = 10;
    do {
        printf("hello:do-while\n");
        num2--;
    } while (num2 > 10);

    return 0;
}

案例3:ATM取款

声明变量balance并初始化为0,用以表示银行账户的余额,下面通过ATM机程序实现存款,取款等功能。

=========ATM========
   1、存款
   2、取款
   3、显示余额
   4、退出
请选择(1-4):

int main() {
    //初始化条件
    double balance = 0.0;//表示银行账户的余额
    int selection; //记录客户的选择

    double addMoney, minusMoney; //分别记录存钱、取钱的额度

    int isFlag = 1;//用于控制循环的结束

    do {
        printf("=========ATM========\n");
        printf("\t1、存款\n");
        printf("\t2、取款\n");
        printf("\t3、显示余额\n");
        printf("\t4、退出\n");
        printf("请选择(1-4):");

        scanf("%d", &selection);

        switch (selection) {
            case 1:
                printf("要存款的额度为:");
                scanf("%lf", &addMoney);
                if (addMoney > 0) {
                    balance += addMoney;
                }
                break;
            case 2:
                printf("要取款的额度为:");
                scanf("%lf", &minusMoney);
                if (minusMoney > 0 && balance >= minusMoney) {
                    balance -= minusMoney;
                } else {
                    printf("您输入的数据非法或余额不足\n");
                }
                break;
            case 3:
                printf("当前的余额为:%lf\n", balance);
                break;
            case 4:
                printf("欢迎下次进入此系统。^_^\n");
                isFlag = 0;
                break;
            default:
                printf("请重新选择!\n");
                break;
        }

    } while (isFlag);

    return 0;
}


2.6.3 小结:三种循环结构
  • 三种循环结构都具有四个要素:
    • 循环变量的初始化条件
    • 循环条件
    • 循环体语句块
    • 循环变量的修改的迭代表达式
  • 从循环次数角度分析
    • do-while循环至少执行一次循环体语句。
    • for和while循环先判断循环条件语句是否成立,然后决定是否执行循环体。
  • 如何选择
    • 遍历有明显的循环次数(范围)的需求,选择for循环
    • 遍历没有明显的循环次数(范围)的需求,选择while循环
    • 如果循环体语句块至少执行一次,可以考虑使用do-while循环
    • 本质上:三种循环之间完全可以互相转换,都能实现循环的功能

2.7 "无限"循环

2.7.1 基本语法

语法格式:

  • 最简单"无限"循环格式:while(1) , for(;;)

适用场景:

  • 开发中,有时并不确定需要循环多少次,需要根据循环体内部某些条件,来控制循环的结束(使用break)。
  • 如果此循环结构不能终止,则构成了死循环!开发中要避免出现死循环。
2.7.2 举例

**案例1:**实现爱你到永远…

int main() {
    for (;;) {
        printf("你好!\n");
    }
    //printf("end\n");//永远无法到达的语句

    return 0;
}

int main() {
    for (int i = 1; i <= 10; ){ //循环变量没有修改,条件永远成立,死循环
        printf("你好!\n");
    }

    return 0;
}

**案例2:**从键盘读入个数不确定的整数,并判断读入的正数和负数的个数,输入为0时结束程序。

int main() {

    int positiveNumber = 0;//统计正数的个数
    int negativeNumber = 0;//统计负数的个数
    int num;  //记录输入的整数

    while(1){   //for (;;){
        printf("请输入一个整数(输入为0时结束程序):");
        scanf("%d", &num);
        if (num > 0) {
            positiveNumber++;
        } else if (num < 0) {
            negativeNumber++;
        } else {
            printf("程序结束\n");
            break;
        }
    }
    printf("正数的个数为:%d\n", positiveNumber);
    printf("负数的个数为:%d\n", negativeNumber);


    return 0;
}

2.8 嵌套循环(或多重循环)

2.8.1 使用说明
  • 所谓嵌套循环,是指一个循环结构A的循环体是另一个循环结构B。比如,for循环里面还有一个for循环,就是嵌套循环。其中,for ,while ,do-while均可以作为外层循环或内层循环。
    • 外层循环:循环结构A
    • 内层循环:循环结构B

在这里插入图片描述

  • 实质上,嵌套循环就是把内层循环当成外层循环的循环体。只有当内层循环的循环条件为false(值为0)时,才会完全跳出内层循环,才可结束外层的当次循环,开始下一次的外层循环。
  • 设外层循环次数为m次,内层为n次,则内层循环体实际上需要执行m*n次。
  • **技巧:**从二维图形的角度看,外层循环控制行数,内层循环控制列数
  • **开发经验:**实际开发中,我们最多见到的嵌套循环是两层。一般不会出现超过三层的嵌套循环。如果将要出现,一定要停下来重新梳理业务逻辑,重新思考算法的实现,控制在三层以内。否则,可读性会很差。

例如:两个for嵌套循环格式

for(初始化语句①; 循环条件语句②; 迭代语句⑦) {
    for(初始化语句③; 循环条件语句④; 迭代语句⑥) {
      	循环体语句⑤;
    }
}

//执行过程:① - ② - ③ - ④ - ⑤ - ⑥ - ④ - ⑤ - ⑥ - ... - ④ - ⑦ - ② - ③ - ④ - ⑤ - ⑥ - ④..

**执行特点:**外层循环执行一次,内层循环执行一轮。

2.8.2 举例

**案例1:*打印5行6个

int main() {

    /*

    ******
    ******
    ******
    ******
    ******

    */

    for (int j = 1; j <= 5; j++) {

        for (int i = 1; i <= 6; i++) {
            printf("*");
        }

        printf("\n");
    }

    return 0;
}

**案例2:**打印5行直角三角形

*
**
***
****
*****

int main() {

    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= i; j++) {
            printf("*");
        }
        printf("\n");
    }

    return 0;
}	

**案例3:**打印5行倒直角三角形

		i	j(*的上限)   i + j = 6  --> j = 6 - i
*****   1	5
****	2	4
***		3	3
**		4	2
*		5	1

int main() {

    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= 6 - i; j++) {
            printf("*");

        }
        printf("\n");
    }

    return 0;
}

案例4:九九乘法表

int main() {

    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            printf("%d*%d=%d\t", i, j, i * j);
        }
        printf("\n");
    }
    return 0;
}

2.9 break和continue关键字

2.9.1 使用说明
关键字适用范围循环结构中的作用相同点
breakswitch-case--
break循环结构一旦执行,就结束(或跳出)当前循环结构此关键字的后面,不能声明语句
continue循环结构一旦执行,就结束(或跳出)当次循环结构此关键字的后面,不能声明语句

举例1:在全系1000名学生中举行慈善募捐,当总数达到10万元时就结束,统计此时捐款的人数以及平均每人捐款的数目。

#include <stdio.h>

#define SUM 100000 //指定符号常量SUM代表10万

//在全系1000名学生中举行慈善募捐,当总数达到10万元时就结束,统计此时捐款的人数以及平均每人捐款的数目。
int main() {
    double amount, total = 0;//分别代表着每人捐款的数额,总捐款额
    int count = 0; //捐款人数
    for (int i = 1; i <= 1000; i++) {
        printf("请输入你的捐款额:");
        scanf("%lf", &amount);
        total = total + amount;
        count++;
        if (total >= SUM)
            break;
    }
    double aver = total / count; //人均捐款额度
    printf("捐款总人数是:%d\n人均捐款额为:%10.2f\n", count, aver);
    return 0;
}

举例2:要求输出100~200之间的不能被3整除的数。

int main() {
    int n;
    for (n = 100; n <= 200; n++) {
        if (n % 3 == 0)
            continue;
        printf("%d ", n);
    }
    printf("\n");
    return 0;
}

2.10 goto关键字

使用goto,可以实现无条件的语句的转移。

一般格式:

goto 标号;

其中,标号,属于标识符,以“:”为标记,位于某语句前面。

执行 goto 语句后,程序将跳转到指定标号处执行。这样可以随意将控制转移到程序中的任意一条语句上,然后执行它。

举例1:

int main() {
    
    loop_label:printf("Hello, world!\n");
    goto loop_label;

    return 0;
}

loop_label是一个标签名,可以放在正常语句的前面。程序执行到 goto 语句,就会跳转到它指定的标签名位置继续执行。因此,上面的代码会产生无限循环。

实际使用中,goto语句通常与条件语句配合。可用来实现条件转移,跳出循环体等功能。

**举例2:**录入学生成绩,并计算学生的平均分。当输入-1时程序结束。

int main() {
    int score, i = 0, sum = 0;
    next:printf("请输入第 %d 个学生成绩(输入-1结束):", i+1);
    scanf("%d", &score);
    if (score != -1) {
        sum += score;
        i++;
        goto next;
    }
    if (i != 0)
        printf("%d个学生的平均分是 %d\n",i, sum / i);

    return 0;
}

注意:goto 只能在同一个函数之中跳转,并不能跳转到其他函数。

**举例3:**goto 的一个主要用法是跳出多层循环

for(...) {
  for (...) {
    while (...) {
      do {
        if (some_error_condition)
          goto bail;    
     } while(...);
   }
 }
}
    
bail:
// ... ...

上面代码有很复杂的嵌套循环,不使用 goto 的话,想要完全跳出所有循环,写起来很麻烦。

**举例4:**goto 的另一个用途是提早结束多重判断

if (do_something() == ERR)
  goto error;
if (do_something2() == ERR)
  goto error;
if (do_something3() == ERR)
  goto error;
if (do_something4() == ERR)
  goto error;

上面示例有四个判断,只要有一个发现错误,就使用 goto 跳过后面的判断。

小结:

从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。使用goto反而容易造成程序流程的混乱,致使程序容易出错。故建议不要轻易使用。

这里只是为了语法的完整,介绍一下它的用法。

共150讲,时长共 33小时18分钟 1) 优秀的程序应该是这样的:阅读时,感觉很优雅;新增功能时,感觉很轻松;运行时,感觉很快速,这就需要设计模式支撑。 2) 设计模式包含了大量的编程思想,讲授和真正掌握并不容易,网上的设计模式课程不少,大多讲解的比较晦涩,没有真实的应用场景和框架源码支撑,学习后,只知其形,不知其神。就会造成这样结果: 知道各种设计模式,但是不知道怎么使用到真实项目。本课程针对上述问题,有针对性的进行了升级 (1) 授课方式采用 图解+框架源码分析的方式,让课程生动有趣好理解 (2) 系统全面的讲解了设计模式,包括 设计模式七大原则、UML类图-类的六大关系、23种设计模式及其分类,比如 单例模式的8种实现方式、工厂模式的3种实现方式、适配器模式的3种实现、代理模式的3种方式、深拷贝等 3) 如果你想写出规范、漂亮的程序,就花时间来学习下设计模式吧 课程内容和目标 本课程是使用Java来讲解设计模式,考虑到设计模式比较抽象,授课采用 图解+框架源码分析的方式 1) 内容包括: 设计模式七大原则(单一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML类图(类的依赖、泛化和实现、类的关联、聚合和组合) 23种设计模式包括:创建型模式:单例模式(8种实现)、抽象工厂模式、原型模式、建造者模式、工厂模式。结构型模式:适配器模式(3种实现)、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式(3种实现)。行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式) 2) 学习目标:通过学习,学员能掌握主流设计模式,规范编程风格,提高优化程序结构和效率的能力
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值