1 C# 基础篇

概述

C# 是一个现代的,通用的,面向对象的编程语言,由微软 ( Microsoft ) 开发并获得欧洲计算机制造商协会 ( ECMA ) 和国际标准化组织 ( ISO ) 认可。

C# 由 Anders Hejlsberg 和他的团队在 .Net 的框架开发期间开发。

C# 是专为公共语言基础结构 ( CLI ) 设计的,包括可执行代码和运行环境,允许在不同的计算机系统和体系结构上使用各种高级语言。

下面列出了 C# 成为一种广泛应用的专业语言的原因:

  • 它是一种现代的、通用的编程语言。
  • 它是面向对象的。
  • 它是面向组件的。
  • 它是容易学习的。
  • 它是一种结构化语言。
  • 它产生高效的程序。
  • 它可以在多种计算机平台上编译。
  • 它是 .Net 框架的一部分。

强大的编程功能

C# 的架构十分接近于传统高级语言 C 和 C++,是一门面向对象的编程语言。它与 Java 非常相似,有许多强大的编程功能,因此得到世界范围内广大程序员的亲睐。

下面列出 C# 一些重要的功能:

  • 布尔条件(Boolean Conditions)
  • 自动垃圾回收(Automatic Garbage Collection)
  • 标准库(Standard Library)
  • 组件版本(Assembly Versioning)
  • 属性(Properties)和事件(Events)
  • 委托(Delegates)和事件管理(Events Management)
  • 易于使用的泛型(Generics)
  • 索引器(Indexers)
  • 条件编译(Conditional Compilation)
  • 简单的多线程(Multithreading)
  • LINQ 和 Lambda 表达式
  • 集成 Windows

环境配置

在这一章中,我们将讨论创建 C# 编程所需要的工具。我们已经提到过 C# 是 .Net 框架的一部分, 且用于编写 .Net 应用程序。因此,在讨论运行一个 C# 程序的可用工具之前,让我们先了解一下 C# 与 .Net 框架之间的关系。

.Net 框架

.Net 框架是一个革命性的平台,能帮您编写出下面类型的应用程序:

  • Windows 应用程序
  • Web 应用程序
  • Web 服务

.Net 框架应用程是多平台的应用程序。框架的设计方式使它适用于下列各种语言:C#、C++、Visual Basic、Jscript、COBOL 等等。所有这些语言可以访问框架,并且彼此之间可以互相交互。

.Net 框架由一个巨大的代码库组成,用于 C# 等客户端语言。下面列出一些 .Net 框架的组件:

  • 公共语言运行库(Common Language Runtime - CLR)
  • .Net 框架类库(.Net Framework Class Library)
  • 公共语言规范(Common Language Specification)
  • 通用类型系统(Common Type System)
  • 元数据(Metadata)和组件(Assemblies)
  • Windows 窗体(Windows Forms)
  • ASP.Net 和 ASP.Net AJAX
  • ADO.Net
  • Windows 工作流基础(Windows Workflow Foundation - WF)
  • Windows 显示基础(Windows Presentation Foundation)
  • Windows 通信基础(Windows Communication Foundation - WCF)
  • LINQ

如需了解每个组件的功能,请参阅 ASP.Net - Introduction ,更详细的信息,请参阅微软(Microsoft)的文档。

C# 的集成开发环境 (IDE)

微软提供了下列用于 C# 编程的开发工具:

  • Visual Studio 2010 (VS)
  • Visual C# 2010 Express (VCE)
  • Visual Web Developer

后面两个是免费使用的,可从微软官方网址下载。使用这些工具,您可以编写各种 C# 程序, 从简单的命令行应用程序到更复杂的应用程序。您也可以使用基本的文本编辑器编写 C# 源代码文件,比如 Notepad,并使用命令行编译器(.NET 框架的一部分),编译代码成组件。

Visual C# Express 和 Visual Web Developer Express 版本是 Visual Studio 的定制版本,且具有相同的外观和感观。它们保留 Visual Studio 的大部分功能。 在本教程中,我们使用的是 Visual C# 2010 Express。

您可以从 Microsoft Visual Studio 上进行下载。它会自动安装在您的机器上。请注意,您在完成速成版的安装时需要提供一个可用的网络连接。

在 Linux 或 Mac OS 上编写 C# 程序

虽然 .NET 框架是运行在 Windows 操作系统上,但是也有一些运行于其它操作系统上的版本可供选择。 Mono 是 .NET 框架的一个开源版本, 它包含了一个 C# 编译器,且可运行于多种操作系统上,比如各种版本对 Linux 和 Mac OS 的支持。如需了解更多详情,请访问 Go Mono

Mono 的目的不仅仅是跨平台地运行微软 .NET 应用程序,而且也为 Linux 开发者提供了更好的开发工具。 Mono 可运行在多种操作系统上,包括 Android、BSD、iOS、Linux、OS X、Windows、Solaris 和 UNIX。

程序结构

在我们学习 C# 编程语言的基础构件块之前,让我们先看一下 C# 的最小的程序结构, 以便作为接下来章节的参考。

创建 Hello World 实例

一个 C# 程序主要包括以下部分:

  • 命名空间声明
  • 一个类
  • 类方法
  • 类属性
  • 一个 Main 方法
  • 语句和表达式
  • 注释

让我们看一个可以打印出 "Hello World" 的简单的代码:

  1. using System;
  2. namespace HelloWorldApplication
  3. {
  4. class HelloWorld
  5. {
  6. static void Main(string[] args)
  7. {
  8. /* 我的第一个 C# 程序*/
  9. Console.WriteLine("Hello World");
  10. Console.ReadKey();
  11. }
  12. }
  13. }

编译执行上述代码,得到如下结果:

  1. Hello World

让我们看一下上面给出程序的各个部分:

  • 程序的第一行 using System;-using 关键字用于在程序中包含 System 命名空间。 一个程序一般有多个 using 语句。
  • 下一行是 namespace 声明。一个 namespace 是一系列的类。HelloWorldApplication 命名空间包含了类 HelloWorld 。
  • 下一行是 class 声明。类 HelloWorld 包含了程序所使用的数据和方法的声明。类一般包含多个方法。方法定义了类的行为。 在这里,HelloWorld 类只有一个 Main 方法。
  • 下一行定义了 Main 方法,是所有 C# 程序的入口。Main 方法说明当类执行时,它将做什么动作。
  • 下一行 // 将会被编译器忽略,且它会在程序中添加注释 。
  • Main 方法通过语句 Console.WriteLine("Hello World"); 指定了它的行为。 WriteLine 是一个定义在 System 命名空间中的 Console 类的一个方法。该语句会在屏幕上显示消息 "Hello, World!" 。
  • 最后一行 Console.ReadKey(); 是针对 VS.NET 用户的。这使得程序会等待一个用户按键的动作,防止程序在 Visual Studio .NET 启动时屏幕会快速运行并关闭。

以下几点值得注意:

  • C# 是大小写敏感的。
  • 所有的语句和表达式必须以分号(;)结尾。
  • 程序的执行从 Main 方法开始。
  • 与 Java 不同的是,文件名可以不同于类的名称。

编译执行 C# 程序

如果您使用 Visual Studio.Net 编译和执行 C# 程序,请按下面的步骤进行:

  • 启动 Visual Studio。
  • 在菜单栏上,选择 File -> New -> Project。
  • 从模板中选择 Visual C#,然后选择 Windows。
  • 选择 Console Application。
  • 为您的项目制定一个名称,然后点击 OK 按钮。
  • 新项目会出现在解决方案资源管理器中。
  • 在代码编辑器中编写代码。
  • 点击 Run 按钮或者按下 F5 键来运行程序。会出现一个命令提示符窗口,显示 Hello World。

您也可以使用命令行代替 Visual Studio IDE 来编译 C# 程序:

  • 打开一个文本编辑器,添加上面提到的代码。
  • 保存文件为 helloworld.cs。
  • 打开命令提示符工具,定位到文件所保存的目录。
  • 键入 csc helloworld.cs 并按下回车键来编译代码。
  • 如果代码没有错误,命令提示符会进入下一行,并生成 helloworld.exe 可执行文件。
  • 接下来,键入 helloworld 来执行程序。
  • 您将看到 "Hello World" 打印在屏幕上。

基本语法

C# 是一种面向对象的编程语言。在面向对象的程序设计方法中,程序由各种相互作用的对象组成。一个对象采取的动作称为方法。 相同种类的对象通常具有相同的属性,或者说,是在相同的类中。

例如,以 Rectangle(矩形)对象为例。它具有 length 和 width 属性。 根据设计,它可能需要接受这些属性值、计算面积和显示细节的方法。

让我们来看看一个 Rectangle(矩形)类的实现,并借此讨论 C# 的基本语法:

  1. using System;
  2. namespace RectangleApplication
  3. {
  4. class Rectangle
  5. {
  6. // member variables
  7. double length;
  8. double width;
  9. public void Acceptdetails()
  10. {
  11. length = 4.5;
  12. width = 3.5;
  13. }
  14. public double GetArea()
  15. {
  16. return length * width;
  17. }
  18. public void Display()
  19. {
  20. Console.WriteLine("Length: {0}", length);
  21. Console.WriteLine("Width: {0}", width);
  22. Console.WriteLine("Area: {0}", GetArea());
  23. }
  24. }
  25.  
  26. class ExecuteRectangle
  27. {
  28. static void Main(string[] args)
  29. {
  30. Rectangle r = new Rectangle();
  31. r.Acceptdetails();
  32. r.Display();
  33. Console.ReadLine();
  34. }
  35. }
  36. }

编译执行上述代码,得到如下结果:

  1. Length: 4.5
  2. Width: 3.5
  3. Area: 15.75

using 关键字

在任何 C# 程序中的第一条语句都是:

  1. using System;

using 关键字用于在程序中包含命名空间。一个程序可以包含多个 using 语句。

class 关键字

class 关键字用于声明一个类。

C# 中的注释

注释用于解释代码。编译器会忽略注释的部分。在 C# 程序中,多行注释以 / 开始,并以字符 / 终止,如下所示:

  1. /* This program demonstrates
  2. The basic syntax of C# programming
  3. Language */

单行注释是用 '//' 符号表示。例如:

  1. }//end class Rectangle

成员变量

变量是一个类的属性或数据成员,用于存储数据。在上面的程序中,Rectangle 类有两个成员变量,名为 lengthwidth

成员函数

函数是一系列执行特定任务的语句。类的成员函数是在类内声明的。我们举例的类 Rectangle 包含了三个成员函数: AcceptDetailsGetAreaDisplay

实例化一个类

在上面的程序中,类 ExecuteRectangle 是一个包含 Main() 方法和实例化 Rectangle 类的类。

标识符

标识符是用来识别类、变量、函数或任何其它用户定义的项目。在 C# 中,类的命名必须遵循如下基本规则:

  • 标识符必须以字母开头,后面可以跟一系列的字母、数字(0 - 9)或下划线(_)。标识符中的第一个字符不能是数字。
  • 标识符必须不包含任何嵌入的空格或符号,比如 ? - + ! @ # % ^ & * ( ) [ ] { } . ; : " ' / \。但是,可以使用下划线(_)。
  • 标识符不能是 C# 关键字。

C# 关键字

关键字是 C# 编译器预定义的保留字。这些关键字不能用作标识符,但是,如果您想使用这些关键字作为标识符,可以在关键字前面加上 @ 字符作为前缀。

在 C# 中,有些标识符在代码的上下文中有特殊的意义,如 get 和 set,这些被称为上下文关键字。

下表列出了 C# 中的保留关键字(Reserved Keywords)和上下文关键字(Contextual Keywords):

保留关键字
abstract as base bool break byte case
catch char checked class const continue decimal
default delegate do double else enum event
explicit extern false finally fixed float for
foreach goto if implicit in in (generic modifier) int
interface internal is lock long namespace new
null object operator out out (generic modifier) override params
private protected public readonly ref return sbyte
sealed short sizeof stackalloc static string struct
switch this throw true try typeof uint
ulong unchecked unsafe ushort using virtual void
volatile while
上下文关键字
add alias ascending descending dynamic from get
global group into join let orderby partial (type)
partial(method) remove select set

数据类型

在 C# 中,变量分为以下几种类型:

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

值类型

值类型变量可以直接分配给其一个值。它们是从类 System.ValueType 中派生的。

值类型直接包含数据。比如 intcharfloat,它们分别存储数字、字母和浮点数。当您声明一个 int 类型的变量时,系统将会分配内存来存储它的值。

下表列出了 C# 2010 中可用的值类型:

