C#大学实用教程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第3章 变量和表达式

本章主要介绍:C#的基本语法知识;变量的声明和初始化;变量的命名及基本类型;表达式的基本组成元素以及命名空间的相关知识。

3.1 C#的基本语法

所谓语法,是指管理语言结构和语句的一组规则。在用高级语言编写程序时,设计人员必须严格按照语法规则构造语句。

3.1.1 C#程序结构

下面通过回顾第2章中的C#控制台应用程序示例来介绍C#的基本语法知识。

在Visual Studio 2008环境中打开该示例程序,选择“视图”菜单中的“解决方案资源管理器”命令,打开“解决方案资源管理器”窗口,如图3-1所示。

图3-1 “解决方案资源管理器”窗口

在项目的解决方案中可以看到由Visual Studio 2008自动生成的两个源代码文件AssemblyInfo.cs和Program.cs。其中,AssemblyInfo.cs文件用来设置项目的一些属性。在创建项目时,可以查看赋给项目的属性,并且可以使用自定义属性向项目中添加信息。自定义属性有以下两组:

第1组属性包括3个字段(Title、Description、Configuration)和5个有关公司或产品的自定义属性(Trademark、Product、Company、Copyright、Culture)。这些属性在System.Reflection命名的空间中使用8个类来表示。

第2组属性包含在System.Runtime.CompilerServices命名空间中,是一些可以添加到代码中的自定义属性,并且这些信息在生成项目时添加到可执行文件中。

AssemblyInfo.cs文件中的代码如下:

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

程序集的常规信息通过下列属性集控制,更改这些属性值可修改与程序集关联的信息:

[assembly: AssemblyTitle("hello")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("swpu")]
[assembly: AssemblyProduct("hello")]
[assembly:AssemblyCopyright("版权所有 (C)swpu 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

将ComVisible设置为false,使用此程序集中的类型对COM组件不可见。如果需要从COM访问此程序集中的类型,则将该类型上的ComVisible属性设置为true:

[assembly: ComVisible(false)]

如果此项目向COM公开,则下列GUID用于类型库的ID

[assembly: Guid("ab5f952b-4f8e-4718-b703-41291eb44bf5")]

程序集的版本信息由下面4个值组成:主版本、次版本、内部版本号、修订号。

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

而Program.cs文件则包含程序所定义的类Program。从图3-2所示的代码中可以看出,C#应用程序是在一个类(上例中的类为Program)中实现的。这个类中包含一个静态成员方法——Main(),此方法叫做程序的入口方法。每当执行应用程序时,均从这个方法开始执行。另外,在自动生成的代码中,类的定义位于一个命名空间中。对于上例而言,就是Program类的定义位于命名空间hello中。这是因为.NET框架使用命名空间对类型进行逻辑分组,而C#应用程序都是作为类型实现的。如果其他应用程序要使用上例所创建的类Program,可以通过hello.Program来引用。

图3-2 “代码编辑器”窗口

下面就是Program.cs的代码内容:

using System;
namespace hello
{
  class Program
  {
    static void Main(string[] args)
    {
        Console.WriteLine("Hello Word");          //需显示的字符串
        Console.ReadLine();
    }
  }
}

3.1.2 C#程序入口

每个应用程序都应有一个入口点,表明该程序从何处开始执行。为了让系统能找到入口点,在C#程序中,入口方法名规定为Main()方法。从前面的C#程序可以看出,Main()方法在一个类中声明,作为整个程序的入口;Main()方法必须是静态的,此类不需要实例化,即可直接调用Main()方法。在Main()中,如果调用此类中的非静态方法,必须先实例化此类,才能调用。在其他类中,Main()虽然属于静态方法,也不能调用类名.Main()方法,Main()静态方法比较特殊;一个程序中有且只有一个Main()。Main()方法返回的类型可以是void(无返回值)或int(返回代表应用程序错误级别的整数)。因此,Main()可有以下4种形式:

①  static void Main()
②  static void Main(string[]args)
③  static int Main()
④  static int Main(string[]args)

如果需要,可以忽略这里讨论的args。直到现在还在使用这个参数的原因,就是在Visual Studio中创建控制台应用程序时自动生成的Main()版本。上面的第③、④种形式返回一个int值,可以用于表示应用程序如何终止,通常作为一种错误提示(但这不是强制的)。一般情况下,返回0,表示“正常”终止(即应用程序执行完毕,并安全地终止);返回1,则表示从命令行调用不成功。Main()的参数args是从应用程序的外部接受信息的方法,这些信息在运行期间指定,其形式是命令行参数。在命令行传输参数时,存放在string数组args中。使用Length属性来测试输入参数的个数,使用foreach语句来检索所有的参数。

上面C#程序中的Program类的代码内容如下:

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("Hello Word");              //需显示的字符串
  }
}

