Essential.C#第三章 操作符和控制流

达式内的操作数产生合适动作,Operators provide syntax for performing different calculations or actions appropriate for the operands within the calculation。控制流语句提供了程序内的条件逻辑和多次循环一段代码。稍后我会引入IF控制流语句。本章着眼于布尔表达式这个概念,它内嵌在许多控制流语句内,并指出不能将整数型转换成布尔型的优势。本章的末尾还会讨论预处理,以及它的指令。

image

操作符

   你已经学习了预定义数据类型,现在你该开始学习如何使用操作符将这些类型组合起来进行运算。

操作符

    操作符指示了表达式内的操作。比如,一个数学表达式,操作一组值(这就叫做操作数)得到新值。比如,清单3.1就有两个操作数,数字4和2。它们使用减号操作符,- ,组合在一起。然后将结果赋值给变量total

清单3.1

total = 4 - 2;

    操作符通常被分成三类:一元,二元,三目运算符,相对于的操作数的数目就是1,2,3。本章重点是一元,和二元运算符,三目运算符在本章后面会有讲解。

加减单目运算符(+,-)

    有时你想改变数字变量的正负号。这时,单目减号运算符就会派上用场,负号操作符相当于将一个数字乘以-1。加号单目运算符不影响值,对于C#来说有点多余,不过是为了对称。

二元算术操作符(+,-,*,/,%)

    二元操作符需要两个操作数,分别把操作数置于操作符的两端。二元操作符需要避免丢失结果精度。在清单3.3中就是一个二元操作符,更具体的这是一个算术二元操作符。操作数存在于算术操作符的两端,并将它们的结果赋值。其他算术操作符是加,除,乘,余(有时也叫模)

清单3.3

class Program
{
    static void Main(string[] args)
    {
        int numerator;
        int denominator;
        int quotient;
        int remainder;
        System.Console.Write("Enter the numerator: ");
        numerator = int.Parse(System.Console.ReadLine());
        System.Console.Write("Enter the denominator: ");
        denominator = int.Parse(System.Console.ReadLine());
        System.Console.WriteLine(
            "{0} / {1} = {2} with remainder {3}",
            numerator, denominator, quotient, remainder);
    }
}

输出3.3

Enter the numerator: 23
Enter the denominator: 3
23 / 3 = 7 with remainder 2

注意使用二元运算符的顺序关联性。二元运算符的顺序是从左至右。而赋值操作符的顺序是从右至左。 On its own, however, associativity does not specify whether the division will occur before or after the assignment。操作符的优先级按如下顺序:

1)*,/,%  2)+,-  3) =

所以,你可以假想除法和求余操作符在赋值之前会发生什么。

    如果你忘记为这些二元操作符结果赋值,你将会收到像输出3.2那样的编译器错误。

输出3.2

... error CS0201: Only assignment, call, increment, decrement,
and new object expressions can be used as a statement

优先级和顺序关联性

    在程序语言中也有数学上的顺序关联性概念。顺序关联性是指操作数如何分类,就是确定操作符顺序。对于在一个表达式中多次出现单一操作符,操作符的顺序关联性将不会影响结果。二元操作符比如+和- 具有顺序关联性因为操作符出现的顺序并不重要,a+b+c的结果于先执行a+b还是执行b+c都是一样的。

    当在一个语句内出现了不同的操作符,就需要规定操作符的执行顺序了,这就叫优先级。比如,规定乘法操作符先于加法操作符执行。a+b*c

在字符串中使用加法操作符

    在非数字类型中也能使用操作符,比如可以使用加法操作符连接两个以上的字符串。

清单3.4

class Program
{
    static void Main(string[] args)
    {
        short windSpeed = 42;
        System.Console.WriteLine(
            "The original Tacoma Bridge in Washington\nwas "
            + "brought down by a "
            + windSpeed + " mile/hour wind.");
    }
}

输出3.3

The original Tacoma Bridge in Washington
was brought down by a 42 mile/hour wind.

由于在不同文化中句子的结构式部一样的。所以在需要本地化时,程序员应该尽量避免使用加法操作符连接字符串。应该首选混合格式(去看第一章)

在算术运算中使用字符

    上一章介绍了Char类型,尽管char类型中存储的是字符而不是数字,但它依然是一个整数型。它可以和别的整型一起参与算术运算。所以,char类型值不会被解释为字符,而是它的基本值。比如,对于字符3,它的Unicode值是0x33,十进制值51.数字4,Unicode值是0x34,十进制值52。3加4的十六进制结果是0x167,而十进制结果是103.这个值和字母g的值相同。

