# 二、编写表达式和语句

在第 1 章中，您看到了如何编写、编译和执行 C# 程序。示例程序在`Main`方法中只有一条语句。在这一章中，您将学习如何编写更多的语句并向程序中添加逻辑。为了提高效率，本书其余部分中的许多示例都是片段，但是您仍然可以将这些语句添加到`Main`方法中进行编译，从而更好地理解 C# 语法。也会有很多完整的程序。

## 写简单的陈述

通过结合语言运算符和语法，您可以用 C# 构建表达式和语句。这里有几个简单的 C# 语句的例子。

```cs
    int count = 7;
    char keyPressed = 'Q';
    string title = "Weekly Report";

```

代码清单 4

前面代码清单中的每个示例都有共同的语法元素:类型、变量标识符、赋值运算符、值和语句完成。类型有`int`、`char`和`string`，分别代表一个数字、一个字符和一个字符序列。这是 C# 提供的几个内置类型中的几个。变量是一个可以在以后的代码中使用的名称。`=`运算符将表达式的右侧指定给左侧。每个语句都以分号结束。

前面的例子展示了如何声明一个变量并同时执行赋值，但这不是必需的。只要你在尝试使用一个变量之前声明它，你就没事了。这里有一个单独的声明。

```cs
    string title;

```

代码清单 5

以及变量后来的赋值。

```cs
    title = "Weekly Report";

```

代码清单 6

| ![](img/note.png) | 注意:C# 区分大小写，所以“title”和“Title”是两个独立的变量名。 |

## C# 类型和运算符概述

C# 是一种强类型语言，这意味着编译器不会在不兼容的类型之间隐式转换。例如，您不能将`string`分配给`int`，或者将`int`分配给`string`—至少不能隐式分配。下面的代码不会编译。

```cs
    int total = "359";
    string message = 7;

```

代码清单 7

带双引号的“`359`”是字符串，不带引号的`7`是`int`。虽然您不能隐式执行转换，但是有一些方法可以显式执行转换。例如，您经常会收到来自用户的文本输入，应该是`int`或其他类型。下面的代码清单显示了如何显式执行这些任务的几个示例。

```cs
    int total = int.Parse("359");
    string message = 7.ToString();

```

代码清单 8

在前面的列表中，如果字符串代表有效的`int`，则`Parse`会将字符串转换为`int`。对任何值调用`ToString`都会产生一个会编译的`string`。

除了前面的转换示例之外，C# 还有一个强制转换运算符，允许您在允许显式转换的类型之间进行转换。假设你有一个`double`，它是一个 64 位的浮点类型，你想把它分配给一个`int`，它是一个 32 位的整数。你可以这样投:

```cs
    double preciseLength = 5.61;
    int roundedLength = (int)preciseLength;

```

代码清单 9

如果没有强制转换操作符，您将会收到一个编译器错误，因为`double`不是`int`。本质上，C# 编译器是在保护你不搬起石头砸自己的脚，因为给`int`赋值`double`意味着你失去了精确性。在前面的例子中，`roundedLength`变成了`5`。使用强制转换操作符可以让您告诉 C# 编译器，您知道这个操作在错误的情况下可能是危险的，但是对于您的特定情况是有意义的。

下表列出了内置类型，以便您可以查看可用的类型:

表 1:内置类型