在上面的C#程序中,Main()方法就是Program类语句的一部分。C#应用程序(可执行)的入口点就是静态Main()方法,它必须包含在一个类中。仅有一个类能使用该标志定义,除非告诉编译器它应使用哪一个Main()方法(否侧会产生一个编译错误)。与C++相比,Main的第一个字母是大写的M,而不是小写字母。在Main前还有一个修饰字“static”,表示此方法只在这个类中起作用,而不是在实例中。因为当程序刚开始执行时,没有对象实例存在。Main()方法指定执行了代码“Console.WriteLine("Hello Word")”。Console是名称空间System的一个类。WriteLine()是Console类的一个方法,因此用操作符“.”将它们分开。

3.1.3 程序区块

在C#语言中,每个应用程序可以由一个或多个类组成,所有的程序都必须封装在某个类中,每个类又可以包含一个或多个方法。类和方法都以“{”开始,以“}”结束。“{”和“}”共同定义了程序区块。一个程序区块可以由位于“{”和“}”中的多条语句组成,每个程序描述语句必须以分号“;”作为语句的结束标志。程序区块可以嵌套,即程序区块中还可以包含程序区块。例如:

public class MyClass
{
  public static void Main()
  {
    System.Console.Write("C#here");         //每一程序语句行都以分号结尾
  }                                         //标出Main()方法的区块
}                                           //标出MyClass类的区块

从上面的代码中可以发现:为了提高程序的可读性,代码采用了缩进的格式,使代码结构层次清晰。

3.1.4 C#程序的注释方法

在源代码中加上注释信息,是优秀编程人员应养成的良好习惯。注释信息对程序的执行不起作用,即:在编译时,注释不会被编译进程序里,但必要的注释有助于程序的阅读。特别地,当需要对程序功能进行扩充时,可以极大地帮助程序员对原始程序的理解。C#语言添加注释的方法有多种,最大的特点的是可以使用XML的格式添加注释,从而减少了许多复杂的工作。

(1)常规注释方式

① 单行注释:以“//”开头,表示从“//”开始到该行行尾都属于注释部分。例如:

namespace hello
{
  class Program
  {
    static void Main(string[] args)
    {
        Console.WriteLine("Hello Word");          //需显示的字符串
    }
  }
}

② 多行注释:以符号“/*”开始,“*/”结束。任何介于这两个标记之间的文字都被视为注释信息。例如:

/*   这是一个C#控制台程序示例    */
namespace hello
{
  class Program
  {
    static void Main(string[] args)
    {
        Console.WriteLine("Hello Word");          //需显示的字符串
    }
  }
}

延伸学习──XML注释方式

(2)XML注释方式

“///”符号是一种特殊的注释方式,只要在用户自定义的类型,如类、接口或类的成员上方,或者命名空间的声明上方,连续输入3个斜杠字符“/”,系统将自动生成对应的XML标记。例如:

///<summary>
///
///</summary>
///<param name=”i”></param>
///<param name=”j”></param>
///<returns></returns>
Public int add(int i, int j)
{
  Return i+j;
}

注意:使用XML注释方式时,为了让系统自动生成对应的注释标记,应先编写方法,然后在方法名的上方输入“///”,否则不会自动生成对应的参数注释等标记。

3.1.5 命名空间

Microsoft.NET框架提供了1000多个类,用于完成各种各样的功能。根据类的功能不同,这些类又被划分到不同的命名空间中(Namespace)。命名空间包含可在程序中使用的类、结构、枚举、委托和接口。一个命名空间又可以包含其他命名空间。命名空间只是一种逻辑上的划分,而不是物理上的存储分类。命名空间是一种组织C#程序中出现的不同类型的方式。命名空间在概念上与计算机文件系统中的文件夹有些类似。与文件夹一样,命名空间可使类具有唯一的完全限定名称。一个C#程序包含一个或多个命名空间,每个命名空间或者由程序员定义,或者作为之前编写的类库的一部分定义。

C#程序主要是利用命名空间来组织的,函数库即由一个个的命名空间来组成。每个命名空间都可以视为一个容器,容器里可以存放类、接口以及结构等程序。.NET利用命名空间来对程序进行分类,把功能相似的类、结构等程序放在同一个命名空间中,便于管理,也便于程序设计人员使用。最常见的命名空间是System命名空间,它包含了许多常用的结构类型(例如int、bool)和许多类(如Console、Expection)。