类型 描述 范围 默认值
bool 布尔值 True 或 False False
byte 8 位无符号整数 0 到 255 0
char 16 位 Unicode 字符 U +0000 到 U +ffff '\0'
decimal 128 位精确的十进制值,28-29 有效位数 (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 0.0M
double 64 位双精度浮点型 (+/-) 5.0 x 10-324 到 (+/-) 1.7 x 10308 0.0D
float 32 位单精度浮点型 -3.4 x 1038 到 + 3.4 x 1038 0.0F
int 32 位有符号整数类型 -2 , 147 , 483 , 648 到 2 , 147 , 483 , 647 0
long 64 位有符号整数类型 -923 , 372 , 036 , 854 , 775 , 808 到 9 , 223 , 372 , 036 , 854 , 775 , 807 0L
sbyte 8 位有符号整数类型 -128 到 127 0
short 16 位有符号整数类型 -32 , 768 到 32 , 767 0
uint 32 位无符号整数类型 0 到 4 , 294 , 967 , 295 0
ulong 64 位无符号整数类型 0 到 18 , 446 , 744 , 073 ,709 ,551 , 615 0
ushort 16 位无符号整数类型 0 到 65 , 535 0

如需得到一个类型或一个变量在特定平台上的准确大小,可以使用 sizeof 方法。表达式 sizeof(type) 产生以字节 为单位存储对象或类型的存储尺寸。下面举例获取任何机器上 int 类型的存储大小:

  1. namespace DataTypeApplication
  2. {
  3. class Program
  4. {
  5. static void Main(string[] args)
  6. {
  7. Console.WriteLine("Size of int: {0}", sizeof(int));
  8. Console.ReadLine();
  9. }
  10. }
  11. }

编译执行上述代码,得到如下结果:

  1. Size of int: 4

引用类型

引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。

换句话说,它们指向的是一个内存位置。使用多重变量时,引用类型可以指向一个内存位置。 如果内存位置的数据是由多重变量之中的一个改变的,其他变量会自动相应这种值的变化。 例如,内置的引用类型有:objectdynamicstring

对象类型

对象类型是 C# 通用类型系统(CTS)中所有数据类型基类。 Object 是 System.Object 类的别名。 对象类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。

当一个值类型转换为对象类型时,则被称为装箱;另一方面,当一个对象类型转换为值类型时,则被称为拆箱

  1. object obj;
  2. obj = 100; // 这是装箱

动态类型

您可以在动态数据类型变量中存储任何类型的值。这些变量的类型检查是在运行时进行的。

声明动态类型的语法:

  1. dynamic <variable_name> = value;

例如:

  1. dynamic d = 20;

动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时进行的,而动态类型变量的类型检查是在运行时进行的。

字符串类型

字符串 类型允许您给变量分配任何字符串值。字符串类型是 System.String 类的别名。它是从对象类型派生的。字符串类型的值可以通过两种形式进行分配:引号和 @ 引号。

例如:

  1. String str = "Tutorials Point";

一个 @ 引号字符串:

  1. @"Tutorials Point";

用户自定义引用类型有:class、interface 或 delegate。我们将在以后的章节中讨论这些类型。

指针类型

指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。

声明指针类型的语法:

  1. type* identifier;

例如:

  1. char* cptr;
  2. int* iptr;

我们将在章节"不安全的代码"中讨论指针类型。

类型转换

类型转换是把数据从一种类型转换为另一种类型。在 C# 中,类型转换有两种形式:

  • 隐式类型转换 这些转换是 C# 默认的以安全方式进行的转换。例如,从小的整数类型转换为大的整数类型,从派生类转换为基类。
  • 显式类型转换 这些转换是通过用户使用预定义的函数显示完成的。显式转换需要强制转换运算符。

下面的实例显示了一个显式的类型转换:

  1. namespace TypeConversionApplication
  2. {
  3. class ExplicitConversion
  4. {
  5. static void Main(string[] args)
  6. {
  7. double d = 5673.74;
  8. int i;
  9.  
  10. // 强制转换 double 为 int
  11. i = (int)d;
  12. Console.WriteLine(i);
  13. Console.ReadKey();
  14.  
  15. }
  16. }
  17. }

编译执行上述代码,得到如下结果:

  1. 5673

C# 类型转换方法

C# 提供了下列内置的类型转换方法:

序号 方法与描述
1 ToBoolean 如果可能的话,把类型转换为布尔型。
2 ToByte 把类型转换为字节类型。
3 ToChar 如果可能的话,把类型转换为单个 Unicode 字符类型。
4 ToDateTime 把类型(整数或字符串类型)转换为日期-时间结构。
5 ToDecimal 把浮点型或整数类型转换为十进制类型。
6 ToDouble 把类型转换为双精度浮点型。
7 ToInt16 把类型转换为 16 位整数类型。
8 ToInt32 把类型转换为 32 位整数类型。
9 ToInt64 把类型转换为 64 位整数类型。
10 ToSbyte 把类型转换为有符号字节类型。
11 ToSingle 把类型转换为小浮点数类型。
12 ToString 把类型转换为字符串类型。
13 ToType 把类型转换为指定类型。
14 ToUInt16 把类型转换为 16 位无符号整数类型。
15 ToUInt32 把类型转换为 32 位无符号整数类型。
16 ToUInt64 把类型转换为 64 位无符号整数类型。

下面的实例把不同值类型变量转换为字符串类型变量:

  1. namespace TypeConversionApplication
  2. {
  3. class StringConversion
  4. {
  5. static void Main(string[] args)
  6. {
  7. int i = 75;
  8. float f = 53.005f;
  9. double d = 2345.7652;
  10. bool b = true;
  11.  
  12. Console.WriteLine(i.ToString());
  13. Console.WriteLine(f.ToString());
  14. Console.WriteLine(d.ToString());
  15. Console.WriteLine(b.ToString());
  16. Console.ReadKey();
  17.  
  18. }
  19. }
  20. }

编译执行上述代码,得到如下结果:

  1. 75
  2. 53.005
  3. 2345.7652
  4. True

变量

一个变量只不过是一个供程序操作的存储区的名字。在 C# 中,每个变量都有一个特定的类型,类型决定了变量的内存大小、布局、可以存储在内存中的值的范围以及可以对变量进行的一系列操作。

C# 中提供的基本的值类型大致可以分为以下几类:

类型 举例
整数类型 sbyte、byte、short、ushort、int、uint、long、ulong 和 char
浮点型 float 和 double
十进制类型 decimal
布尔类型 true 或 false 值,指定的值
空类型 可为空值的数据类型

C# 允许定义其他值类型的变量,比如 enum,也允许定义引用类型变量,比如 class。这些我们将在以后的章节中进行讨论。

变量定义

C# 中变量定义的语法:

  1. <data_type> <variable_list>;

在这里,data_type 必须是一个有效的 C# 数据类型,可以是 char、int、float、double 或其他用户自定义的数据类型。variable_list 可以由一个或多个用逗号分隔的标识符名称组成。

一些有效的变量定义如下所示:

  1. int i, j, k;
  2. char c, ch;
  3. float f, salary;
  4. double d;

您可以在定义一个变量时对其进行初始化:

  1. int i = 100;

变量初始化

变量通过等号后的一个常量表达式进行初始化(也就是赋值)。初始化的一般语法为:

  1. variable_name = value;

变量可以在声明时被初始化。初始化由等号后的一个常量表达式完成,如下所示:

  1. <data_type> <variable_name> = value;

一些实例:

  1. int d = 3, f = 5; /* 初始化 d 和 f. */
  2. byte z = 22; /* 初始化 z. */
  3. double pi = 3.14159; /* 声明 pi 的近似值 */
  4. char x = 'x'; /* 变量 x 的值为 'x' */

适时地初始化变量是一个良好的编程习惯,否则有时程序会产生意想不到的结果。

请看下面的实例,使用了各种类型的变量:

  1. namespace VariableDefinition
  2. {
  3. class Program
  4. {
  5. static void Main(string[] args)
  6. {
  7. short a;
  8. int b ;
  9. double c;
  10.  
  11. /* 实际初始化 */
  12. a = 10;
  13. b = 20;
  14. c = a + b;
  15. Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c);
  16. Console.ReadLine();
  17. }
  18. }
  19. }

编译执行上述代码,得到如下结果:

  1. a = 10, b = 20, c = 30

接受来自用户的值

System 命名空间中的 Console 类提供了一个函数 ReadLine(),用于接收来自用户的输入,并把它存储到一个变量中。 例如:

  1. int num;
  2. num = Convert.ToInt32(Console.ReadLine());

函数 Convert.ToInt32() 把用户输入的数据转换为 int 数据类型,因为函数 Console.ReadLine() 只接受字符串格式的数据。

C# 中的 Lvalues 和 Rvalues

在 C# 中的两种表达式:

  • lvalue:lvalue 表达式可以出现在赋值语句的左边或右边。
  • rvalue:rvalue 表达式可以出现在赋值语句的右边,不能出现在赋值语句的左边。

变量是 lvalue 的,所以可以出现在赋值语句的左边。数值是 rvalue 的,因此不能被赋值,也不能出现在赋值语句的左边。下面是一个有效的语句:

  1. int g = 20;

下面是一个无效的语句,会在编译时产生错误:

  1. 10 = 20;

常量和文字

常量是固定值,程序执行期间不会改变。这些固定值也被称为文字。常量可以是任何基本数据类型,如整数常量,浮点常量,字符常量或者字符串常量,还有枚举常量。

常量可以被当作常规的变量,只是它们的值在定义后不能被修改。

整数常量

整数常量可以是十进制、八进制、或十六进制的常量。前缀指定基或基数:0x 或 0X 表示十六进制,0 表示八进制,没有前缀则表示十进制。

整数常量也可以有后缀,可以是 U 和 L 的组合,其中 U 和 L 分别表示 unsigned 和 long。后缀可以是大写或小写,多个后缀以任意顺序进行组合。

这里是一些整数常量的例子:

  1. 212 /* 合法 */
  2. 215u /* 合法 */
  3. 0xFeeL /* 合法 */
  4. 078 /* 非法: 8 不是一个八进制数字 */
  5. 032UU /* 非法: 不能重复后缀 */

以下是各种类型的整数常量的实例:

  1. 85 /* 十进制 */
  2. 0213 /* 八进制 */
  3. 0x4b /* 十六进制 */
  4. 30 /* int */
  5. 30u /* 无符号 int */
  6. 30l /* long */
  7. 30ul /* 无符号 long */

浮点常量

一个浮点常量是由整数部分,小数点,小数部分和指数部分组成。您可以使用小数形式或指数形式来表示浮点常量。

这里是一些浮点常量的例子:

  1. 3.14159 /* 合法 */
  2. 314159E-5L /* 合法 */
  3. 510E /* 非法: 不完全指数 */
  4. 210f /* 非法: 没有小数或指数 */
  5. .e55 /* 非法: 缺少整数或小数 */

使用小数形式表示时,必须包含小数点、指数或同时包含两者。使用指数形式表示时,必须包含整数部分、小数部分或同时包含两者。有符号的指数是用 e 或 E 表示的。

字符常量

字符常量是括在单引号里,例如 'x',并可以存储在一个简单的字符类型变量中。一个字符常量可以是一个普通字符(例如 'x')、一个转义序列(例如 '\t')或一个通用字符(例如 '\u02C0')。

在 C# 中有一些特定的字符,当它们的前面带有反斜杠时有特殊的意义,可用于表示换行符(\n)或制表符(\t)。在这里,列出一些转义序列码:

转义序列 含义
\ \ 字符
\' ' 字符
\" " 字符
\? ? 字符
\a Alet 或 bell
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一到三位的八进制数
\xhh… 一个或多个数字的十六进制数

下面的实例说明了一些转义字符序列:

  1. using System;
  2. namespace EscapeChar
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. Console.WriteLine("Hello\tWorld\n\n");
  9. Console.ReadLine();
  10. }
  11. }
  12. }

编译执行上述代码,得到如下结果:

  1. Hello World

字符串常量

字符串常量是括在双引号""里,或者是括在@""里。字符串常量包含的字符类似于字符常量,可以是普通字符、转义序列和通用字符。

使用字符串常量时,可以把一个很长的行拆成多个行,可以使用空格分隔各个部分。

这里是一些字符串常量的例子。下面所列的各种形式表示相同的字符串。

  1. "hello, dear"
  2. "hello, \
  3. dear"
  4. "hello, " "d" "ear"
  5. @"hello dear"

定义常量

常量是使用 const 关键字来定义的。定义一个常量的语法如下:

  1. const <data_type> <constant_name> = value;

下面的代码演示了如何在程序中定义和使用常量:

  1. using System;
  2. namespace DeclaringConstants
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. const double pi = 3.14159; // 常量声明
  9. double r;
  10. Console.WriteLine("Enter Radius: ");
  11. r = Convert.ToDouble(Console.ReadLine());
  12. double areaCircle = pi * r * r;
  13. Console.WriteLine("Radius: {0}, Area: {1}", r, areaCircle);
  14. Console.ReadLine();
  15. }
  16. }
  17. }

编译执行上述代码,得到如下结果:

  1. Enter Radius:
  2. 3
  3. Radius: 3, Area: 28.27431

运算符

运算符是一种告诉编译器执行特定的数字或逻辑操作的符号。C# 中有丰富的内置运算符,分类如下:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其它运算符

本教程将逐一讲解算运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和其他运算符。

算术运算符

下表列出了 C# 支持的所有算术运算符。假设变量 A 的值为10,变量 B 的值为20,则:

算术运算符实例

运算符 描述 实例
+ 两个操作数相加 A + B = 30
- 两个操作数相减(第一个减去第二个) A - B = -10
两个操作数相乘 A B = 200
/ 分子除以分母 B / A = 2
% 取模运算符,整除后的余数 B % A = 0
++ 自增运算符,整数值增加1 A++ = 11
自减运算符,整数值减少1 A— = 9

关系运算符

下表列出了 C# 支持的所有关系运算符。假设变量 A 的值为10,变量 B 的值为 20,则:

关系运算符实例

运算符 描述 实例
== 检查两个操作数的值是否相等,如果相等则条件为真 (A == B) 不为真
!= 检查两个操作数的值是否相等,如果不相等则条件为真 (A != B) 为真
> 检查左边的操作数的值是否大于右操作数的值,如果是则条件为真 (A > B) 不为真
< 检查左边的操作数的值是否小于右操作数的值,如果是则条件为真 (A < B) 为真
>= 检查左边的操作数的值是否大于或等于右操作数的值,如果是则条件为真 (A >= B) 不为真
<= 检查左边的操作数的值是否小于或等于右操作数的值,如果是则条件为真 (A <= B) 为真

逻辑运算符

下表列出了 C# 所支持的所有逻辑运算符。假设变量 A 为布尔值 true,变量 B 为布尔值 false,则:

逻辑运算符实例

运算符 描述 实例
&& 称为逻辑与操作,如果两个操作数都非零,则条件为真 (A && B) 结果为 false
|| 称为逻辑或操作,如果两个操作数中有任意一个非零,则条件为真 (A || B) 结果为 true
! 称为逻辑非运算符,用来逆转操作数的逻辑状态。如果条件为真则逻非运算符将使其为假

位运算符

位运算符作用于位,并逐位执行操作。&、| 和 ^ 的真值表如下:

ppp & qp | qp ^ q
00000
01011
11110
10011

假设,如果 A=60,B=13,现以二进制格式表示如下:

A = 0011 1100

B = 0000 1101


A & B = 0000 1100

A | B = 0011 1101

A ^ B = 0011 0001

~A = 1100 0011

下表列出了 C# 支持的位运算符。假设变量 A 的值为60,变量 B 的值为13 则:

位运算符示例

运算符 描述 实例
& 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 (A & B) 将得到 12. 即 0000 1100
| 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 (A | B) 将得到 61, 即 0011 1101
^ 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 (A ^ B) 将得到 49, 即 0011 0001
~ 二进制补码运算符是一元运算符,具有“翻转”的位效果。 (~A ) 将得到 -61, 即 1100 0011
<< 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 A << 2 将得到 240, 即 1111 0000
>> 二进制右移运算符。左操作数的值由右移动右操作数指定的位数。 A >> 2 将得到 15, 即 0000 1111

赋值运算符

下表列出了 C# 支持的赋值运算符:

赋值运算符实例

运算符 描述 实例
= 简单的赋值运算符,把右边操作数的值赋给到左边操作数 C = A + B 把 A + B 的值赋给 C
+= 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 C += A 相当于 C = C + A
-= 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 C -= A 相当于 C = C - A
= 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 C = A 相当于 C = C * A
/= 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 C /= A 相当于 C = C / A
%= 求模且赋值运算符,求两个操作数的模赋值给左边操作数 C %= A 相当于 C = C % A
<<= 左移且赋值运算符 C <<= 2 相当于 C = C << 2
>>= 右移且赋值运算符 C >>= 2 相当于 C = C >> 2
&= 按位与且赋值运算符 C &= 2 相当于 C = C & 2
^= 按位异或且赋值运算符 C ^= 2 相当于 C = C ^ 2
|= 按位或且赋值运算符 C |= 2 相当于 C = C | 2

其它运算符

下表列出了 C# 支持的其他一些重要的运算符,包括 sizeoftypeof 运算和? :