清单3.5

int n = '3' + '4';
char c = (char)n;
System.Console.WriteLine(c);  // Writes out g.

    利用这种特性,你可以快速的将两个字符变成另一个。比如,字母f和字母c的距离是3.你可以将两者相减得到这个值。

清单3.6

int distance = 'f'-'c';
System.Console.WriteLine(distance);//output 3

浮点的特性

    浮点类型,float和double,有一些特别的性质。比如它们处理精度的方式。本节会看一些特别的例子,还会展示浮点型的一些特有的性质。

    float,有7位有效精度。可以容纳的值是1234567和0.1234567。如果你将两个float相加,其结果将被四舍五入为1234567,因为float数字的小数部分只能容纳7位数字。这个类型的约数是很重要的,尤其是在多次计算或等比时。

    注意,对于简单的赋值操作也会有误差,比如double number = 4.2F。由于double能比float更精确,C#编译器将将根据情况处理这个表达式double number=4.1999998092651367。float会把4.1999998092651367看作4.2,而double绝对不会将其以4.2呈现在大家面前。

浮点类型不等式的非确定性

    当比较相等值时,对于float型,它的误差是很让人头痛的。它们居然不相等!

清单3.7

class Program
{
    static void Main(string[] args)
    {
        decimal decimalNumber = 4.2M;
        double doubleNumber1 = 0.1F * 42F;
        double doubleNumber2 = 0.1D * 42D;
        float floatNumber = 0.1F * 42F;
        System.Diagnostics.Trace.Assert(decimalNumber != (decimal)doubleNumber1);
        // Displays: 4.2 != 4.20000006258488 
        System.Console.WriteLine(
            "{0} != {1}", decimalNumber, (decimal)doubleNumber1);
        System.Diagnostics.Trace.Assert((double)decimalNumber != doubleNumber1);
        // Displays: 4.2 != 4.20000006258488 
        System.Console.WriteLine(
            "{0} != {1}", (double)decimalNumber, doubleNumber1);
        System.Diagnostics.Trace.Assert((float)decimalNumber != floatNumber);
        // Displays: (float)4.2M != 4.2F
        System.Console.WriteLine(
            "(float){0}M != {1}F",
            (float)decimalNumber, floatNumber);
        System.Diagnostics.Trace.Assert(doubleNumber1 != (double)floatNumber);
        // Displays: 4.20000006258488 != 4.20000028610229 
        System.Console.WriteLine(
            "{0} != {1}", doubleNumber1, (double)floatNumber);
        System.Diagnostics.Trace.Assert(doubleNumber1 != doubleNumber2);
        // Displays: 4.20000006258488 != 4.2
        System.Console.WriteLine(
            "{0} != {1}", doubleNumber1, doubleNumber2);
        System.Diagnostics.Trace.Assert(floatNumber != doubleNumber2);
        // Displays: 4.2F != 4.2D
        System.Console.WriteLine(
            "{0}F != {1}D", floatNumber, doubleNumber2);
        System.Diagnostics.Trace.Assert((double)4.2F != 4.2D);
        // Display: 4.19999980926514 != 4.2
        System.Console.WriteLine(
            "{0} != {1}", (double)4.2F, 4.2D);
        System.Diagnostics.Trace.Assert(4.2F != 4.2D);
        // Display: 4.2F != 4.2D
        System.Console.WriteLine(
            "{0}F != {1}D", 4.2F, 4.2D);
    }
}

输出3.6

4.2 != 4.20000006258488
4.2 != 4.20000006258488
(float)4.2M != 4.2F
4.20000006258488 != 4.20000028610229
4.20000006258488 != 4.2
4.2F != 4.2D
4.19999980926514 != 4.2
4.2F != 4.2D

    Assert()方法用来当其参数表达式为false时,会弹出一个对话框。然而在本代码中的所有条件都是true。尽管代码中的这些值表面上是相等的,事实上,由于float的误差它们不相等。不过,这不会产生四舍五入错误。C#编译器会执行这些算式。即使,你不通过计算,而是直接用4.2F赋值,比对依然是不相等的。

    为了避免由于浮点类型误差原因而产生的未预料结果,开发者应该避免使用这些类型做条件判断。否则条件判断就要包含误差。有一种最简单的实现方式,用一个值减去另一个值,然后比较这个结果是否小于最大误差。最好的方法是用decimal类型代替float类型。

    你应该知道浮点型的一些特质。比如,你想用一个整数除以零,对于精确性的数据类型比如int和decimal,就会产生错误。而对于float和double类型,会产生一个特殊的值。就像清单3.8那样