C#语言使用命名空间来组织系统类型或用户自定义的数据类型。如果没有明确地声明一个命名空间,则用户代码中所定义的类型将位于一个未命名的全局命名空间中。这个全局命名空间中的类型对于所有的命名空间都是可见的。不同命名空间中的类型可以具有相同的名字,但是同一命名空间中的类型的名字不能相同。在编写C#程序时,通常都要先声明一个命名空间,然后在这个命名空间中定义自己的类型。其语法为:

namespace名称[.名称]
{
  ……
}

命名空间作为一个容器,其里面的区域需要用一个大括号“{}”来标识,这与类(Class)和方法(Method)的定义一样。如“Hello Word”的例子:

namespace hello
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Hello Word");               //需显示的字符串
    }
  }
}

若要调用命名空间下某个类提供的方法,可以使用下面的语法之一:

①  命名空间. 命名空间……命名空间.类名称.静态方法名(参数, ……);
②  命名空间. 命名空间……命名空间.实例名称.方法名(参数, ……);

例如,前面“Hello Word”例子中的字符串输出语句可以写成:

System. Console.WriteLine("Hello Word");

这条语句使用的命名空间为System。在System命名空间下,有一个Console类,该类提供了静态的WriteLine方法,调用此方法来输出字符串“Hello Word”。

显然,从上面的例子可以看到,每条语句都加上命名空间太烦琐了。为了加快引用需要的功能,一般可以在程序的开头引用命名空间来简化代码的书写形式。例如,上面的语句“System. Console.WriteLine("Hello Word");”,前缀“System.”表示Console类在System命名空间下,如果在程序的开头写上:

using System;

那么,语句中就不需要加上命名空间前缀了,而可以直接写为:

Console.WriteLine("Hello Word");

这种形式体现了名称空间的方便。前面的“Hello Word”例子中正是采用了这种形式。

在C#程序中,不管是简单的数据类型,还是执行其他复杂操作,都必须通过函数库才能实现。.NET类库(Library)中包含了许多类,如按钮、复选框等。利用类库,可以开发出具有优美界面的应用程序。

.NET类库中还包含了许多可以实现其他丰富功能的类,如存取网络、数据库操作等,这些类库使C#编写的程序功能无比强大。

为了方便地运用这些函数库,C#程序使用using关键字将函数库包含进来。可以看到,C#的using与C/C++中的#include十分相似,都是为了使用已经设计好的程序。

以下程序代码的执行结果是:在DOS命令窗口中,按提示输入名字后,显示一条欢迎信息,如图3-3所示。如果去掉using这一行,则程序编译无法通过。

图3-3 运行结果

using System;
namespace ConsoleApplication1
{
  class Class1
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Please enter your name:");         //输出提示信息
      Console.ReadLine();                                //从键盘读入一行字符
      Console.WriteLine("Welcome to the world of C#!");       //显示欢迎信息
    }
  }
}

程序中使用了System下的一个叫做Console的类。利用Console类,该程序在DOS命令窗口里输出、读入字符信息。

第一行语句使用using关键字的主要目的是让编译器知道,程序中将使用定义在System中的所有类。程序设计人员在程序中便可以不必通过完整的类的名称来使用类,如System.Console.Write。

如果不使用using关键字来设计C#程序,要实现范例中的功能也是可以的,只是此时编译器不知道程序中会使用定义在System中的类,设计人员在使用System中的类时,则需要输入完整的类名称。例如,上面的范例程序在去掉using关键字的第一行程序后,程序修改如下:

System.Console.WriteLine("Please enter your name:");      //输出提示信息
System.Console.ReadLine();                               //从键盘读入一行字符
System.Console.WriteLine("Welcome to the world of C#!"); //显示欢迎信息

3.2 变量

当程序对数据进行读、写、运算等操作,需要保存特定的值或计算结果时,需要用到变量(Variable)。变量是用来描述一条信息的名称,在变量中可以存储各种类型的信息,如人的姓名、车票的价格、文件的长度等。

3.2.1 变量的命名

变量是计算机中占据一定内存单元的存储区域,通过指定变量的名称来使用和管理变量。从数据存储角度看,变量名相当于存储数据的变量所占的存储区域的标识名。

当需要访问存储在变量中的信息时,只需要使用变量的名称即可。为变量起名时要遵守C#语言的规定:

⊙ 变量名必须以字母开头。

⊙ 变量名只能由字母、数字和下划线组成,不能包含空格、标点符号、运算符等其他符号。

⊙ 变量名不能与C#中的关键字名称相同。

⊙ 变量名不能与C#中的库函数名称相同。

在C#中有一点是例外,即允许在变量名前加上前缀“@”。在这种情况下,可以使用前缀“@”加上关键字作为变量的名称。这主要是为了与其他语言进行交互时避免冲突。因为前缀“@”实际上并不是名称的一部分,其他编程语言会把它作为一个普通的变量名。在其他情况下,不推荐使用前缀“@”作为变量名的一部分。

在命名变量时,除了必须遵守C#规则以外,还应遵守一些命名约定。良好的编程习惯有助于提高程序的正确性、可读性、可维护性和可用性。常用数据类型的前缀如表3.1所示。

表3.1 常用数据类型的前缀

3.2.2 数据类型

C#程序完全由类型构成,并且它的所有功能也都是通过各种类型来实现的。类型定义了允许的值以及在这些值上允许的操作,包括 .NET框架提供的类型以及用户自定义的类型。

C#语言中的数据类型可以分为两类:值类型(Value Type)和引用类型(Reference Type)。值类型包括:简单类型(如char、int和float)、结构类型和枚举类型,而引用类型包括:类类型、接口类型、数组类型和委托类型。值类型和引用类型的区别在于,值类型的变量直接存放实际数据,而引用类型的变量存放的则是数据的地址,即对象的引用。

在C#语言中,所有的值类型都是从System.Object类派生的,System.Object类是所有类型的基类,并且不能派生新的值类型。同时,所有的值类型都具有一个隐含的构造函数用于初始化。C#的值类型可以分为以下几种:简单类型(Simple Type)、结构类型(Struct Type)、枚举类型(Enumeration Type)。

简单类型有时也称为纯量类型,是直接由一系列元素构成的数据类型。C#语言中的简单类型包括:整数类型、布尔类型、实数类型和字符类型。本章重点介绍简单数据类型,而其他数据类型的知识将在后续章节介绍。

1.整数类型

顾名思义,整数类型的变量的值为整数。数学上的整数可以从负无穷大到正无穷大,但是由于计算机的存储单元是有限的,所以计算机语言提供的整数类型的值总是在一定范围之内。C#语言提供了9种整数类型:短字节型(sbyte)、字节型(byte)、短整型(short)、无符号短整型(ushort)、整型(int)、无符号整型(uint)、长整型(long)、无符号长整型(ulong)和字符型(char)。划分的依据是根据该类型的变量在内存中所占的位数。这些整数类型在数学上的表示以及在计算机中的取值范围如表3.2所示。

表3.2 整数类型

【例3-1】整型变量的使用。创建一个控制台程序,验证短整型整数类型的取值范围。

using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
  class Program
  {
    static void Main(string[] args)
    {
      short x=32766;                    //声明一个短整型变量x,并赋值为32766
      x++;                              //将变量x的值加1
      Console.WriteLine(x);             //输出x的值
      x++;                              //将变量x的值加1
      Console.WriteLine(x);             //输出x的值
      Console.Read();  //按回车键结束
    }
  }
}

说明:程序中的short类型整数x已经超出了系统定义的范围(-32768~32767)。

程序运行结果如图3-4所示。

图3-4 例3-1运行结果

2.布尔类型

布尔类型用来表示“真”和“假”。在C#语言中,布尔类型用bool表示,其值为true或false。例如:

bool a=true;
int i=20;
bool b=(i>0 && i<10);

注意:C#条件表达式的运算结果一定是布尔类型。另外,在C/C++中,可用0来表示“false”,其他任何非0的值可表示为“true”,但这在C#中已经被废弃。在C#中,true值不能被其他任何非0值所代替。在其他整数类型和布尔类型之间不再存在任何转换,将整数类型转换成布尔类型是不合法的。例如:

bool c=1;                             //错误,不允许这种写法

3.实数类型

数学中的实数不仅包括整数,还包括小数。带小数的实数在C#中采用两种数据类型来表示:单精度(float)和双精度(double)。它们的差别在于取值范围和精度不同。计算机对于浮点数的运算速度大大低于对整数的运算。在对精度要求不是很高的浮点数计算中,可以采用float型,而采用double型获得的结果将更为精确。单精度型和双精度型可统称浮点类型,它们的具体说明如表3.3所示。

表3.3 浮点数类型