其它运算符实例

运算符 描述 示例
sizeof() 返回一个数据类型的大小 sizeof(int),将返回 4
typeof() 返回一个类的类型 typeof(StreamReader)
& 返回一个变量的地址 &a; 将给出变量的实际地址
指针的变量 a; 将指向一个变量
? : 条件表达式 如果条件为真,那么 ? 值为 X : 否则 ? 值为 Y
is 判断一个对象是否是特定的类型 If( Ford is Car) // 判断 Ford 对象是否属于 Car 类型
as 转换,如果转换失败则引发异常 Object obj = new StringReader("Hello");StringReader r = obj as StringReader;

C# 运算符优先级

运算符优先级确定表达式中项的组合。这会影响到一个表达式如何计算。某些运算符比其他运算符有更高的优先级,例如,乘除运算符比加减运算符的优先级高:

例如 X = 7 + 3 2;这里,x 被赋值为 13,而不是 20,因为运算符 的优先级高于 +,所以这里首先进行 3*2 运算,然后再加上 7。

下表按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格上面,具有较低优先级的运算符出现在表格下面。在表达式中,较高优先级的运算符会优先被运算。

运算符优先级实例

分类 运算符 结合性
后缀 () [] -> . ++ - - 从左到右
一元 + - ! ~ ++ - - (type) & sizeof 从右到左
乘法 / % 从左到右
相加 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
按位与 & 从左到右
按位异或 | ^ 从左到右
按位或 | 从左到右
逻辑 AND && 从左到右
逻辑 OR || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %=>>= <<= &= ^= |= 从右到左
逗号 从左到右

判断

判断结构需要程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。

下面是大多数编程语言中典型判断结构的一般形式:

image 图片 1.1 image

C# 提供了以下类型的判断语句。点击链接查看每个语句的详细信息。

语句 描述
if 语句 一个if 语句由一个布尔表达式后跟一个或多个语句组成。
if…else 语句 一个if 语句后跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。
嵌套 if 语句 您可以在一个 ifelse if 语句内使用另一个 ifelse if 语句。
switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。
嵌套 switch 语句 您可以在一个 switch 语句内使用另一个 switch 语句。

? : 运算符:

我们已经在前面的章节中介绍了条件运算符 ? : ,可以用来代替 if…else 语句。它的一般形式如下:

  1. Exp1 ? Exp2 : Exp3;

其中,Exp1、Exp2 和 Exp3 是表达式。请注意,冒号的使用和放置。

? 表达式的值是由 Exp1 决定的。如果 Exp1 为真,则计算 Exp2 的值,结果即为整个?表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个?表达式的值。

循环

有的情况下,可能需要多次执行同一块代码。一般情况下,语句是顺序执行的:函数中的第一个语句先执行,然后是第二个语句,以此类推。

编程语言提供了允许更为复杂的执行路径的多种控制结构。

循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的一般形式:

image 图片 1.2 image

C# 提供了以下几种类型的循环类型。点击链接查看每个类型的详细信息。

循环类型 描述
while 循环 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
for 循环 多次执行一个语句序列,简化管理循环变量的代码。
do…while 循环 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。
嵌套循环 您可以在 do…while 循环内使用一个或多个循环。

循环控制语句

循环控制语句更改执行的正常序列。当执行离开一个范围时,所有在该范围内创建的自动对象都会被销毁。

C# 提供了以下控制语句。点击链接查看每个语句的详细信息。

控制语句 描述
break 语句 终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句。
continue 语句 引起循环跳过主体的剩余部分,立即重新开始测试条件。

无限循环

如果条件永远不为假,则循环将变为无限循环。for 循环在传统意义上可用于实现无限循环。由于构成循环的三个表达式中任何一个都不是必需的,您可以将某些条件表达式留空来构成一个无限循环。

举例

  1. using System;
  2. namespace Loops
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. for (; ; )
  9. {
  10. Console.WriteLine("Hey! I am Trapped");
  11. }
  12. }
  13. }
  14. }

当条件表达式不存在时,它被假定为真。您也可以设置一个初始值和增量表达式,但一般情况下,程序员偏向于使用 for(;;) 结构来表示一个无限循环。

封装

封装被定为义为“把一个或多个项目封闭在一个物理的或者逻辑的包中”。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。

抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装使程序员实现所需级别的抽象

封装使用访问修饰符来实现。一个访问修饰符定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:

  • Public
  • Private
  • Protected
  • Internal
  • Protected internal

Public 访问修饰符

Public 访问修饰符允许一个类将其成员变和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。

下面的例子说明了这点:

  1. using System;
  2. namespace RectangleApplication
  3. {
  4. class Rectangle
  5. {
  6. //成员变量
  7. public double length;
  8. public double width;
  9.  
  10. public double GetArea()
  11. {
  12. return length * width;
  13. }
  14. public void Display()
  15. {
  16. Console.WriteLine("长度: {0}", length);
  17. Console.WriteLine("宽度: {0}", width);
  18. Console.WriteLine("面积: {0}", GetArea());
  19. }
  20. }//end class Rectangle
  21.  
  22. class ExecuteRectangle
  23. {
  24. static void Main(string[] args)
  25. {
  26. Rectangle r = new Rectangle();
  27. r.length = 4.5;
  28. r.width = 3.5;
  29. r.Display();
  30. Console.ReadLine();
  31. }
  32. }
  33. }

编译执行上述代码,得到如下结果:

  1. 长度: 4.5
  2. 宽度: 3.5
  3. 面积: 15.75

在上面的例子中,成员变量 length 和 width 被声明为 public,所以它们可以被函数 Main() 使用 Rectangle 类的实例 r 访问。

成员函数 Display()GetArea() 也可以不通过类的实例直接直接访问这些变量。

成员函数 Display() 也被声明为 public,所以从它也能被 Main() 使用 Rectangle 类的实例 r 访问。

Private 访问修饰符

Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。

下面的例子说明了这点:

  1. using System;
  2. namespace RectangleApplication
  3. {
  4. class Rectangle
  5. {
  6. //成员变量
  7. private double length;
  8. private double width;
  9.  
  10. public void Acceptdetails()
  11. {
  12. Console.WriteLine("请输入长度: ");
  13. length = Convert.ToDouble(Console.ReadLine());
  14. Console.WriteLine("请输入宽度: ");
  15. width = Convert.ToDouble(Console.ReadLine());
  16. }
  17. public double GetArea()
  18. {
  19. return length * width;
  20. }
  21. public void Display()
  22. {
  23. Console.WriteLine("长度: {0}", length);
  24. Console.WriteLine("宽度: {0}", width);
  25. Console.WriteLine("面积: {0}", GetArea());
  26. }
  27. }//end class Rectangle
  28.  
  29. class ExecuteRectangle
  30. {
  31. static void Main(string[] args)
  32. {
  33. Rectangle r = new Rectangle();
  34. r.Acceptdetails();
  35. r.Display();
  36. Console.ReadLine();
  37. }
  38. }
  39. }

编译执行上述代码,得到如下结果:

  1. 请输入长度:
  2. 4.4
  3. 请输入宽度:
  4. 3.3
  5. 长度: 4.4
  6. 宽度: 3.3
  7. 面积: 14.52

在上面的例子中,成员变量 length 和 width 被声明为 private,所以它们不能被函数 Main() 访问。成员函数 AcceptDetails()Display() 可以访问这些变量。由于成员函数 AcceptDetails()Display() 被声明为 public,所以它们可以被 Main() 函数使用 Rectangle 类的实例 r 访问。

Protected 访问修饰符

Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这种方式有助于实现继承。我们将在继承的章节详细讨论这个问题。

Internal 访问修饰符

Internal 访问修饰符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 Internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。

下面的例子说明了这点:

  1. using System;
  2. namespace RectangleApplication
  3. {
  4. class Rectangle
  5. {
  6. //成员变量
  7. internal double length;
  8. internal double width;
  9.  
  10. double GetArea()
  11. {
  12. return length * width;
  13. }
  14. public void Display()
  15. {
  16. Console.WriteLine("长度: {0}", length);
  17. Console.WriteLine("宽度: {0}", width);
  18. Console.WriteLine("面积: {0}", GetArea());
  19. }
  20. }//end class Rectangle
  21.  
  22. class ExecuteRectangle
  23. {
  24. static void Main(string[] args)
  25. {
  26. Rectangle r = new Rectangle();
  27. r.length = 4.5;
  28. r.width = 3.5;
  29. r.Display();
  30. Console.ReadLine();
  31. }
  32. }
  33. }

编译执行上述代码,得到如下结果:

  1. 长度: 4.5
  2. 宽度: 3.5
  3. 面积: 15.75

在上面的例子中,请注意成员函数 GetArea() 声明的时候不带有任何访问修饰符。如果没有指定访问修饰符,则使用类成员的默认访问修饰符,即为 private

Protected internal 访问修饰符

Protected internal 访问修饰符允许一个类将其成员变量和成员函数对同一应用程内的子类以外的其他的类对象和函数进行隐藏。这也被用于实现继承。

方法

方法是一组在一起执行任务的语句。每个 C# 程序都至少有一个含有方法的类,名为 Main。

若要使用方法,您需要:

  • 定义一个方法
  • 调用方法

在 C# 中定义方法

当你定义一个方法时,你基本上要声明其结构的组成元素。在 C# 中定义方法的语法如下所示:

  1. <Access Specifier> <Return Type> <Method Name>(Parameter List)
  2. {
  3. Method Body
  4. }

以下是方法中的各种元素:

  • 访问说明符:它用于从一个类中确定一个变量或方法的可见性。
  • 返回类型:方法可能会返回一个值。返回类型是方法返回值的数据类型。如果该方法不返回任何值,那么返回类型是 void。
  • 方法名称:方法名称是唯一的标识符,并区分大小写。它不能与在类中声明的任何其他标识符相同。
  • 参数列表:括号括起来,使用参数从方法中传递和接收数据。参数列表是指类型、顺序和方法的参数数目。参数是可选的;方法可能包含任何参数。
  • 方法主体:它包含的一组指令完成所需要的活动所需。

示例

下面的代码段显示了一个函数 FindMax,从两个整数值中,返回其中较大的一个。它具有公共访问说明符,所以它可以通过使用类的外部例子来访问。

  1. class NumberManipulator
  2. {
  3. public int FindMax(int num1, int num2)
  4. {
  5. /* local variable declaration */
  6. int result;
  7.  
  8. if (num1 > num2)
  9. result = num1;
  10. else
  11. result = num2;
  12.  
  13. return result;
  14. }
  15. ...
  16. }

在 C# 中调用方法

你可以使用方法的名称来调用方法。下面的示例说明了这一点:

  1. using System;
  2. namespace CalculatorApplication
  3. {
  4. class NumberManipulator
  5. {
  6. public int FindMax(int num1, int num2)
  7. {
  8. /* local variable declaration */
  9. int result;
  10.  
  11. if (num1 > num2)
  12. result = num1;
  13. else
  14. result = num2;
  15. return result;
  16. }
  17. static void Main(string[] args)
  18. {
  19. /* local variable definition */
  20. int a = 100;
  21. int b = 200;
  22. int ret;
  23. NumberManipulator n = new NumberManipulator();
  24.  
  25. //calling the FindMax method
  26. ret = n.FindMax(a, b);
  27. Console.WriteLine("Max value is : {0}", ret );
  28. Console.ReadLine();
  29. }
  30. }
  31. }

编译执行上述代码,得到如下结果:

  1. Max value is : 200

你也可以通过使用类的实例来从其他类中调用公开方法。

例如,FindMax 它属于 NumberManipulator 类中的方法,你可以从另一个类测试中调用它。

  1. using System;
  2. namespace CalculatorApplication
  3. {
  4. class NumberManipulator
  5. {
  6. public int FindMax(int num1, int num2)
  7. {
  8. /* local variable declaration */
  9. int result;
  10.  
  11. if(num1 > num2)
  12. result = num1;
  13. else
  14. result = num2;
  15.  
  16. return result;
  17. }
  18. }
  19.  
  20. class Test
  21. {
  22. static void Main(string[] args)
  23. {
  24. /* local variable definition */
  25. int a = 100;
  26. int b = 200;
  27. int ret;
  28. NumberManipulator n = new NumberManipulator();
  29.  
  30. //calling the FindMax method
  31. ret = n.FindMax(a, b);
  32. Console.WriteLine("Max value is : {0}", ret );
  33. Console.ReadLine();
  34. }
  35. }
  36. }

编译执行上述代码,得到如下结果:

  1. Max value is : 200

递归方法调用

有一种方法可以调用本身。这就是所谓的递归。下面是使用递归函数计算一个给定数字阶乘的示例:

  1. using System;
  2. namespace CalculatorApplication
  3. {
  4. class NumberManipulator
  5. {
  6. public int factorial(int num)
  7. {
  8. /* local variable declaration */
  9. int result;
  10. if (num == 1)
  11. {
  12. return 1;
  13. }
  14. else
  15. {
  16. result = factorial(num - 1) * num;
  17. return result;
  18. }
  19. }
  20.  
  21. static void Main(string[] args)
  22. {
  23. NumberManipulator n = new NumberManipulator();
  24. //calling the factorial method
  25. Console.WriteLine("Factorial of 6 is : {0}", n.factorial(6));
  26. Console.WriteLine("Factorial of 7 is : {0}", n.factorial(7));
  27. Console.WriteLine("Factorial of 8 is : {0}", n.factorial(8));
  28. Console.ReadLine();
  29. }
  30. }
  31. }

编译执行上述代码,得到如下结果:

  1. Factorial of 6 is: 720
  2. Factorial of 7 is: 5040
  3. Factorial of 8 is: 40320

将参数传递给方法

当调用带参数的方法时,您需要将参数传递给该方法。有三种方法,可以将参数传递给方法:

方法 描述
值参数 此方法将参数的实际值复制到该函数的形参。在这种情况下,对该参数在函数内部所做的更改没有对参数产生影响。
引用参数 此方法将对实参的内存位置的引用复制到形参。这意味着对参数所做的更改会影响参数本身。
输出参数 这种方法有助于返回多个值。

可空类型

C#提供了一个特殊的数据类型,可空类型,可以在其中指定正常范围值,以及空 (null) 值。

例如,在一个可空 变量中,你可以从 -2,147,483,648 到 2,147,483,647 或空值中存储任意值。

同样,你可以指定 true,false 或 null 的 Nullable 变量。声明一个可空类型 (Nullable) 的语法如下:

  1. < data_type> ? <variable_name> = null;

下面的例子演示了使用可空数据类型:

  1. using System;
  2. namespace CalculatorApplication
  3. {
  4. class NullablesAtShow
  5. {
  6. static void Main(string[] args)
  7. {
  8. int? num1 = null;
  9. int? num2 = 45;
  10. double? num3 = new double?();
  11. double? num4 = 3.14157;
  12.  
  13. bool? boolval = new bool?();
  14.  
  15. // display the values
  16.  
  17. Console.WriteLine("Nullables at Show: {0}, {1}, {2}, {3}", num1, num2, num3, num4);
  18. Console.WriteLine("A Nullable boolean value: {0}", boolval);
  19. Console.ReadLine();
  20. }
  21. }
  22. }