清单3.8

float n = 0f;
// Displays: NaN 
System.Console.WriteLine(n / 0);

输出3.7

NaN //对于中文系统会产生“非数字”

    在数学上,有些操作符没有定义。在C#中,0除任何数都显示NaN。同理负数的平方根也是NaN(System.Math.Sqrt(-1))

    浮点数字可能会边界溢出。比如,float类型的上边界是3.4E38。当数字溢出这个边界,会输出Infinity。同样,float的下界是-3.4E38.当分配的值低于此值,就会显示-Infinity。

清单3.9

// Displays: -Infinity 
System.Console.WriteLine(-1f / 0);
// Displays: Infinity 
System.Console.WriteLine(3.402823E+38f * 2f);

    浮点数字只能是非常非常接近零值,但不能是零。如果值低于float或double类型的下界,那么数值将显示负零或正零,这依赖数字是正数还是负数,它会显示成-0或0

括号操作符

    括号操作符允许你将操作数和操作数分组,以便一起求他们的值。它具有最高的优先顺序。比如,下面两个算式的值完全不同的。

    (60/10)*2
    60/(10*2)

第一个算式等于12,第二个表达式结果是3。因此是括号影响了表达式的最终结果。

    有时括号操作符并能改变结果,因为它没有改变优先顺序。不过,使用括号可以提高代码可读性。

赋值操作符(+=,-=,*=,/=,%=)

    在第一张讨论了一个简单的赋值操作符,它是将右侧的值放入左侧的变量中。Other assignment operators combine common binary operator calculations with the assignment operator。

清单3.10

int x;
x = x + 2;

     首先计算X+2,然后将计算结果返回给x。由于这种类型的操作比较频繁,就产生了把计算和赋值放在一起的一个操作符。+=操作符表示左侧的变量加右侧的值。

清单3.1

int x;
x += 2;

此代码和3.09的是一个样的。

    还有许多类似功能的组合赋值操作符。加,减,乘,除都有。

递增和递减操作符

    C#还包含递增或递减一个数字的操作符。递增操作符++,是每次调用都增加。清单3.13中的所有语句都表达一个意思。

清单3.13

spaceCount = spaceCount + 1;
spaceCount += 1;
spaceCount++;

    同样,你也可以使用递减操作符--,来处理变量

清单3.14

lines = lines - 1;
lines -= 1;
lines--;

在循环中递减

    递增和递减操作符常常用在循环语句中,比如while循环。比如,清单3.15用递增操作符来迭代字母表中的每个字符。

清单3.15

class Program
{
    static void Main(string[] args)
    {
        char current;
        int asciiValue;
        // Set the initial value of current.
        current = 'z';
        do
        {
            // Retrieve the ASCII value of current.
            asciiValue = current;
            System.Console.Write("{0}={1}\t", current, asciiValue);
            // Proceed to the previous letter in the alphabet;
            current--;
        }
        while (current >= 'a');
    }
}

输出3.9

z=122   y=121   x=120   w=119   v=118   u=117   t=116   s=115   r=114 
q=113   p=112   o=111   n=110   m=109   l=108   k=107   j=106   i=105 
h=104   g=103   f=102   e=101   d=100   c=99    b=98    a=97

递增和递减操作符常用来计数指定的操作执行的多少次。注意,在本例中,递增操作符使用的是字符类型。

    作为一个赋值操作符,递增操作符也可以返回一个值,换句话说,在使用递增或递减操作符的同时也能使用赋值操作符。

清单3.16

static void Main(string[] args)
{
    int count;
    int result;
    count = 0;
    result = count++;
    System.Console.WriteLine("result = {0} and count = {1}",
                            result, count);
}

输出3.10

result = 0 and count = 1

    你也许很惊讶,count在递增以前被赋值给了result。最终结果就是result的值是0,而count的值是1.

    如果你想让递增或递减操作符优先于赋值操作符。你需要将递增操作符置到变量前面。

清单3.17

static void Main(string[] args)
{
    int count;
    int result;
    count = 0;
    result = ++count;
    System.Console.WriteLine("result = {0} and count = {1}",
                            result, count);
}

输出3.11

