C#入门经典(第7版):C# 6.0 & Visual Studio 2015(.NET开发经典名著)
上QQ阅读APP看书,第一时间看更新

3.4 表达式

C#包含许多执行这类处理的运算符。把变量和字面值(在使用运算符时,它们都称为操作数)与运算符组合起来,就可以创建表达式,它是计算的基本构件。

运算符范围广泛,有简单的,也有非常复杂的,其中一些可能只在数学应用程序中使用。简单的操作包括所有的基本数学操作,例如+运算符是把两个操作数加在一起,而复杂的操作包括通过变量内容的二进制表示来处理它们。还有专门用于处理布尔值的逻辑运算符,以及赋值运算符,如=运算符。

本章主要介绍数学和赋值运算符,而逻辑运算符将在第4章中介绍,因为第4章主要论述控制程序流程的布尔逻辑。

运算符大致分为如下3类:

● 一元运算符,处理一个操作数

● 二元运算符,处理两个操作数

● 三元运算符,处理三个操作数

大多数运算符都是二元运算符,只有几个一元运算符和一个三元运算符,即条件运算符(条件运算符是一个逻辑运算符,详见第4章)。下面首先介绍数学运算符,它包括一元运算符和二元运算符。

3.4.1 数学运算符

有5个简单的数学运算符,其中两个(+和-)有二元和一元两种形式。表3-6列出了这些运算符,并用一个简短示例来说明它们的用法,以及使用简单的数值类型(整数和浮点数)时它们的结果。

表3-6 简单的数学运算符

+(一元)运算符有点古怪,因为它对结果没有影响。它不会把值变成正的:如果var2是-1,则+var2仍是-1。但这是一个得到普遍认可的运算符,所以也把它包含进来。这个运算符最有用的方面是,可以定制它的操作,本书在后面探讨运算符的重载时会介绍它。

上面的示例都使用简单的数值类型,因为使用其他简单类型,结果可能不太清晰。例如把两个布尔值加在一起,会得到什么结果?因此,如果对bool变量使用+(或其他数学运算符),编译器会报错。char变量的相加也会有点让人摸不着头脑。记住,char变量实际上存储的是数字,所以把两个char变量加在一起也会得到一个数字(其类型为int)。这是一个隐式转换示例,稍后将详细介绍这个主题和显式转换,因为它也可以应用到var1、var2和var3是混合类型的情况。

二元运算符+在用于字符串类型变量时也是有意义的。此时,它的作用如表3-7所示。

表3-7 字符串连接运算符

但其他数学运算符不能用于处理字符串。

这里应介绍的另两个运算符是递增和递减运算符,它们都是一元运算符,可通过两种方式加以使用:放在操作数的前面或后面。简单表达式的结果如表3-8所示。

表3-8 简单表达式的结果

这些运算符改变存储在操作数中的值。

● ++总是使操作数加1

● - - 总是使操作数减1

var1中存储的结果有区别,其原因是运算符的位置决定了它什么时候发挥作用。把运算符放在操作数的前面,则操作数是在进行任何其他计算前受到运算符的影响;而如果把运算符放在操作数的后面,则操作数是在完成表达式的计算后受到运算符的影响。

再看一个示例。考虑以下代码:

        int var1, var2 = 5, var3 = 6;
        var1 = var2++ * --var3;

要把什么值赋予var1?在计算表达式前,var3前面的运算符--会起作用,把它的值从6改为5。可以忽略var2后面的++运算符,因为它是在计算完成后才发挥作用,所以var1的结果是5与5的乘积,即25。

许多情况下,这些简单的一元运算符使用起来非常方便,它们实际上是下述表达式的简写形式:

        var1 = var1 + 1;

这类表达式有许多用途,特别适于在循环中使用,这将在第4章讲述。下面的示例说明如何使用数学运算符,并介绍另外两个有用的概念。代码提示用户键入一个字符串和两个数字,然后显示计算结果。

试一试:用数学运算符处理变量:Ch03Ex02\Program.cs

(1)在目录C:\BegVCSharp\Chapter03下创建一个新的控制台应用程序Ch03Ex02。