编译运行上述代码,得到以下结果:

  1. Nullables at Show: , 45, , 3.14157
  2. A Nullable boolean value:

空合并运算符(??)

空合并运算符是用于空值类型和引用类型。它是用于一个操作数转换为另一种可为空的(或不为空)值类型的操作数,其中,隐式转换是可能的类型。

如果第一个操作数的值为 null,则该运算符返回第二个操作数的值,否则返回第一个操作数的值。下面的例子说明了这一点:

  1. using System;
  2. namespace CalculatorApplication
  3. {
  4. class NullablesAtShow
  5. {
  6. static void Main(string[] args)
  7. {
  8. double? num1 = null;
  9. double? num2 = 3.14157;
  10. double num3;
  11. num3 = num1 ?? 5.34;
  12. Console.WriteLine(" Value of num3: {0}", num3);
  13. num3 = num2 ?? 5.34;
  14. Console.WriteLine(" Value of num3: {0}", num3);
  15. Console.ReadLine();
  16. }
  17. }
  18. }

编译运行上述代码,得到以下结果:

  1. Value of num3: 5.34
  2. Value of num3: 3.14157

数组

数组存储一个大小固定的顺序集合中相同类型的元素。数组用于存储数据的集合,但我们通常认为数组是一个存储在连续的内存位置的相同类型的集合。

相反,声明单个变量,如 number0, number1, …, 和 number99,声明一个数组变量,如 numbers[0], numbers[1],…, 和 numbers[99] 表示单个变量。在数组的特定元素由一个索引进行访问。

所有数组都由连续的内存位置构成。最低的地址对应于第一元素,最高地址为最后一个元素地址。

image 图片 1.3 image

声明数组

要在 C# 中声明数组,可以使用下面的语法:

  1. datatype[] arrayName;

这里,

  • datatype 用于指定要被存储在数组中的元素的类型
  • 指定数组的大小
  • arrayName 指定数组的名称

例如,

  1. double[] balance;

初始化数组

声明没有在存储器初始化的数组。当数组变量初始化时,您可以赋值给数组。

数组是引用类型,所以需要使用 new 关键字来创建数组的一个实例。

例如,

  1. double[] balance = new double[10];

赋值数组

通过使用索引号,可以将值指派给单独的数组元素,比如:

  1. double[] balance = new double[10];
  2. balance[0] = 4500.0;

你可以在声明数组的同时给它赋值,如下:

  1. double[] balance = { 2340.0, 4523.69, 3421.0};

你也可以创建和初始化一个数组,如下:

  1. int [] marks = new int[5] { 99, 98, 92, 97, 95};

你也可以省略数组的长度,如下:

  1. int [] marks = new int[] { 99, 98, 92, 97, 95};

你可以将一个数组变量赋给另一个目标。这种情况,两个数组都指向同一内存地址。

  1. int [] marks = new int[] { 99, 98, 92, 97, 95};
  2. int[] score = marks;

当你创建一个数组时,C# 编译器初始化每个数组元素为数组类型的默认值。对于 int 数组的所有元素都初始化为 0。

访问数组元素

一个元素由索引数组名访问。这是通过放置在数组名后面的方括号里的元素索引完成的。例如:

  1. double salary = balance[9];

以下是一个例子,将使用所有上述三个概念即声明,分配和访问数组:

  1. using System;
  2. namespace ArrayApplication
  3. {
  4. class MyArray
  5. {
  6. static void Main(string[] args)
  7. {
  8. int [] n = new int[10]; /* n is an array of 10 integers */
  9. int i,j;
  10.  
  11. /* initialize elements of array n */
  12. for ( i = 0; i < 10; i++ )
  13. {
  14. n[ i ] = i + 100;
  15. }
  16.  
  17. /* output each array element's value */
  18. for (j = 0; j < 10; j++ )
  19. {
  20. Console.WriteLine("Element[{0}] = {1}", j, n[j]);
  21. }
  22. Console.ReadKey();
  23. }
  24. }
  25. }

编译运行上述代码,得到以下结果:

  1. Element[0] = 100
  2. Element[1] = 101
  3. Element[2] = 102
  4. Element[3] = 103
  5. Element[4] = 104
  6. Element[5] = 105
  7. Element[6] = 106
  8. Element[7] = 107
  9. Element[8] = 108
  10. Element[9] = 109

使用 foreach 循环

在前面的例子中,我们已经使用了一个 for 循环用于访问每个数组元素。还可以使用 foreach 语句来遍历数组。

  1. using System;
  2. namespace ArrayApplication
  3. {
  4. class MyArray
  5. {
  6. static void Main(string[] args)
  7. {
  8. int [] n = new int[10]; /* n is an array of 10 integers */
  9.  
  10. /* initialize elements of array n */
  11. for ( int i = 0; i < 10; i++ )
  12. {
  13. n[i] = i + 100;
  14. }
  15.  
  16. /* output each array element's value */
  17. foreach (int j in n )
  18. {
  19. int i = j-100;
  20. Console.WriteLine("Element[{0}] = {1}", i, j);
  21. i++;
  22. }
  23. Console.ReadKey();
  24. }
  25. }
  26. }

编译运行上述代码,得到以下结果:

  1. Element[0] = 100
  2. Element[1] = 101
  3. Element[2] = 102
  4. Element[3] = 103
  5. Element[4] = 104
  6. Element[5] = 105
  7. Element[6] = 106
  8. Element[7] = 107
  9. Element[8] = 108
  10. Element[9] = 109

数组详解

数组在 C# 中是很重要的,应该需要很多更详细的解释。下列有关数组的几个重要概念,C# 程序员应当清楚:

概念 描述
多维数组 C# 支持多维数组。多维数组的最简单的形式是二维数组
锯齿状数组 C# 支持多维数组,这是数组的数组
通过数组到函数 可以通过指定数组的名称没有索引传递给函数的指针数组
参数数组 这是用于使未知数量的参数传到函数
Array 类 定义在系统命名空间中,它是基类所有的数组,并使用数组提供了各种属性和方法

字符串

在 C# 中,可以使用字符串作为字符数组,但更常见的做法是使用 string 关键字来声明一个字符串变量。该 string 关键字是 System.String 类的别名。

创建一个 String 对象

可以使用下列方法之一字符串对象:

  • 通过指定一个字符串给一个字符串变量
  • 通过使用 String 类的构造函数
  • 通过使用字符串连接运算符(+)
  • 通过检索属性或调用返回一个字符串的方法
  • 通过调用格式化方法的值或对象转换成它的字符串表示

下面的例子说明了这一点:

  1. using System;
  2. namespace StringApplication
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. //from string literal and string concatenation
  9. string fname, lname;
  10. fname = "Rowan";
  11. lname = "Atkinson";
  12.  
  13. string fullname = fname + lname;
  14. Console.WriteLine("Full Name: {0}", fullname);
  15.  
  16. //by using string constructor
  17. char[] letters = { 'H', 'e', 'l', 'l','o' };
  18. string greetings = new string(letters);
  19. Console.WriteLine("Greetings: {0}", greetings);
  20.  
  21. //methods returning string
  22. string[] sarray = { "Hello", "From", "Tutorials", "Point" };
  23. string message = String.Join(" ", sarray);
  24. Console.WriteLine("Message: {0}", message);
  25.  
  26. //formatting method to convert a value
  27. DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1);
  28. string chat = String.Format("Message sent at {0:t} on {0:D}", waiting);
  29. Console.WriteLine("Message: {0}", chat);
  30. }
  31. }
  32. }

编译执行上述代码,得到如下结果:

  1. Full Name: Rowan Atkinson
  2. Greetings: Hello
  3. Message: Hello From Tutorials Point
  4. Message: Message sent at 5:58 PM on Wednesday, October 10, 2012

String 类的属性

String 类有以下两个属性:

序号 属性名称和描述
1 Chars 获取在当前字符串对象中的指定位置的字符对象
2 Length 获取字符在当前字符串对象的数目

String 类的方法

String 类有许多方法,以帮助使用 String 对象。下表提供了一些最常用的方法:

序号 属性名称和描述
1 public static int Compare(string strA,string strB) 比较两个指定的字符串对象,并返回一个整数,指示其在排序顺序相对位置
2 public static int Compare(string strA,string strB,bool ignoreCase) 比较两个指定的字符串对象,并返回一个整数,指示其在排序顺序相对位置。但是,它忽略情况下,如果布尔参数为true
3 public static string Concat(string str0,string str1) 连接两个字符串对象
4 public static string Concat(string str0,string str1,string str2) 拼接三个字符串对象
5 public static string Concat(string str0,string str1,string str2,string str3) 符连接四个字符串对象
6 public bool Contains(string value) 返回一个值,指示指定的字符串对象是否发生此字符串中
7 public static string Copy(string str) 创建具有相同的值作为指定字符串的新String对象
8 public void CopyTo(int sourceIndex,char[] destination,int destinationIndex,int count) 复制从字符串对象到指定位置Unicode字符数组的指定位置指定的字符数
9 public bool EndsWith(string value) 确定字符串对象的末尾是否与指定的字符串匹配
10 public bool Equals(string value) 确定当前字符串对象,并指定字符串对象是否具有相同的值
11 public static bool Equals(string a,string b) 确定两个指定的String对象是否具有相同的值
12 public static string Format(string format,Object arg0) 替换指定的字符串指定对象的字符串表示在一个或多个格式项
13 public int IndexOf(char value) 返回当前字符串指定Unicode字符中第一次出现从零开始的索引
14 public int IndexOf(string value) 返回在这种情况下指定字符串中第一次出现从零开始的索引
15 public int IndexOf(char value,int startIndex) 返回此字符串指定Unicode字符中第一次出现从零开始的索引,搜索开始在指定的字符位置
16 public int IndexOf(string value,int startIndex) 返回在这种情况下指定字符串中第一次出现的从零开始的索引,搜索开始在指定的字符位置
17 public int IndexOfAny(char[] anyOf) 返回Unicode字符指定数组中第一次出现的任何字符的这个实例从零开始的索引
18 public int IndexOfAny(char[] anyOf,int startIndex) 返回Unicode字符指定数组,开始搜索从指定字符位置中第一次出现的任何字符的这个实例从零开始的索引
19 public string Insert(int startIndex,string value) 返回在指定的字符串被插入在当前字符串对象指定索引位置一个新的字符串
20 public static bool IsNullOrEmpty(string value) 指示指定的字符串是否为空或空字符串
21 public static string Join(string separator,params string[] value) 连接字符串数组中的所有元素,使用每个元件之间指定的分隔
22 public static string Join(string separator,string[] value,int startIndex,int count) 连接字符串数组的指定元素,利用每一个元素之间指定分隔符
23 public int LastIndexOf(char value) 返回当前字符串对象中指定的Unicode字符的最后出现从零开始的索引位置
24 public int LastIndexOf(string value) 返回当前字符串对象中的指定字符串最后一次出现的从零开始的索引位置
25 public string Remove(int startIndex) 删除在当前实例中的所有字符,开始在指定的位置,并继续通过最后位置,并返回字符串
26 public string Remove(int startIndex,int count) 删除在当前字符串的字符开始的指定位置的指定数量,并返回字符串
27 public string Replace(char oldChar,char newChar) 替换与指定的Unicode字符当前字符串对象指定的Unicode字符的所有匹配,并返回新的字符串
28 public string Replace(string oldValue,string newValue) 替换用指定的字符串当前字符串对象指定的字符串的所有匹配,并返回新的字符串
29 public string[] Split(params char[] separator) 返回一个字符串数组,其中包含的子字符串在当前字符串对象,由指定的Unicode字符数组的元素分隔
30 public string[] Split(char[] separator,int count) 返回一个字符串数组,其中包含的子字符串在当前字符串对象,由指定的Unicode字符数组的元素分隔。整型参数指定的子串返回最大数量
31 public bool StartsWith(string value) 确定此字符串实例的开头是否与指定的字符串匹配
32 public char[] ToCharArray() 返回一个Unicode字符数组,在当前字符串对象中的所有字符
33 public char[] ToCharArray(int startIndex,int length) 返回一个Unicode字符数组,在当前字符串对象中的所有字符,从指定的索引开始,并到指定的长度
34 public string ToLower() 返回此字符串的一个副本转换为小写
35 public string ToUpper() 返回此字符串的一个副本转换为大写
36 public string Trim() 从当前String对象去除所有开头和结尾的空白字符

方法上述名单并不是详尽的信息,请访问MSDN库的方法和String类的构造函数的完整列表。

示例:

下面的例子说明了一些上面提到的方法:

比较字符串:

  1. using System;
  2. namespace StringApplication
  3. {
  4. class StringProg
  5. {
  6. static void Main(string[] args)
  7. {
  8. string str1 = "This is test";
  9. string str2 = "This is text";
  10.  
  11. if (String.Compare(str1, str2) == 0)
  12. {
  13. Console.WriteLine(str1 + " and " + str2 + " are equal.");
  14. }
  15. else
  16. {
  17. Console.WriteLine(str1 + " and " + str2 + " are not equal.");
  18. }
  19. Console.ReadKey() ;
  20. }
  21. }
  22. }

编译执行上述代码,得到如下结果:

  1. This is test and This is text are not equal.

String包含字符串:

  1. using System;
  2. namespace StringApplication
  3. {
  4. class StringProg
  5. {
  6. static void Main(string[] args)
  7. {
  8. string str = "This is test";
  9. if (str.Contains("test"))
  10. {
  11. Console.WriteLine("The sequence 'test' was found.");
  12. }
  13. Console.ReadKey() ;
  14. }
  15. }
  16. }

编译执行上述代码,得到如下结果:

  1. The sequence 'test' was found.

获取一个子字符串:

  1. using System;
  2. namespace StringApplication
  3. {
  4. class StringProg
  5. {
  6. static void Main(string[] args)
  7. {
  8. string str = "Last night I dreamt of San Pedro";
  9. Console.WriteLine(str);
  10. string substr = str.Substring(23);
  11. Console.WriteLine(substr);
  12. }
  13. }
  14. }

编译执行上述代码,得到如下结果:

  1. San Pedro

连接字符串:

  1. using System;
  2. namespace StringApplication
  3. {
  4. class StringProg
  5. {
  6. static void Main(string[] args)
  7. {
  8. string[] starray = new string[]{"Down the way nights are dark",
  9. "And the sun shines daily on the mountain top",
  10. "I took a trip on a sailing ship",
  11. "And when I reached Jamaica",
  12. "I made a stop"};
  13.  
  14. string str = String.Join("\n", starray);
  15. Console.WriteLine(str);
  16. }
  17. }
  18. }

