为了展示流程控制的作用,需要每次执行程序时都使用不一样的值。
无论是每次都修改源码,还是从控制台读取输入都太麻烦了。
所以使用随机数进行测试。
随机数的使用方式如下,使用随机数类,获取一个默认的随机数工具,然后用这个工具生成随机数。1
int r1 = Random.Shared.Next();//在0到int上限中随机
int r2 = Random.Shared.Next(10);//从0到这个值之间随机,不会取到上限值
int r3 = Random.Shared.Next(20, 30);//从20到30之间随机,不会取到30
double d1 = Random.Shared.NextDouble();//获取一个0到1之间的小数,永远不会取到1
变量储存值而不储存操作。这里的作用是把一个随机数的值储存到一个变量中。
除非修改这个变量的值,否则他会一直保持这个值。并不会每次访问都会发生变化。
条件在大部分情况下都由bool
类型来担任。
他只有true(真,成立,正确)和false(假)两个值。
除了直接声明变量外,还可以使用运算符进行运算。例如大于和小于。
bool b1= 1 > 2;
if选择接收一个条件,如果条件成立才会执行
int r4 = Random.Shared.Next(100);
if (r4 > 80)
{
Console.WriteLine("触发暴击");
}
Console.WriteLine("随机数是" + r4);
所有流程控制语句中,如果跟随一个大括号,那么语句跟大括号之间不要用分号隔开
在if的语句块后可以跟随else if语句。
当前面的if没有判中,就会由后面的else if处理。
else if后还能继续接else if,直到你不想接为止。
此外,最后一个else可以省略if,表示不用再判定,必定执行。
int r5 = Random.Shared.Next(100);
if (r5 > 98)
{
Console.WriteLine("评分为SS");
}
else if (r5 > 95)
{
Console.WriteLine("评分为S");
}
else if (r5 > 90)
{
Console.WriteLine("评分为A");
}
else if (r5 > 80)
{
Console.WriteLine("评分为B");
}
else if (r5 > 60)
{
Console.WriteLine("评分为C");
}
else
{
Console.WriteLine("未通过");
}
Console.WriteLine("随机数是" + r5);
这一串判断都是对同一个值进行,不能使用
Random.Shared.Next(100)
代替所有的r5
。
switch选择是用于配合模式匹配
的选择。
它的使用场景类似于长if-else if,但更整洁。
int r5 = Random.Shared.Next(100);
switch (r5)//switch只会取值一次,这里可以不声明变量直接写Random.Shared.Next(100)
{
case > 98:
Console.WriteLine("评分为SS");
break;
case > 95:
Console.WriteLine("评分为S");
break;
case > 90:
Console.WriteLine("评分为A");
break;
case > 80:
Console.WriteLine("评分为B");
break;
case > 60:
Console.WriteLine("评分为C");
break;
default:
Console.WriteLine("未通过");
break;
}
- 每一个case分支类似于if的条件。
- default类似于无条件的else。
- 每个分支的结尾都需要加上break来结束switch。
除非一个分支后紧跟着另一个分支,中间没有任何执行语句。switch (Random.Shared.Next(1, 13)) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: Console.WriteLine("这月有31天"); break; case 2: Console.WriteLine("这月有28天"); break; default: Console.WriteLine("这月有30天"); break; }
但是现在有了模式匹配,可以在一个case中进行多个值的判断,这种联合判断已经没有意义了。
swtich的主判断只能使用模式匹配,而模式匹配只能使用常量。
switch的次判断可以使用变量,但即便只想使用次判断,也需要加一个无意义的主判断。
在主要判断后加上when来启动次要判断。
int r6 = Random.Shared.Next(100);
int r7 = Random.Shared.Next(100);
switch (true)
{
case true when r6 > r7:
Console.WriteLine("r6更大");
break;
case true when r6 < r7:
Console.WriteLine("r7更大");
break;
}
类似于if,条件满足才会执行。
但是,在执行完毕以后,会再判断条件,并决定是否再次执行。
直到条件不满足。
int r8 = Random.Shared.Next(100);
while (r8 > 4)
{
Console.WriteLine("没有抽中SSR");
r8 = Random.Shared.Next(100);
}
Console.WriteLine("抽中了SSR");
do-while循环的条件判断在循环结束而不是循环开始。
和普通while的区别在于,进入循环的时候没有条件判断,所以一定会执行第一次循环。
而普通while可能第一次条件就判断失败,完全不执行。而之后的流程是一样的。
string? s1;
do
{
Console.WriteLine("初次启动或密码错误,请输入密码");
s1 = Console.ReadLine();
} while (s1 == "123456");
Console.WriteLine("登录成功");
do-while循环的使用场景偏向于类似输入密码的场合,判断条件是根据执行内容判断,所以至少要执行一次才能判断。
而这种场合也是声明变量是分离声明和赋值的场合。在这里使用var并赋值初始值,初始值是会被覆盖的。
do-while的条件后面是不跟大括号的,所以while后面是要加分号的。
for循环有3个部分,临时变量声明
,条件判断
,迭代语句
for循环可以理解为东西更多的while循环。不过如果不需要临时变量声明
和迭代语句
,那么while循环更加精简。
for (int i = 0; i < 10; i++)
{
Console.WriteLine("第" + i + "次丢骰子,丢出来" + Random.Shared.Next(1, 7));
}
for循环括号内的三个部分都可以留空,只要保证存在两个分号就不会报错。
- 条件部分留空会视为true,始终通过。
- 临时变量声明部分可以省略,也可以声明多个同类型变量。
但是因为语法问题,这里不能同时声明不同类型的变量。- 迭代部分和循环体的差别,是迭代部分不会与跳转语句continue交互。除此之外没有区别。
for循环的执行顺序是:
临时变量声明
->条件判断
->循环体
->迭代部分
->条件判断
->循环体
->迭代部分
break用来中断当前的循环,或用于switch内终止switch。
for (int i = 1; i < 10; i++)
{
for (int j = 1; j < 10; j++)
{
Console.Write($"{i} * {j} = {i * j}\t");
if (j > i - 1)
{
break;
}
}
Console.WriteLine();
}
可以调整循环条件和if来达到相同的效果。
但如果存在多层if嵌套,多条件判断。那么使用break可能更清晰和简单。
continue用来重启当前循环。
重启循环会忽略循环的剩余部分,直接回到条件判断。
但对于for循环,会先进入他的迭代部分,再进行判断。
int ssr = 0;
for (int i = 0; i < 100; i++)
{
int number = Random.Shared.Next(1, 101);
if (number > 4)
{
Console.WriteLine("抽到r");
continue;
}
Console.WriteLine("抽中了ssr!");
ssr = ssr + 1;
}
Console.WriteLine($"你抽到的ssr有" + ssr);
break和continue的缺点是,只能控制当前所在的switch或循环。
goto语句可以做到更精细的跳转,包括跳出多层循环。
for (int i = 1; i <= 10; i++)
{
switch (i)
{
case 1:
Console.WriteLine("One");
break;
case 2:
Console.WriteLine("Two");
break;
case 3:
Console.WriteLine("Three");
break;
case 4:
Console.WriteLine("Four");
goto End; //跳转到End 标签处
default:
Console.WriteLine("Other");
break;
}
}
End://定义一个标签,用于goto语句的跳转
Console.WriteLine("结束");
使用goto
首先要定义一个标签,并且这个标签后面必须再跟随一条语句。
也就是说goto
不能直接结束代码。
如果swich
分支使用的条件是纯常量,没有使用模式匹配,
则可以以分支作为目标跳转。
int num = 3;
switch (num)
{
case 1:
Console.WriteLine("这是第一个 case");
goto case 3; // 跳转到 case 3
case 2:
Console.WriteLine("这是第二个 case");
break;
case 3:
Console.WriteLine("这是第三个 case");
break;
default:
Console.WriteLine("这是默认的 case");
break;
}
每一个大括号都是一个范围。
定义的变量只有在范围内生效。
大括号可以不配合流程控制语句,单独放置。
{
int a = 12;
}
Console.WriteLine(a);//错误,无法访问到a
{
int b = 6;
Console.WriteLine(b);
}
{
int b = 6;//上面的b跟这里的b互不影响,变量可以同名
Console.WriteLine(b);
}
{
int c = 2;
{
Console.WriteLine(c);//允许。因为这个代码也处于声明c的大括号范围内。
}
}
而for循环的临时变量声明
部分,作用域只限于循环内部。
所以多个并列的for循环可以都用同名的临时变量。
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i * i);
}
如果if,else,while,do-while,for后面跟随的语句只有一条,那么可以省略大括号。
for (int i = 0; i < 10; i++)
if (i > 5)
Console.WriteLine(i + "大于5");
else
Console.WriteLine(i + "不比5大");
这是一种简写形式,有一定局限性。例如在多个if嵌套时,如果坚持省略掉大括号,就不能控制else跟随哪一个if。
int i1 = 12;
if (i1 > 4)
if (i1 > 10)
Console.WriteLine("通过");
else
Console.WriteLine("及格");
嵌入语句同样有作用域限制。所以不能是声明变量语句,因为无法使用。
但是完整的写大括号则没有这样的限制,甚至可以一条语句都没有。
int i2 = 66;
if (i2 > 50)
{
int i3 = 60;
}
else
int i4 = 40;//报错
如果用作流程控制语句的判断条件完全由常量构成,那么编译器可以判断不可达代码。
if (1 < 2)
{
Console.WriteLine("1");
}
else
{
Console.WriteLine(2);//检测到无法访问的代码
}
while (true)//死循环
{
}
Console.WriteLine("结束");//检测到无法访问的代码
类似的,如果你声明了一个没有初始值的变量,
而它经过的流程控制语句不能保证它有初始值,那么你是无法使用他的。
int i5 = 0;
int i6;
if (i5 < 10)
{
i6 = 10;
}
Console.WriteLine(i6);//尽管你认为一定会赋值初始值,但编译器不觉得。
如果你通过if-else,或者带有常量的条件(死循环),那么可以保证它在使用前必定赋值。
int i7 = 0;
int i8;
if (i7 < 10)
{
i8 = 10;
}
else
{
i8 = 20;
}
Console.WriteLine(i8);
所有的字面量都是常量。此外,可以像定义变量一样定义常量。
const double Pi = 3.1415926;
一些特殊情况不详细说明。
例如int r2 = Random.Shared.Next(10);
,随机数不会是负数,所以这个上限不能是负数。
但如果这里填0,那么不会取到上限值
就是无效的,他能取到0而且每次都是0。 ↩︎