(2)在Program.cs中添加如下代码:

            static void Main(string[] args)
            {
              double firstNumber, secondNumber;
              string userName;
              Console.WriteLine("Enter your name:");
              userName = Console.ReadLine();
              Console.WriteLine($"Welcome {userName}! ");
              Console.WriteLine("Now give me a number:");
              firstNumber = Convert.ToDouble(Console.ReadLine());
              Console.WriteLine("Now give me another number:");
              secondNumber = Convert.ToDouble(Console.ReadLine());
              Console.WriteLine($"The sum of {firstNumber} and {secondNumber} is " +
                          $"{firstNumber + secondNumber}.");
              Console.WriteLine($"The result of subtracting {secondNumber} from " +
                          $"{firstNumber} is {firstNumber - secondNumber}.");
              Console.WriteLine($"The product of {firstNumber} and {secondNumber} " +
                          $"is {firstNumber * secondNumber}.");
              Console.WriteLine($"The result of dividing {firstNumber} by " +
                          $"{secondNumber} is {firstNumber / secondNumber}.");
              Console.WriteLine($"The remainder after dividing {firstNumber} by " +
                          $"{secondNumber} is {firstNumber % secondNumber}.");
              Console.ReadKey();
            }

(3)执行代码,结果如图3-2所示。

图3-2

(4)输入名称,按下回车键,如图3-3所示。

图3-3

(5)输入一个数字,按下回车键,再输入另一个数字,按下回车键,结果如图3-4所示。

图3-4

示例说明

除了演示数学运算符外,这段代码还引入了两个重要概念,在以后的示例中将多次用到这些概念:

● 用户输入

● 类型转换

用户输入使用与前面Console.WriteLine()命令类似的语法。但这里使用Console.ReadLine()。这个命令提示用户输入信息,并把它们存储在string变量中:

        string userName;
        Console.WriteLine("Enter your name:");
        userName = Console.ReadLine();
        Console.WriteLine($"Welcome {userName}! ");

这段代码直接将已赋值变量userName的内容写到屏幕上。

这个示例还读取了两个数字。这有些复杂,因为Console.ReadLine()命令生成一个字符串,而我们希望得到一个数字,所以这就引入了类型转换的问题。第5章将详细讨论类型转换,下面首先分析本例使用的代码。

首先声明要存储数字输入的变量:

        double firstNumber, secondNumber;

接着给出提示,对Console.ReadLine()得到的字符串使用命令Convert.ToDouble(),把字符串转换为double类型,把这个数值赋给前面声明的变量firstNumber:

        Console.WriteLine("Now give me a number:");
        firstNumber = Convert.ToDouble(Console.ReadLine());

这个语法相当简单,其他许多转换也用类似的方式进行。

其余代码按同样方式获取第二个数:

        Console.WriteLine("Now give me another number:");
        secondNumber = Convert.ToDouble(Console.ReadLine());

然后输出两个数字加、减、乘、除的结果,并用余数运算符(%)显示除操作的余数:

        Console.WriteLine($"The sum of {firstNumber} and {secondNumber} is " +
                      $"{firstNumber + secondNumber}.");
        Console.WriteLine($"The result of subtracting {secondNumber} from " +
                      $"{firstNumber} is {firstNumber - secondNumber}.");
        Console.WriteLine($"The product of {firstNumber} and {secondNumber} " +
                      $"is {firstNumber * secondNumber}.");
        Console.WriteLine($"The result of dividing {firstNumber} by " +
                      $"{secondNumber} is {firstNumber / secondNumber}.");
        Console.WriteLine($"The remainder after dividing {firstNumber} by " +
                      $"{secondNumber} is {firstNumber % secondNumber}.");

注意,我们提供了表达式firstNumber + secondNumber等,作为Console.WriteLine()语句的一个参数,而没有使用中间变量:

        Console.WriteLine($"The sum of {firstNumber} and {secondNumber} is " +
                      $"{firstNumber + secondNumber}.");

这种语法可以提高代码的可读性,并减少需要编写的代码量。

3.4.2 赋值运算符

我们迄今一直在使用简单的=赋值运算符,其实还有其他赋值运算符,而且它们都很有用。除了=运算符外,其他赋值运算符都以类似方式工作。与=一样,它们都是根据运算符和右边的操作数,把一个值赋给左边的变量。

表3-9列出了这些运算符及其说明。

表3-9 赋值运算符

可以看出,这些运算符把var1也包括在计算过程中,下面的代码:

        var1 += var2;

与下面的代码结果相同。

        var1 = var1 + var2;

注意:与+运算符一样,+=运算符也可以用于字符串。