| 类型(文字后缀) | 描述 | 值/范围 |
| --- | --- | --- |
| 字节 | 8 位无符号整数 | 0 到 255 |
| sbyte(字节) | 8 位有符号整数 | -128 到 127 |
| 短的 | 16 位有符号整数 | -32，768 至 32，767 |
| 乌肖特 | 16 位无符号整数 | 0 至 65，535 |
| （同 Internationalorganizations）国际组织 | 32 位有符号整数 | -2，147，483，648 至 2，147，483，647 |
| uint | 32 位无符号整数 | 0 至 4，294，967，295 |
| 长(l) | 64 位有符号整数 | –9，223，372，036，854，775，808 至 9，223，372，036，854，775，807 |
| 乌龙(乌卢) | 64 位无符号整数 | 0 至 18，446，744，073，709，551，615 |
| 浮动(f) | 32 位浮点 | -3.4 × 10 <sup>38</sup> 至+3.4 × 10 <sup>38</sup> |
| 双(d) | 64 位浮点 | 5.0×10<sup>324</sup>至 1.7 × 10 <sup>308</sup> |
| 十进制（m） | 128 位、28 位或 29 位精度(非常适合金融应用) | (-7.9 × 10 <sup>28</sup> 至 7.9×10<sup>28</sup>)/(10<sup>0 至 28</sup> |
| 弯曲件 | 布尔代数学体系的 | 对还是错 |
| 茶 | 16 位 Unicode 字符(使用单引号) | U+0000 至 U+FFFF |
| 线 | Unicode 字符序列(使用双引号) | 例如“abc” |

当意思不明确时，你应该给数字加上后缀。在以下示例中，`m`后缀确保了`9.95`文字被视为一个`decimal`数字:

```cs
    decimal price = 9.95m;

```

代码清单 10

您可以将 Unicode 值直接分配给字符。下面的示例显示了如何分配回车。

```cs
    char cr = '\u0013';

```

代码清单 11

您也可以使用强制转换运算符获取字符的 Unicode 值，如下所示。

```cs
    int crUnicode = (int)cr;

```

代码清单 12

到目前为止，您只看到了带有赋值操作符的语句，但是 C# 有许多其他操作符，允许您执行任何通用编程语言所期望的所有逻辑操作。下表列出了一些可用的运算符。

表 2: C# 运算符

| 种类 | 描述 |
| --- | --- |
| 主要的 | `x.y x?.y f(x) a[x] x++ x-- new typeof default checked unchecked nameof` |
| 一元的 | `+ - ! ~ ++ x --x (T)x await x` |
| 增加的 | `* / %` |
| 添加剂 | `+ -` |
| 变化 | `<< >>` |
| 关系测试和类型测试 | `< > <= >= is as` |
| 平等 | `== !=` |
| 逻辑“与” | `&` |
| 逻辑异或 | `^` |
| 逻辑或 | `&#124;` |
| 条件“与” | `&&` |
| 条件或 | `&#124;&#124;` |
| 零合并 | `??` |
| 有条件的 | `?:` |
| 分配 | `= *= /= %= += -= <<= >>= &= ^= &#124;= =>` |

前缀运算符在赋值前更改变量的值，后缀运算符在赋值后更改变量，如下例所示。

```cs
    int val1 = 5;
    int val2 = ++ val1;
    int val3 = 2;
    int val4 = val3--;

```

代码清单 13

在前面的代码清单中，`val1`和`val2`都是`6`。`val3`变量是`1`，但是`val4`是`2`，因为后缀运算符在赋值后求值。

三元运算符为 if-then-else 逻辑提供了简单的语法。这里有一个例子:

```cs
    decimal priceGain = 2.5m;
    string action = priceGain > 2m ? "Buy" : "Sell";

```

代码清单 14

`?`左侧是布尔表达式，`priceGain > 2m`。如果这是真的，在本例中，三元运算符返回第一个介于`?`和`:`之间的值，即`"Buy"`。否则，三元运算符将在`:`之后返回值，即`"Sell"`。该语句将三元运算符`"Buy"`的结果赋给字符串变量`action`。

除了内置类型之外，FCL 还有许多类型可供您日常使用。其中一个是`DateTime`，代表日期和时间。这里有一个快速演示，展示了一些你可以用`DateTime`做的事情。

```cs
    DateTime currentTime = DateTime.Now;
    string shortDateString = currentTime.ToShortDateString();
    string longDateString = currentTime.ToLongDateString();
    string defaultDateString = currentTime.ToString();
    DateTime tomorrow = currentTime.AddDays(1);

```

代码清单 15

前面的代码展示了如何获取当前的`DateTime`，日期的短表示(例如 12/8/2014)，日期和时间的长表示(所有内容都拼写出来)，默认的数字表示，以及如何使用`DateTime`方法进行计算。

| ![](img/tip.png) | 提示:在创建自己的类型库之前，搜索 FCL。您每天使用的许多常见类型，如日期时间，将已经存在。 |

## 运算符优先级和关联性

在[表 2](#Table2) 中列出的 C# 运算符按照它们的一般优先顺序列出了运算符。优先级定义了首先计算哪些运算符。优先级较高的运算符在优先级较低的运算符之前求值。

赋值和条件运算符是右关联的，所有其他运算符都是左关联的。您可以通过使用括号来更改正常的操作顺序，如下面的代码清单所示。

```cs
    int result1 = 2 + 3 * 5;
    int result2 = (2 + 3) * 5;

```

代码清单 16

在之前的代码中，`result1`是`17`，但是`result2`是`25`。

## 格式化字符串

在 C# 中有不同的方法来构建和格式化字符串:串联、数字格式字符串或字符串插值。下面的代码清单演示了字符串连接。

```cs
    string name = "Joe";
    string helloViaConcatenation = "Hello, " + name + "!";
    Console.WriteLine(helloViaConcatenation);

```

代码清单 17

这上面印着**“你好，乔！”**到控制台。下面的例子做了同样的事情，但是使用了`string.Format`。

```cs
    string helloViaStringFormat = string.Format("Hello, {0}!", name);
    Console.WriteLine(helloViaStringFormat);

```

代码清单 18

`string.Format`取一个格式字符串，在花括号中有数字占位符。它是从 0 开始的，所以第一个占位符是`{0}`。字符串后面的参数按照出现的顺序放入格式字符串中。由于`name`是第一个(也是唯一的)参数，`string.Format`将`{0}`替换为`Joe`以创建一个字符串`"Hello, Joe!"`。为了方便控制台应用程序，`WriteLine`使用相同的格式。下面的代码完成了与前面代码清单中两行代码相同的任务。

```cs
    Console.WriteLine("Hello, {0}!", name);

```

代码清单 19

更进一步，字符串格式更强大，允许您指定列长度、对齐方式和值格式，如下面的代码所示。

```cs
    string item = "bread";
    decimal amount = 2.25m;
    Console.WriteLine("{0,-10}{1:C}", item, amount);

```

代码清单 20

在本例中，第一个占位符的长度为 10 个字符。默认对齐方式是右对齐，但减号会将它更改为左对齐。在第二个占位符上，`C`是货币格式字符串。

| ![](img/note.png) | 注意:有许多字符串格式选项。可以访问[https://msdn . Microsoft . com/en-us/library/dwhawy9k(v = vs . 110)。aspx](https://msdn.microsoft.com/en-us/library/dwhawy9k(v=vs.110).aspx) 对于标准格式，[https://msdn . Microsoft . com/en-us/library/0c 899 ak8(v = vs . 110)。自定义格式的 aspx](https://msdn.microsoft.com/en-us/library/0c899ak8(v=vs.110).aspx) 和[https://msdn . Microsoft . com/en-us/library/az4se 3 k1(v = vs . 110)。日期时间格式的 aspx](https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx) 。 |

C# 6 引入了一种格式化字符串的新方法，称为字符串插值。这是一种速记语法，允许您用表达式替换数字占位符，如下所示:

```cs
    Console.WriteLine($"{item}      {amount}");

```

代码清单 21

需要`$`前缀。这里，`item`变量的值代替`{item}`，而`amount`变量的值代替`{amount}`。类似于数字占位符，您可以包含其他格式。

```cs
    Console.WriteLine($"{nameof(item)}: {item,-10} {nameof(amount)}: {amount:C}");

```

代码清单 22

`nameof`运算符打印出名称 ***【项目】*** ，演示如何在占位符中使用表达式。您还可以在`item`和`amount`上看到空间和货币格式。

## 分支语句

您可以在代码中使用`if—else`或`switch`语句进行分支逻辑。当您只需要为真实条件执行代码时，使用`if`语句，如下例所示。

```cs
    string action2 = "Sell";
    if (priceGain > 2m)
    {
        action2 = "Buy";
    }

```

代码清单 23

在这个例子中花括号是可选的，因为如果`priceGain > 2m`只有一条语句要执行。然而，它们对于多个语句是必需的。所有分支和逻辑语句都是如此。您也可以有一个`else`案例，如下列表所示。

```cs
    string action3 = "Do Nothing";
    if (priceGain <= 2m)
    {
        action3 = "Sell";
    }
    else
    {
        action3 = "Buy";
    }

```

代码清单 24

每当`if`语句的布尔条件为假时，就像在前面的代码示例中`priceGain <= 2m`一样，`else`子句执行。在这种情况下，`action3`就变成了`"Buy"`。当然，你可以通过增加更多的`else if`子句来拥有多个条件。

```cs
    string action4 = null;
    if (priceGain <= 2m)
    {
        action4 = "Sell";
    }
    else if (priceGain > 2m && priceGain <= 3m)
    {
        action4 = "Do Nothing";
    }
    else
    {
        action4 = "Sell";
    }

```

代码清单 25

在前面的例子中，您可以在`else if`子句中看到一个更复杂的布尔表达式。当`priceGain`为`2.5`时，`action4`的值变为`"Do Nothing"`。`&&`是一个逻辑运算符，如果左边和右边的表达式都为真，则成功。如果左侧或右侧的表达式为真，则逻辑`||`运算符成功。这些运算符还执行短路操作，如果左边的表达式导致整个表达式不为真，右边的表达式就不执行。在代码清单 25 中的`else if`的情况下，如果`priceGain`等于或小于`2m`，则`&&`运算符不会计算`priceGain <= 3`表达式，因为整个操作已经是假的。一旦执行了`if`语句的一个分支，就不会评估或执行其他分支。

请注意，我将`action4`设置为`null`。`null`关键词意味着没有价值。下一章我会讲到`null`并说明你可以在哪里使用。

一个`if`语句适用于简单的分支或者复杂的条件，比如前面的`else if`子句。但是，当您有多个案例并且所有表达式都是常量值时，例如`int`或`string`，您可能更喜欢`switch`语句。以下示例使用`switch`语句根据天气预报选择合适的设备。

```cs
    string currentWeather = "rain";
    string equipment = null;
    switch (currentWeather)
    {
        case "sunny":
            equipment = "sunglasses";
            break;
        case "rain":
            equipment = "umbrella";
            break;
        case "cold":
        default:
            equipment = "jacket";
            break;
    }

```

代码清单 26

`switch`语句试图用它的一个`case`语句来匹配一个值，在这个例子中是`currentWeather`。它使用`default`不匹配的情况。所有`case`语句必须以`break`语句结束。唯一允许的时间是当`case`没有身体时，如`"cold"` `case`和`default`所示，它们都将`equipment`设置为`"jacket"`。由于`currentWeather`为`"rain"`，`equipment`变为`"umbrella"`，其他情况不执行。

除了分支语句之外，您还需要多次执行一组操作的能力，这就是 C# 循环的由来。在讨论循环之前，让我们看看数组和集合，它们保存着循环可以使用的数据。

## 数组和集合

有时，您需要将集合中的许多项组合在一起，以便在内存中管理它们。为此，可以使用数组或。NET 框架。下面的示例演示如何创建数组。

```cs
    int[] oddNumbers = { 1, 3, 5 };
    int firstOdd = oddNumbers[0];
    int lastOdd = oddNumbers[2];

```

代码清单 27

在这里，我已经用三个值声明并初始化了数组。数组和集合是从 0 开始的，所以`firstOdd`是`1`，`lastOdd`是`5`。`[x]`语法，其中`x`是一个数字，被称为索引器，因为它允许您在索引指定的位置访问数组。这里还有一个用`string`代替`int`的例子。

```cs
    string[] names = new string[3];
    names[1] = "Joe";

```

代码清单 28

在这个例子中，我实例化了一个数组来保存三个字符串。默认情况下，所有字符串都等于`null`。该代码将第二个字符串设置为`"Joe"`。

除了数组之外，您还可以使用所有类型的数据结构，例如`List`、`Stack`、`Queue`等等，它们都是 FCL 的一部分。下面的例子展示了如何使用`List`。记得为`System.Collections.Generic`添加一个`using`子句来使用`List<T>`类型。

```cs
    List<decimal> stockPrices = new List<decimal>();
    stockPrices.Add(56.23m);
    stockPrices.Add(72.80m);
    decimal secondStockPrice = stockPrices[1];

```

代码清单 29

在这个示例中，我实例化了一个新的`List`集合。`<decimal>`是一个泛型类型，表示这是一个强类型列表，只能保存类型为`decimal`的值；这是`decimal`的`List`。那份清单有两项。请注意我是如何使用类似数组的索引器语法来读取(从 0 开始)`stockPrices`列表中的第二项的。

## 循环语句

C# 支持多个循环，包括`for`、`foreach`、`while`和`do`。下面的代码清单执行类似的逻辑。

```cs
    double[] temperatures = { 72.3, 73.8, 75.1, 74.9 };
    for (int i = 0; i < temperatures.Length; i++)
    {
        Console.WriteLine(i);
    }

```

代码清单 30

`for`循环将`i`初始化为`0`，确定`i`小于`temperature`数组中的项目数，执行`Console.WriteLine`，然后递增`i`。它继续执行，直到条件(`i < temperatures.Length`)为假，然后继续执行程序中的下一条语句。

```cs
    foreach (int temperature in temperatures)
    {
        Console.WriteLine(temperature);
    }

```

代码清单 31

代码清单 31 中使用的`foreach`循环更简单，将对`temperatures`数组中的每个值执行。

接下来是一个`while`循环的例子。

```cs
    int tempCount = 0;
    while (tempCount < temperatures.Length)
    {
        Console.WriteLine(tempCount);
        tempCount++ ;
    }

```

代码清单 32

`while`循环评估条件，如果条件为真，则执行。请注意，我在每次迭代中将`tempCount`初始化为`0`，并在循环内部增加`tempCount`。

最后，下面的例子展示了如何编写一个`do` - `while`循环。

```cs
    int tempCount2 = 0;
    do
    {
        Console.WriteLine(tempCount2++);
    }
    while (tempCount2 <= temperatures.Length);

```

代码清单 33

一个`do` - `while`循环适合你至少要执行一次逻辑的时候。本示例将`tempCount2`作为参数增加到`Console.WriteLine`。请记住，后缀运算符会在求值后更改变量。

## 收尾

这是一个计算器程序，汇集了本章的一些概念，加上一些额外的功能。你可以把它输入到你的编辑器中，然后执行它来练习。

```cs
    using System;
    using System.Text;

    /*
        Title: Calculator
        By: Joe Mayo
    */

    class Calculator
    {
        /// <summary>
        /// This is the entry point.
        /// </summary>
        static void Main()
        {
            char firstChar = 'Q';
            bool keepRunning = true;

            do
            {
                Console.WriteLine();
                Console.Write("What do you want to do? (Add, Subtract, Multiply, Divide, Quit): ");
                string input = Console.ReadLine();
                firstChar = input[0];

                // This is used in both the if statement and the do-while loop.
                keepRunning = !(firstChar == 'q' || firstChar == 'Q');

                double firstNumber = 0;
                double secondNumber = 0;

                if (keepRunning)
                {
                    Console.Write("First Number: ");
                    string firstNumberInput = Console.ReadLine();
                    firstNumber = double.Parse(firstNumberInput);

                    Console.Write("Second Number: ");
                    string secondNumberInput = Console.ReadLine();
                    secondNumber = double.Parse(secondNumberInput);
                }

                double result = 0;
                switch (firstChar)
                {
                    case 'a':
                    case 'A':
                        result = firstNumber + secondNumber;
                        break;
                    case 's':
                    case 'S':
                        result = firstNumber - +secondNumber;
                        break;
                    case 'm':
                    case 'M':
                        result = firstNumber * secondNumber;
                        break;
                    case 'd':
                    case 'D':
                        result = firstNumber / secondNumber;
                        break;
                    default:
                        result = -1;
                        break;
                }

                Console.WriteLine();
                Console.WriteLine("Your result is " + result);

            } while (keepRunning);
        }
    }

```

代码清单 34

上一个程序演示了一个`do` - `while`循环、`if`语句、`switch`语句以及与用户的基本控制台通信。

这里有几个你还没见过的字符串特性。第一种是程序使用`Console.ReadLine`从用户那里读取输入字符串的输入文本。请注意从字符串中读取第一个字符的索引器语法。您可以通过这种方式读取字符串的任何字符。另外，看看程序底部打印`"Your result is " + result`的地方，它将一个字符串和数字连接起来。使用`+`运算符进行连接是构建字符串的简单方法。另一种构建字符串的方法是使用名为`StringBuilder`的类型，您可以这样使用:

```cs
    StringBuilder sb = new StringBuilder();
    sb.Append("Your result is ");
    sb.Append(result.ToString());
    Console.WriteLine(sb.ToString());

```

代码清单 35

您还需要在文件顶部添加一个`using System.Text;`子句。在同一个字符串上使用了大约四次 concatenate 运算符`+`之后，您可以考虑改为使用`StringBuilder`重写。`string`类型是不可变的，意味着不能修改。这也意味着每个串联操作都会导致 CLR 在内存中创建一个新的字符串。

计算器程序也有不编译的多行和单行注释，但是可以帮助您根据需要记录代码。以下是多行注释:

```cs
    /*
        Title: Calculator
        By: Joe Mayo
    */

```

代码清单 36

以下是单行注释:

```cs
    // This is used in both the if statement and the do-while loop.

```

代码清单 37

单行注释的扩展是使用三个斜线和一组 XML 标记的约定，称为文档注释。

```cs
    /// <summary>
    /// This is the entry point.
    /// </summary>

```

代码清单 38

## 总结

C# 有一整套运算符和类型，允许您编写各种表达式和语句。使用分支语句和循环，您可以编写自己选择的逻辑。本章中的所有代码都在`Main`方法中，但显然这是不够的，你会很快从中受益。下一章将探索一些新的 C# 特性，以帮助用方法和属性组织代码。