编译执行上述代码,得到如下结果:

  1. Down the way nights are dark
  2. And the sun shines daily on the mountain top
  3. I took a trip on a sailing ship
  4. And when I reached Jamaica
  5. I made a stop

结构体

在 C# 中,结构体是一种值数据类型。包含数据成员和方法成员。 struct 关键字是用于创建一个结构体。

结构体是用来代表一个记录。假设你想追踪一个图书馆的书。你可能想追踪每本书的属性如下:

  • 标题
  • 作者
  • 类别
  • 书号

定义一个结构体

定义一个结构体,你必须要声明这个结构体。结构体声明定义了一种新的数据类型,这个数据类型为你的程序包含了一个以上的成员变量。

例如,你可以声明一个书的结构如下:

  1. struct Books
  2. {
  3. public string title;
  4. public string author;
  5. public string subject;
  6. public int book_id;
  7. };

下面的程序显示了结构体的用法:

  1. using System;
  2. struct Books
  3. {
  4. public string title;
  5. public string author;
  6. public string subject;
  7. public int book_id;
  8. };
  9. public class testStructure
  10. {
  11. public static void Main(string[] args)
  12. {
  13. Books Book1; /* 将 Book1 声明为 Book 类型 */
  14. Books Book2; /* 将 Book2 声明为 Book 类型 */
  15. /* book 1 specification */
  16. Book1.title = "C Programming";
  17. Book1.author = "Nuha Ali";
  18. Book1.subject = "C Programming Tutorial";
  19. Book1.book_id = 6495407;
  20. /* book 2 详细数据 */
  21. Book2.title = "Telecom Billing";
  22. Book2.author = "Zara Ali";
  23. Book2.subject = "Telecom Billing Tutorial";
  24. Book2.book_id = 6495700;
  25. /* 打印 Book1 信息 */
  26. Console.WriteLine( "Book 1 title : {0}", Book1.title);
  27. Console.WriteLine("Book 1 author : {0}", Book1.author);
  28. Console.WriteLine("Book 1 subject : {0}", Book1.subject);
  29. Console.WriteLine("Book 1 book_id :{0}", Book1.book_id);
  30. /* 打印 Book2 信息 */
  31. Console.WriteLine("Book 2 title : {0}", Book2.title);
  32. Console.WriteLine("Book 2 author : {0}", Book2.author);
  33. Console.WriteLine("Book 2 subject : {0}", Book2.subject);
  34. Console.WriteLine("Book 2 book_id : {0}", Book2.book_id);
  35. Console.ReadKey();
  36. }
  37. }

编译执行上述代码,得到如下结果:

  1. Book 1 title : C Programming
  2. Book 1 author : Nuha Ali
  3. Book 1 subject : C Programming Tutorial
  4. Book 1 book_id : 6495407
  5. Book 2 title : Telecom Billing
  6. Book 2 author : Zara Ali
  7. Book 2 subject : Telecom Billing Tutorial
  8. Book 2 book_id : 6495700

结构体的特征

你已经使用了一个名为 Books 的简单结构体。C# 中的结构体与传统的 C 或者 C++ 有明显的不同。 C# 中的结构体有以下特征:

  • 结构体可以有方法,域,属性,索引器,操作方法,和事件。
  • 结构体可以定义构造函数,但是不能构造析构函数。尽管如此,你还是不能定义一个结构体的默认构造函数。默认构造函数是自动定义的,且不能被改变。
  • 与类不同,结构体不能继承其他的结构体或这其他的类。
  • 结构体不能用于作为其他结构或者类的基。
  • 结构体可以实现一个或多个接口。
  • 结构体成员不能被指定为抽象的,虚拟的,或者保护的对象。
  • 使用 New 运算符创建结构体对象时,将创建该结构体对象,并且调用适当的构造函数。与类不同的是,结构体的实例化可以不使用 New 运算符。
  • 如果不使用 New 操作符,那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用。

类和结构体

类和结构体有以下几个主要的区别:

  • 类是引用类型,结构体是值类型
  • 结构体不支持继承
  • 结构体不能有默认构造函数

针对上述讨论,让我们重写前面的例子:

  1. using System;
  2. struct Books
  3. {
  4. private string title;
  5. private string author;
  6. private string subject;
  7. private int book_id;
  8. public void getValues(string t, string a, string s, int id)
  9. {
  10. title = t;
  11. author = a;
  12. subject = s;
  13. book_id = id;
  14. }
  15. public void display()
  16. {
  17. Console.WriteLine("Title : {0}", title);
  18. Console.WriteLine("Author : {0}", author);
  19. Console.WriteLine("Subject : {0}", subject);
  20. Console.WriteLine("Book_id :{0}", book_id);
  21. }
  22. };
  23. public class testStructure
  24. {
  25. public static void Main(string[] args)
  26. {
  27. Books Book1 = new Books(); /* 将 Book1 声明为 Book 类型 */
  28. Books Book2 = new Books(); /* 将 Book2 声明为 Book 类型 */
  29. /* book 1 详细信息 */
  30. Book1.getValues("C Programming",
  31. "Nuha Ali", "C Programming Tutorial",6495407);
  32. /* book 2 详细信息 */
  33. Book2.getValues("Telecom Billing",
  34. "Zara Ali", "Telecom Billing Tutorial", 6495700);
  35. /* 打印 Book1 信息 */
  36. Book1.display();
  37. /* 打印 Book2 信息 */
  38. Book2.display();
  39. Console.ReadKey();
  40. }
  41. }

编译执行上述代码,得到如下结果:

  1. Title : C Programming
  2. Author : Nuha Ali
  3. Subject : C Programming Tutorial
  4. Book_id : 6495407
  5. Title : Telecom Billing
  6. Author : Zara Ali
  7. Subject : Telecom Billing Tutorial
  8. Book_id : 6495700

枚举

枚举是一组命名的整型常量。枚举类型使用 enum 关键字声明。

C# 枚举是值的数据类型。换句话说,枚举包含它自己的值,不能继承或被继承。

声明枚举变量

用于声明枚举的一般语法:

  1. enum <enum_name>
  2. {
  3. enumeration list
  4. };

这里

  • enum_name 指定枚举类型名称。
  • enumeration list 是一个逗号分隔的标识符的列表。

每个枚举列表中的符号表示整数值,比它前面的符号一个更大。

缺省情况下,第一枚举符号的值是 0。

例如:

  1. enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

示例

下面的例子演示了使用枚举变量:

  1. using System;
  2. namespace EnumApplication
  3. {
  4. class EnumProgram
  5. {
  6. enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
  7.  
  8. static void Main(string[] args)
  9. {
  10. int WeekdayStart = (int)Days.Mon;
  11. int WeekdayEnd = (int)Days.Fri;
  12. Console.WriteLine("Monday: {0}", WeekdayStart);
  13. Console.WriteLine("Friday: {0}", WeekdayEnd);
  14. Console.ReadKey();
  15. }
  16. }
  17. }

编译和执行上述代码,得到如下结果:

  1. Monday: 1
  2. Friday: 5

当你定义一个类,你实际上定义的是一个数据类型的蓝图。实际上你并没有定义任何数据,但是它定义了类的名字意味着什么。也就是说,一个类的对象由一些可以在该类上进行的操作构成。对象是类的实例。构成类的方法和变量被称为类的成员。

定义一个类

一个类定义以关键字 class 开始,其后跟的是类的名称;类的主体部分体由一对花括号括起来。以下是一个类定义的一般形式:

  1. class class_name
  2. {
  3. // 成员变量
  4. variable1;
  5. variable2;
  6. ...
  7. variableN;
  8. // 成员变量
  9. method1(parameter_list)
  10. {
  11. // 方法主体
  12. }
  13. method2(parameter_list)
  14. {
  15. // 方法主体
  16. }
  17. ...
  18. methodN(parameter_list)
  19. {
  20. // 方法主体
  21. }
  22. }

笔记:

  • 访问说明符的访问成员的规则与类本身的访问规则相同。如果没有说明,则默认访问的类的类型为 internal 。对成员的默认访问类型是 private 。

  • 数据类型指定了变量的类型,如果有返回值的话,返回指定方法的数据类型。

  • 访问类的成员,可以使用 "." 点运算符。

  • 点运算符连接的成员的名字和对象的名称。

下面的例子说明了到目前为止对于概念的讨论:

  1. using System;
  2. namespace BoxApplication
  3. {
  4. class Box
  5. {
  6. public double length; // box 的长度
  7. public double breadth; // box 的宽度
  8. public double height; // box 的高度
  9. }
  10. class Boxtester
  11. {
  12. static void Main(string[] args)
  13. {
  14. Box Box1 = new Box(); // 声明 box1 为 box 类型
  15. Box Box2 = new Box(); // 声明 box2 为 box 类型
  16. double volume = 0.0; // 在这里存放 box 的体积
  17. // box 1 详细数据
  18. Box1.height = 5.0;
  19. Box1.length = 6.0;
  20. Box1.breadth = 7.0;
  21. // box 2 详细数据
  22. Box2.height = 10.0;
  23. Box2.length = 12.0;
  24. Box2.breadth = 13.0;
  25. // box 1 的体积
  26. volume = Box1.height * Box1.length * Box1.breadth;
  27. Console.WriteLine("Volume of Box1 : {0}", volume);
  28. // box 2 的体积
  29. volume = Box2.height * Box2.length * Box2.breadth;
  30. Console.WriteLine("Volume of Box2 : {0}", volume);
  31. Console.ReadKey();
  32. }
  33. }
  34. }

编译执行上述代码,得到如下结果:

  1. Volume of Box1 : 210
  2. Volume of Box2 : 1560

成员函数和封装

一个类的成员函数有其自己定义的或其原型写在类体中。它可以操作该类的任何成员对象,并且可以访问一个类的所有成员。

成员变量是一个对象的属性(从设计的角度来看),他们都是私有(private)的以便实施封装。这些变量只能被 public 成员函数访问。

让我们把上述那些概念来 set 和 get 一个类中不同的类成员的值:

  1. using System;
  2. namespace BoxApplication
  3. {
  4. class Box
  5. {
  6. private double length; // box 的长度
  7. private double breadth; // box 的宽度
  8. private double height; // box 的高度
  9. public void setLength( double len )
  10. {
  11. length = len;
  12. }
  13. public void setBreadth( double bre )
  14. {
  15. breadth = bre;
  16. }
  17. public void setHeight( double hei )
  18. {
  19. height = hei;
  20. }
  21. public double getVolume()
  22. {
  23. return length * breadth * height;
  24. }
  25. }
  26. class Boxtester
  27. {
  28. static void Main(string[] args)
  29. {
  30. Box Box1 = new Box(); // 将 Box1 声明为 Box 类型
  31. Box Box2 = new Box();
  32. double volume;
  33. // 将 Box2 声明为 Box 类型
  34. // box 1 详细数据
  35. Box1.setLength(6.0);
  36. Box1.setBreadth(7.0);
  37. Box1.setHeight(5.0);
  38. // box 2 详细数据
  39. Box2.setLength(12.0);
  40. Box2.setBreadth(13.0);
  41. Box2.setHeight(10.0);
  42. // box 1 的体积
  43. volume = Box1.getVolume();
  44. Console.WriteLine("Volume of Box1 : {0}" ,volume);
  45. // box 2 的体积
  46. volume = Box2.getVolume();
  47. Console.WriteLine("Volume of Box2 : {0}", volume);
  48. Console.ReadKey();
  49. }
  50. }
  51. }

编译执行上述代码,得到如下结果:

  1. Volume of Box1 : 210
  2. Volume of Box2 : 1560

C# 构造函数

一个类的构造函数(constructor)是类的一种特殊的成员函数,当我们创建一个类的新的对象时执行该函数。

构造函数与类具有相同的名称,但它没有任何返回类型。下面的例子说明了构造函数的概念:

  1. using System;
  2. namespace LineApplication
  3. {
  4. class Line
  5. {
  6. private double length; // 线段长度
  7. public Line()
  8. {
  9. Console.WriteLine("Object is being created");
  10. }
  11. public void setLength( double len )
  12. {
  13. length = len;
  14. }
  15. public double getLength()
  16. {
  17. return length;
  18. }
  19. static void Main(string[] args)
  20. {
  21. Line line = new Line();
  22. // 设置线段长度
  23. line.setLength(6.0);
  24. Console.WriteLine("Length of line : {0}", line.getLength());
  25. Console.ReadKey();
  26. }
  27. }
  28. }

编译执行上述代码,得到如下结果:

  1. Object is being created
  2. Length of line : 6

默认构造函数(default constructor)没有任何的参数,但是如果你需要,构造函数是可以有参数的。这种构造函数被称为参数化的构造函数(parameterized constructors)。这种技术有助于你在一个对象被创建时指定它的初始值,如下述示例:

  1. using System;
  2. namespace LineApplication
  3. {
  4. class Line
  5. {
  6. private double length; // 线段长度
  7. public Line(double len) //参数化构造函数
  8. {
  9. Console.WriteLine("Object is being created, length = {0}", len);
  10. length = len;
  11. }
  12. public void setLength( double len )
  13. {
  14. length = len;
  15. }
  16. public double getLength()
  17. {
  18. return length;
  19. }
  20. static void Main(string[] args)
  21. {
  22. Line line = new Line(10.0);
  23. Console.WriteLine("Length of line : {0}", line.getLength());
  24. // 设置线段长度
  25. line.setLength(6.0);
  26. Console.WriteLine("Length of line : {0}", line.getLength());
  27. Console.ReadKey();
  28. }
  29. }
  30. }

编译执行上述代码,得到如下结果:

  1. Object is being created
  2. Length of line : 6
  3. Object is being deleted

C# 析构函数

析构函数(destructor)是类的一种特殊的成员函数,当类的对象在超出作用域时被执行的一种成员函数。一个析构函数的名称是在其类名称前加上一个前缀字符(~),它既不能返回一个值,也不能带有任何参数。

析构函数对退出程序前释放内存资源时非常有用。析构函数不可以被继承或重载。

下面的示例解释了析构函数的概念:

  1. using System;
  2. namespace LineApplication
  3. {
  4. class Line
  5. {
  6. private double length; // 线段长度
  7. public Line() // 构造函数
  8. {
  9. Console.WriteLine("Object is being created");
  10. }
  11. ~Line() //析构函数
  12. {
  13. Console.WriteLine("Object is being deleted");
  14. }
  15. public void setLength( double len )
  16. {
  17. length = len;
  18. }
  19. public double getLength()
  20. {
  21. return length;
  22. }
  23. static void Main(string[] args)
  24. {
  25. Line line = new Line();
  26. // 设置线段长度
  27. line.setLength(6.0);
  28. Console.WriteLine("Length of line : {0}", line.getLength());
  29. }
  30. }
  31. }

编译执行上述代码,得到如下结果:

  1. Object is being created
  2. Length of line : 6
  3. Object is being deleted