C#语言还提供了一种十进制类型(decimal),主要用于方便在金融和货币方面的计算。在现代的企业应用程序中,不可避免地要进行大量的关于这方面的计算和处理。C#通过提供这种专门的数据类型,可更为快捷地开发有关金融和货币方面的应用程序。

十进制类型是一种大范围、高精度的数据类型,占16字节,所表示的范围大约为1.0×10-28~7.9×1028,可精确到28~29位有效数字。虽然十进制类型的取值范围远小于双精度类型,但它更精确,特别适用于金融、货币等需要高精度数值的领域。当声明一个decimal变量并赋值时,使用M/m类型指定符以表明它是一个十进制类型。例如:

decimal a=10000.56m;

如果省略m,在变量被赋值之前,它将被编译器当作双精度类型来处理。

4.字符类型

除了数字以外,计算机处理的信息主要就是字符了。字符包括数字字符、英文字符、符号等。C#提供的字符类型(char)按照国际上公认的标准,采用Unicode字符集。一个Unicode字符的标准长度为2字节,共16位,可以表示世界上大多数语言。字符型变量可以用单引号括起来的字符直接赋值。字符型(char)还可表示无符号的16位整数,它的可能值集与Unicode字符集相对应,虽然表示形式上与短整型(ushort)相同,但意义不同,ushort代表的是数值本身,而char代表的是一个字符。

3.2.3 变量的声明

变量使用之前,必须先声明,即给变量指定名称和类型。声明变量以后,便可以将它们用作存储单元来存取相应类型的数据。

声明变量的一般形式如下:

①   [附加声明][访问修饰符] 数据类型  变量名;
②   [附加声明][访问修饰符] 数据类型  变量名=正确数据类型的初始值;

说明:[ ]表示该内容为可选项,修饰符可以是public、private、internal、new等关键字。下面给出一些合法和非法的变量名的例子:

int i;                           //合法
string total;                    //合法
char use;                     //不合法,与关键字名称相同
char@use;                   //合法
float Main;                    //不合法,与函数名称相同

C#中允许在一条语句中命名多个类型相同的变量。如:

int a,b,c=50;

声明变量后并对其赋值称为变量的初始化。在上面给出的声明变量的两种格式中,第一种格式仅完成变量的声明,而第二种格式既声明了变量,同时又完成了变量的初始化。下面给出一些变量初始化的例子:

int i;
i = 100;
char name;
name = 'a';
//上面两个例子是先声明变量,再赋值
int i = 100;
char name = 'a';
//这两个例子是在声明变量的同时对其初始化

3.3 常量

所谓常量,就是其值固定不变的量。从数据类型角度来看,常量的类型可以是任何一种值类型或引用类型。一个常量的声明就是声明程序中要用到的常量的名称和它的值。常量的命名规则同变量一样。

常量有直接常量和符号常量两种。

1.直接常量

直接常量即数据值本身。

(1)整型常量

整型常量通常有三种形式:

⊙ 十进制形式:123,+123,-123等。

⊙ 八进制形式:在数字前加0,如0123,+0123,-0123等。

⊙ 十六进制形式:在数字前加“0x”或“0X”,如0x123,+0X123,-0X123等。

(2)实型常量

实型常量通常有两种形式:

⊙ 小数形式:0.123,-0.123等。

⊙ 指数形式:123e4,-123E-4等。

(3)字符(串)常量

单个字符用一对英文单引号界定,表示字符常量,如'A'、'中'、'+'等。一串字符用一对英文双引号界定,表示字符串常量,如"欢迎学习C#!"、"你好!"、"hello!"等。

注意:单引号和双引号也可以看成是字符常量,能不能也用上面的方法来表示呢?下面介绍有关“转义符”的相关知识。

同C语言一样,转义字符是C#语言中的一种特殊的字符型常量表示形式,可以将转义字符简单地理解为“转变了本身含义的字符”。例如,“\n”并非表示一个“\”和一个“n”,而是表示换行符。

使用转义字符主要是由于以下三个原因:

⊙ 某些字符具有控制功能,直接的键盘按键不能输入这些字符(如换行符、退格符等)。

⊙ 扩展的ASCII编码可以有255字符,而计算机的标准键盘只能输入基本的ASCII字符,而不能输入扩展部分的字符,如字符“§”。

⊙ 某些字符在C#语言中具有特定的含义,不能作为普通字符处理。例如,单引号对作为字符型常量的定界符,如果使用“'''”的形式表示字符“'”,将出现语法错误。

