C# 2012 说明指南(三)

原文:Illustrated C# 2012

协议:CC BY-NC-SA 4.0

八、表达式和运算符

表情

本章定义表达式并描述 C# 提供的运算符。它还解释了如何定义 C# 运算符来处理用户定义的类。

一个操作符是一个符号,代表一个返回单一结果的操作。操作数是操作者用作输入的数据元素。操作员执行以下操作:

  • Take its operand as input.
  • Perform an action
  • According to the action

返回值

一个表达式是一串操作符和操作数。C# 运算符接受一个、两个或三个操作数。以下是一些可以作为操作数的构造:

  • characters
  • constant
  • variable
  • Method call
  • Element accessors, such as array accessors and indexers
  • Other expressions

可以使用运算符组合表达式,以创建更复杂的表达式,如以下包含三个运算符和四个操作数的表达式所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

求值表达式是将每个运算符以正确的顺序应用于其操作数以产生一个值的过程。

  • Returns the value to the position where the expression is evaluated. There, it may be used as an operand in a closed expression again.
  • Besides the return value, some expressions also have side effects, such as setting a value in memory.

文字量

文字是键入源代码中的数字或字符串,表示特定类型的特定集合值。

例如,下面的代码显示了六种类型的文字。例如,请注意double文字和float文字之间的差异。

   static void Main()         Literals    {                                  ↓       Console.WriteLine("{0}", 1024);            // int literal       Console.WriteLine("{0}", 3.1416);          // double literal       Console.WriteLine("{0}", 3.1416F);         // float literal       Console.WriteLine("{0}", true);            // boolean literal       Console.WriteLine("{0}", 'x');             // character literal       Console.WriteLine("{0}", "Hi there");      // string literal    }

这段代码的输出如下:


1024 3.1416 3.1416 True x Hi there


因为文字被写入源代码,所以它们的值在编译时必须是已知的。几个预定义类型有自己的文字形式:

  • Type bool has two characters: true and false.
  • For reference type variables, the word null indicates that the variable does not point to data in memory.
整数文字量

整数文字是最常用的文字。它们被写成十进制数字序列,具有以下特征:

  • No decimal point
  • An optional suffix to specify the type of integer.

例如,下面几行显示了整数 236 的四个文本。每个都被编译器解释为不同类型的整数,这取决于它的后缀。

   236               // int    236L              // long    236U              // unsigned int    236UL             // unsigned long

整数类型的文字也可以写成十六进制(hex)形式。数字必须是十六进制数字(0 到 F),字符串必须以0x0X开头(数字 0 ,字母 x )。

图 8-1 显示了整数文字格式的形式。名称在方括号中的组件是可选的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-1。*整数文字格式

表 8-1 列出了整数文字后缀。对于给定的后缀,编译器会将数字字符串解释为四种列出的整数类型中最小的一种,这四种整数类型可以在不丢失数据的情况下表示该值。

例如,以文字2365000000000为例,它们都没有后缀。由于236可以用 32 位来表示,所以它将被编译器解释为int。然而,第二个数字不适合 32 位,所以编译器将它表示为一个long

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实字面值

实数的文字由以下内容组成:

  • decimal digits
  • Optional decimal point
  • Optional exponential part
  • Optional suffix

例如,以下代码显示了实数类型的各种文本格式:

   float  f1 = 236F;    double d1 = 236.714;    double d2 = .35192;    double d3 = 6.338e-26;

图 8-2 显示了真实文字的有效格式。名称在方括号中的组件是可选的。表 8-2 显示了真正的后缀及其含义。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-2。*真正的文字格式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意不带后缀的真实文字是double类型,不是float

字符字面量

字符文字由两个单引号之间的字符表示组成。字符表示可以是以下任何一种:单个字符、简单转义序列、十六进制转义序列或 Unicode 转义序列。

  • The type of a character is char.
  • A simple escape sequence is a backslash followed by a single character.
  • The hexadecimal escape sequence is a backslash followed by an uppercase or lowercase x , followed by up to four hexadecimal digits.
  • Unicode escape sequence is a backslash followed by an uppercase or lowercase u , followed by up to four hexadecimal digits.

例如,以下代码显示了各种格式的字符文本:

   char c1 = 'd';                       // Single character    char c2 = '\n';                      // Simple escape sequence    char c3 = '\x0061';                  // Hex escape sequence    char c4 = '\u005a';                  // Unicode escape sequence

表 8-3 显示了一些重要的特殊字符及其编码。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

字符串文字

字符串文字使用双引号,而不是字符文字中使用的单引号。有两种类型的字符串文字:

  • General string literal
  • Word for word character string

常规字符串由一组双引号之间的字符序列组成。常规字符串文字可以包括以下内容:

  • character
  • Simple escape sequence
  • Hexadecimal and Unicode escape sequences

这里有一个例子:

   string st1 = "Hi there!";    string st2 = "Val1\t5, Val2\t10";    string st3 = "Add\x000ASome\u0007Interest";

逐字字符串文字的书写方式类似于常规字符串文字,但以@字符开头。逐字字符串的重要特征如下:

  • Word-for-word literals differ from regular string literals in that the escape sequence is not calculated. Everything between double quotation marks (including what is usually considered as an escape sequence) is printed out as listed in the string.
  • The only exception to word for word is multiple sets of consecutive double quotes, which are interpreted as a double quote character.

例如,下面的代码比较了一些常规字符串和逐字字符串:

`   string rst1 = “Hi there!”;
   string vst1 = @“Hi there!”;

string rst2 = “It started, “Four score and seven…””;
   string vst2 = @“It started, ““Four score and seven…”””;

string rst3 = “Value 1 \t 5, Val2 \t 10”;    // Interprets tab esc sequence
   string vst3 = @“Value 1 \t 5, Val2 \t 10”;   // Does not interpret tab

string rst4 = “C:\Program Files\Microsoft\”;
   string vst4 = @“C:\Program Files\Microsoft”;

string rst5 = " Print \x000A Multiple \u000A Lines";
   string vst5 = @" Print
    Multiple
    Lines";`

打印这些字符串会产生以下输出:


`Hi there!
Hi there!

It started, “Four score and seven…”
It started, “Four score and seven…”

Value 1          5, Val2         10
Value 1 \t 5, Val2 \t 10

C:\Program Files\Microsoft
C:\Program Files\Microsoft\

Print
 Multiple
 Lines

Print
 Multiple
 Lines`


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意编译器通过让相同的字符串在堆中共享相同的内存位置来节省内存。

评估的顺序

一个表达式可以由许多嵌套的子表达式组成。子表达式的求值顺序会对表达式的最终值产生影响。

例如,给定表达式 3 * 5 + 2,根据子表达式的求值顺序,有两种可能的结果,如图 8-3 所示。

  • If you multiply first, the result is 17.
  • If you add 5 and 2 together first, the result is 21.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-3。*简单的评估顺序

优先顺序

您从小学时代就知道,在前面的例子中,乘法必须在加法之前执行,因为乘法的优先级高于加法。但是与小学时代不同,那时你有四个操作符和两个优先级,C# 的情况稍微复杂一些,它有超过 45 个操作符和 14 个优先级。

表 8-4 显示了操作符的完整列表以及每个操作符的优先级。该表在顶部列出了优先级最高的运算符,并在底部列出了优先级最低的运算符。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关联性

当编译器计算所有运算符都具有不同优先级的表达式时,将计算每个子表达式,从具有最高优先级的子表达式开始,向下计算优先级。

但是如果两个顺序操作符具有相同的优先级呢?例如,给定表达式 2 / 6 * 4,有两种可能的评估序列:

(2 / 6) * 4 = 4/3

或者

2 / (6 * 4) = 1/12

当顺序运算符具有相同的优先级时,求值顺序由*运算符结合性决定。*也就是说,给定两个优先级相同的操作符,根据操作符的结合律,一个或另一个将具有优先级。运算符结合性的一些重要特征如下,并在表 8-5 中进行了总结:

  • The left union operator is evaluated from left to right.
  • The right association operator evaluates from right to left.
  • Binary operators, except assignment operators, are left associative.
  • Assignment operators and conditional operators are right associative.

因此,根据这些规则,前面的示例表达式应该从左到右分组,得到(2 / 6) * 4,即 4/3。

通过使用括号,可以显式设置表达式子表达式的求值顺序。带括号的子表达式

  • Override priority and binding rules
  • From the innermost nested set to the outermost set

的顺序进行评估

简单的算术运算符

简单算术运算符执行四种基本算术运算,列于表 8-6 。这些运算符是二元的和左结合的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

算术运算符对所有预定义的简单算术类型执行标准算术运算。

以下是简单算术运算符的示例:

`   int x1 = 5 + 6;          double d1 = 5.0 + 6.0;
   int x2 = 12 - 3;         double d2 = 12.0 - 3.0;
   int x3 = 3 * 4;          double d3 = 3.0 * 4.0;
   int x4 = 10 / 3;         double d4 = 10.0 / 3.0;

byte b1 = 5 + 6;
   sbyte sb1 = 6 * 5;`

余数运算符

余数运算符(%)将第一个操作数除以第二个操作数,忽略商,并返回余数。表 8-7 给出了它的描述。

余数运算符是二元的和左结合的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下几行显示了整数余数运算符的示例:

  • 0 % 3 = 0, because 0 divided by 3 is 0 with a remainder of 0.
  • 1 % 3 = 1, because 1 divided by 3 is 0 and the remainder is 1.
  • 2 % 3 = 2, because 2 divided by 3 is 0 and the remainder is 2.

%

  • 4 % 3 = 1, because 4 divided by 3 equals 1 and the remainder is 1.

余数运算符也可用于实数,给出的实数余数

   Console.WriteLine("0.0f % 1.5f is {0}" , 0.0f % 1.5f);    Console.WriteLine("0.5f % 1.5f is {0}" , 0.5f % 1.5f);    Console.WriteLine("1.0f % 1.5f is {0}" , 1.0f % 1.5f);    Console.WriteLine("1.5f % 1.5f is {0}" , 1.5f % 1.5f);    Console.WriteLine("2.0f % 1.5f is {0}" , 2.0f % 1.5f);    Console.WriteLine("2.5f % 1.5f is {0}" , 2.5f % 1.5f);

该代码产生以下输出:


0.0f % 1.5f is 0               // 0.0 / 1.5 = 0 remainder 0 0.5f % 1.5f is 0.5             // 0.5 / 1.5 = 0 remainder .5 1.0f % 1.5f is 1               // 1.0 / 1.5 = 0 remainder 1 1.5f % 1.5f is 0               // 1.5 / 1.5 = 1 remainder 0 2.0f % 1.5f is 0.5             // 2.0 / 1.5 = 1 remainder .5 2.5f % 1.5f is 1               // 2.5 / 1.5 = 1 remainder 1


关系和相等比较运算符

关系和相等比较操作符是二元操作符,它们比较它们的操作数并返回一个类型为bool的值。表 8-8 列出了这些操作员。

关系运算符和等式运算符是二元的和左关联的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

带有关系或相等运算符的二元表达式返回一个类型为bool.的值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意与 C 和 C++不同,C# 中的数字没有布尔解释。

   int x = 5;    if( x )           // Wrong.  x is of type int, not type boolean.       ...    if( x == 5 )      // Fine, since expression returns a value of type boolean       ...

打印时,布尔值truefalse由字符串输出值TrueFalse表示。

   int x = 5, y = 4;    Console.WriteLine("x == x is {0}" , x == x);    Console.WriteLine("x == y is {0}" , x == y);

这段代码产生以下输出:


x == x is True x == y is False


比较和相等操作

当比较大多数引用类型是否相等时,只比较引用。

  • If the references are equal-that is, if they point to the same object in memory-the equal comparison is true; Otherwise, it is false, even though two independent objects in memory are completely equivalent in other aspects.
  • This is called shallow ratio .
    illustrates the comparison of reference types. On the left side of the figure
  • , the references held by a and b are the same, so the comparison will return true. On the right side of the picture
  • , the reference is different, so even if the contents of two AClass objects are exactly the same, the comparison will return false.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-4。*比较引用类型是否相等

类型string的对象也是引用类型,但是比较方式不同。当比较字符串是否相等时,会比较字符串的长度和区分大小写的内容。

  • If two strings have the same length and the same case-sensitive content, the equal comparison returns true, even if they occupy different memory areas.
  • This is called deep comparison .

在第十五章中提到的代表也是引用类型,也使用深度比较。当比较委托是否相等时,如果两个委托都是null,或者如果两个委托的调用列表中有相同数量的成员并且调用列表匹配,则比较返回true

比较数值表达式时,会比较类型和值。当比较enum类型时,比较是在操作数的基础值上进行的。枚举包含在第十三章中。

递增和递减运算符

递增运算符将操作数加 1。减量运算符从操作数中减去 1。表 8-9 列出了操作者及其描述。

这些操作符是一元的,有两种形式,即前置形式和后置形式,它们的作用不同。

  • In the prefix, the operator is placed before the operand; Such as ++x and --y.
  • In postposition, the operator is placed after the operand; Such as x++ and y--.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在比较运算符的前置和后置形式时

  • The only difference is the value returned by the operator to the expression.

*表 8-10 显示了一个总结行为的例子。

例如,下面简单演示了四种不同版本的运算符。为了显示同一输入的不同结果,在每个赋值语句之前,操作数x的值被重置为5

`   int x = 5, y;
   y = x++;   // result: y: 5, x: 6
   Console.WriteLine(“y: {0}, x: {1}” , y, x);

x = 5;
   y = ++x;   // result: y: 6, x: 6
   Console.WriteLine(“y: {0}, x: {1}” , y, x);

x = 5;
   y = x–;   // result: y: 5, x: 4
   Console.WriteLine(“y: {0}, x: {1}” , y, x);

x = 5;
   y = --x;   // result: y: 4, x: 4
   Console.WriteLine(“y: {0}, x: {1}” , y, x);`

该代码产生以下输出:


y: 5, x: 6 y: 6, x: 6 y: 5, x: 4 y: 4, x: 4


条件逻辑运算符

逻辑运算符用于比较或否定其操作数的逻辑值,并返回结果逻辑值。表 8-11 列出了操作员。

逻辑 AND 和逻辑 OR 运算符是二进制和左关联的。逻辑 NOT 是一元的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些运算符的语法如下,其中 Expr1Expr2 计算为布尔值:

   Expr1 && Expr2    *Expr1* || *Expr2*       !    *Expr*

以下是一些例子:

`   bool bVal;
   bVal = (1 == 1) && (2 == 2);      // True, both operand expressions are true
   bVal = (1 == 1) && (1 == 2);      // False, second operand expression is false

bVal = (1 == 1) || (2 == 2);      // True, both operand expressions are true
   bVal = (1 == 1) || (1 == 2);      // True, first operand expression is true
   bVal = (1 == 2) || (2 == 3);      // False, both operand expressions are false

bVal = true;                      // Set bVal to true.
   bVal = !bVal;                     // bVal is now false.`

条件逻辑运算符以“短路”模式运行,这意味着,如果在对 Expr1 求值之后,结果已经确定,那么它跳过对 Expr2 的求值。以下代码显示了在计算第一个操作数后可以确定其值的表达式示例:

`   bool bVal;
   bVal = (1 == 2) && (2 == 2);    // False, after evaluating first expression

bVal = (1 == 1) || (1 == 2);    // True, after evaluating first expression`

由于短路行为,不要将有副作用的表达式(如更改值)放在 Expr2 中,因为它们可能不会被求值。在下面的代码中,变量iVal的后增量不会被执行,因为在第一个子表达式被执行后,可以确定整个表达式的值是false

`   bool bVal; int iVal = 10;

bVal = (1 == 2) && (9 == iVal++);       // result:  bVal = False, iVal = 10
             ↑                 ↑
            False           Never evaluated`

逻辑运算符

按位逻辑运算符通常用于设置方法参数的位模式。表 8-12 列出了按位逻辑运算符。

除了按位求反运算符之外,这些运算符都是二元和左关联的。按位求反运算符是一元的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二元按位运算符比较两个操作数中每个位置的相应位,并根据逻辑运算设置返回值中的位。

图 8-5 展示了按位逻辑运算的四个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-5。*按位逻辑运算符的例子

下面的代码实现了前面的示例:

`   const byte x = 12, y = 10;
   sbyte a;

a = x & y;                   //  a = 8
   a = x | y;                   //  a = 14
   a = x ^ y;                   //  a = 6
   a = ~x;                      //  a = -13`

移位操作符

按位移位运算符将位模式向右或向左移动指定的位置数,空出的位用 0 或 1 填充。表 8-13 列出了移位操作符。

移位运算符是二元的和左结合的。这里显示了按位移位运算符的语法。移动的位置数量由 Count 给出。

   *Operand* << *Count*                         // Left shift    *Operand* >> *Count*                         // Right shift

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于绝大多数的 C# 编程来说,你不需要了解任何底层的硬件。但是,如果您正在对有符号的数字进行按位操作,了解数字表示会很有帮助。底层硬件以一种叫做二进制补码的形式表示带符号的二进制数。在二进制补码表示中,正数有它们正常的二进制形式。要对一个数求反,需要对该数进行逐位求反,然后加 1。这个过程将正数转化为负数,反之亦然。在二进制补码中,所有负数在最左边的位上都有一个 1。图 8-6 显示了对数字 12 的否定。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-6。*要得到一个二进制补码数的反数,取其按位反数并加 1。

当移位有符号数字时,底层表示很重要,因为将整数值左移一位的结果与将它乘以二的结果相同。向右移动和除以二是一样的。

然而,如果您将一个负数向右移位,并且最左边的位被填充为 0,则会产生错误的结果。最左边的 0 表示正数。但这是不正确的,因为负数除以 2 不会产生正数。

为了解决这种情况,当操作数是一个有符号整数时,如果操作数最左边的位是 1(表示一个负数),左边的位位置用 1 而不是 0 填充。这保持了正确的二进制补码表示。对于正数或无符号数,左边空出的位用 0 填充。

图 8-7 显示了表达式 14 << 3 如何在byte中求值。此操作会导致以下情况:

  • Each bit in operand (14) is shifted to the left by three bits.
  • The left three digits at the right end are filled with zeros.
  • The resulting value is 112.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-7。*左移三位的例子

图 8-8 说明了按位移位操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-8。*按位移位

下面的代码实现了前面的示例:

`   int a, b, x = 14;

a = x << 3;              // Shift left
   b = x >> 3;              // Shift right

Console.WriteLine(“{0} << 3 = {1}” , x, a);
   Console.WriteLine(“{0} >> 3 = {1}” , x, b);`

该代码产生以下输出:


14 << 3 = 112 14 >> 3 = 1


赋值运算符

赋值运算符计算运算符右侧的表达式,并使用该值设置运算符左侧的变量表达式的值。表 8-14 列出了赋值运算符。

赋值操作符是二元的和右关联的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

语法如下:

VariableExpression Operator Expression

对于简单赋值,计算运算符右边的表达式,将其值赋给左边的变量。

   int x;    x = 5;    x = y * z;

记住赋值表达式是一个表达式,因此返回一个值到它在语句中的位置。执行赋值后,赋值表达式的值是左操作数的值。因此,在表达式x = 10的情况下,值 10 被赋给变量x。x 的值,现在是 10,变成了整个表达式的值。

由于赋值是一个表达式,它可以是一个更大表达式的一部分,如图 8-9 所示。表达式的评估如下:

  • Since the assignment is right-related, the evaluation starts from the right, and the variable x is assigned a value of 10.
  • That expression is the right operand assigned by the variable y, so the value of x, now 10, is assigned to y.
  • The assignment of y is the right operand of the assignment of z-all three variables have values of 10.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-9。*赋值表达式在完成赋值后返回其左操作数的值。

可以位于赋值运算符左侧的对象类型如下。它们将在本文后面讨论。

  • Variables (local variables, fields, parameters)
  • attribute
  • Indexer
  • event
复合赋值

通常,您会希望对表达式求值,并将结果添加到变量的当前值中,如下所示:

   x = x + expr;

复合赋值操作符允许一种简化方法,以避免在某些常见情况下左侧变量在右侧重复出现。例如,下面两个语句在语义上是等价的,但是第二个语句更短,也同样容易理解。

   x = x + (y – z);    x += y – z;

其他复合赋值语句是类似的:

                                     Notice the parentheses.                                          ↓     ↓    x *= y – z;      // Equivalent to x = x * (y – z)    x /= y – z;      // Equivalent to x = x / (y – z)      ...

条件运算符

条件运算符是根据条件的结果返回两个值之一的强大而简洁的方法。表 8-15 显示了操作员。

条件运算符是三元的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

条件运算符的语法如下所示。它有一个测试表达式和两个结果表达式。

  • Condition must return a value of type bool.
  • If Condition evaluates to true, Expression1 evaluates and returns. Otherwise, evaluate and return Expression2 .

   Condition ? Expression1 : Expression2

条件运算符可以与if...else结构进行比较。例如,下面的if...else构造检查一个条件,如果条件为真,该构造将5赋给变量intVar。否则,它给它赋值 10。

   if ( x < y )                                       // if...else       intVar = 5;    else       intVar = 10;

条件运算符可以以不太冗长的形式执行相同的操作,如以下语句所示:

   intVar = x < y  ?  5  :  10;                       // Conditional operator

将条件和每个返回表达式放在单独的行上,如下面的代码所示,这使得意图非常容易理解。

   intVar = x < y             ?  5             :  10 ;

图 8-10 比较了示例中所示的两种形式。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-10。*条件运算符 vs. if…否则

例如,下面的代码使用了三次条件运算符——在每个WriteLine语句中使用一次。首先,它返回x的值或者y的值。在后两个实例中,它返回空字符串或字符串“not”。

`   int x = 10, y = 9;
   int highVal = x > y                            // Condition
                    ? x                           // Expression 1
                    : y;                          // Expression 2
   Console.WriteLine(“highVal:  {0}\n” , highVal);

Console.WriteLine(“x is{0} greater than y” ,
                         x > y                    // Condition
                         ? “”                     // Expression 1
                         : " not" );              // Expression 2
   y = 11;
   Console.WriteLine(“x is{0} greater than y” ,
                         x > y                    // Condition
                         ? “”                     // Expression 1
                         : " not" );              // Expression 2`

该代码产生以下输出:


`highVal:  10

x is greater than y
x is not greater than y`


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 if...else语句是一条控制流语句。它应该用于执行两个动作中的一个或另一个。条件运算符返回一个表达式。用于返回两个中的一个或另一个。

一元算术运算符

一元运算符设置数值的符号。它们在表 8-16 中列出。

  • The unary operator simply returns the value of the operand.
  • The unary negative operator returns the value of the operand minus 0.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

例如,以下代码显示了运算符的用法和结果:

   int x = +10;         // x = 10    int y = -x;          // y = -10    int z = -y;          // z = 10

用户自定义类型转换

用户定义的转换在第十六章中有更详细的讨论,但是我在这里也会提到它们,因为它们是操作符。

您可以为自己的类和结构定义隐式和显式转换。这允许您将用户定义类型的对象转换为其他类型,反之亦然。* C# provides implicit and explicit conversions.
* Using implicit conversion , when the compiler resolves what type to use in a specific context, it will automatically convert if necessary.
* With explicit conversion , the compiler will only convert when using the explicit conversion operator.

声明隐式转换的语法如下。所有用户定义的转换都需要publicstatic修饰符。

         Required                     Target               Source    <ins>        ↓             </ins>             ↓        <ins>          ↓          </ins>    public static implicit operator *TargetType* ( *SourceType Identifier* )    {          ...       return *ObjectOfTargetType*;    }

显式转换的语法是相同的,只是用explicit代替了implicit

下面的代码展示了一个转换操作符的声明示例,它将把类型为LimitedInt的对象转换为类型为int的对象,反之亦然。

`   class LimitedInt                   Target     Source
   {
                                        ↓        ↓      
      public static implicit operator int (LimitedInt li)    // LimitedInt to int
      {
         return li.TheValue;
      }                               Target         Source
                                        ↓            ↓  
      public static implicit operator LimitedInt (int x)     // int to LimitedInt
      {
         LimitedInt li = new LimitedInt();
         li.TheValue = x;
         return li;
      }

private int _theValue = 0;
      public int TheValue{ … }
   }`

例如,下面的代码重复并使用了刚刚定义的两个类型转换操作符。在Main中,一个int文字被转换成一个LimitedInt对象,在下一行中,一个LimitedInt对象被转换成一个int

`   class LimitedInt
   {
      const int MaxValue = 100;
      const int MinValue = 0;

public static implicit operator int(LimitedInt li)       // Convert type
      {
         return li.TheValue;
      }

public static implicit operator LimitedInt(int x)        // Convert type
      {
         LimitedInt li = new LimitedInt();
         li.TheValue = x;
         return li;
      }

private int _theValue = 0;
      public int TheValue                                      // Property
      {
         get { return _theValue; }
         set
         {
            if (value < MinValue)
               _theValue = 0;
            else
               _theValue = value > MaxValue
                              ? MaxValue
                              : value;
         }
      }
   }

class Program
   {
      static void Main()                           // Main
      {
         LimitedInt li = 500;                      // Convert 500 to LimitedInt
         int value     = li;                       // Convert LimitedInt to int

Console.WriteLine(“li: {0}, value: {1}” , li.TheValue, value);
      }
   }`

该代码产生以下输出:


li: 100, value: 100


显式转换和强制转换运算符

前面的示例代码展示了从intLimitedInt类型的隐式转换,以及从LimitedIntint类型的隐式转换。但是,如果您将两个转换操作符声明为explicit,那么在进行转换时,您将不得不显式地使用 cast 操作符。

一个转换操作符由你想要将表达式转换成的类型名组成,在一对括号内。例如,在下面的代码中,方法Main将值 500 转换为一个LimitedInt对象。

                               Cast operator                                <ins>     ↓       </ins>                LimitedInt li = (LimitedInt) 500;

例如,下面是代码的相关部分,其中的更改标记为:

在这两个版本的代码中,输出如下:


li: 100, value: 100


还有另外两个运算符,它们接受一种类型的值,并返回另一种指定类型的值。这些是is操作符和as操作符。这些都在第十六章的结尾有所涉及。

运算符重载

正如您所看到的,C# 操作符被定义为使用预定义的类型作为操作数。如果遇到用户定义的类型,操作者根本不知道如何处理它。运算符重载允许您定义 C# 运算符应该如何对用户定义类型的操作数进行运算。

  • Operator overloading is only available for classes and structures.
  • By declaring a method named operator x to realize behavior (for example, operator +, operator - and so on), you can overload an operator x for your class or structure.
    • The overloaded method of unary operator adopts a single parameter of type class or struct.
    • Overload method of binary operator takes two parameters, at least one of which must be of type class or struct.    public static LimitedInt operator -(LimitedInt x)               // Unary    public static LimitedInt operator +(LimitedInt x, double y)     // Binary

运算符重载方法的声明需要满足以下条件:

  • Declarations must use both static and public modifiers.
  • Operator must be a member of the class or structure to which the operator belongs.

例如,下面的代码显示了类LimitedInt的两个重载操作符:加法操作符和求反操作符。您可以看出这是否定,而不是减法,因为运算符重载方法只有一个参数,因此是一元的,而减法运算符是二元的。

`   class LimitedInt Return
   {
           Required       Type    Keyword  Operator  Operand
             ↓           ↓         ↓     ↓        ↓                
      public static LimitedInt operator + (LimitedInt x, double y)
      {
         LimitedInt li = new LimitedInt();
         li.TheValue = x.TheValue + (int)y;
         return li;
      }

public static LimitedInt operator - (LimitedInt x)
      {
         // In this strange class, negating a value just sets it to 0.
         LimitedInt li = new LimitedInt();
         li.TheValue = 0;
         return li;
      }
      …
  }`

对运算符重载的限制

并非所有的运算符都可以重载,而且可以重载的类型也有限制。关于运算符重载的限制,您应该知道的一些重要的事情将在本节的后面描述。

只有下列运算符可以重载。列表中明显缺少的是赋值运算符。

可重载的一元运算符:+-!~++--truefalse

可重载的二元运算符:+``-``*``/``%``&``|``^``<<``>>``==``!=``>``<``>=``<=

递增和递减运算符是可重载的。但是与预定义版本不同,重载操作符的使用前和使用后没有区别。

在操作符重载的情况下,您不能做以下事情:

  • Create a new operator
  • Change the syntax of operators.
  • Redefine how operators act on predefined types.
  • Change the priority or combination of operators.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意你的重载操作符要符合操作符的直观含义。

运算符重载的例子

以下示例显示了类LimitedInt的三个运算符的重载:求反、减法和加法。

`class LimitedInt {
      const int MaxValue = 100;
      const int MinValue = 0;

public static LimitedInt operator -(LimitedInt x)
      {
         // In this strange class, negating a value just sets its value to 0.
         LimitedInt li = new LimitedInt();
         li.TheValue = 0;
         return li;
      }

public static LimitedInt operator -(LimitedInt x, LimitedInt y)
      {
         LimitedInt li = new LimitedInt();
         li.TheValue = x.TheValue - y.TheValue;
         return li;
      }

public static LimitedInt operator +(LimitedInt x, double y)
      {
         LimitedInt li = new LimitedInt();
         li.TheValue = x.TheValue + (int)y;
         return li;
      }

private int _theValue = 0;
      public int TheValue
      {
         get { return _theValue; }
         set
         {
            if (value < MinValue)
               _theValue = 0;
            else
               _theValue = value > MaxValue
                              ? MaxValue
                              : value;
         }
      }
   }
                                                                                   class Program {
      static void Main() {
         LimitedInt li1 = new LimitedInt();
         LimitedInt li2 = new LimitedInt();
         LimitedInt li3 = new LimitedInt();
         li1.TheValue = 10; li2.TheValue = 26;
         Console.WriteLine(" li1: {0}, li2: {1}" , li1.TheValue, li2.TheValue);

li3 = -li1;
         Console.WriteLine(“-{0} = {1}” , li1.TheValue, li3.TheValue);

li3 = li2 - li1;
         Console.WriteLine(" {0} - {1} = {2}" ,
                    li2.TheValue, li1.TheValue, li3.TheValue);

li3 = li1 - li2;
         Console.WriteLine(" {0} - {1} = {2}" ,
                    li1.TheValue, li2.TheValue, li3.TheValue);
      }
   }`

该代码产生以下输出:


li1: 10, li2: 26 -10 = 0  26 - 10 = 16  10 - 26 = 0


操作员的类型

typeof操作符返回作为其参数给出的任何类型的System.Type对象。从这个对象,你可以了解类型的特征。(对于任何给定的类型,只有一个System.Type对象。)不能重载typeof操作符。表 8-17 列出了操作员的特征。

typeof运算符是一元的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下是typeof运算符的语法示例。TypeSystem名称空间中的一个类。

   Type t = typeof ( SomeClass )

例如,下面的代码使用了typeof操作符来获取关于名为SomeClass的类的信息,并打印其公共字段和方法的名称。

`   using System.Reflection;  // Use the Reflection namespace to take full advantage
                             // of determining information about a type.
   class SomeClass
   {
      public int  Field1;
      public int  Field2;

public void Method1() { }
      public int  Method2() { return 1; }
   }

class Program
   {
      static void Main()
      {
         Type t = typeof(SomeClass);
         FieldInfo[]  fi = t.GetFields();
         MethodInfo[] mi = t.GetMethods();

foreach (FieldInfo f in fi)
            Console.WriteLine(“Field : {0}” , f.Name);
         foreach (MethodInfo m in mi)
            Console.WriteLine(“Method: {0}” , m.Name);
      }
   }`

该代码产生以下输出:


Field : Field1 Field : Field2 Method: Method1 Method: Method2 Method: ToString Method: Equals Method: GetHashCode Method: GetType


操作符typeof也被GetType方法调用,该方法可用于每种类型的每一个对象。例如,下面的代码检索对象的类型名称:

`   class SomeClass
   {
   }

class Program
   {
      static void Main()
      {
         SomeClass s = new SomeClass();

Console.WriteLine(“Type s: {0}” , s.GetType().Name);
      }
   }`

该代码产生以下输出:


Type s: SomeClass


其他操作员

本章介绍的运算符是内置类型的标准运算符。还有其他特殊用途的操作符,以及它们的操作数类型,将在本书后面讨论。例如,可空类型有一个特殊的操作符,称为空合并操作符,这在第二十五章中有详细描述。*

九、语句

什么是报表?

C# 中的语句与 C 和 C++中的语句非常相似。本章涵盖了 C# 语句的特征,以及该语言提供的控制流语句。

  • Statement is a source code instruction that describes the type or tells the program to perform actions.
  • There are three categories of statements:
    • Declaration statement : A statement that declares a type or variable.
    • Embedded statement : a statement that performs actions or manages control flow.
    • tag statement : the statement that controls the jump to.

前面的章节已经介绍了许多不同的声明语句,包括局部变量、类和类成员的声明。本章介绍嵌入式语句,这些语句不声明类型、变量或实例。相反,它们使用表达式和控制流构造来处理由声明语句声明的对象和变量。

  • Simple statement consists of an expression followed by a semicolon.

  • Declarative statement
  • Embedded statement
  • Tag statement
  • Nested block

以下代码给出了每种情况的示例:

`   int x = 10;              // Simple declaration
   int z;                   // Simple declaration

{                        // Start of a block
      int y = 20;           // Simple declaration
      z = x + y;            // Embedded statement
   top: y = 30;             // Labeled statement
         …
      {                     // Start of a nested block
         …
      }                     // End of nested block
   }                        // End of outer block`

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意一个块在语法上算作一个嵌入式语句。在语法上需要嵌入语句的任何地方,都可以使用块。

一个空语句只包含一个分号。您可以在语言语法需要嵌入语句但程序逻辑不需要任何操作的任何位置使用空语句。

例如,下面的代码显示了使用 empty 语句的示例。

  • The second line in the code is an empty statement. This is necessary because there must be an embedded statement between the if part and the else part of the construction.
  • The fourth line is a simple statement, indicated by a terminating semicolon.

   if( x < y )       ;                     // Empty statement    else       z = a + b;            // Simple statement

表情语句

前一章看了表情。表达式返回值,但是它们也会有副作用。

  • A side effect is an action that affects the state of the program.
  • Many expressions are only evaluated for their side effects.

通过在表达式后放置语句终止符(分号),可以从表达式创建语句。表达式返回的任何值都将被丢弃。例如,下面的代码显示了一个表达式语句。它由赋值表达式(一个赋值运算符和两个操作数)后跟一个分号组成。这做了以下两件事:

x

这被认为是副作用。

  • After setting the value of x, the expression returns the new value of x. But nothing can receive this return value, so it was ignored. x = 10;

评估表达式的全部原因是为了实现副作用。

控制流报表

C# 提供了现代编程语言常见的控制流结构。

  • Conditional execution Execute or skip a piece of code according to conditions. The conditional execution statement is as follows:
    • if
    • if...else
    • switch
  • Loop statement Repeats a piece of code. The loop statement reads as follows:
    • while
    • do
    • for
    • foreach
  • Jump statement Changes the control flow from one code to a specific statement in another code. The jump statement reads as follows:
    • break
    • continue
    • return
    • goto
    • throw

条件执行和循环构造(除了foreach)需要一个测试表达式,或者条件,来决定程序应该在哪里继续执行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意与 C 和 C++不同,在 C# 中测试表达式必须返回一个bool类型的值。在 C# 中,数字没有布尔解释。

if 语句

if语句实现条件执行。此处显示了if语句的语法,并在图 9-1 中进行了说明。

  • TestExpr A value of type bool must be calculated.
  • If TestExpr evaluates to true, Statement is executed.
  • If the evaluation is false, skip Statement .

   if( *TestExpr* )       *Statement* 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-1。*if 语句

以下代码显示了if语句的示例:

`   // With a simple statement
   if( x <= 10 )
       z = x – 1;        // Single statement–no curly braces needed

// With a block
   if( x >= 20 )
   {
       x = x – 5;        // Block–curly braces needed
       y = x + z;
   }

int x = 5;
   if( x )               // Error: test expression must be a bool, not int
   {
       …
   }`

如果…else 语句

if...else语句实现了双向分支。此处显示了if...else语句的语法,并在图 9-2 中进行了说明。

  • If TestExpr evaluates to true, Statement1 is executed.
  • Otherwise, perform Statement2 instead.

   if( *TestExpr* )       *Statement1*    else       *Statement2* 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-2。*如果…else 语句

下面是一个if...else语句的例子:

   if( x <= 10 )       z = x – 1;               // Single statement    else    {                           // Multiple statements--block       x = x – 5;       y = x + z;    }

当然, Statement1Statement2 或者两者本身都可以是if或者if...else语句,可以进一步嵌套。如果您正在查看包含嵌套的if...else语句的代码,并且需要确定哪个else与哪个if相配,有一个简单的规则。每个else都属于没有关联else子句的最近的前一个 if

Statement2 是一个ifif...else语句时,常见的格式如下所示,将第二个if子句与else子句放在同一行。这个例子展示了两个if...else语句,但是您可以创建一个任意长的链。

   if( *TestExpr1* )       *Statement1*    else if ( *TestExpr2* )       *Statement2*    else       *Statement3*

while 循环

while循环是一个简单的循环结构,其中测试表达式在循环的顶部执行。这里显示了while循环的语法,并在图 9-3 中进行了说明。

  • Evaluate TestExpr first
  • If TestExpr evaluates to false, execution will continue after the while loop ends.
  • Otherwise, when TestExpr is evaluated as true, Statement is executed and TestExpr is evaluated again. Every time the calculation result of TestExpr is true, Statement will be executed once When TestExpr evaluates to false, the loop ends.

   while( *TestExpr* )       *Statement* 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-3。*while 循环

下面的代码展示了一个while循环的例子,其中测试表达式变量从值 3 开始,并在每次迭代中递减。当变量值变为 0 时,循环退出。

   int x = 3;    while( x > 0 )    {       Console.WriteLine("x:  {0}", x);       x--;    }    Console.WriteLine("Out of loop");

该代码产生以下输出:


x:  3 x:  2 x:  1 Out of loop


do 循环

do循环是一个简单的循环结构,其中测试表达式在循环的底部执行。这里显示了do循环的语法,并在图 9-4 中进行了说明。

  • First, Statement is executed.
  • Then, TestExpr is evaluated.
  • If TestExpr returns to true, Statement is executed again.
  • Every time TestExpr returns to true, Statement is executed again.
  • When TestExpr returns to false, control is transferred to the statement after the loop construction is completed.

   do       *Statement*    while( *TestExpr* );             // End of do loop 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-4。*do 循环

do循环有几个特征使它区别于其他控制流结构。它们是:

  • The main body of the loop, Statement , is always executed at least once, even if TestExpr is originally false. The reason is that TestExpr is not evaluated until the bottom of the loop.
  • A semicolon is required after the closing bracket of the test expression.

以下代码显示了一个do循环的例子:

   int x = 0;    do       Console.WriteLine("x is {0}", x++);    while (x<3);               ↑            Required

该代码产生以下输出:


x is 0 x is 1 x is 2


for 循环

当测试表达式在循环的顶部求值时,只要它返回true,for循环结构就执行循环体。这里显示了for循环的语法,如图 9-5 中的所示。

  • At the beginning of the for loop, Initializer is executed once.
  • TestExpr is the later evaluation.
  • If TestExpr returns true, Statement is executed, followed by IterationExpr .
  • Control then returns to the top of the loop and evaluates TestExpr again.
  • As long as TestExpr returns true Statement , followed by IterationExpr , it will be executed.
  • Once TestExpr returns to false, continue to execute the statements after Statement .

                 Separated by semicolons                    ↓         ↓                                  for( *Initializer*; *TestExpr*; *IterationExpr* )       *Statement*

语句的某些部分是可选的,其他部分是必需的。

  • Initializer , TestExpr , IterationExpr are optional. Their positions can be left blank. If the TestExpr position is empty, the test assumes that true is returned. Therefore, if the program wants to avoid entering an infinite loop, there must be other ways to exit the statement.
  • These semicolons are always required for field separators, even if any optional items are omitted.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-5。*for 循环

图 9-5 说明了通过for语句的控制流程。您还应该了解其组件的以下信息:

  • Initializer is only executed once, before any other part of for structure. It is usually used to declare and initialize local values to be used in loops.
  • TestExpr is evaluated to determine whether Statement should be executed or skipped. It must calculate a value of type bool. As mentioned earlier, if TestExpr is empty, the assumption is always true.
  • IterationExp R is executed immediately after Statement and before returning to the top of TestExpr cycle.

例如,在下面的代码中

  • First, the initializer (int i=0) defines a variable named i and initializes its value to 0.
  • Then evaluate the condition (i<3). If it is true, then loop body is executed.
  • At the bottom of the loop, after all the loop statements are executed, execute the IterationExpr statement-in this case, increment the value of i.

`   // The body of this for loop is executed three times.
   for( int i=0 ; i<3 ; i++ )
      Console.WriteLine(“Inside loop.  i:  {0}”, i);

Console.WriteLine(“Out of Loop”);`

该代码产生以下输出:


Inside loop.  i:  0 Inside loop.  i:  1 Inside loop.  i:  2 Out of Loop


for 语句中变量的范围

初始化器中声明的变量,称为循环变量,仅在 for 语句中可见。**

  • This is different from C and C++, where declarations introduce variables into closed blocks.
  • The following code illustrates this point:

`   Type is needed here for declaration.
        ↓
   for(int i=0; i<10; i++ ) // Variable i is in scope here, and also
     Statement;             // here within the statement.
                            // Here, after the statement, i no longer exists.

Type is needed here again because the previous variable i has gone out of existence.
       ↓
   for(int i=0; i<10; i++ ) // We need to define a new variable i here,
      Statement;            // the previous one has gone out of existence.`

在循环体内声明的局部变量只有在循环内才是已知的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意循环变量通常被赋予标识符ijk。这是 FORTRAN 编程时代的传统。在 FORTRAN 中,以字母 IJKLMN 开头的标识符默认为INTEGER类型,您不必声明它们。由于循环变量通常是整数,程序员简单地使用了简单的惯例,即使用I作为循环变量的名称。它简短易用,而且你不用申报。如果他们有一个嵌套循环,内部循环变量通常被命名为J。如果还有另一个内部嵌套循环,该变量被命名为K

虽然有些人反对使用非描述性的名称作为标识符,但我喜欢历史联系,以及使用这些标识符作为循环变量时的清晰和简洁。

初始值设定项和迭代表达式中的多个表达式

初始化表达式和迭代表达式都可以包含多个表达式,只要用逗号分隔。

例如,以下代码在初始值设定项中有两个变量声明,在迭代表达式中有两个表达式:

`   static void Main( )
   {
      const int MaxI = 5;

Two declarations           Two expressions
                   ↓                           for (int i = 0, j = 10; i < MaxI; i++, j += 10)
      {
         Console.WriteLine(“{0}, {1}”, i, j);
      }
   }`

该代码产生以下输出:


0, 10 1, 20 2, 30 3, 40 4, 50


切换语句

switch语句实现多路分支。图 9-6 显示了switch语句的语法和结构。

  • The switch statement contains zero or more switch segments .
  • Each switch segment starts with one or more switch labels.
  • Each switch segment must end with a break statement or one of the other four jump statements.
    • The jump statements are break, return, continue, goto and throw. These will be introduced later in this chapter.
    • Among the five jump statements, the break statement is the most commonly used statement to end a switch paragraph. break statement transfers execution to the end of switch statement. I will discuss all jump statements later in this chapter.

开关标签按顺序进行评估。如果一个匹配测试表达式的值,它的 switch 部分被执行,然后控制跳转到switch语句的底部。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-6。*switch 语句的结构

开关标签具有以下形式:

<ins>case</ins> ConstantExpression:   ↑                    ↑ Keyword           Switch label terminator

图 9-6 中通过结构的控制流程如下:

  • Test the expression TestExpr and evaluate it at the top of the construct.
  • If the value of TestExpr is equal to the value of ConstExpr1 , the constant expression in the first switch tag is executed, and then the statements in the statement list of after the switch tag are executed until one of the jump statements is encountered.
  • default paragraph is optional, but if it is included, it must end with one of the jump statements.

图 9-7 说明了通过switch语句进行控制的一般流程。您可以通过一个带有goto语句或return语句的switch语句来修改流程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-7。*通过 switch 语句控制的流程

一个开关例子

下面的代码执行了五次switch语句,其中x的值从15。从输出中,可以看出在循环的每个周期中执行了哪个 case 部分。

`   for( int x=1; x<6; x++ )
   {
      switch( x )                          // Evaluate the value of variable x.
      {
         case 2:                                        // If x equals 2
            Console.WriteLine(“x is {0} – In Case 2”, x);
            break;                                      // Go to end of switch.

case 5:                                        // If x equals 5
            Console.WriteLine(“x is {0} – In Case 5”, x);
            break;                                      // Go to end of switch.

default:                                       // If x is neither 2 nor 5
            Console.WriteLine(“x is {0} – In Default case”, x);
            break;                                      // Go to end of switch.
      }
   }`

该代码产生以下输出:


x is 1 -- In Default case x is 2 -- In Case 2 x is 3 -- In Default case x is 4 -- In Default case x is 5 -- In Case 5


更多关于开关的语句

一个switch语句可以有任意数量的开关部分,包括零个。不需要default部分,如下例所示。但是,包含它通常被认为是一种好的做法,因为它可以捕捉潜在的错误。

例如,下面代码中的switch语句没有default部分。switch语句在一个for循环中,该循环执行该语句五次,x的值从1开始,到5结束。

   for( int x=1; x<6; x++ )    {        switch( x )        {           case 5:              Console.WriteLine("x is {0} -- In Case 5", x);              break;        }    }

该代码产生以下输出:


x is 5 -- In Case 5


以下代码只有默认部分:

   for( int x=1; x<4; x++ )    {       switch( x )       {          default:             Console.WriteLine("x is {0} -- In Default case", x);             break;       }    }

该代码产生以下输出:


x is 1 -- In Default case x is 2 -- In Default case x is 3 -- In Default case


切换标签

开关标签中关键字case后面的表达式必须是常量表达式,因此在编译时,编译器必须完全可以对其求值。它还必须与测试表达式的类型相同。

例如,图 9-8 显示了三个示例switch报表。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-8。*带有不同类型开关标签的开关报表

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意与 C 和 C++不同,在 C# 中每个switch段,包括可选的默认段,都必须以其中一个跳转语句结束。在 C# 中,你不能在一个开关部分执行代码,然后通过进入下一个。

尽管 C# 不允许从一个开关部分切换到另一个开关部分,但您可以执行以下操作:

  • You can attach multiple switch labels to any switch section.
  • After the list of statements associated with a case, there must be a jump statement before the next switch tag, unless there is no to insert the executable statement between the switch tags.

例如,在下面的代码中,由于前三个开关标签之间没有可执行语句,所以一个接一个就可以了。然而,案例 5 和案例 6 之间有一个可执行语句,所以在案例 6 之前必须有一个跳转语句。

   switch( x )    {       case 1:                    // Acceptable       case 2:       case 3:            ...                     // Execute this code if x equals 1, 2, or 3.          break;       case 5:          y = x + 1;       case 6:                    // Not acceptable because there is no break          ...

跳转报表

当控制流到达跳转语句时,程序执行被无条件地转移到程序的另一部分。跳转语句如下:

  • break
  • continue
  • return
  • goto
  • throw

本章涵盖了这些陈述中的前四条。第十一章的中解释了throw声明。

break 语句

在本章的前面,你看到了在switch语句中使用的break语句。它也可用于以下语句类型:

  • for
  • foreach
  • while
  • do

在这些语句的主体中,break导致执行退出最里面的封闭循环。

例如,如果下面的while循环只依赖于它的测试表达式,那么它将是一个无限循环,测试表达式总是true。但是,在循环的三次迭代之后,遇到了break语句,循环被退出。

   int x = 0;    while( true )    {        x++;        if( x >= 3 )            break;    }

继续语句

continue语句使程序执行到以下类型的最内层封闭循环顶层:

  • while
  • do
  • for
  • foreach

例如,下面的for循环执行五次。在前三次迭代中,它遇到了continue语句并直接返回到循环的顶部,错过了循环底部的WriteLine语句。执行仅在最后两次迭代中到达WriteLine语句。

`   for( int x=0; x<5; x++ )            // Execute loop five times
   {
      if( x < 3 )                      // The first three times
         continue;                     // Go directly back to top of loop

// This line is only reached when x is 3 or greater.
      Console.WriteLine(“Value of x is {0}”, x);
   }`

该代码产生以下输出:


Value of x is 3 Value of x is 4


下面的代码展示了一个在while循环中的continue语句的例子。这段代码产生与前面的for循环示例相同的输出。

`   int x = 0;
   while( x < 5 )
   {
      if( x < 3 )
      {
          x++;
          continue;                         // Go back to top of loop
      }

// This line is reached only when x is 3 or greater.
      Console.WriteLine(“Value of x is {0}”, x);
      x++;
   }`

标注报表

带标签的语句由一个标识符、一个冒号和一个语句组成。它具有以下形式:

         Identifier: Statement

执行带标签的语句时,就好像标签不存在,只包含了 Statement 部分。

  • Tagging statements allows control to be transferred from another part of the code to statements.
  • Tagged statements can only be used inside a block.
标签

标签有自己的声明空间,因此带标签语句中的标识符可以是任何有效的标识符,包括那些可能在重叠范围内声明的标识符,如局部变量或参数名。

例如,以下代码显示了与局部变量具有相同标识符的标签的有效用法:

   {       int xyz = 0;                                     // Variable xyz          ...       xyz: Console.WriteLine("No problem.");           // Label xyz    }

然而,还是有一些限制。标识符不能是

  • A keyword
  • Same as another label identification and overlapping range
标注报表的范围

带标签的语句不能从声明它们的块之外的处看到(或访问)。带标签的语句的范围如下:

  • Declare as
  • Block, nested in the block.

内的任何块

例如,图 9-9 左边的代码包含了几个嵌套的块,并标记了它们的作用域。在程序的作用域 B 中声明了两个带标签的语句:incrementend

  • The shaded part on the right side of the figure shows the area where the markup statement is located in the code.
  • Code in scope B, and all nested blocks, can see and access the marked statements.
  • Code from any internal scope can jump from to marked statement.
  • Code from outside (scope A in this example) cannot jump to a block with a tag statement.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-9。*标签的范围包括嵌套块。

goto 语句

goto语句无条件地将控制权转移给一个*标记的语句。*其一般形式如下,其中 Identifier 是被标注语句的标识符:

         goto *Identifier* ;

例如,下面的代码显示了一个goto语句的简单用法:

`   bool thingsAreFine;
   while (true)
   {
      thingsAreFine = GetNuclearReactorCondition();

if ( thingsAreFine )
         Console.WriteLine(“Things are fine.”);
      else
         goto NotSoGood;
   }

NotSoGood: Console.WriteLine(“We have a problem.”);`

goto语句必须是标记语句的范围内。

  • A goto statement can jump to any marked statement in its own block, or can jump out of to any block it is nested in.
  • The goto statement cannot jump to any block nested within its own block.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 警告强烈反对使用goto语句,因为这会导致代码结构不良,难以调试和维护。Edsger Dijkstra 在 1968 年写给 ACM 的题为“转到被认为有害的语句”的信是对计算机科学的一个重要贡献;这是最先发表的关于使用goto语句的缺陷的描述之一。

switch 语句中的 goto 语句

还有另外两种形式的goto语句,在switch语句中使用。这些goto语句将控制转移到switch语句中相应命名的开关标签。

   goto case ConstantExpression;    goto default;

使用语句

某些类型的非托管对象数量有限,或者占用大量系统资源。很重要的一点是,当你的代码完成后,要尽快发布它们。using语句有助于简化过程,并确保这些资源得到适当的处置。

资源是实现System.IDisposable接口的类或结构。接口在第十五章中有详细介绍——但简而言之,接口是类和结构可以选择实现的未实现函数成员的集合。IDisposable接口包含一个名为Dispose的方法。

使用资源的阶段如图 9-10 所示,包括以下内容:

  • Allocate resources
  • Use resources
  • Dispose of resources

如果在使用资源的代码部分发生意外的运行时错误,则释放资源的代码可能无法执行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-10。*使用资源的组件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 注意using语句不同于using指令。第二十一章中详细介绍了using指令。

包装一种资源的使用

using语句通过巧妙地打包资源的使用,有助于减少意外运行时错误的潜在问题。

using语句有两种形式。第一种形式如下,如图 9-11 所示。

  • Code in brackets allocates resources.
  • Statement is the code for using resources.
  • The using statement implicitly generates the code of processing resources.

   using ( <ins>*ResourceType* *Identifier* = *Expression*</ins> ) <ins>*Statement*</ins>                              ↑                        ↑                          Allocates resource              Uses resource

意外的运行时错误被称为异常,在第二十二章中有详细介绍。处理异常可能性的标准方法是将可能导致异常的代码放在try块中,并将必须执行的任何代码放在finally块中,无论是否有异常。

这种形式的using语句正是这样做的。它执行以下操作:

  • Allocate resources
  • Put Statement into try block.
  • Create a call to the Dispose method of the resource and put it in the finally block.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-11。*using 语句的效果

using 语句的例子

下面的代码使用了两次using语句——一次用于名为TextWriter的类,一次用于名为TextReader的类,两次都来自于System.IO名称空间。按照using语句的要求,这两个类都实现了IDisposable接口。

  • TextWriter The resource opens a text file for writing, and writes a line into the file.
  • TextReader The resource then opens the same text file and reads and displays the contents line by line.
  • In both cases, the using statement ensures that the Dispose method of the object is called.
  • Pay attention to the difference between using statement in Main and using instruction in the first two lines.

`   using System;                    // using DIRECTIVE; not using statement
   using System.IO;                 // using DIRECTIVE; not using statement

namespace UsingStatement
   {
      class Program
      {
         static void Main( )
         {
            // using statement
            using (TextWriter tw = File.CreateText(“Lincoln.txt”) )
            {
               tw.WriteLine(“Four score and seven years ago, …”);
            }

// using statement
            using (TextReader tr = File.OpenText(“Lincoln.txt”))
            {
               string InputString;
               while (null != (InputString = tr.ReadLine()))
                  Console.WriteLine(InputString);
            }
         }
      }
   }`

该代码产生以下输出:


Four score and seven years ago, ...


多个资源和嵌套

using语句也可以用于相同类型的多个资源,用逗号分隔资源声明。语法如下:

        Only one type         Resource        Resource              ↓          <ins>     ↓    </ins>    <ins>     ↓      </ins>    using ( *ResourceType* Id1 = Expr1,  Id2 = Expr2, ... ) *EmbeddedStatement*

例如,在下面的代码中,每个using语句分配并使用两个资源:

`   static void Main()
   {
      using (TextWriter tw1 = File.CreateText(“Lincoln.txt”),
                        tw2 = File.CreateText(“Franklin.txt”))
      {
         tw1.WriteLine(“Four score and seven years ago, …”);
         tw2.WriteLine(“Early to bed; Early to rise …”);
      }

using (TextReader tr1 = File.OpenText(“Lincoln.txt”),
                        tr2 = File.OpenText(“Franklin.txt”))
      {
         string InputString;

while (null != (InputString = tr1.ReadLine()))
            Console.WriteLine(InputString);

while (null != (InputString = tr2.ReadLine()))
            Console.WriteLine(InputString);
      }
   }`

using语句也可以嵌套。在下面的代码中,除了嵌套using语句之外,还要注意没有必要对第二个using语句使用一个块,因为它只包含一个简单的语句。

`   using ( TextWriter tw1 = File.CreateText(“Lincoln.txt”) )
   {
      tw1.WriteLine(“Four score and seven years ago, …”);

using ( TextWriter tw2 = File.CreateText(“Franklin.txt”) ) // Nested
         tw2.WriteLine(“Early to bed; Early to rise …”);       // Single
   }`

using 语句的另一种形式

using语句的另一种形式如下:

  Keyword  Resource        Uses resource      ↓      ↓                ↓    using ( *Expression* ) *EmbeddedStatement*

在这种形式中,资源是在using语句之前声明的。

`   TextWriter tw = File.CreateText(“Lincoln.txt”);           // Resource declared

using ( tw )                                              // using statement
      tw.WriteLine(“Four score and seven years ago, …”);`

尽管这种形式仍然可以确保在您使用完资源后总是调用Dispose方法,但是它并不能防止您在using语句释放其非托管资源后尝试使用该资源,使其处于不一致的状态。因此,它提供的保护较少,不被鼓励。该表格如图图 9-12 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-12。*using 语句前的资源声明

其他报表

还有一些与该语言的特定特征相关的陈述。这些陈述包含在处理这些特性的章节中。其他章节中涉及的声明如表 9-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

十、结构

什么是结构?

结构是程序员定义的数据类型,非常类似于类。它们有数据成员和函数成员。尽管结构类似于类,但还是有许多重要的区别。最重要的如下:

  • Similar to reference type, structure is value type.
  • Structures are implicitly sealed, which means that they cannot be derived from.

声明结构的语法类似于声明类的语法:

`Keyword


   struct StructName
   {
      MemberDeclarations
   }`

例如,下面的代码声明了一个名为Point的结构。它有两个公共字段,名为XY。在Main中,声明了三个结构类型为Point的变量,它们的值被赋值并打印出来。

`   struct Point
   {
      public int X;
      public int Y;
   }

class Program
   {
      static void Main()
      {
         Point first, second, third;

first.X  = 10; first.Y = 10;
         second.X = 20; second.Y = 20;
         third.X  = first.X + second.X;
         third.Y  = first.Y + second.Y;

Console.WriteLine(“first:   {0}, {1}”, first.X,  first.Y);
         Console.WriteLine(“second:  {0}, {1}”, second.X, second.Y);
         Console.WriteLine(“third:   {0}, {1}”, third.X,  third.Y);
      }
   }`

这段代码产生以下输出:


first:   10, 10 second:  20, 20 third:   30, 30


结构是值类型

与所有值类型一样,结构类型的变量包含自己的数据。因此

  • A variable of type struct cannot be null.
  • Two structs variables cannot refer to the same object.

例如,下面的代码声明了一个名为CSimple的类,一个名为Simple的结构,以及它们各自的一个变量。图 10-1 显示了这两者在内存中是如何排列的。

`   class CSimple
   {
      public int X;
      public int Y;
   }

struct Simple
   {
      public int X;
      public int Y;
   }

class Program
   {
      static void Main()
      {
         CSimple cs = new CSimple();
         Simple  ss = new Simple();
            …` 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-1 。类与结构的内存排列

分配给一个结构

将一个结构赋给另一个结构会将值从一个结构复制到另一个结构。这与从类变量复制截然不同,后者只复制引用。

图 10-2 显示了类变量和结构变量赋值的区别。注意,在类赋值之后,cs2指向堆中与cs1相同的对象。但是在 struct 赋值之后,ss2成员的值是那些在ss1中的值的副本。

`   class CSimple
   { public int X; public int Y; }

struct Simple
   { public int X; public int Y; }

class Program
   {
      static void Main()
      {
         CSimple cs1 = new CSimple(), cs2 = null;          // Class instances
         Simple  ss1 = new Simple(),  ss2 = new Simple();  // Struct instances

cs1.X = ss1.X = 5;                  // Assign 5 to ss1.X and cs1.X.
         cs1.Y = ss1.Y = 10;                 // Assign 10 to ss1.Y and cs1.Y.

cs2 = cs1;                          // Assign class instance.
         ss2 = ss1;                          // Assign struct instance.
      }
   }` 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10-2 。分配一个类变量和一个结构变量

构造函数和析构函数

结构可以有实例和静态构造函数,但不允许析构函数。

实例构造函数

该语言隐式地为每个结构提供了一个无参数的构造函数。此构造函数将结构的每个成员设置为该类型的默认值。值成员被设置为其默认值。参考成员被设置为null

每个结构都有预定义的无参数构造函数,您不能删除或重定义它。但是,您可以创建额外的构造函数,只要它们有参数。请注意,这与类不同。对于类,只有当没有其他构造函数被声明时,编译器才会提供一个隐式的无参数构造函数*。*

要调用一个构造函数,包括隐式的无参数构造函数,使用new操作符。注意,即使内存不是从堆中分配的,也使用了new操作符。

例如,下面的代码声明了一个简单的 struct,其构造函数带有两个int参数。Main创建该结构的两个实例——一个使用隐式无参数构造函数,另一个使用声明的双参数构造函数。

`   struct Simple
   {
      public int X;
      public int Y;

public Simple(int a, int b)             // Constructor with parameters
      {
         X = a;
         Y = b;
      }
   }

class Program
   {
      static void Main()
      {              Call implicit constructor
                                   Simple s1 = new Simple();
         Simple s2 = new Simple(5, 10);
                              ↑
                         Call constructor
         Console.WriteLine(“{0},{1}”, s1.X, s1.Y);
         Console.WriteLine(“{0},{1}”, s2.X, s2.Y);
      }
   }`

你也可以不使用new操作符来创建一个结构的实例。但是,如果这样做,会有一些限制,如下所示:

  • The values of data members cannot be used unless explicitly set.
  • You can’t call any function members of this structure until all data members are assigned values.

例如,下面的代码显示了在没有使用new操作符的情况下创建的 struct Simple的两个实例。当试图在没有显式设置数据成员值的情况下访问s1时,编译器会产生一条错误消息。在给其成员赋值后,从s2中读取没有问题。

`struct Simple
   {
      public int X;
      public int Y;
   }

class Program
   {
      static void Main()
      {
            No constructor calls
                ↓   ↓
         Simple s1, s2;
         Console.WriteLine(“{0},{1}”, s1.X, s1.Y);              // Compiler error
                                       ↑      ↑
         s2.X = 5;                   Not yet assigned
         s2.Y = 10;
         Console.WriteLine(“{0},{1}”, s2.X, s2.Y);              // OK
      }
   }`

静态构造函数

与类一样,结构的静态构造函数创建并初始化静态数据成员,不能引用实例成员。结构的静态构造函数遵循与类相同的规则。

在以下两个操作中的第一个操作之前调用静态构造函数:

  • Call an explicitly declared constructor.
  • A static member of a reference structure
构造函数和析构函数概要

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不允许字段初始值设定项

结构声明中不允许字段初始值设定项,如下面的代码所示:

   struct Simple    {            Not allowed                    <ins> ↓  </ins>       public int x = 0;                // Compile error       public int y <ins>= 10</ins>;               // Compile error    }                     ↑                  Not  allowed

结构是密封的

结构总是隐式密封的,因此不能从它们派生其他结构。

由于结构不支持继承,对结构成员使用几个类成员修饰符没有意义;因此,不能在它们的声明中使用它们。不能与结构一起使用的修饰符如下:

  • protected
  • inside
  • abstract
  • virtual

结构本身是从System.ValueType派生出来的,而System.ValueType又是从object派生出来的。

当创建一个与基类System.ValueType成员同名的成员时,你可以对结构成员使用的两个继承相关的关键字是newoverride修饰符,所有的结构都是从基类【】派生的。

装箱和拆箱

与其他值类型数据一样,如果要将 struct 实例用作引用类型对象,则必须进行装箱复制。装箱是制作值类型变量的引用类型副本的过程。装箱和拆箱在第十六章中有详细解释。

结构体作为返回值和参数

结构可以用作返回值和参数。

  • Return value : When struct is the return value, create a copy and return it from the function member.
  • Parameter: when a struct is used as Value parameter, a copy of the actual parameter struct is created. This copy is used for the execution of the method.
  • ref and out Parameters : If a structure is used as a ref or out parameter, the reference to the structure will be passed to the method so that the data member can be changed.

关于结构的附加信息

分配结构比创建类的实例需要更少的开销,所以使用结构而不是类有时可以提高性能——但是要小心装箱和拆箱的高成本。

最后,关于结构,您应该知道的最后一些事情如下:

  • Predefined simple types (int, short, long, etc.), although in. NET and C# are actually all in. NET as a structure.
  • You can declare the partial structure as you declare the partial class, as described in Chapter 6 of .

像类一样,结构也可以实现接口,这将在第十五章中讨论。

基于SSM框架的智能家政保洁预约系统,是一个旨在提高家政保洁服务预约效率和管理水平的平台。该系统通过集成现代信息技术,为家政公司、家政服务人员和消费者提供了一个便捷的在线预约和管理系统。 系统的主要功能包括: 1. **用户管理**:允许消费者注册、登录,并管理他们的个人资料和预约历史。 2. **家政人员管理**:家政服务人员可以注册并更新自己的个人信息、服务类别和服务时间。 3. **服务预约**:消费者可以浏览不同的家政服务选项,选择合适的服务人员,并在线预约服务。 4. **订单管理**:系统支持订单的创建、跟踪和管理,包括订单的确认、完成和评价。 5. **评价系统**:消费者可以在家政服务完成后对服务进行评价,帮助提高服务质量和透明度。 6. **后台管理**:管理员可以管理用户、家政人员信息、服务类别、预约订单以及处理用户反馈。 系统采用Java语言开发,使用MySQL数据库进行数据存储,通过B/S架构实现用户与服务的在线交互。系统设计考虑了不同用户角色的需求,包括管理员、家政服务人员和普通用户,每个角色都有相应的权限和功能。此外,系统还采用了软件组件化、精化体系结构、分离逻辑和数据等方法,以便于未来的系统升级和维护。 智能家政保洁预约系统通过提供一个集中的平台,不仅方便了消费者的预约和管理,也为家政服务人员提供了一个展示和推广自己服务的机会。同时,系统的后台管理功能为家政公司提供了强大的数据支持和决策辅助,有助于提高服务质量和管理效率。该系统的设计与实现,标志着家政保洁服务向现代化和网络化的转型,为管理决策和控制提供保障,是行业发展中的重要里程碑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值