result = 1 and count = 1

    递增或递减操作符在表达式中的位置,将直接影响代码功能。如果递增或递减操作符出现在操作数之前,将会返回一个新的值。比如,x是1,而++x会返回2,而如果使用操作符后置,即x++,它的返回值依然会是1。不管操作符是前置还是后置,x的最后结果都会改变。

清单3.18

class Program
{
    static void Main(string[] args)
    {
        int x;
        x = 1;
        // Display 1, 2.
        System.Console.WriteLine("{0}, {1}, {2}", x++, x++, x);
        // x now contains the value 3.
        // Display 4, 5.
        System.Console.WriteLine("{0}, {1}, {2}", ++x, ++x, x);
        // x now contains the value 5.
        // ...
    }
}

输出3.12

1, 2, 3
4, 5, 5

    根据清单3.18的演示,递增或递减操作符在操作数的出现位置,能影响最终结果。前置递增/递减操作符的结果是计算操作数之后的值。后置递增/递减操作符的结果是操作数改变之前的值。开发者应该十分小心使用这些操作符。当对将要发生什么有怀疑时,请独立使用这些操作符,将他们放在单独的语句中。这种方式能提高代码可读性,并不会产生歧义。

递增和递减的线程安全

    尽管递增和递减操作符十分简洁了,但这些操作符并没有原子性。在线程上下文中可能会影响这个操作符的执行,并产生竞态问题。使用lock语句来避免竞态问题。System.Threading.Interlocked中包含线程安全的递增和递减方法Increment(),Decrement().These methods rely on processor functions for performing fast thread-safe increments and decrements.

常量表达式

    上一章讨论了常量。使用操作符就可以将多个常量组合成常量表达式。默认时,常量表达式是在C#编译器编译时运行一次(而不是程序每次调用时执行)。比如,可以将一天内的秒数作为一个常量表达式,它的结果可以用在别的表达式中。

    清单3.19中的关键字const,在编译时就锁定了结果,在代码中任何修改结果的尝试都会产生编译器错误。

清单3.19
image

注意,secondsPerWeek也是一个常量表达式。由于表达式中的操作数也是常量,所以编译能输出结果。

流控制

    本节讨论如何用条件改变语句的执行顺序。然后,你将学习如何通过循环结构多次执行一组语句。

语句 常用语法规则 实例
if if(布尔表达式)
  内嵌语句
if (input == “quite”)
{
  System.Console.WriteLine(“Game end”);
  return;
}
while while(布尔表达式)
  内嵌语句
while(count < total)
{
  System.Console.WriteLine(“count ={0}”,count);
  count++;
}
do…while do
  内嵌语句
while(布尔表达式)
do
{
  System.Console.WriteLine(“Enter name:” );
  input = System.Console.ReadLine();
}
while(input != “exit”);
for for(for初始值;
  布尔表达式;
  for迭代器)
  内嵌语句
for(int count = 1; count <= 10; count++)
{
   System.Console.WriteLine(“count ={0}”,count);
}
foreach

continue
foreacth(类型标示符 in 表达式)
  内嵌语句

continue;
foreach(char Letter in email)
{
  if(!insideDomain)
  {
     if(Letter == ’@’)
        insideDomain = true;
     continue;
  }
  System.Console.Write(Letter);
}
switch

break

goto
switch(控制类型表达式)
{
  …
  case 常量表达式:
     语句列
     跳转语句
  default:
     语句列
}

switch(input)
{
    case "exit":
    case "quit":
        System.Console.WriteLine(
          "Exiting app....");
        break;
    case "restart":
        Reset();
        goto case "start";
    case "start":
         GetMove();
         break;
    default:
        System.Console.WriteLine(
          input);
        break;
}..

上表列出了主要的流程控制语句。注意常用语法规则列只是常用的语句,而不是完整的语句汇总。内嵌语句是指任何语句,但不包括声明语句或标签。

    附录B的程序中可以找到表3.1的所有控制语句。

    本章剩余部分会对每个语句的细节做描述。接下来是if语句,它需要介绍,包含代码块,范围,布尔表达式。并且在继续介绍其他控制流语句前还要引入位操作符。读者会发现本表很熟悉,因为C#和其他语言和相似,你就可以跳转到C#预处理指令那一节,或直接跳到本章末尾。

老实说,这一章翻译起来太无聊了,就是if…else…,不搞这一章了。回头再说。不过到目前为止我已经培养出了对英语的兴趣,不再惧怕这东西。词汇量还是个问题。暂时不去碰语法。

原文地址:https://www.cnblogs.com/yaoshi641/p/1546148.html