在C#语言中也采用转义符来表示一些特殊的字符,包括一些控制字符。在表3.4中列出了一些定义字符型数据和字符串类型数据时常用的转义符。

表3.4 常用转义符

【例3-2】转义字符的使用。使用转义字符控制输出结果的显示格式。

using System;
namespace ConsoleApplication1
{
  class Test
  {
    public static void Main()
    {
      Console.WriteLine("\n\tHello\n\tWorld!");
      Console.Read();
    }
  }
}

说明:程序中使用了换行字符“\n”和水平制表字符“\t”。在输出的字符串中,“\n”产生换行效果,而“\t”引起屏幕位置向右移动8个字符位置,故称为水平制表字符。

程序的运行结果如图3-5所示。

图3-5 例3-2运行结果

(4)布尔常量

布尔常量即布尔值:true或false。

2.符号常量

符号常量声明的格式如下:

[附加声明][访问修饰符]  const数据类型  常量名=正确数据类型的值;

说明:[ ]表示该内容为可选项,修饰符可以是public、private、internal、new和protected。

例如:

public const double X=1.0, Y=2.0, Z=3.0;

同样地,C#允许在一条语句中同时声明多个常量。常量也有一个命名约定。习惯上,常量名全部使用大写字母表示。

3.4 表达式

C#语言中的表达式类似于数学运算中的表达式,是操作符、操作对象和标点符号等连接而成的式子。操作符是用来定义类实例中表达式操作符的。表达式是指定计算的操作符、操作数序列。操作符指出了对操作数的操作。比如,操作符有+、-、/和new;操作数可以是文字、域、当前变量或表达式。单个的常量、变量和函数也可以看作是一个最简单的表达式。

与运算类型相对应,包含某种运算符的表达式则称为某种类型的表达式,因而C#语言主要有算术表达式、关系表达式、逻辑表达式、赋值表达式、条件表达式等。

表达式无论长短与否,最终应该计算出一个确定的值,结果值的类型要取决于表达式的类型或表达式中混合运算时的类型转换情况。

3.4.1 操作符

依照操作符作用的操作数的个数来分,C#中有三种类型的操作符。

⊙ 一元操作符:作用于一个操作数。一元操作符又包括前缀操作符和后缀操作符。

⊙ 二元操作符:作用于两位操作数,使用时在操作数中间插入操作符。

⊙ 三元操作符:C#中仅有一个三元操作符“?:”,三元操作符作用于三个操作数,使用时在操作数中间插入操作符。

下面分别给出使用操作符的例子:

int x=5, y=10, z;
x++;  //后缀一元操作符
--x;  //前缀一元操作符
z=x+y;  //二元操作符
y=(x>10?0:1);  //三元操作符

常用操作符如表3.5所示。

表3.5 常用操作符

当一个表达式包含多个操作符时,表达式的值就由各操作符的优先级决定。表3.6列出了常见操作符的优先级(从高到低)。

表3.6 操作符的优先级

技术要点

① 从表3.6中可以看出,一元运算符的优先级高于二元和三元运算符。

② 不同种类运算符的优先级有高低之分。算术运算符的优先级高于关系运算符,关系运算符的优先级高于逻辑运算符,逻辑运算符的优先级高于条件运算符,条件运算符的优先级高于赋值运算符。

③ 有些同类运算符优先级也有高低之分。在算术运算符中,乘、除、求余的优先级高于加、减;在关系运算符中,小于、大于、小于等于、大于等于的优先级高于相等和不等;逻辑运算符的优先级按从高到低为非、与、或。

④ 当一个操作数出现在两个有相同优先级的操作符之间时,操作符按照出现的顺序由左至右执行。

⑤ 为了提高表达式的可读性,可以使用圆括号明确运算顺序或改变运算顺序。

⑥ 除了赋值操作符,所有的二进制的操作符都是左结合(left-associative)的,也就是说,操作按照从左向右的顺序执行。例如,x + y + z按(x + y) + z进行求值。

⑦ 赋值操作符和条件操作符(?:)按照右接合(right-associative)的原则,即操作按照从右向左的顺序执行,如x = y = z按照x = (y = z)进行求值。

3.4.2 算术表达式

C#中提供的算术操作符有5种:+,加法操作符;-,减法操作符;*,乘法操作符;/,除法操作符;%,求余操作符。

在表达式的运算中,表达式总是按它们本身书写的顺序求值。由算术操作符连接操作数而组成的表达式称为算术表达式。

【例3-3】递增运算。后缀一元操作符的使用。

