一、介绍
1.1 有哪些分支(选择)和循环语句
- 分支:if;switch
- 循环:while;for;do…while
- goto语句
1.2 C语言的结构化体现在哪?
- 任何一种事情 都可以由这三种结构排列组合而成
- 这是一种高度抽象
1.3 C语言有哪些语句类型
- 2+3;
- Add(2,3);
- 控制语句用于控制程序的执行流程,以实现程序的各种结构方式 :顺序结构,选择结构,循环结构
- C语言有九种控制语句:
二、分支语句if(选择结构)
2.1 if-if-if和if-elif-el的区别
-
if-if 两段if都要执行 都要进行判断 ( )里的判断关系是或者||关系
-
if elseif elseif 看做整体 只有一个入口
-
if elif elif elif … else 是一个整体 也只有一个入口
-
最外面的两个if都要判断 因为闰年本来就有两种可能的情况 这样写没问题
-
这种写法 只有一个入口 也很合逻辑 所以( )里的条件肯定是互斥的
-
这就是一个错误
-
我的逻辑肯定是只有一个入口的 因为这是单值函数
-
但是这么写有可能会进去两次 y的值被改了两次
2.2 擅用if嵌套
if(是人)
{
//比如在判断一个人是不是成年之前 我得确保他是个人 才进来判断
if-else if-else
}
2.3 擅用{ }和缩进!!!
-
这坨狗屎代码 风格很差! 虽然没错!
-
注意:下面蓝色的if else整体可以看作一条语句(因为要么执行if的hehe 要么执行else的haha 最终只执行一条语句的) 所以可以不用加上大括号
-
可以不加就不加了么 加上去多清晰啊
-
这段代码什么都不输出 因为a == 1为假 不进去了
-
既然意思是一样的 这样写多好
2.4 悬空else和最近的if匹配
- 就算故意这么缩进 else也是和最近的一个if匹配的
- else就近匹配 和离他最近的 还未匹配的if匹配
- 代码风格很重要 这种代码一眼就看出谁和谁匹配
2.5 return之后程序就结束了
- 1和2本质上是一样的 都表示:条件成立就return x 否则就return y
- 因为return之后程序就不执行了
- 当然如果这里不是return的话 那就不一样了
代码1就不是非此即彼的关系了 第二句一定会执行 - 建议写成代码2这种风格 多写点没事 要一目了然
//代码1
if (condition) {
return x;
}
return y;
//代码2
if(condition)
{
return x;
}
else
{
return y;
}
2.6 if里判断变量==常量比较的时候 建议把常量放在左边
如果我这样不小心写错了 编译器可以成功编译 不能发现问题
如果我这么写 就编译失败 容易发现错误
- 如果是表达式的话 就无所谓了 左边右边都行 因为表达式本身就不可以被赋值
2.7 再次理解一下if elseif的逻辑
在后面写判断闰年的时候 我们很明确:
闰年有两种判断方法 应该是或者的关系
给你一个年份 只要满足两种规则的其一 他就是闰年 所以我们说 肯定是if if都判断 而不能是if elseif
但是实际上还是要具体去理解整体代码的逻辑的 比如下图 虽然是if elseif 但是逻辑并没有错
下面是五种写法 可以都看看 思考思考逻辑
下图是运行结果可以看出函数5把2000年漏掉了
5这种写法才必须是if if的 因为2000一上去只进入第一个if 不会再去尝试else if了总结来说 也没这么复杂
只要(year % 400 == 0)
或者(year % 4 == 0) && (year % 100 != 0)
这两个规则都可以被试到就行了
int isLeapYear1(int year)
{
return (year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0));
}
int isLeapYear2(int year)
{
if ((year % 4 == 0) && (year % 100 != 0))
return 1;
//这里也可以写成if if
else if (year % 400 == 0)
return 1;
return 0;
}
int isLeapYear3(int year)
{
if ((year % 4 == 0) && (year % 100 != 0))
return 1;
if (year % 400 == 0)
return 1;
return 0;
}
int isLeapYear4(int year)
{
if (year % 4 == 0)
{
if (year % 100 != 0)
return 1;
}
//这里必须写if 不能是else if
if (year % 400 == 0)
return 1;
return 0;
}
int isLeapYear5(int year)
{
if (year % 4 == 0)
{
if (year % 100 != 0)
return 1;
}
//这里必须写if 不能是else if
else if (year % 400 == 0)
return 1;
return 0;
}
int main()
{
int i = 0;
for (i = 1949; i <= 2024; i++)
{
if (isLeapYear1(i))
printf("%d ", i);
}
printf("=================\n");
for (i = 1949; i <= 2024; i++)
{
if (isLeapYear2(i))
printf("%d ", i);
}
printf("=================\n");
for (i = 1949; i <= 2024; i++)
{
if (isLeapYear3(i))
printf("%d ", i);
}
printf("=================\n");
for (i = 1949; i <= 2024; i++)
{
if (isLeapYear4(i))
printf("%d ", i);
}
printf("=================\n");
for (i = 1949; i <= 2024; i++)
{
if (isLeapYear5(i))
printf("%d ", i);
}
printf("=================\n");
return 0;
}
三、分支语句switch(选择结构)
3.1 使用场景
假如要判断是星期几 用if很繁琐
3.2 注意穿透现象
- 没有break 会发生穿透 要灵活运用break
- case决定入口;break决定出口(不再继续执行下一个case 跳出当前switch)
- 但是这个 “穿透” 并不一定就是坏的 这只是一种现象 下图反而利用了穿透
- 多个case语句执行同一条语句 就可以利用穿透
3.3 default不是
必须放在最后的
- default放在最后 他的break才能省略
- 其实default也是一个入口 表示"其他情况的入口"
3.4 switch+do…while的一种经典用法
如果输入0
break到switch外面
继续执行while(input) 发现为假 则出循环
3.5 switch( )的括号里必须是整型表达式
- switch(必须是整型表达式) 可以是变量 也可以是常量
- 枚举的值也可以 枚举是枚举类型 但是枚举的值肯定是整型
3.6 case后面必须跟整型常量表达式
case跟1;2;1+1 都行 但不可以是变量n 常变量也不行
- 字面常量ok
- #define定义的标识符常量ok
- 枚举常量ok
- const修饰的常变量NO(因为本质还是个变量 只是具备常量不可修改的属性)
3.7 switch可以嵌套使用 break只会跳出当前一层的switch
- break只会跳出当前的switch分支 只跳一层
- 第一个break执行 只跳出内层switch 继续执行外层switch的case 4
答案:m=5 n=3
3.8 switch里面没有continue 只有break
- break可以说是跳出当前一层的switch 不再继续执行下一个case了
- continue只有循环里会有 跳过当次循环 继续下一次
3.9 break的两种语法
- 跳出当前switch分支 只跳一层
- 跳出当前的整个循环(continue只跳过当次循环)
所以循环里有switch 不要搞混淆了
这个break只跳出当前的switch 然后会继续执行循环判断条件
四、循环语句
4.1 有三种循环语句
- while
- for
- do while
4.2 while的执行流程
- continue是跳到判断部分去了
4.3 break和continue
- continue就是跳过本次循环 直接跳到循环判断部分
下面的代码会死循环 打印1 2 3 4然后死循环 我又写成1 2 3 4 6 7 8 9 10了
continue跳过本次循环 当n=5的时候 再也没机会自增了!!! 就死循环了!!
做这三个地方的修改 才是我想的打印1~10 除了5
- break是直接把当前这一层的循环结束了
4.4 for循环
- for循环弥补了while循环三个必要条件太分散的缺点
- continue是跳到调整部分去了
4.5 for和while的对比
可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远, 这样查找修改就不够集中和方便 所以,for循环的风格更胜一筹;for循环使用的频率也最高。
int i = 0;
//实现相同的功能,使用while
i=1;//1.初始化部分
while(i<=10)//2.判断部分
{
printf("hehe\n");
i = i+1;//3.调整部分
}
//实现相同的功能,使用while
for(i=1; i<=10; i++)
{
printf("hehe\n");
}
4.6 continue在while和for中的差异
- break都是终止循环 在while和for里都很好理解 没什么差异
- continue虽然都是跳过本次循环 但有所差异
- 下图也能进一步说明 for循环更好!
在for循环中 continue跳到调整部分 不容易死循环
下图打印1 2 3 4 6 7 8 9 10 不会死循环因为每次continue 会跳到i++
在while/do while循环 continue跳到判断部分 小心死循环
4.7 for循环的变种
- 判断部分如果被省略 就是死循环
4.8 可以使用多个变量控制循环
- 下图打印两个hehe
- 多个变量之间用逗号隔开
4.9 do while
-
就一个注意点 do while的特点很明确
他至少会执行一次
-
stmt是循环体 expr是判断表达式
-
他和while一样
continue会跳到判断部分
-
故下图代码打印1 2 3 4 5之后会死循环的打印5(看清楚 这里5也会打印哦~)
4.10 建议在循环体外定义变量 在循环体内赋值
-
循环里定义的变量 不会有重定义报错
-
建议就在循环外面int flag; 然后在循环里面flag=1;
-
不建议像下图这么写 只是理解一下为什么不报错(我曾经感觉这是重定义行为)
五、经典案例:猜数字游戏
RAND_MAX
- 也就是32767
time_t
- 所以time_t的本质就是long long
NULL空指针
- 他的本质其实是0
rand()函数的问题-stdlib.h
头文件是stdlib.h
问题在于
第一次执行程序 他生产的随机数 和我第二次运行生成的随机数 是一模一样的
其实rand函数并不是这么用的
srand()设置随机数种子-stdlib.h
来看一下
rand()函数的介绍
翻译:这个数字是由算法生成的,每次调用它都会返回一个明显不相关的数字序列。该算法使用种子来生成序列,并使用srand函数将其初始化为特定的值。
大致意思就是rand()使用之前 必须先调用一下srand()
-
但是又存在一个问题 如果我给srand()的参数是一个固定的0 如下图 那我首先的确会生成一个随机数:38 但是我每次选择玩游戏的时候 他生成的都是38
-
经过测试 发现 我把0换成1 2 100等等 生成的随机数也会随之变化
-
所以问题就是在于 如果我把srand()的参数写死了 对应的随机数就死了!!
-
猜想:该怎么给srand()传一个不断变化的参数呢?---时间戳
时间戳与time()函数-time.h
-
C语言中 time()函数 会返回时间戳
-
time()需要time.h
-
time()的返回值本质是long long
-
time()需要的参数类型是time_t* 一个指针—>不管他 就给他传一个空指针NULL就行 只要time()可以把时间戳返回就行了
srand()在程序中只要调用一次就行
-
如果每次选择1 玩游戏 都重新设置一下起点 会出现问题 如下图
-
因为我把srand()放在了game()里面 每次选择玩游戏 都会执行srand() 设置一下随机数种子 每次都重新设置起点
-
而srand()在整个程序只需要调用一次就行了
-
把他放在main函数 确保只调用一次即可
总结
- 上来先用ran()生成随机数 但是发现假如第一次运行生成的随机数是1 99 8 第二次运行生成的还是1 99 8 是同一个序列
- 而rand()已经告诉我们 需要用到srand() 设置随机数生成器
也就是说在调用rand() 先调用srand()设置一下随机数种子
- 但此时又发现 如果传给srand()的参数是固定的 生成的随机数也是固定的–>
所以希望传一个不断变化的数给srand() 也就是时间戳
- 最红写为
srand((unsigned int)time(NULL));
然后int answer = rand() % 100 - 1;
就可以生成随机数了 - rand()调用之前 的确需要调用srand()
但是srand()调用一次即可 不要频繁调用 否则就不够随机
- time()需要的参数是time_t*
返回值是time_t(本质是long long)
所以传入NULL 只要time()把时间戳返回就行 srand()需要的参数是unsigned int 返回值是void
所以还要把time返回的time_t的时间戳强转一下 再传给srand
怎么设置随机数的范围
终于可以开始猜数字了
- 完整参考代码:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void menu()
{
printf("****************************\n");
printf("***1.play**********0.exit***\n");
printf("****************************\n");
}
void game()
{
//生成随机数
int ret = rand() % 100 + 1;//1~100
int guess = 0;
//猜数字
while (1)
{
scanf("%d", &guess);
if (guess < ret)
{
printf("猜小了\n");
}
else if (guess > ret)
{
printf("猜大了\n");
}
else
{
printf("猜对了\n");
break;
}
printf("再猜\n");
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//注意只要设置一次随机数生成起点
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请猜数字:\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择:\n");
break;
}
} while (input);
//只有input为0的时候才会跳出循环,其余情况都会进入循环
//而且input是定义在循环外面的 所以这里才可以使用 如果定义在循环里的话 input就只能在循环体内用
}
- 这是我后来写过的一次 并没有模块化编程
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int input = 0;
int choose = 0;
srand((unsigned int)time(NULL));//随机数种子只要生成一次
while (1)
{
int answer = rand() % 100 - 1;
//可千万不要把这个写在下面猜数字的地方了 要不然每次都生成一个随机数
printf("**********1.play 0.exit**********\n");
printf("请输入你的选择:\n");
scanf("%d", &choose);
switch (choose)
{
case 0:
printf("bye~\n");
break;
case 1:
while (1)
{
printf("输入你猜的数字:\n");
scanf("%d", &input);
if (answer < input)
printf("猜大了\n");
else if (answer > input)
printf("猜小了\n");
else
{
printf("猜对了\n");
break;
}
}
break;
default:
printf("输入错误!请重新选择\n");
break;
}
//可能是case 1 ,case 0, default三种情况
//case 0 就需要结束整个游戏 剩余的 需要继续让玩家选择
if (choose == 0)
break;
}
return 0;
}
一些离谱的错误
- 每次猜数字之前 都生成一个随机数 这个错误很逆天
六、补充:goto语句
6.1 介绍
-
C语言允许goto 让他随意跳转
-
但是goto语句只能在同一个函数内部跳转 不能跨函数跳转
-
死循环打印hehe了
6.2 goto适用场景
- 尽量少用goto 但也不是不可以用
- 一次跳出两层或多层循环 这种情况使用break是达不到目的的 break只能从最内层循环退出到上一层的循环
for(...)
for(...)
{
for(...)
{
if(disaster)
goto error;
}
}
…
error:
if(disaster)
// 处理错误情况
6.3 shutdown
- -s立即关机 -t给他设置关机时间
- -a取消关机
6.4 goto小案例-关机程序
- system()的头文件是stdlib.h
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char input[20] = " ";
system("shutdown -s -t 60");
again:
printf("输入我是猪不然关机\n");
scanf("%s", input);
if (strcmp(input, "我是猪") == 0)
{
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
//这里调试的时候把debug改成release
- 其实完全可以用while语句来实现