C# 类的静态成员

我们可以使用 static 关键字将类成员定义为静态的。当我们声明一个类的静态成员时,意味着无论有多少类的对象被创建,只有一个副本的静态成员。

关键字 static 意味着一个类的成员只有一个实例存在。静态变量被用于定义常数,因为他们的值可以通过调用不创建实例的类而被检索出来。静态变量可以在成员函数或者类的定义以外的地方初始化。你也可以在类的定义中初始化静态变量。

下面的示例论证了静态变量(static variables)的使用:

  1. using System;
  2. namespace StaticVarApplication
  3. {
  4. class StaticVar
  5. {
  6. public static int num;
  7. public void count()
  8. {
  9. num++;
  10. }
  11. public int getNum()
  12. {
  13. return num;
  14. }
  15. }
  16. class StaticTester
  17. {
  18. static void Main(string[] args)
  19. {
  20. StaticVar s1 = new StaticVar();
  21. StaticVar s2 = new StaticVar();
  22. s1.count();
  23. s1.count();
  24. s1.count();
  25. s2.count();
  26. s2.count();
  27. s2.count();
  28. Console.WriteLine("Variable num for s1: {0}", s1.getNum());
  29. Console.WriteLine("Variable num for s2: {0}", s2.getNum());
  30. Console.ReadKey();
  31. }
  32. }
  33. }

编译执行上述代码,得到如下结果:

  1. Variable num for s1: 6
  2. Variable num for s2: 6

你也可以声明 static成员函数。此类函数只能访问静态变量。静态函数的存在甚至先于创建对象。下面的示例论证了静态函数(static functions)的用法:

  1. using System;
  2. namespace StaticVarApplication
  3. {
  4. class StaticVar
  5. {
  6. public static int num;
  7. public void count()
  8. {
  9. num++;
  10. }
  11. public static int getNum()
  12. {
  13. return num;
  14. }
  15. }
  16. class StaticTester
  17. {
  18. static void Main(string[] args)
  19. {
  20. StaticVar s = new StaticVar();
  21. s.count();
  22. s.count();
  23. s.count();
  24. Console.WriteLine("Variable num: {0}", StaticVar.getNum());
  25. Console.ReadKey();
  26. }
  27. }
  28. }

编译执行上述代码,得到如下结果:

  1. Variable num: 3

继承

面向对象程序设计中最重要的一个概念就是继承(inheritance)。继承允许我们在另一个类中定义一个新的类,这使得它更容易创建和维护一个应用程序。这也提供了一个机会来重用代码的功能,加快实现时间。

创建一个类的时候,不是要写全新的数据成员和成员函数,程序员可以指定新的类继承一个已经存在的类的成员。已有的类称为基类(base class),新的类称为派生类(derived class)

继承的思想实现了 IS-A 的关系。例如,哺乳动物是(IS-A)动物,狗是(IS-A)哺乳动物,因此狗是(IS-A)一个动物等。

基类和派生类

一个类可以从多个类或接口被派生,这意味着它可以从多个基类或接口继承数据和函数。

用 C# 创建派生类的语法如下:

  1. <acess-specifier> class <base_class>
  2. {
  3. ...
  4. }
  5. class <derived_class> : <base_class>
  6. {
  7. ...
  8. }

比如基类为 Shape ,其派生类为 Rectangle :

  1. using System;
  2. namespace InheritanceApplication
  3. {
  4. class Shape
  5. {
  6. public void setWidth(int w)
  7. {
  8. width = w;
  9. }
  10. public void setHeight(int h)
  11. {
  12. height = h;
  13. }
  14. protected int width;
  15. protected int height;
  16. }
  17. // 派生类
  18. class Rectangle: Shape
  19. {
  20. public int getArea()
  21. {
  22. return (width * height);
  23. }
  24. }
  25. class RectangleTester
  26. {
  27. static void Main(string[] args)
  28. {
  29. Rectangle Rect = new Rectangle();
  30. Rect.setWidth(5);
  31. Rect.setHeight(7);
  32. // 打印对象的面积
  33. Console.WriteLine("Total area: {0}", Rect.getArea());
  34. Console.ReadKey();
  35. }
  36. }
  37. }

编译执行上述代码,得到如下结果:

  1. Total area: 35

初始化基类

派生类继承基类的成员变量和成员方法。因此,父类对象应该是先于子类被创建。你可以在初始化列表中说明父类的初始化。

下面的程序论证了上述方法:

  1. using System;
  2. namespace RectangleApplication
  3. {
  4. class Rectangle
  5. {
  6. //成员变量
  7. protected double length;
  8. protected double width;
  9. public Rectangle(double l, double w)
  10. {
  11. length = l;
  12. width = w;
  13. }
  14. public double GetArea()
  15. {
  16. return length * width;
  17. }
  18. public void Display()
  19. {
  20. Console.WriteLine("Length: {0}", length);
  21. Console.WriteLine("Width: {0}", width);
  22. Console.WriteLine("Area: {0}", GetArea());
  23. }
  24. }// Rectangle 类结束
  25. class Tabletop : Rectangle
  26. {
  27. private double cost;
  28. public Tabletop(double l, double w) : base(l, w)
  29. { }
  30. public double GetCost()
  31. {
  32. double cost;
  33. cost = GetArea() * 70;
  34. return cost;
  35. }
  36. public void Display()
  37. {
  38. base.Display();
  39. Console.WriteLine("Cost: {0}", GetCost());
  40. }
  41. }
  42. class ExecuteRectangle
  43. {
  44. static void Main(string[] args)
  45. {
  46. Tabletop t = new Tabletop(4.5, 7.5);
  47. t.Display();
  48. Console.ReadLine();
  49. }
  50. }
  51. }

编译执行上述代码,得到如下结果:

  1. Length: 4.5
  2. Width: 7.5
  3. Area: 33.75
  4. Cost: 2362.5

C# 中的多重继承

C# 不支持多重继承。但是你可以使用接口来实现多重继承。下面的程序实现了该功能:

  1. using System;
  2. namespace InheritanceApplication
  3. {
  4. class Shape
  5. {
  6. public void setWidth(int w)
  7. {
  8. width = w;
  9. }
  10. public void setHeight(int h)
  11. {
  12. height = h;
  13. }
  14. protected int width;
  15. protected int height;
  16. }
  17. // 基类 PaintCost
  18. public interface PaintCost
  19. {
  20. int getCost(int area);
  21. }
  22. // 派生类
  23. class Rectangle : Shape, PaintCost
  24. {
  25. public int getArea()
  26. {
  27. return (width * height);
  28. }
  29. public int getCost(int area)
  30. {
  31. return area * 70;
  32. }
  33. }
  34. class RectangleTester
  35. {
  36. static void Main(string[] args)
  37. {
  38. Rectangle Rect = new Rectangle();
  39. int area;
  40. Rect.setWidth(5);
  41. Rect.setHeight(7);
  42. area = Rect.getArea();
  43. //打印对象面积
  44. Console.WriteLine("Total area: {0}", Rect.getArea());
  45. Console.WriteLine("Total paint cost: ${0}" , Rect.getCost(area));
  46. Console.ReadKey();
  47. }
  48. }
  49. }

编译执行上述代码,得到如下结果:

  1. Total area: 35
  2. Total paint cost: $2450

多态性

多态性(polymorphism) 这个词意味着有多种形式。在面向对象的编程范式中,多态性往往表现为“一个接口,多个函数”。

多态性可以是静态的,也可以是动态的。在 静态多态(static polymorphism)性 中,一个函数的响应是在编译时确定。动态多态性( dynamic polymorphism) 中,其函数响应是在运行时决定。

静态多态性

在编译时将一个函数与一个对象连接起来的机制被称为早期绑定机制。它也被称为静态绑定。C # 提供了两种技术实现静态多态性。他们是:

  • 函数重载
  • 运算符重载

我们将在下一章讨论运算符重载。

函数重载

你可以在同一范围对同一函数名有多重定义。该函数的定义必须用不同的类型或通过参数列表中的参数的数量进行区分。不可以只用不同的返回类型区分不同的重载函数声明。

下面的示例显示使用函数 print() 打印不同的数据类型:

  1. using System;
  2. namespace PolymorphismApplication
  3. {
  4. class Printdata
  5. {
  6. void print(int i)
  7. {
  8. Console.WriteLine("Printing int: {0}", i );
  9. }
  10. void print(double f)
  11. {
  12. Console.WriteLine("Printing float: {0}" , f);
  13. }
  14. void print(string s)
  15. {
  16. Console.WriteLine("Printing string: {0}", s);
  17. }
  18. static void Main(string[] args)
  19. {
  20. Printdata p = new Printdata();
  21. // 调用 print 函数打印整型
  22. p.print(5);
  23. // 调用 print 函数打印浮点型
  24. p.print(500.263);
  25. // 调用 print 函数打印字符型
  26. p.print("Hello C++");
  27. Console.ReadKey();
  28. }
  29. }
  30. }

编译执行上述代码,得到如下结果:

  1. Printing int: 5
  2. Printing float: 500.263
  3. Printing string: Hello C++

动态多态性

C# 允许你创建一个抽象类,被用于提供部分类的接口实现。执行完成时,派生类继承它。抽象类(Abstract classes) 包含抽象方法,这是由派生类来实现的。派生类具有更具体化,专业化的功能。

以下是关于抽象类的规则:

  • 你不能创建抽象类的实例
  • 你不能在抽象类的外部声明抽象方法
  • 当一个类声明为 密封的(sealed) ,它不能被继承,抽象类被不能声明为密封的。

下面的程序演示了一个抽象类:

  1. using System;
  2. namespace PolymorphismApplication
  3. {
  4. abstract class Shape
  5. {
  6. public abstract int area();
  7. }
  8. class Rectangle: Shape
  9. {
  10. private int length;
  11. private int width;
  12. public Rectangle( int a=0, int b=0)
  13. {
  14. length = a;
  15. width = b;
  16. }
  17. public override int area ()
  18. {
  19. Console.WriteLine("Rectangle class area :");
  20. return (width * length);
  21. }
  22. }
  23. class RectangleTester
  24. {
  25. static void Main(string[] args)
  26. {
  27. Rectangle r = new Rectangle(10, 7);
  28. double a = r.area();
  29. Console.WriteLine("Area: {0}",a);
  30. Console.ReadKey();
  31. }
  32. }
  33. }

编译执行上述代码,得到如下结果:

  1. Rectangle class area :
  2. Area: 70

当你在一个类中有定义函数,并且希望它可以在一个被继承的类中实现功能时,你可以使用虚函数(virtual functions) 。虚函数可以在不同的被继承的类中实现,并且将在程序运行时调用此类函数。

动态多样性是通过抽象类虚函数实现的。

下列程序证实了上述说法:

  1. using System;
  2. namespace PolymorphismApplication
  3. {
  4. class Shape
  5. {
  6. protected int width, height;
  7. public Shape( int a=0, int b=0)
  8. {
  9. width = a;
  10. height = b;
  11. }
  12. public virtual int area()
  13. {
  14. Console.WriteLine("Parent class area :");
  15. return 0;
  16. }
  17. }
  18. class Rectangle: Shape
  19. {
  20. public Rectangle( int a=0, int b=0): base(a, b)
  21. {
  22. }
  23. public override int area ()
  24. {
  25. Console.WriteLine("Rectangle class area :");
  26. return (width * height);
  27. }
  28. }
  29. class Triangle: Shape
  30. {
  31. public Triangle(int a = 0, int b = 0): base(a, b)
  32. {
  33. }
  34. public override int area()
  35. {
  36. Console.WriteLine("Triangle class area :");
  37. return (width * height / 2);
  38. }
  39. }
  40. class Caller
  41. {
  42. public void CallArea(Shape sh)
  43. {
  44. int a;
  45. a = sh.area();
  46. Console.WriteLine("Area: {0}", a);
  47. }
  48. }
  49. class Tester
  50. {
  51. static void Main(string[] args)
  52. {
  53. Caller c = new Caller();
  54. Rectangle r = new Rectangle(10, 7);
  55. Triangle t = new Triangle(10, 5);
  56. c.CallArea(r);
  57. c.CallArea(t);
  58. Console.ReadKey();
  59. }
  60. }
  61. }

编译执行上述代码,得到如下结果:

  1. Rectangle class area:
  2. Area: 70
  3. Triangle class area:
  4. Area: 25

运算符重载

你可以重新定义或重载大部分 C# 可用的内置操作符。因此,程序员也可以使用用户定义类型的操作符。重载操作符是特殊关键字 operator 其后跟被定义的名字的符号。像其他函数一样,重载操作符也有返回类型和参数列表。

例如,浏览如下函数:

  1. public static Box operator+ (Box b, Box c)
  2. {
  3. Box box = new Box();
  4. box.length = b.length + c.length;
  5. box.breadth = b.breadth + c.breadth;
  6. box.height = b.height + c.height;
  7. return box;
  8. }

上述函数实现了用户定义的 Box 类中加运算符,它增加了两个 Box 对象,并返回这个结果 Box 对象。

实现运算符重载

下列程序显示了完整地实现:

  1. using System;
  2. namespace OperatorOvlApplication
  3. {
  4. class Box
  5. {
  6. private double length; // box 的长度
  7. private double breadth; // box 的宽度
  8. private double height; // box 的高度
  9. public double getVolume()
  10. {
  11. return length * breadth * height;
  12. }
  13. public void setLength( double len )
  14. {
  15. length = len;
  16. }
  17. public void setBreadth( double bre )
  18. {
  19. breadth = bre;
  20. }
  21. public void setHeight( double hei )
  22. {
  23. height = hei;
  24. }
  25. // 重载 + operator 来增加两个 Box 对象
  26. public static Box operator+ (Box b, Box c)
  27. {
  28. Box box = new Box();
  29. box.length = b.length + c.length;
  30. box.breadth = b.breadth + c.breadth;
  31. box.height = b.height + c.height;
  32. return box;
  33. }
  34. }
  35. class Tester
  36. {
  37. static void Main(string[] args)
  38. {
  39. Box Box1 = new Box(); // 声明 box1 为 box 类型
  40. Box Box2 = new Box(); // 声明 box2 为 box 类型
  41. Box Box3 = new Box(); // 声明 box3 为 box 类型
  42. double volume = 0.0; // 在这里存放 box 的体积
  43. // box 1 详细数据
  44. Box1.setLength(6.0);
  45. Box1.setBreadth(7.0);
  46. Box1.setHeight(5.0);
  47. // box 2 详细数据
  48. Box2.setLength(12.0);
  49. Box2.setBreadth(13.0);
  50. Box2.setHeight(10.0);
  51. // box 1 的体积
  52. volume = Box1.getVolume();
  53. Console.WriteLine("Volume of Box1 : {0}", volume);
  54. // box 2 的体积
  55. volume = Box2.getVolume();
  56. Console.WriteLine("Volume of Box2 : {0}", volume);
  57. // 将两个对象相加如下:
  58. Box3 = Box1 + Box2;
  59. // box 3 的体积
  60. volume = Box3.getVolume();
  61. Console.WriteLine("Volume of Box3 : {0}", volume);
  62. Console.ReadKey();
  63. }
  64. }
  65. }

