书接上文:
4.选择语句:if语句和switch语句
4.1If****语句:statement:两种格式:A与B
A:If(boolean-expression)embedded-statement(嵌入式语句)
说明:本身A属于一条if语句,
第一点:在这条if语句里可以嵌入一条语句,被嵌入的语句叫做嵌入语句。
第二点:需要注意的是:既然embedded-statement这是嵌入语句则只可以放入嵌入式语句,非嵌入式语句不能放入如:声明语句和标签语句。
第三点:embedded-statement为单数表示只能写一条嵌入式语句,当一条嵌入式语句就可以表达逻辑时(不用{}),直接写就行:
if (5 > 3) Console.WriteLine("Hello");
那需要多条语句表达逻辑怎么办?嵌入式语句里面不是有block块语句吗?这就是一般形式if(){}后面这对花括号的由来,它是块语句。块语句是一个容器,而编译器会把块语句看做一条语句,所以符合if语句的定义。
B:If(boolean-expression)embedded-statement else embedded-statement
习惯上if后面的嵌入式语句叫做true分支,else后面的嵌入式语句叫做false分支。带有else的语句覆盖了全部情况。
注意到选择语句也包含在嵌入式语句中,这意味着,if后面跟的那一条嵌入式语句也可以是if语句这就是所谓的if的嵌套。平时叫优化逻辑的过程叫做代码的重构。
*无论多么长的if else代码都是一条语句。
Else if语句的由来,同一个if语句后面的花括号是可以省略的,
把else
{
If{}
}
把else后面的{}省略之后就得到了else if语句,实际上都是else 和if语句。
4.2switch语句:
_switch-statement:_switch ( expression ) switch-block
switch-block:{ switch-sections__opt }
_switch-sections:_switch-section__switch-sections switch-section
_switch-section:_switch-labels statement-list
_switch-labels:_switch-label__switch-labels switch-label
_switch-label:_case constant-expression :default :
示例:
int Score = 95;
switch (Score/10)
{
case 8:
case 9:
case 10:
Console.WriteLine("A");
break;
case 7:
case 6:
Console.WriteLine("B");
break;
case 4:
case 5:
Console.WriteLine("C");
break;
case 0:
case 1:
case 2:
case 3:
Console.WriteLine("D");
break;
default: Console.WriteLine("Error");
break;
}
特点第一:是case标签后面要跟一个常量表达式(没有浮点类型);
第二:case标签后面只要跟了语句就变成了一个switch-section:则后面一定要跟一个break;如果发现两个标签做同一件事情只要把两个标签连在一起即可。
*switch选择语句的强大之处在于判断枚举类型时,在switch后面的括号写完枚举变量之后按enter键就会把枚举变量所有可能的取值分支都自动补全。
switch (myLevel)
{
case Level.Hight:
break;
case Level.Middle:
break;
case Level.Low:
break;
default:
break;
}
5.try语句
三种格式:
try block({}) catch-clauses
try block finally-clause
try block catch-clauses finally-clause
Catch语句有两种形式:
catch ( class-type identifieropt ) block:专用的catch语句只能捕捉class-type类型的异常。
catch block:通用的catch语句,全部类型的异常都能捕获。
详见语言定义文档。(*从这些例子很容易看出很多语句后面都跟一对花括号,其实这对花括号是block块语句)
*作为专业的程序员要经常使用try语句尝试捕获异常。祖略一点的就是多条可能出错的语句使用同一个try...catch结构,精细一点的就是每条可能出错的语句都配对try....catch语句。
可能会想添加这么多的try...catch结构有必要吗?对于比较精细的系统如金融系统这是必要的!(多个这样的结构可以有效提升容错率)
例如:
try
{
int a = int.Parse(args1);
int b = int.Parse(args2);
}
catch
{
Console.WriteLine("Error");
}
需要注意的是block(块语句)里面声明的变量作用域仅为这根块语句,块语句外的语句访问不了块语句内声明的变量。如上面的a和b,正确的是:把变量在块语句外进行声明。
int a=0;
int b=0;
try
{
int.Parse(args1);
int.Parse(args2);
}
上面的catch语句捕捉的是通用类型的,那如何使用精确的(捕捉特定类型的)catch语句呢?关于可能出现哪种异常可以查看MSDN(微软开发者网Microsoft Developer Network)文档。
catch (ArgumentException)
{
Console.WriteLine("Error");
}
catch (FormatException)
{
Console.WriteLine("格式异常");
}
还有一种catch方式是在异常类型后面加上一个标识符(异常类型每个单词的首字母组成的字符):
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
}
catch (OverflowException ofe)
{
Console.WriteLine(ofe.Message);
}
则出现异常时通过异常标识符会显示相应的异常信息。
*try还有一种最详细的形式:加上finally
第一类:我们应该把释放系统资源的语句放在finally语句里面。以数据库为例无论try语句是否发生异常我的数据库连接总能正常关闭,这样整个软件系统就不会出问题了。
第二类:我们会在程序的finally语句里面写程序的执行记录。比如:程序异常时会输出默认值0,正常执行也可能出现0怎么判断这两种情况呢?就是这种方法的应用,详见下列代码:
public int Add(string args1,string args2)
{
int a=0;
int b=0;
bool hasError = false;
try
{
int.Parse(args1);
int.Parse(args2);
}
catch (ArgumentException ae)
{
Console.WriteLine(ae.Message);
hasError = true;
}
catch (FormatException fe)
{
Console.WriteLine(fe.Message);
hasError = true;
}
catch (OverflowException ofe)
{
//Console.WriteLine(ofe.Message);
throw ofe;//谁调用谁就处理
hasError = true;
}
finally
{
if (hasError)
{
Console.WriteLine("出现异常了");
}
else
{
Console.WriteLine("Done!");
}
}
int result =checked( a + b);
return result;
}
*throw这个关键字:遇到异常时不处理而是丢出去,谁调用谁处理就以”try语句”为例,main方法调用,所以main方法里面处理
catch (OverflowException ofe)
{
//Console.WriteLine(ofe.Message);
throw ofe;//谁调用谁就处理
hasError = true;
}
throw关键字是比较灵活的,即使不加表示符标识符ofe也知道你要抛出当前捕获的异常。
这里面丢出去的异常,所以应该在main方法中增添相关的try...catch语句:
try
{
r = c.Add("1222222222222222222222222222222", "0");
}
catch (OverflowException ofe)
{
Console.WriteLine(ofe.Message);
}
Throw一般用不到,当做一个知识即可。
**要养成好习惯,当你觉得某条语句可能出现错误的时候就要加try语句进行异常的捕获;所以程序开发人员要积极的捕获异常,不要让漏掉的异常引起程序的崩溃,否则年终你的bug最多就没有奖金了。
6.迭代语句和跳转语句
6.1****迭代语句:也就是我们常说的循环语句,经常和跳转语句一起使用。
迭代语句重复执行嵌入语句。
_iteration-statement:_while-statement__do-statement__for-statement__foreach-statement
这四种语句在很多情况下是可以互相替换的,但是每种循环语句都有它比较专长的语境。
针对要根据语境选择可读性最强的循环语句。
①while语句:循环体可能执行0次:
_while-statement:_while ( boolean-expression ) embedded-statement
我们知道出现一条嵌入式语句的地方可以放一条块语句,块语句的花括号里面可以写多条语句,这样就可以得到表达复杂逻辑的需求。
故while循环可以分为三部分:while关键字+布尔类型的表达式的循环条件+循环体(一个嵌入式语句(可以是块语句))
②do语句:
do 语句按不同条件执行一个嵌入语句一次或多次。
_do-statement:_do embedded-statement while ( boolean-expression ) ;
需要注意的是while关键字do{}放在后面之后要加分号: while (counter < 10);
*跳转语句中的:Continue语句和break语句(因为这两个语句和循环语句结合非常紧密,经常放在一起使用)
continue 语句将开始直接封闭它的 while、do、for 或 foreach 语句的一次新迭代。
直接结束本次循环。
break 语句将退出直接封闭它的 switch、while、do、for 或 foreach 语句。
直接跳出循环不再进行循环。即:跳出while循环后面或者do后面的块语句那个花括号,直接执行while循环后的下一条语句。
*int x = int.Parse(str1);//这里是利用parse函数把得到的字符串解析成整数,容易出错常常放在try语句里面。
*需要注意的是迭代语句也属于嵌入式语句,所以可以实现多重的迭代,所谓的嵌套循环。在多重循环中,continue和break都只会影响到直接包含它的那一层循环,它管不了更外层的循环。
③for(语句)循环:
_for-statement:_for ( for-initializer__opt ; for-condition__opt ; for-iterator__opt ) embedded-statement
比较适用于计数循环:即某个循环,它的循环次数是固定的,这个循环次数由一个变量控制。
分为三部分:for-initializeropt:for循环的初始化器; for-conditionopt :for循环得以执行的条件,为true才执行,为false则不会执行;for-iteratoropt :(每次执行完循环之后这里都会被执行);
重点为记住执行顺序:先执行 for-initializeropt且仅执行一次,再执行 for-conditionopt来判断我们的for循环体是不是能够的到执行,当该步为true时for语句循环体才得以执行(一般为块语句);每次循环体执行完都会反过来执行 for-iteratoropt ;然后再返回 for-conditionopt 判断条件;即每执行一次循环体判断一次循环条件,为true继续执行循环体,为false则停止循环。
一般for-initializeropt都是定义变量并初始化为0; for-iteratoropt 这里都是该变量的++或--;这样for-conditionopt 迟早会变成false,这样for循环就结束了。
*要特别注意循环结束后循环条件的值。一定是不符合循环条件的最小值例如: for (int i = 0; i < 10; i++)则执行完循环语句后i的值为10,而不是9;
*平时写代码特别要注意可读性和代码是不是容易错。
*注意到 for循环()内三个语句都有opt说明这三个语句都是可以省略的,不过现实中不会这么写,只有在bt的面试题会考。
*应该多做算法题,并且没做完一道算法题都要进行总结,做多了会发现其实套路就那些,着对面试很有帮助,面试时压力大不可能现场想新题,这时候就要想这道题是那种提醒,我之前做的时候是怎样总结的。
④foreach语句:
foreach 语句用于枚举一个集合的元素(即从头到尾一个挨一个的把集合元素访问一遍,即我们常说的遍历这个集合),并对该集合中的每个元素执行一次相关的嵌入语句。
换一句话说,使用foreach语句时每访问集合的一个元素就执行一次循环语句,遍历完了集合元素,这个循环就结束了。
*知识点1:什么样的集合可以被遍历:带[]的类型都是数组类型,其基类都是Array类,以下为Array类的定义:
public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable
可见它实现了许多接口,接口好认,开头为大写I的一般都是(Interface)。在C#中所有实现了IEnumerable这个接口的类就是可以被遍历的集合。
IEnumerator GetEnumerator();
这个接口只有一个方法:获得这个集合的迭代器,即C#中所有能够被迭代的集合都可以获得自己的迭代器。
*知识点2:迭代器(Enumerator):先讲一个禅宗的故事”指月”说的是顺着我手指的方向看看到的就是月亮,而我的手指本身不是月亮。假如天上有十个月亮,这个时候我的手指就相当于月亮这个集合的迭代器,我们可以从头到尾一个一个地指,每指到一个月亮的时候,你就可以去访问这个月亮,这就是迭代器。
那如何使用迭代器对集合进行遍历呢?
int[] myArray = new int[] { 1,2,3,4,5,6};
IEnumerator enumerator=myArray.GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
enumerator.Reset();
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
当没有enumerator.Reset();时调用两次只输出一次结果是因为,当第一次遍历完之后,迭代器已经指向了集合的最后一个元素,这时候再调用enumerator.MoveNext()值为false;
调用了enumerator.Reset();把迭代器重置则会输出两次;
之所以对遍历讲这么深是因为Foreach是对集合遍历的简记法。现在再来看看foreach语句的定义:
_foreach-statement:_foreach ( local-variable-type identifier in expression ) embedded-statement
圆括号里local-variable-type identifier:先声明一个迭代变量,就相当于迭代器,in也是个关键字,expression是一个集合,有了迭代器和集合就可以对集合中的元素一个一个地访问了,每访问到一个元素就执行foreach语句的循环体,就是后面这句嵌入式语句(同样可以是一个块语句,用块语句里面的多条语句来组成复杂的逻辑);举例:
List
foreach (var count in myArray2)
{
Console.WriteLine(count);
}
鼓励使用var编译器会自动推断集合的类型, count 为自定义的迭代器名字是集合中元素的别称。可见运用foreach遍历集合十分方便。
*再次强调foreach最佳应用场合是对集合进行遍历
6.2跳转语句:
跳转语句用于无条件地转移控制。
_jump-statement:_break-statement__continue-statement__goto-statement__return-statement__throw-statement
看到throw就想起try语句,他很灵活,后面可以跟catch也可以不跟。
*使用return需要注意的地方。
尽早return;例如:同一个方法原来的版本:
static void Greeting(string name)
{
if (!string.IsNullOrEmpty(name))
{
Console.WriteLine("Hello!{0}",name);
}
}
尽早return的版本:
static void Greeting(string name)
{
if (string.IsNullOrEmpty(name))
{
return;
}
else
{
Console.WriteLine("Hello!{0}", name);
}
}
为什么要这样写呢?这样写的好处就在于你可以让读这条代码的人立刻就鉴别出来这个参数在什么情况下是有问题的,而且还可以避免整个方法写出来”头重脚轻”,如果: Console.WriteLine("Hello!{0}",name);
这里的逻辑不是只有有一行而是很多行,我么多行就不必要包含在一个if语句当中,条件判断if语句就会变得非常短小,一旦发现某些参数或条件不合格的时候立刻return出去了,这样整个方法的结构就非常清晰了。这就是尽早return原则。
*下一个需要注意的是:如果你的方法的返回值不是void类型,而这个方法体里面使用了选择语句,那必须保证选择语句的每一个分枝当中,都能够让这个方法return:如该方法
static string WhoseWho(string name)
{
if (name=="M")
{
return "Yes";
}
else
{
return "No";
}
我们知道if和else可以覆盖100%的情况。这就是我们说的要保证方法一定能够return。
回到开头的那张总图片:绿色虚线以下的不讲,要么不常用要么对于初学者太难了。比如:空语句,用的不多;标签语句总是与goto一起使用goto语句并不是主流的语句了所以用的不多;checked和unchecked语句,可以当做操作符或者语句使用,党作为语句使用时:
Checked{,,,}花括号为块语句里面是要检查的语句;三个比较难的就是:using(接口)、yield(集合)、和lock语句(多线程)。
*作为一个程序员学习能力体现在三个地方:
一个是看书读文档;
一个是动手写代码;
还有一个是熟练地使用搜索引擎。