七、表达式、语句详解
表达式的定义
C#中的委托实际上是模仿c/c++中的函数指针,Java没有这个语法
namespace ExpressionSimple {
internal class Program {
static void Main(string[] args) {
Action myAction = new Action(Console.WriteLine);
/*Console.WriteLine
* 就是一个表达式,
* 返回一个方法
*/
}
}
}
各类表达式概览
使用某个操作符所组成的表达式的返回类型,都是不确定的,要看具体情况。
var x = 3 < 5;
Console.WriteLine(x);
Console.WriteLine(x.GetType().Name);
//bool表达式可以产生一个bool类型的值
}
某些操作符,会使得数据类型提升,此时表达式所返回的值,就是提升后的数据类型
as表达式
如果as成功了,as表达式的值,则和as
右边的数据类型一致,如果不成功,就返回null
赋值表达式的值,就是等号左边的变量
int x = 5;
int y;
Console.WriteLine(y = x);
//这里打印出来是5
Console.WriteLine((y = x).GetType().FullName);
语句定义
namespace StatementsExample {
internal class Program {
static void Main(string[] args) {
string input = Console.ReadLine();
/*程序写好之后语句虽然不会再改变
* 但是因为输入的不一样,控制流是会发生改变的
*
*/
try {
double score = double.Parse(input);
//将输入的字符串转化成double
if(score >= 60) {
Console.WriteLine("Pass");
}else {
Console.WriteLine("Failed");
}
}catch (Exception ex) {
Console.WriteLine("No a number!");
}
}
}
}
语句详解
主要分为三大类:
标签语句
声明语句
嵌入式语句
例如:if语句中可以嵌套其他语句
if (3 > 5) Console.WriteLine("nihao");//这句话就是嵌套在if语句中的
某些语句(如迭代语句)后面始终跟有一条嵌入式语句。 此嵌入式语句可以是单个语句,也可以是语句块中括在括号 {} 内的多个语句。 甚至可以在括号 {} 内包含单行嵌入式语句。
if
语句就是嵌入式语句中的一种
发现一件神奇的事情
int[] res = [0, 0, 0];
res[^1] = 1;
Console.WriteLine(res[2] == res[^1]);
//res[^1] == res[2]
这是为什么呢?
^
:与尾部索引相关(res[res.Length - 1]相当于res[^1]);
声明语句
分为局部变量和局部常量
局部变量声明,即普通的声明
局部常量声明,需要使用
const
关键字,且声明的时候必须调用初始化器赋值,不能先声明后面再赋值,而且值也不能再改变
int x = 100;
int x;
x = 100;
/* 这两种声明变量的方式是不一样的
* 第一种声明变量为变量追加了初始化器
* 第二种声明变量,没有对变量初始化,然后对变量进行了赋值
*/
表达式语句
int x;
x = 100;
x++;
/*上面这两个表达式语句都是有值的,但我们需要的只是语句的功能,
* 这个语句的值我们是不需要的
* (x = 100)这个语句的值,就是100
* 但是我们不需要这个值,只是需要将100赋给x的这个功能
* 而x++,这个语句也是有值的,值就是x,然后才执行自增的功能
* 但是我们实际上要的也是x自增的功能,不需要x的值
*/
当然也并不是所有表达式计算出来的值都会被丢弃
只返回值的语句是不被允许的
int x = 100;
int y = 10;
x + y;
x == 1;
/* 这种语句是毫无意义的
* 因为这种语句的功能只有返回一个值,而没有除了返回值以外的其他功能
* 这种语句在C#中是不被允许的
* 而在c/c++中可以
*/
块语句
编译器永远把块语句当做一条完整的语句来看待,不管块里面容纳了多少子语句
块语句实际上就是,大括号里面有多条语句
用于在只允许使用单个语句的上下文种编写多条语句,这句话的意思很简单
if (3 < 5)
Console.WriteLine("现在只能写一句");
if (3 < 5) {
Console.WriteLine("现在呢就可以写很多句了");
Console.WriteLine("现在呢就可以写很多句了");
Console.WriteLine("现在呢就可以写很多句了");
}
/*
* {
Console.WriteLine("现在呢就可以写很多句了");
Console.WriteLine("现在呢就可以写很多句了");
Console.WriteLine("现在呢就可以写很多句了");
}
* 这个就是块语句
*/
块语句单独用,并不常见。更多的是和其他语句一起用,比如if
{
int x;
if (3 < 5)
Console.WriteLine(123);
hello: Console.WriteLine("123");
//这个就是标签语句
goto hello;//现在这个会造成死循环
}
选择语句
if
、if-else
、switch
就只有这三种,用法和Java之类的语言是一样的,因为这个都继承自C/C++。
因为
switch
不太熟悉,着重写一下
switch 表达式的值必须是常量
sbyte
、byte
、short
、ushort
、int
、uint
、ong
、ulong
、bool
、char
、string
或emm-type
,或者是对应于以上某种类型的可以为 null 的类型
case
值标签,作为匹配值。并且
case
标签后面所跟的表达式,的值的类型必须和switch
后面的表达式的值的类型一致如果没有匹配的
case
值标签,则使用可选default
标签作为匹配值**
default
关键字的用途是提供默认操作代码块,无论匹配的 case 值如何,将始终执行该代码块。**这和if-else
中的else
是类似的单个 switch 部分可以有多个 case 标签。
break
关键字指示运行时停止计算和阻止执行switch
构造中的其他 case。因为
switch
语句后面要跟一个常量表达式,所以会不太方便。所以使用if-else
是多于使用switch
的C#中要求,
case
和default
后面必须带break
,不然不能编译通过,更为严谨一些。而在C中,不带
break
是可以被允许的
使用示例:
namespace StatementsExample {
internal class Program {
static void Main(string[] args) {
//需求:80~100 ->A; 60~79->B; 40~59 ->C; 0~39->D; 其他->Error
string str = Console.ReadLine();
int score = int.Parse(str);
switch (score /10) {
case 10://当输入101时,仍然是10,这是有问题的,需要特殊处理
if(score == 100) {
goto case 8;
} else {
goto default;
}//这样子就没问题了
case 9:
case 8:
Console.WriteLine("A");
break;
case 7:
case 6:
Console.WriteLine("B");
break;
case 5:
case 4:
Console.WriteLine("C");
break;
case 3:
case 2:
case 1:
case 0:
Console.WriteLine("D");
break;
default:
Console.WriteLine("Error!");
break;
}
}
}
}
int employeeLevel = 100;
string employeeName = "John Smith";
string title = "";
switch (employeeLevel)
{
case 100:
case 200:
title = "Senior Associate";
break;
case 300:
title = "Manager";
break;
case 400:
title = "Senior Manager";
break;
default:
title = "Associate";
break;
}
Console.WriteLine($"{employeeName}, {title}");
$
是一个特殊字符
/* $表示字符串内插
*/
var name = "Mark";
var date = DateTime.Now;
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
/*
* 使用$字符可以使下面这条语句代替上面那条语句
*/
异常处理语句
包含**
throw
、try-catch
、try-finally
和try-catch-finally
**
当使用
try-finally
语句时,即使出现异常,也不会去捕捉,并且也不会让程序崩溃。文档中可以查看到,该方法可能会抛出的异常
throw
关键字,抛出异常,谁调用这个方法,谁就来处理这个异常无论是否发生异常,finally中的内容都会被执行,一般只会在finally中写两类内容:
释放系统资源的语句
这样无论是否使用资源,都不会出错
写程序的执行记录
这样就有助于看清楚,出错的位置,并且提醒输出的值是否是有错的
这个示例很重要,演示了大多数用法
namespace StatementsExample {
internal class Program {
static void Main(string[] args) {
Calculator c = new Calculator();
int r = 0;
try {
r = c.Add("99999999999999999", "100");
}catch(OverflowException oe) {
Console.WriteLine(oe.Message);
}
Console.WriteLine(r);
}
}
class Calculator {
public int Add(string str1, string str2) {
int a = 0;
int b = 0;
bool hasError = false;
try {
a = int.Parse(str1);
b = int.Parse(str2);
} catch (ArgumentNullException ane) {//可以抓住异常变量
Console.WriteLine(ane.Message);
//于是可以调用这个异常,把异常信息打出来
hasError = true;
} catch (FormatException) {
Console.WriteLine("Your argument(s) are not number");
hasError = true;
} catch (OverflowException) {
throw;
/* throw关键字,对没有处理的异常,会交给方法的调用者处理
* 当没有oe 这个标识的时候,throw仍然可以触发
*/
} finally {//无论是否发生异常,finally中的内容都会被执行
/*一般只会在finally中写两类内容
* 1.释放系统资源的语句,
* 这样无论是否使用资源,都不会出错
* 2. 写程序的执行记录
*/
if(hasError) {
Console.WriteLine("Execution has error!");
} else {
Console.WriteLine("Done!");
}
/*
* 这样子打出log,可以更清晰的看到程序哪个地方出错了
*/
}
/*
* 精细化的捕捉异常
* 需要捕捉哪种异常,可以去文档中查对应的方法
*/
int res = a + b;
return res;
}
}
}
平时写代码的时候,如果觉得有问题,一定要使用try
语句,养成习惯。
迭代语句
此迭代语句重复执行语句或语句块。
for
语句:在指定的布尔表达式的计算结果为true
时会执行其主体。foreach
语句:枚举集合元素并对集合中的每个元素执行其主体。do
语句:有条件地执行其主体一次或多次。while
语句:有条件地执行其主体零次或多次。在迭代语句体中的任何点,都可以使用
break
语句跳出循环。 可以使用continue
语句进入循环中的下一个迭代。这四个语句当中,
foreach
语句最为重要
do
语句一定会执行一次循环体中的内容,在某些场合,会比while
语句更好用
do
语句、break
语句和continue
语句的示例
int score = 0;
int sum = 0;
do {
Console.WriteLine("Please input first number");
string str1 = Console.ReadLine();
if (str1.ToLower() == "end") {
break;
}
int x = 0;
try {
x = int.Parse(str1);
} catch {
Console.WriteLine("First number has problem! Restart.");
continue;
}
Console.WriteLine("Please input second number");
string str2 = Console.ReadLine();
if (str2.ToLower() == "end") {
break;
}
int y = 0;
try {
y = int.Parse(str2);
} catch {
Console.WriteLine("Second number has problem! Restart.");
continue;
}
sum = x + y;
if (sum == 100) {
score++;
Console.WriteLine($"Correct! {x} + {y} = {sum}");
Console.WriteLine($"Your score is {score}");
} else {
Console.WriteLine($"Error! {x} + {y} = {sum}");
}
} while (sum == 100);
Console.WriteLine($"Your score is {score}");
只要是实现了IEnumerable
这个接口的,都可以被迭代
集合遍历的底层逻辑
int[] intArray = { 1, 2, 3, 4, 5 };
IEnumerator enumerator = intArray.GetEnumerator();
/* bool MoveNext()这个方法
* 意思就是如果这个迭代器,还能向后移动的话,就返回true,否则就返回flase
* Current() 获取当前访问的元素
*
* Reset()方法,把迭代器的指示箭头拨回到最开始
*/
while (enumerator.MoveNext()) {
Console.WriteLine(enumerator.Current);
}
/* 1
* 2
* 3
* 4
* 5
*/
//需要把指针重置才能继续访问
enumerator.Reset();
while (enumerator.MoveNext()) {
Console.WriteLine(enumerator.Current);
}
foreach
语句实际上就是一个迭代器,就是集合遍历的简记法
List<int> intList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8};
foreach (var current in intList)
{
Console.WriteLine(current);
}
/* 1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
*/
跳转语句
跳转语句无条件转移控制。
break
语句将终止最接近的封闭迭代语句或switch
语句。continue
语句启动最接近的封闭迭代语句的新迭代。return
语句终止它所在的函数的执行,并将控制权返回给调用方。goto
语句将控制权转交给带有标签的语句。
goto
语句基本不怎么用了。
break
和continue
上面有演示,throw
语句在异常处理那里有介绍。这里着重强调一下
return
要尽早
return
。简单来说就是,需要return
的情况,比不需要return
的情况少,那么最好把需要return
的情况分出来避免头重脚轻,事实上,很多算法的模板都是这样的,例如:深度优先搜索和广度优先搜索算法。
namespace StatementsExample {
internal class Program {
static void Main(string[] args) {
Greeting("Mr.Ye");
}
static void Greeting(string name) {
if(string.IsNullOrEmpty(name)) {//只有判定某些情况,就立即返回
return;
}
//后面可能会有很大一块逻辑
Console.WriteLine($"Hello, {name}!");
}
}
}
如果方法的返回值,不是void
类型的。并且在方法中使用了选择语句,那么需要在选择语句的每一个分支当中都能返回。
namespace StatementsExample {
internal class Program {
static void Main(string[] args) {
string str = WhoIsWho("Is");
Console.WriteLine(str);
}
static string WhoIsWho(string name) {
if(name == "Mr.Ye") {
return "Is he";
}
else
{
return "I don't know";
}
}
}
}
checked/unchecked
语句、标签语句、空语句,这三种语句不太重要或不太常用,就不细讲了,具体可以看文档。
using
语句、yield
语句、look
语句(应用于多线程)这三种语句比较难,后面再讲。
($“Hello, {name}!”);
}
}
}
如果方法的返回值,不是`void`类型的。并且在方法中使用了选择语句,那么需要在选择语句的每一个分支当中都能返回。
```c#
namespace StatementsExample {
internal class Program {
static void Main(string[] args) {
string str = WhoIsWho("Is");
Console.WriteLine(str);
}
static string WhoIsWho(string name) {
if(name == "Mr.Ye") {
return "Is he";
}
else
{
return "I don't know";
}
}
}
}
checked/unchecked
语句、标签语句、空语句,这三种语句不太重要或不太常用,就不细讲了,具体可以看文档。
using
语句、yield
语句、look
语句(应用于多线程)这三种语句比较难,后面再讲。