using System;
class Test
{
  static void F(int x,int y,int z)
  {
    Console.WriteLine("x={0},y={1},z={2}",x,y,z);
  }
  public static void Main()
  {
    int i=1;
    F(i++,i++,i++);
  }
}

说明:程序中的i变量依次递增1。

程序的运行结果如图3-6所示。

图3-6 例3-3运行结果

【例3-4】 除法运算。演示同一操作符、不同操作数及不同数据类型得到不同的计算结果。

using System;
class Test
{
  public static void Main()
  {
    Console.WriteLine(5/3);
    Console.WriteLine(4/3);
    Console.WriteLine((5/3)= =(4/3));
    Console.WriteLine(5.0/3);
    Console.WriteLine(4.0/3);
    Console.WriteLine((5.0/3)= =(4.0/3))
  }
}

说明:在除法运算过程中,默认的返回值的类型与精度最高的操作数类型相同。比如5/2的结果为2,而5.0/2结果为2.5。如果两个整数类型的变量相除又不能整除的话,返回的结果是不大于相除之值的最大整数。

程序运行的结果如图3-7所示。

图3-7 例3-4运行结果

3.4.3 赋值表达式

赋值就是给一个变量赋一个新值。C#中提供的赋值操作符有:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=。

赋值的左操作数必须是一个变量、属性访问器或索引访问器的表达式。

C#中可以对变量进行连续赋值,这时赋值操作符是右关联的,这意味着从右向左操作符被分组。例如,形如a = b = c的表达式等价于a = (b = c)。

如果赋值操作符两边的操作数类型不一致,则先进行类型转换。赋值运算包括简单赋值和复合赋值两种。

(1)简单赋值

“=”操作符被称为简单赋值操作符。在一个简单赋值中,右操作数必须为某种类型的表达式,且该类型必须可以隐式地转换成左操作数类型。该运算将右操作数的值赋给作为左操作数的变量、属性或者索引器类型。简单赋值表达式的结果是被赋值给左操作数的值。结果类型和左操作数的类型相同,且总是值类型。

(2)复合赋值

C#语言有把赋值号和其他的运算符结合起来,组成复合赋值运算符,使表达式的形式更加简洁,提高了编译的效率。复合赋值运算符形式是:

<变量名>  <其他运算符>=<表达式>;

其功能相当于:

<变量名> = <变量名> <其他运算符> <表达式>;

形如x op = y的运算可以处理成形如x op y的二进制操作符重载方法。例如:

x+=5;  //等于x=x+5
x%=3;  //等于x=x%3
x*=y+1;  //等于x=x*(y+1)

复合赋值进行的步骤如下:

① 如果所选操作符的返回类型可以隐式转换成x的数据类型,执行x = x op y的运算,除此之外,仅对x执行一次运算。

② 否则,所选操作符是一个预定义操作符,所选操作符的返回值类型可以显式地转换成x的类型,且y可以隐式地转换成x的类型,那么该运算等价于x = (T) (x op y)运算。这里T是x的类型,除此之外,x仅被执行一次。

③ 否则,复合赋值是无效的,将产生编译时错误。

【例3-5】复合赋值运算。

复合赋值运算符在使用过程中,关键需要掌握的是运算的基本规则和结合性问题。下面的例子用来说明复合赋值运算符的使用方法。

using System;
class Test
{
  public static void Main()
  {
    int a = 12;
    a += a -= a *= a;
    Console.WriteLine(a);
    Console.Read();
  }
}

说明:由于赋值运算符是右结合性的,所以表达式a+=a-=a*=a等价于a+=(a-=(a*=a)),该表达式的求解过程为:

首先计算a*=a,则a=144;

接着计算a-=144,则a=12-144= -132;

最后计算a+=-132,则a= 12+(-132)=-120。

程序的运行结果为-120。

3.5 小结

本章介绍了C#语言的基础知识,包括C#的基本语法知识、变量常量的命名规则、变量的声明及初始化、直接常量的种类、符号常量的声明,举例说明了算术运算符和赋值运算符及其表达式的用法。

课外阅读──算术溢出

算术运算符(+、-、*、/)产生的结果可能会超出涉及的数值类型可能值的范围。一般情况下:

整数算术溢出或者引发OverflowException,或者丢弃结果的最高有效位。整数被零除总是引发DivideByZeroException。

浮点算术溢出或被零除从不引发异常,因为浮点类型基于IEEE 754,因此可以表示无穷和NaN(不是数字)。