编译执行上述代码,得到如下结果:

  1. Volume of Box1 : 210
  2. Volume of Box2 : 1560
  3. Volume of Box3 : 5400

可重载与不可重载的运算符

下表列出了 C# 中运算符的可重载能力:

操作符 描述
+, -, !, ~, ++, — 这些一元运算符使用一个操作数,可以被重载
+, -, , /, % 这些二元运算符使用一个操作数,可以被重载
==, !=, <, >, <=, >= 这些比较运算符,可以被重载
&&, | | 这些条件逻辑运算符不可被直接重载
+=, -=, =, /=, %= 赋值运算符不能重载
=, ., ?:, ->, new, is, sizeof, typeof 这些运算符不能被重载

示例

针对上述讨论,让我们扩展上面的例子,重载更多的操作符:

  1. using System;
  2. namespace OperatorOvlApplication
  3. {
  4. class Box
  5. {
  6. private double length; // box 的长度
  7. private double breadth; // box 的宽度
  8. private double height; // box 的高度
  9. public double getVolume()
  10. {
  11. return length * breadth * height;
  12. }
  13. public void setLength( double len )
  14. {
  15. length = len;
  16. }
  17. public void setBreadth( double bre )
  18. {
  19. breadth = bre;
  20. }
  21. public void setHeight( double hei )
  22. {
  23. height = hei;
  24. }
  25. // 重载 + operator 来增加两个 Box 对象
  26. public static Box operator+ (Box b, Box c)
  27. {
  28. Box box = new Box();
  29. box.length = b.length + c.length;
  30. box.breadth = b.breadth + c.breadth;
  31. box.height = b.height + c.height;
  32. return box;
  33. }
  34. public static bool operator == (Box lhs, Box rhs)
  35. {
  36. bool status = false;
  37. if (lhs.length == rhs.length && lhs.height == rhs.height && lhs.breadth == rhs.breadth)
  38. {
  39. status = true;
  40. }
  41. return status;
  42. }
  43. public static bool operator !=(Box lhs, Box rhs)
  44. {
  45. bool status = false;
  46. if (lhs.length != rhs.length || lhs.height != rhs.height || lhs.breadth != rhs.breadth)
  47. {
  48. status = true;
  49. }
  50. return status;
  51. }
  52. public static bool operator <(Box lhs, Box rhs)
  53. {
  54. bool status = false;
  55. if (lhs.length < rhs.length && lhs.height < rhs.height && lhs.breadth < rhs.breadth)
  56. {
  57. status = true;
  58. }
  59. return status;
  60. }
  61. public static bool operator >(Box lhs, Box rhs)
  62. {
  63. bool status = false;
  64. if (lhs.length > rhs.length && lhs.height > rhs.height && lhs.breadth > rhs.breadth)
  65. {
  66. status = true;
  67. }
  68. return status;
  69. }
  70. public static bool operator <=(Box lhs, Box rhs)
  71. {
  72. bool status = false;
  73. if (lhs.length <= rhs.length && lhs.height <= rhs.height && lhs.breadth <= rhs.breadth)
  74. {
  75. status = true;
  76. }
  77. return status;
  78. }
  79. public static bool operator >=(Box lhs, Box rhs)
  80. {
  81. bool status = false;
  82. if (lhs.length >= rhs.length && lhs.height >= rhs.height && lhs.breadth >= rhs.breadth)
  83. {
  84. status = true;
  85. }
  86. return status;
  87. }
  88. public override string ToString()
  89. {
  90. return String.Format("({0}, {1}, {2})", length, breadth, height);
  91. }
  92. }
  93. class Tester
  94. {
  95. static void Main(string[] args)
  96. {
  97. Box Box1 = new Box(); // 声明 box1 为 box 类型
  98. Box Box2 = new Box(); // 声明 box2 为 box 类型
  99. Box Box3 = new Box(); // 声明 box3 为 box 类型
  100. Box Box4 = new Box(); // 声明 box4 为 box 类型
  101. double volume = 0.0; // 这里存放 box 的体积
  102. // box 1 详细数据
  103. Box1.setLength(6.0);
  104. Box1.setBreadth(7.0);
  105. Box1.setHeight(5.0);
  106. // box 2 详细数据
  107. Box2.setLength(12.0);
  108. Box2.setBreadth(13.0);
  109. Box2.setHeight(10.0);
  110. //使用重载的 ToString() 显示 Boxes
  111. Console.WriteLine("Box 1: {0}", Box1.ToString());
  112. Console.WriteLine("Box 2: {0}", Box2.ToString());
  113. // box 1 的体积
  114. volume = Box1.getVolume();
  115. Console.WriteLine("Volume of Box1 : {0}", volume);
  116. // box 2 的体积
  117. volume = Box2.getVolume();
  118. Console.WriteLine("Volume of Box2 : {0}", volume);
  119. // 将两个对象相加如下:
  120. Box3 = Box1 + Box2;
  121. Console.WriteLine("Box 3: {0}", Box3.ToString());
  122. // box 3 的体积
  123. volume = Box3.getVolume();
  124. Console.WriteLine("Volume of Box3 : {0}", volume);
  125. //比较 boxes
  126. if (Box1 > Box2)
  127. Console.WriteLine("Box1 is greater than Box2");
  128. else
  129. Console.WriteLine("Box1 is greater than Box2");
  130. if (Box1 < Box2)
  131. Console.WriteLine("Box1 is less than Box2");
  132. else
  133. Console.WriteLine("Box1 is not less than Box2");
  134. if (Box1 >= Box2)
  135. Console.WriteLine("Box1 is greater or equal to Box2");
  136. else
  137. Console.WriteLine("Box1 is not greater or equal to Box2");
  138. if (Box1 <= Box2)
  139. Console.WriteLine("Box1 is less or equal to Box2");
  140. else
  141. Console.WriteLine("Box1 is not less or equal to Box2");
  142. if (Box1 != Box2)
  143. Console.WriteLine("Box1 is not equal to Box2");
  144. else
  145. Console.WriteLine("Box1 is not greater or equal to Box2");
  146. Box4 = Box3;
  147. if (Box3 == Box4)
  148. Console.WriteLine("Box3 is equal to Box4");
  149. else
  150. Console.WriteLine("Box3 is not equal to Box4");
  151. Console.ReadKey();
  152. }
  153. }
  154. }

编译执行上述代码,得到如下结果:

  1. Box 1: (6, 7, 5)
  2. Box 2: (12, 13, 10)
  3. Volume of Box1 : 210
  4. Volume of Box2 : 1560
  5. Box 3: (18, 20, 15)
  6. Volume of Box3 : 5400
  7. Box1 is not greater than Box2
  8. Box1 is less than Box2
  9. Box1 is not greater or equal to Box2
  10. Box1 is less or equal to Box2
  11. Box1 is not equal to Box2
  12. Box3 is equal to Box4

接口

一个接口定义为一种句法的合同,所有类继承接口应遵循。这个接口定义了部分的句法合同“是什么(what)”和派生类定义了部分的句法合同“怎么做(how)”

接口定义的属性,方法和事件,是接口的成员。接口只包含成员的声明。它是派生类定义的成员的责任。它提供一个派生类可以采用的标准的结构。

抽象类在一定程度上服务于同一个目的,然而,它们主要用于基类的方法和派生类中实现的功能。

接口的声明

接口使用关键字 interface 声明。它类似于类的声明。接口声明缺省为 public 类型。以下是一个接口声明的例子:

  1. public interface ITransactions
  2. {
  3. // 接口成员
  4. void showTransaction();
  5. double getAmount();
  6. }

示例

下面的示例演示上述接口的实现:

  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using System.Text;
  4. using System;
  5. namespace InterfaceApplication
  6. {
  7. public interface ITransactions
  8. {
  9. // 接口成员
  10. void showTransaction();
  11. double getAmount();
  12. }
  13. public class Transaction : ITransactions
  14. {
  15. private string tCode;
  16. private string date;
  17. private double amount;
  18. public Transaction()
  19. {
  20. tCode = " ";
  21. date = " ";
  22. amount = 0.0;
  23. }
  24. public Transaction(string c, string d, double a)
  25. {
  26. tCode = c;
  27. date = d;
  28. amount = a;
  29. }
  30. public double getAmount()
  31. {
  32. return amount;
  33. }
  34. public void showTransaction()
  35. {
  36. Console.WriteLine("Transaction: {0}", tCode);
  37. Console.WriteLine("Date: {0}", date);
  38. Console.WriteLine("Amount: {0}", getAmount());
  39. }
  40. }
  41. class Tester
  42. {
  43. static void Main(string[] args)
  44. {
  45. Transaction t1 = new Transaction("001", "8/10/2012", 78900.00);
  46. Transaction t2 = new Transaction("002", "9/10/2012", 451900.00);
  47. t1.showTransaction();
  48. t2.showTransaction();
  49. Console.ReadKey();
  50. }
  51. }
  52. }

编译执行上述代码,得到如下结果:

  1. Transaction: 001
  2. Date: 8/10/2012
  3. Amount: 78900
  4. Transaction: 002
  5. Date: 9/10/2012
  6. Amount: 451900

命名空间

命名空间(namespace) 专为提供一种来保留一套独立名字与其他命名区分开来的方式。一个命名空间中声明的类的名字与在另一个命名空间中声明的相同的类名并不会发生冲突。

命名空间的定义

命名空间的定义以关键字 namespace 开始,其后跟命名空间的名称:

  1. namespace namespace_name
  2. {
  3. // 代码声明
  4. }

调用的函数或变量的命名空间启用版本,在命名空间名称如下:

  1. namespace_name.item_name;

下面的程序演示了命名空间的使用:

  1. using System;
  2. namespace first_space
  3. {
  4. class namespace_cl
  5. {
  6. public void func()
  7. {
  8. Console.WriteLine("Inside first_space");
  9. }
  10. }
  11. }
  12. namespace second_space
  13. {
  14. class namespace_cl
  15. {
  16. public void func()
  17. {
  18. Console.WriteLine("Inside second_space");
  19. }
  20. }
  21. }
  22. class TestClass
  23. {
  24. static void Main(string[] args)
  25. {
  26. first_space.namespace_cl fc = new first_space.namespace_cl();
  27. second_space.namespace_cl sc = new second_space.namespace_cl();
  28. fc.func();
  29. sc.func();
  30. Console.ReadKey();
  31. }
  32. }

编译执行上述代码,得到如下结果:

  1. Inside first_space
  2. Inside second_space

关键字 using

关键词 using 指出了该程序是在使用给定的命名空间的名称。例如,我们在程序中使用的是系统命名空间。其中有 Console 类的定义。我们只需要写:

  1. Console.WriteLine ("Hello there");

我们还可以写完全限定名称:

  1. System.Console.WriteLine("Hello there");

你也可以使用 using 指令避免还要在前面加上 namespace 。这个指令会告诉编译器后面的代码使用的是在指定的命名空间中的名字。命名空间是因此包含下面的代码:

让我们重写前面的示例,使用 using 指令:

  1. using System;
  2. using first_space;
  3. using second_space;
  4. namespace first_space
  5. {
  6. class abc
  7. {
  8. public void func()
  9. {
  10. Console.WriteLine("Inside first_space");
  11. }
  12. }
  13. }
  14. namespace second_space
  15. {
  16. class efg
  17. {
  18. public void func()
  19. {
  20. Console.WriteLine("Inside second_space");
  21. }
  22. }
  23. }
  24. class TestClass
  25. {
  26. static void Main(string[] args)
  27. {
  28. abc fc = new abc();
  29. efg sc = new efg();
  30. fc.func();
  31. sc.func();
  32. Console.ReadKey();
  33. }
  34. }

编译执行上述代码,得到如下结果:

  1. Inside first_space
  2. Inside second_space

嵌套命名空间

你可以在一个命名空间中定义另一个命名空间,方法如下:

  1. namespace namespace_name1
  2. {
  3. // 代码声明
  4. namespace namespace_name2
  5. {
  6. //代码声明
  7. }
  8. }

你可以使用点运算符“.”来访问嵌套命名空间中的成员

  1. using System;
  2. using first_space;
  3. using first_space.second_space;
  4. namespace first_space
  5. {
  6. class abc
  7. {
  8. public void func()
  9. {
  10. Console.WriteLine("Inside first_space");
  11. }
  12. }
  13. namespace second_space
  14. {
  15. class efg
  16. {
  17. public void func()
  18. {
  19. Console.WriteLine("Inside second_space");
  20. }
  21. }
  22. }
  23. }
  24. class TestClass
  25. {
  26. static void Main(string[] args)
  27. {
  28. abc fc = new abc();
  29. efg sc = new efg();
  30. fc.func();
  31. sc.func();
  32. Console.ReadKey();
  33. }
  34. }

编译执行上述代码,得到如下结果:

  1. Inside first_space
  2. Inside second_space

预处理指令

预处理指令是一种给编译器的指令,用来在实际的编译开始之前预处理一些信息。

所有的预处理指令都以 # 开始,并且在一行预处理指令中,只有空白字符可以出现在指令之前。预处理指令没有声明,所以他们不需要以分号(;)结尾。

C# 编译器不具有独立的预处理机制;然而,指令执行的时候就像是只有这一条一样。在 C# 中,预处理指令被用来帮助条件编译。不像 C 或 C++ 的指令,他们不能创建宏。一个预处理指令必须是这一行代码中的唯一的指令。

C# 中的预处理指令

下面的表格中列出了 C# 中可用的预处理指令:

预处理指令 描述
#define 定义了一串字符,称为符号。
#undef 可以取消定义的符号。
#if 测试一个或多个表达式的结果是否为真。
#else 用于创建复合条件指令,和 #if 一起使用。
#elif 用于创建复合条件指令。
#endif 指出条件指令的结尾。
#line 可以修改编译器的行号,选择性修改输出错误和警告的文件名
#error 从代码的特定位置生成误差
#warning 从代码的特定位置生成一级预警
#region 当你使用 Visual Studio 代码编译器时,你可以展开或折叠一部分代码块
#engregion 它标志着 #region 块的结束

#define 指令

#define预处理指令是用来创建符号常量的。

应用 #define 可以定义一个符号,这个符号会作为一个表达式传递给 #if 指令,这个判断会得到 ture 的 结果。语法如下:

  1. #define symbol

下面的程序说明了这一点:

  1. #define PI
  2. using System;
  3. namespace PreprocessorDAppl
  4. {
  5. class Program
  6. {
  7. static void Main(string[] args)
  8. {
  9. #if (PI)
  10. Console.WriteLine("PI is defined");
  11. #else
  12. Console.WriteLine("PI is not defined");
  13. #endif
  14. Console.ReadKey();
  15. }
  16. }
  17. }