使用这些运算符,特别是在使用长变量名时,可使代码更便于阅读。

3.4.3 运算符的优先级

在计算表达式时,会按顺序处理每个运算符。但这并不意味着必须从左至右地运用这些运算符。例如,考虑下面的代码:

        var1 = var2 + var3;

其中+运算符就是在=运算符之前进行计算的。在其他一些情况下,运算符的优先级并没有这么明显,例如:

        var1 = var2 + var3 * var4;

其中*运算符首先计算,其后是+运算符,最后是=运算符,这是标准的数学运算顺序,其结果与我们在纸上进行算术运算的结果相同。

像这样的计算,可以使用括号控制运算符的优先级,例如:

        var1 = (var2 + var3) * var4;

首先计算括号中的内容,即+运算符在*运算符之前计算。

对于前面介绍的运算符,其优先级如表3-10所示,优先级相同的运算符(如*和/)按照从左至右的顺序计算。

表3-10 运算符的优先级

注意:如上所述,括号可用于重写优先级顺序。另外,++和- -用作后缀运算符时,在概念上其优先级最低,如上表所示。它们不对赋值表达式的结果产生影响,所以可以认为它们的优先级比所有其他运算符都高。但是,它们会在计算表达式后改变操作数的值,所以认为它们的优先级如表3-10所示会十分方便。

3.4.4 名称空间

在继续学习前,应花一定的时间了解一个比较重要的主题—— 名称空间。它们是.NET中提供应用程序代码容器的方式,这样就可以唯一地标识代码及其内容。名称空间也用作.NET Framework中给项分类的一种方式。大多数项都是类型定义,例如,本章描述的简单类型(System.Int32等)。

默认情况下,C#代码包含在全局名称空间中。这意味着对于包含在这段代码中的项,全局名称空间中的其他代码只要通过名称进行引用,就可以访问它们。可使用namespace关键字为花括号中的代码块显式定义名称空间。如果在该名称空间代码的外部使用名称空间中的名称,就必须写出该名称空间中的限定名称。

限定名称包括它所有的分层信息。这意味着,如果一个名称空间中的代码需要使用在另一个名称空间中定义的名称,就必须包括对该名称空间的引用。限定名称在不同的名称空间级别之间使用句点字符(.),如下所示:

        namespace LevelOne
        {
          // code in LevelOne namespace
          // name "NameOne" defined
        }
        // code in global namespace

这段代码定义了一个名称空间LevelOne,以及该名称空间中的一个名称NameOne(注意这里在应该定义名称空间的地方添加了一个注释,而没有列出实际代码,这是为了使我们的讨论更具普遍性)。在名称空间LevelOne中编写的代码可以直接使用NameOne来引用该名称,但全局名称空间中的代码必须使用限定名称LevelOne.NameOne来引用这个名称。

需要注意特别重要的一点:using语句本身不能访问另一个名称空间中的名称。除非名称空间中的代码以某种方式链接到项目上,或者代码是在该项目的源文件中定义的,或者是在链接到该项目的其他代码中定义的,否则就不能访问其中包含的名称。另外,如果包含名称空间的代码链接到项目上,那么无论是否使用using,都可以访问其中包含的名称。using语句便于我们访问这些名称,减少代码量,以及提高可读性。

回头分析本章开头的ConsoleApplication1中的代码,会看到下面这些被应用到名称空间上的代码:

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Text;
        using System.Threading.Tasks;
        namespace ConsoleApplication1
        {
          ...
        }

以using关键字开头的5行代码声明在这段C#代码中使用System、System.Collections.Generic、System.Linq、System.Text和System.Threading.Tasks名称空间,它们可以在该文件的所有名称空间中访问,不必进行限定。System名称空间是.NET Framework应用程序的根名称空间,包含控制台应用程序需要的所有基本功能。其他4个名称空间常用于控制台应用程序,所以该程序包含了它们。最后,为应用程序代码本身声明一个名称空间ConsoleApplication1。

C# 6新增了using static关键字。这个关键字允许把静态成员直接包含到C#程序的作用域中。例如,本章的两个示例都使用了System.Console静态类中的System.Console.WriteLine()方法。注意,在这些例子中,应包括Console类和WriteLine()方法。把using static System.Console添加到名称空间列表中时,访问WriteLine()方法就不再需要在前面加上静态类名。

之后需要System .Console静态类的代码示例就包括using static System.Console关键字。