小数算术溢出总是引发OverflowException。小数被零除总是引发DivideByZeroException。

当发生整数溢出时,产生的结果取决于执行上下文,该上下文可为checked或unchecked。在checked上下文中引发OverflowException。在未选中的上下文中,放弃结果的最高有效位并继续执行。因此,C# 使您有机会选择处理或忽略溢出。

除算术运算符以外,整型之间的强制转换也会导致溢出(如将long强制转换为int)并受checked或unchecked执行的限制。然而,按位运算符和移位运算符永远不会导致溢出。

练习 3

一、选择题

1.C#语言属于____。

A.机器语言

B.汇编语言

C.面向过程的语言

D.面向对象的程序设计语言

2.一个C#语言程序总是从____开始执行。

A.书写顺序的第一个函数

B.书写顺序的第一条执行语句

C.Main( )方法

D.不确定

3.把已经编写好的源程序翻译成二进制的目标代码的步骤是____。

A.编辑

B.编译

C.连接

D.执行

4.C#语言中的标识符只能由字母,数字和下划线组成,且第一个字符____。

A.必须为字母

B.必须为下划线

C.必须为字母或下划线

D.可以是字母,数字或下划线中任一种

5.以下选项中,不属于C#语言数据类型的是____。

A.int

B.short

C.long

6.在C#语言中,合法的数据形式是____。

A.±23

B.±1.23

C.234

7.以下选项中,合法的字符常量是____。

A.'B'

B."A"

C.CD

8.以下选项中,合法的用户标识符是____。

A.long

B._2Test

C.3Dmax

9.在C#语言中,下列运算符优先级最高的是____。

A.!

B.*

C.>

D.number

D.D123

D.1

D.Area

D.%

10.下列变量的声明方式不正确的是____。

A.int a;

B.int a=b;

C.char a;

D.string ab="ab";

11.下列数据类型不属于C#整数类型的是____。

A.sbyte

B.short

C.ushort

D.string

12.关于数据类型float说法不正确的是____。

A.该数据类型用于表示实数

B.该数据类型占4个字节空间

C.该数据类型表示双精度浮点数

D.该数据类型表示单精度浮点数

13.关于数据类型char说法不正确的是____。

A.该数据类型可用于表示单个字符

B.该数据类型可表示32位整数

C.该数据类型可表示16位无符号整数

D.该数据类型用于表示整数时与ushort在形式上类似,但意义不同

14.下列关于“声明变量”的说法中,最准确的是____。

A.为变量命名

B.给变量赋值

C.在C#中,一次最多声明一个变量

D.命名变量并说明变量的数据类型

15.在下列转义符形式中,表示“回车符”的是____。

A.\0

B.\r

C.\n

D.\t

二、填空题

16.在C#语言中,每条语句都以____结束。

17.C#有多种注释方法,其中 【A】 适用于单行注释,【B】 适用于多行注释。

18.在C#语言中,实数类型包含三种: 【A】【B】【C】

19.C#语言规定变量在使用之前必须先____后使用。

20.在C#表达式中,可通过____改变运算符的运算顺序,使表达式更清晰易懂。

21.在C#语言中,使用____关键字声明符号常量。

22.在代码中,明确指示将某一数据类型转换为另一种数据类型称为____。

23.在C#语言中,系统命名空间使用____关键字导入。

24.除了赋值操作符,所有的二进制的操作符都是____的,也就是说,操作按照从左向右的顺序执行。

25.不同种类运算符的优先级有高低之分。【A】 的优先级高于关系运算符,关系运算符的优先级高于逻辑运算符,逻辑运算符的优先级高于条件运算符,条件运算符的优先级高于 【B】

26.C#中的表达式类似于数学运算中的表达式,是由 【A】【B】 和标点符号等连接而成的式子。

27.在C#中,有些字符不能直接放在单引号中作为字符常量,这就需要使用转义符来表示这些字符常量,转义符由____加字符组成。

三、求下列表达式的值

28.

  int a=1, b=4, c=3;
  float d=10.4, e=4.1, f
  f=(a + b)/c + e * 1.1/c + d;

29.

  int a=7, b=3, c=5;
  float d;
  d=b + a % 3 + (b + c) % 2 / 4;

四、将下列数学表达式改写为等价的C#表达式

30.

31.(4+3x)2-abc

32.

五、问答题

33.简述C#语言中变量的命名规则?

34.什么是命名空间?命名空间有何作用?

35.在C#代码编写中,使用缩进格式及注释语句是C#的语法规则吗?它们的作用是什么?