编译执行上述代码,得到如下结果:

  1. PI is defined

条件指令

你可以使用 #if 指令创建一个条件指令。条件指令可以用来判断一个或多个符号是否为真。如果他们的结果为真,编译器就会执行 #if 和下一条指令间的所有代码。

条件指令的语法如下:

  1. #if symbol [operator symbol]...

当你想测试的符号是 “symbol”这个名字的时候。你也可以使用 ture 和 false 或者提前使用反运算符操作这个符号。

operator symbol(运算符符号)是一种用于符号求值的运算符。运算符可以是下列之一:

  • == (相等)
  • != (不相等)
  • && (与)
  • || (或)

你也可以通过括号使用组符号和组运算符。条件指令用于编译代码生成 debug 或者是编译特定配置时。一个条件指令以 #if 开头并且必须明确的以 #endif 指令结束。

下面的程序示范了条件指令的使用方法:

  1. #define DEBUG
  2. #define VC_V10
  3. using System;
  4. public class TestClass
  5. {
  6. public static void Main()
  7. {
  8. #if (DEBUG && !VC_V10)
  9. Console.WriteLine("DEBUG is defined");
  10. #elif (!DEBUG && VC_V10)
  11. Console.WriteLine("VC_V10 is defined");
  12. #elif (DEBUG && VC_V10)
  13. Console.WriteLine("DEBUG and VC_V10 are defined");
  14. #else
  15. Console.WriteLine("DEBUG and VC_V10 are not defined");
  16. #endif
  17. Console.ReadKey();
  18. }
  19. }

编译执行上述代码,得到如下结果:

  1. DEBUG and VC_V10 are defined

正则表达式

正则表达式是一种可以和输入文本相匹配的表达式。.Net framework 提供了一个正则表达式引擎让这种匹配成为可能。一个表达式可以由一个或多个字符,运算符,或结构体组成。

构建正则表达式的定义

有很多种类的字符,运算符,结构体可以定义正则表达式。

  • 转义字符
  • 字符类
  • 集合
  • 分组构造
  • 限定符
  • 回溯引用构造
  • 可替换结构
  • 替换
  • 混合结构

Regex 正则表达式类

Regex 类用于表示一个正则表达式。它有下列常用的方法:

Sr.No 方法
1 public bool IsMatch(string input) 指出输入的字符串中是否含有特定的正则表达式。
2 public bool IsMatch(string input, int startat) 指出输入的字符串中是否含有特定的正则表达式,该函数的 startat 变量指出了字符串开始查找的位置。
3 public static bool IsMatch(string input, string pattern) 指出特定的表达式是否和输入的字符串匹配。
4 public MatchCollection Matches(string input) 在所有出现的正则表达式中搜索特定的输入字符
5 public string Replace(string input, string replacement) 在一个特定的输入字符中,用特定的字符串替换所有满足某个表达式的字符串。
6 public string[] Split(string input) 将一个输入字符拆分成一组子字符串,从一个由正则表达式指出的位置上开始。

有关属性和方法的完成列表,请参见微软的 C# 文档。

示例 1

下列的例子中找出了以 s 开头的单词:

  1. using System;
  2. using System.Text.RegularExpressions;
  3.  
  4. namespace RegExApplication
  5. {
  6. class Program
  7. {
  8. private static void showMatch(string text, string expr)
  9. {
  10. Console.WriteLine("The Expression: " + expr);
  11. MatchCollection mc = Regex.Matches(text, expr);
  12. foreach (Match m in mc)
  13. {
  14. Console.WriteLine(m);
  15. }
  16. }
  17.  
  18. static void Main(string[] args)
  19. {
  20. string str = "A Thousand Splendid Suns";
  21.  
  22. Console.WriteLine("Matching words that start with 'S': ");
  23. showMatch(str, @"\bS\S*");
  24. Console.ReadKey();
  25. }
  26. }
  27. }

编译执行上述代码,得到如下结果:

  1. Matching words that start with 'S':
  2. The Expression: \bS\S*
  3. Splendid
  4. Suns

示例 2

下面的例子中找出了以 m 开头以 e 结尾的单词

  1. using System;
  2. using System.Text.RegularExpressions;
  3.  
  4. namespace RegExApplication
  5. {
  6. class Program
  7. {
  8. private static void showMatch(string text, string expr)
  9. {
  10. Console.WriteLine("The Expression: " + expr);
  11. MatchCollection mc = Regex.Matches(text, expr);
  12. foreach (Match m in mc)
  13. {
  14. Console.WriteLine(m);
  15. }
  16. }
  17. static void Main(string[] args)
  18. {
  19. string str = "make maze and manage to measure it";
  20.  
  21. Console.WriteLine("Matching words start with 'm' and ends with 'e':");
  22. showMatch(str, @"\bm\S*e\b");
  23. Console.ReadKey();
  24. }
  25. }
  26. }

编译执行上述代码,得到如下结果:

  1. Matching words start with 'm' and ends with 'e':
  2. The Expression: \bm\S*e\b
  3. make
  4. maze
  5. manage
  6. measure

示例 3

这个例子替换了额外的空白符:

  1. using System;
  2. using System.Text.RegularExpressions;
  3.  
  4. namespace RegExApplication
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. string input = "Hello World ";
  11. string pattern = "\\s+";
  12. string replacement = " ";
  13. Regex rgx = new Regex(pattern);
  14. string result = rgx.Replace(input, replacement);
  15.  
  16. Console.WriteLine("Original String: {0}", input);
  17. Console.WriteLine("Replacement String: {0}", result);
  18. Console.ReadKey();
  19. }
  20. }
  21. }

编译执行上述代码,得到如下结果:

  1. Original String: Hello World
  2. Replacement String: Hello World

异常处理

异常是程序执行过程中产生的问题。C# 异常是对程序运行过程中出现的额外情况的一种反馈,例如除数为零时。

异常提供了一种将控制权从程序的一个部分转移到另一个部分的方式。C# 异常处理有四个关键词:trycatchfinallythrow

  • try:try 块标识代码块的哪些特定的异常将被激活。它的后面是一个或多个 catch 块。
  • catch:一个用于捕获异常的程序段,将 catch 放在你希望能处理这个异常的地方。“catch”这个关键字标志着异常的捕获。
  • finally:finally 保证了无论是否有异常抛出,此代码段中的程序都会被执行。例如,如果你打开了一个文件,那么不管是否发生了异常,文件都需要关闭。
  • throw:当出现问题时,程序会抛出异常。这项工作是通过使用 throw 来实现的。

语法

假设一个代码块产生了一个异常,通过使用 try 和 catch 的组合可以捕获这个异常。一个 try/catch 代码块需要放置在可能会产生异常的代码段周围。try/catch 代码段就像是保护代码,它的使用语法如下:

  1. try
  2. {
  3. // statements causing exception
  4. }
  5. catch( ExceptionName e1 )
  6. {
  7. // error handling code
  8. }
  9. catch( ExceptionName e2 )
  10. {
  11. // error handling code
  12. }
  13. catch( ExceptionName eN )
  14. {
  15. // error handling code
  16. }
  17. finally
  18. {
  19. // statements to be executed
  20. }

当你的 try 语句块可能会抛出多种异常时,你可以列出多种的 catch 语句,以便捕获不同种类的异常。

C#中的异常类

C# 异常由类表示。在 C# 中的异常类主要是直接或间接地来源于 System.Exception 类。有些从 System.Exception 类派生的异常类,它们是 System.ApplicationException 和 System.SystemException 类。

System.ApplicationException 类支持由应用程序生成的异常。因此,由程序员定义的异常应该源于这个类。

System.SystemException 类是所有预定义的系统异常的基类。

下表提供了一些从 Sytem.SystemException 类派生的预定义的异常类:

Exception类 描述
System.IO.IOException 处理 I/O 错误
System.IndexOutOfRangeException 处理的方法是指当一个数组索引超出范围的错误产生
System.ArrayTypeMismatchException 处理时,类型不匹配的数组类型产生的错误
System.NullReferenceException 处理从取消引用一个空对象产生的错误
System.DivideByZeroException 处理来自将一个除零而产生的错误
System.InvalidCastException 处理类型转换过程中产生的错误
System.OutOfMemoryException 处理来自可用内存不足产生的错误
System.StackOverflowException 处理从堆栈溢出产生的错误

处理异常

C# 为在 try catch 语句块中处理异常提供了一种结构化的解决方案。这种方法可以使核心代码段和异常处理部分分离开。

这些异常处理代码段是通过使用 try,catch,finally 关键字实现的。下面是一个除数为零的异常处理情况:

  1. using System;
  2. namespace ErrorHandlingApplication
  3. {
  4. class DivNumbers
  5. {
  6. int result;
  7. DivNumbers()
  8. {
  9. result = 0;
  10. }
  11. public void division(int num1, int num2)
  12. {
  13. try
  14. {
  15. result = num1 / num2;
  16. }
  17. catch (DivideByZeroException e)
  18. {
  19. Console.WriteLine("Exception caught: {0}", e);
  20. }
  21. finally
  22. {
  23. .WriteLine("Result: {0}", result);
  24. }
  25. }
  26. static void Main(string[] args)
  27. {
  28. DivNumbers d = new DivNumbers();
  29. d.division(25, 0);
  30. Console.ReadKey();
  31. }
  32. }
  33. }

编译执行上述代码,得到如下结果:

  1. Exception caught: System.DivideByZeroException: Attempted to divide by zero.
  2. at ...
  3. Result: 0

创建自定义异常

你也可以定义你自己的异常。自定义异常类继承自 ApplicationException 类。示范如下:

  1. using System;
  2. namespace UserDefinedException
  3. {
  4. class TestTemperature
  5. {
  6. static void Main(string[] args)
  7. {
  8. Temperature temp = new Temperature();
  9. try
  10. {
  11. temp.showTemp();
  12. }
  13. catch(TempIsZeroException e)
  14. {
  15. Console.WriteLine("TempIsZeroException: {0}", e.Message);
  16. }
  17. Console.ReadKey();
  18. }
  19. }
  20. }
  21.  
  22. public class TempIsZeroException: ApplicationException
  23. {
  24. public TempIsZeroException(string message): base(message)
  25. {
  26. }
  27. }
  28.  
  29. public class Temperature
  30. {
  31. int temperature = 0;
  32. public void showTemp()
  33. {
  34. if(temperature == 0)
  35. {
  36. throw (new TempIsZeroException("Zero Temperature found"));
  37. }
  38. else
  39. {
  40. Console.WriteLine("Temperature: {0}", temperature);
  41. }
  42. }
  43. }

编译执行上述代码,得到如下结果:

  1. TempIsZeroException: Zero Temperature found

抛出对象

如果某个对象是直接或间接地继承自 System.Exception 类,你可以抛出这个对象。你可以在 catch 语句块中用 throw 语句抛出这个对象:

  1. Catch(Exception e)
  2. {
  3. ...
  4. Throw e
  5. }

文件 I/O

文件是存储在磁盘具有特定名称和目录路径的数据的集合。当一个文件被打开阅读或书写时,就变成了流。

流基本上是通过通信路径中的字节顺序。主要有两个流:输入流和输出流。输入流用于从文件系统中读取数据,输出流用于向文件中写数据。

I/O 类

System.IO 的命名空间有多种类,这些类被用于执行大量和文件有关的操作,例如创建和删除文件,读写文件,关闭文件等等。

下面的表格中列出了一些 System.IO 命名空间中常用的非抽象类:

I/O 类 描述
BinaryReader 从二进制流读取原始数据
BinaryWriter 以二进制形式写入原始数据
BufferedStream 字节流的临时存储
Directory 用于操作目录结构
DirectoryInfo 用于创建复合条件指令。
DriveInfo 用于执行目录操作
File 用于操作文件
FileInfo 用于执行文件操作
FileStream 用于读写文件中任意位置的内容
MemoryStream 用于随机存取存储器中存储的流数据
Path 用于执行有关路径信息的操作
StreamReader 用于从字节流中读取字符
StreamWriter 用于向流中写字符
StringReader 用于读取字符串数组
StringWriter 用于写入字符串数组

FileStream 类

System.IO 命名空间中的 FileStream 类有助于读取,写入和关闭文件。这个类派生自抽象类流。

你需要创建一个 FileStream 对象用于创建一个新的文件或打开一个已存在的文件。创建 FileStream 对象的语法如下:

  1. FileStream <object_name> = new FileStream( <file_name>, <FileMode Enumerator>, <FileAccess Enumerator>, <FileShare Enumerator>);

例如,创建一个 FileStream 对象F,读取一个名为 sample.txt 的文件的方法如下:

  1. FileStream F = new FileStream("sample.txt", FileMode.Open, FileAccess.Read, FileShare.Read);
参数 描述
FileMode fileMode 枚举定义了各种方法来打开文件。fileMode 枚举的成员是: Append: 打开一个已有的文件,并将光标放置在文件的末尾。如果文件不存在,则创建文件。 Create: 它创建一个新的文件 CreateNew: 指定操作系统应创建一个新的文件。如果文件已存在,则抛出异常。 Open: 它会打开一个现有的文件 OpenOrCreate: 指定操作系统应打开一个已有的文件。如果文件不存在,则用指定的名称创建一个新的文件打开。 Truncate: 打开一个已有的文件,文件一旦打开,就将被截断为零字节大小。
FileAccess FileAccess 枚举成员有:Read,ReadWrite 和 Write。
FileShare FileShare 枚举有以下成员: Inheritable: 允许文件句柄可由子进程继承。 None: 它拒绝共享当前文件 Read: 它允许打开文件进行读取 ReadWrite: 它允许打开文件进行读取和写入 Write: 它允许打开文件写入

示例

下面的程序示范了 FileStream 类:

  1. using System;
  2. using System.IO;
  3.  
  4. namespace FileIOApplication
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. FileStream F = new FileStream("test.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite);
  11. for (int i = 1; i <= 20; i++)
  12. {
  13. F.WriteByte((byte)i);
  14. }
  15.  
  16. F.Position = 0;
  17. for (int i = 0; i <= 20; i++)
  18. {
  19. Console.Write(F.ReadByte() + " ");
  20. }
  21. F.Close();
  22. Console.ReadKey();
  23. }
  24. }
  25. }

编译执行上述代码,得到如下结果:

  1. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -1

C# 中的高级文件操作

上面的实例演示了 C# 中简单的文件操作。但是,要充分利用 C# System.IO 类的强大功能,您需要知道这些类常用的属性和方法。

主题和描述
文本文件的读写 它涉及到文本文件的读写。StreamReader 和 StreamWriter 类有助于完成文本文件的读写。
二进制文件的读写 它涉及到二进制文件的读写。BinaryReader 和 BinaryWriter 类有助于完成二进制文件的读写。
Windows文件系统的操作 它让 C# 程序员能够浏览并定位 Windows 文件和目录。