3. 类、结构体、接口
3.1. 示例中的对象
3.1.1. 概述
接下来,我们将通过实例来探索面向对象编程。对象是一个实体,它包含定义其状态的数据(称为属性)和函数(称为方法)。对象是基于称为类的模板创建的:
Public Class c1
' attributes
Private p1 As type1
Private p2 As type2
....
' method
Public Sub m1(....)
...
End Sub
' method
Public Function m2(...)
....
End Function
End Class
基于前面的类 C1,我们可以创建多个对象 O1、O2、……它们都将具有属性 p1、p2、……以及方法 m3、m4、……但它们的属性 pi 具有不同的值,因此每个对象都有其自身的状态。类比而言,声明
会创建两个类型(类)为 Integer 的对象(此处的术语并不准确)。它们唯一的属性就是其数值。如果 O1 是类型为 C1 的对象,则 O1.p1 指代 O1 的属性 p1,O1.m1 指代 O1 的方法 m1。让我们考虑一个最初的对象模型:Person 类。
3.1.2. Person类的定义
Person类的定义如下:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
这里是一个类的定义,类是一种数据类型。当我们创建该类型的变量时,称其为对象或类实例。因此,类是用于构建对象的模板。类的成员或字段可以是数据(属性)、方法(函数)或属性。属性是用于获取或设置对象属性值的特殊方法。这些字段可以伴随以下三个关键字之一:
私有字段仅可被类的内部方法访问 | |
公共字段可被任何函数访问,无论该函数是否定义在该类内部 | |
受保护字段仅可被类的内部方法或派生对象访问(参见后文的继承概念)。 |
通常,类的数据被声明为私有,而其方法和属性则被声明为公有。这意味着对象的使用者(即程序员):
- 无法直接访问对象的私有数据
- 可以调用对象的 public 方法,特别是那些提供对其私有数据访问权限的方法。
声明类的语法如下:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
私有、受保护和公共属性的声明顺序是任意的。
3.1.3. initialize 方法
让我们回到之前声明的 [person] 类:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
initialize 方法的作用是什么?由于 lastName、firstName 和 age 是 Person 类的私有数据,因此以下语句:
是无效的。我们必须通过一个公共方法来初始化一个 Person 类型的对象。这就是 initialize 方法的作用。我们将编写:
语法 p1.initialize 是有效的,因为 initialize 是 public 的。
3.1.4. new 运算符
语句序列
是错误的。该语句
将 p1 声明为对类型为 person 的对象的引用。该对象尚未存在,因此 p1 尚未初始化。这就像我们写的是:
其中我们通过关键字 nothing 明确指出变量 p1 尚未引用任何对象。随后当我们编写
时,我们调用了由 p1 引用的对象的 initialize 方法。然而,该对象尚未存在,编译器将报错。要让 p1 引用一个对象,你必须写:
这将创建一个未初始化的 Person 对象:属性 name 和 first_name(它们是 String 对象的引用)将取值为 nothing,而 age 将取值为 0。因此存在默认初始化。现在 p1 引用了一个对象,该对象的初始化语句
是有效的。
3.1.5. 关键字 Me
让我们看看 *Initialize* 方法的代码:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
语句 Me.firstName = P 表示将值 P 赋给当前对象(Me)的 firstName 属性。关键字 Me 指代当前对象:即正在执行该方法的那个对象。我们如何知道这一点?让我们看看在调用程序中,由 p1 引用的对象是如何初始化的:
这里调用的是 p1 对象的 initialize 方法。当在此方法中引用 Me 对象时,它实际上指的是 p1 对象。initialize 方法也可以写成如下形式:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
当对象的方法引用该对象的属性 A 时,默认使用 Me.A 这种写法。当标识符发生冲突时,必须显式使用该写法。以下语句即属于这种情况:
Me.age=age;
在此,age 既指当前对象的属性,也指方法接收的 age 参数。此时必须通过将 age 属性写为 Me.age 来消除歧义。
3.1.6. 一个测试程序
以下是一个简短的测试程序:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
以及得到的结果:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. 使用编译后的类文件(程序集)
请注意,在上一个示例中,我们的测试程序中有两个类:person类和test1类。还有另一种实现方法:
-
将 Person 类编译成一个名为程序集的独立文件。该文件扩展名为 .dll
-
我们通过引用包含 Person 类的程序集来编译 test1 类。
这两个源文件将变为如下形式:
test.vb | |
person2.vb | |
Person 类由以下语句编译:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
编译生成了一个名为 personne2.dll 的文件。正是 /t:library 编译选项指定了创建“程序集”文件。现在让我们编译 test.vb 文件:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
编译选项 /r:personne2.dll 告知编译器,它将在 personne2.dll 文件中查找某些类。当它在源文件 test.vb 中发现对 Person 类的引用(该类未在源文件 test.vb 中声明)时,它将搜索 /r 选项所引用的 .dll 文件中的 Person 类。它将在 Person2.dll 程序集内找到 Person 类。 我们本可以在此程序集里包含其他类。若要在编译过程中使用多个已编译的类文件,应编写如下代码:
运行程序 test1.exe 会产生以下结果:
3.1.8. 另一种初始化方法
让我们继续探讨 Person 类,并向其中添加以下方法:
' method
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
现在我们有两个名为 Initialize 的方法:只要它们接受不同的参数,这种情况就是有效的。本例中正是如此。参数现在是一个指向 Person 的引用 P。随后,Person P 的属性会被赋值给当前对象(Me)。 请注意,尽管这些属性属于私有类型,但 Initialize 方法仍可直接访问对象 P 的属性。这一点始终成立:类 C 的对象 O1 总能访问同类 C 对象的属性。以下是对新 Person 类的测试,该类已如前所述编译为 Person.dll:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
及其结果:
3.1.9. Person类的构造函数
构造函数是一个名为 New 的过程,在创建对象时被调用。它通常用于初始化对象。如果一个类有一个接受 n 个参数 argi 的构造函数,则该类对象的声明和初始化可以如下进行:
dim objet as classe =new classe(arg1,arg2, ... argn)
或者
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
当一个类拥有一个或多个构造函数时,必须使用其中一个构造函数来创建该类的对象。如果类 C 没有构造函数,则它拥有一个默认构造函数,即无参数的构造函数:public New()。此时,对象的属性将使用默认值进行初始化。这就是前面的程序中所发生的情况,我们在其中写道:
让我们为 Person 类创建两个构造函数:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' the person class
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' object initialization methods
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
我们的两个构造函数都只是调用了相应的 initialise 方法。请注意,例如当构造函数中出现 initialise(P) 这种写法时,编译器会将其转换为 Me.initialise(P)。因此,在构造函数中,initialise 方法被调用是为了对 Me 所引用的对象进行操作,也就是当前对象,即正在被构造的那个对象。以下是一个简短的测试程序:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
以及获得的结果:
3.1.10. 对象引用
我们仍在使用相同的 Person 类。测试程序如下:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 references the same object as p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 references an object that will be a copy of the object referenced by p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' change the state of the object referenced by p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' as p2=p1, the object referenced by p2 must have changed state
Console.Out.Write("p2=")
p2.identifie()
' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
所得结果如下:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
在声明变量 p1 时
p1 引用了 Person("Jean", "Dupont", 30) 对象,但它本身并不是该对象。在 C 语言中,我们会说它是一个指针,即所创建对象的地址。如果接着写:
被修改的并非 person("Jean", "Dupont", 30) 对象本身,而是 p1 引用改变了值。如果 person("Jean", "Dupont", 30) 对象不再被任何其他变量引用,它将会“消失”。
当我们写:
我们初始化了指针 p2:它“指向”(即引用)与指针 p1 相同的对象。因此,如果我们修改了 p1 “指向”(或引用的)对象,那么 p2 引用的对象也会随之被修改。
当我们写:
将创建一个新对象,它是 p1 所引用的对象的副本。p3 将引用这个新对象。如果你修改了 p1 “指向”(或引用)的对象,则 p3 所引用的对象不会受到任何影响。结果正是如此。
3.1.11. 临时对象
在表达式中,你可以显式调用对象的构造函数:对象会被创建,但你无法访问它(例如对其进行修改)。这个临时对象是为了求值而创建的,求值完成后即被丢弃。它占用的内存空间稍后将由一个名为“垃圾回收器”的程序自动回收,该程序的作用是回收那些不再被程序数据引用的对象所占用的内存空间。请看以下新的测试程序:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
然后,让我们修改 Person 类的构造函数,使其显示一条消息:
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
我们得到以下结果:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
展示了这两个临时对象的连续构建过程。
3.1.12. 读写私有属性的方法
我们在 Person 类中添加了必要的方法,用于读取或修改对象属性的状态:
Imports System
Public Class personne
' attributes
Private prenom As [String]
Private nom As [String]
Private age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accessors
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'modifiers
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
我们使用以下程序测试新类:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
我们得到以下结果:
3.1.13. 属性
还有另一种访问类属性的方法:创建属性。这些属性允许我们像操作公共属性一样操作私有属性。请看下面的 Person 类,其中之前的 getter 和 setter 已被读写属性所取代:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' class person
Public Class personne
' attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' properties
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
属性允许您读取(get)或设置(set)属性的值。在本例中,我们在属性名前添加了下划线(_),以便属性与原始属性的名称保持一致。这是因为属性不能与它所管理的属性具有相同的名称;否则,类内部会出现命名冲突。 因此,我们将属性命名为 _firstName、_lastName 和 _age,并相应地修改了构造函数和方法。随后,我们创建了三个属性:lastName、firstName 和 age。属性的声明如下:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
其中 Type 必须是该属性所管理的属性的类型。它可以拥有两个名为 get 和 set 的方法。get 方法通常负责返回其所管理的属性的值(它也可以返回其他内容;没有任何规定禁止它这样做)。 set 方法接收一个名为 value 的参数,通常将其赋值给所管理的属性。它可借此机会检查接收到的值是否有效,并在必要时,若值无效则抛出异常。这就是此处对 age 属性所采取的做法。
这些 get 和 set 方法是如何被调用的?请看以下测试程序:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
在语句中
中,我们试图获取人员 P 的 first_name、last_name 和 age 属性的值。随后调用了这些属性的 get 方法,并返回了它们所管理的属性的值。
在该语句中
中,我们希望设置 age 属性的值。随后将调用该属性的 set 方法,其 value 参数将接收 56。
类 C 的属性 P 若仅定义了 get 方法,则称为只读属性。若 c 是类 C 的实例,则操作 c.P=value 将被编译器拒绝。
执行前面的测试程序将得到以下结果:
因此,属性使我们能够像操作公共属性一样操作私有属性。
3.1.14. 类方法与属性
假设我们想统计应用程序中创建的 [person] 对象的数量。我们可以自己管理一个计数器,但可能会遗漏那些零散创建的临时对象。在 [person] 类的构造函数中加入一条增量计数器的指令似乎更稳妥。问题在于如何传递该计数器的引用以便构造函数能对其进行增量操作:我们需要向构造函数传递一个新参数。 我们也可以将计数器包含在类定义中。由于它是类本身的属性,而非该类某个特定对象的属性,因此我们需要使用 Shared 关键字以不同的方式进行声明:
要引用它,我们写 person._nbPeople 来表明它是 Person 类本身的属性。这里,我们创建了一个私有属性,无法从类外部直接访问。 因此,我们创建了一个公共属性,以便访问类属性 nbPeople*。要返回 *nbPeople* 的值,该属性的 Get 方法不需要特定的 Person 对象:事实上,\_nbPeople* 并非特定对象的属性,而是整个类的属性。因此,我们需要一个同样声明为 Shared 的属性:
从外部调用时,将使用 person.nbPeople 这种语法。该属性被声明为只读(ReadOnly),因为它不提供 set 方法。以下是一个示例。Person 类将变为如下形式:
Option Explicit On
Option Strict On
' namespaces
Imports System
' class
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
使用以下程序:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
得到以下结果:
3.1.15. 将对象传递给函数
我们已经提到,默认情况下,VB.NET 按值传递实际函数参数:实际参数的值会被复制到形式参数中。在处理对象时,不要被“对象”这一常见术语所误导,而应将其理解为“对象引用”。 对象只能通过对其的引用(指针)进行操作。因此,传递给函数的并非对象本身,而是对该对象的引用。因此,被复制到形式参数中的是引用的值——而不是对象本身的值——不会创建新的对象。 如果将对象引用 R1 传递给函数,它将被复制到对应的形式参数 R2 中。因此,引用 R2 和 R1 指向同一个对象。如果函数修改了 R2 所指向的对象,显然也会修改 R1 所引用的对象,因为它们是同一个对象。
![]() |
以下示例说明了这一点:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' one person p1
Dim p1 As New personne("Jean", "Dupont", 30)
' p1 display
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modification p1
modifie(p1)
' p1 display
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' display person P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modification P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' display P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
所得结果如下:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
我们可以看到仅创建了一个对象:即 Main 过程中的 person p1,且该对象确实被 modify 函数修改了。
3.1.16. 人员数组
对象与其他数据一样,因此可以将多个对象组合成一个数组:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a table of people
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' display
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
语句 Dim friends(2) As Person 创建了一个包含 3 个 Person 类型元素的数组。这 3 个元素在此处被初始化为 Nothing 值,这意味着它们不引用任何对象。同样,从技术意义上讲,我们所说的“对象数组”实际上只是一个对象引用的数组。创建对象数组(它本身也是一个对象)并不会创建其元素类型的任何对象:这必须在后续操作中完成。 得到以下结果:
3.2. 示例继承
3.2.1. 概述
这里我们将讨论继承的概念。继承的目的是“定制”现有的类,使其满足我们的需求。假设我们要创建一个 Teacher 类:教师是人的一种特殊类型。他们拥有其他人所没有的属性,例如他们所教授的科目。但他们也拥有作为人的基本属性:名字、姓氏和年龄。 因此,教师是 Person 类的完全成员,但拥有额外的属性。与其从头编写 Teacher 类,我们更倾向于基于现有的 Person 类进行构建,并将其适应教师的特定特征。这就是继承概念所能实现的。为了表示 Teacher 类继承了 Person 类的属性,我们写:
Public Class enseignant
Inherits personne
请注意这种特有的两行语法。Person 类被称为父类(或基类),而 Teacher 类被称为派生类(或子类)。一个 Teacher 对象具有 Person 对象的所有特性:它拥有相同的属性和方法。父类的这些属性和方法在子类的定义中不会重复;我们只需指定子类新增的属性和方法即可。我们假设 Person 类的定义如下:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' class person
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P
Me._nom = N
Me._age = age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
Identify 方法已被只读的 Identity 属性所取代,该属性用于标识该人。我们创建一个继承自 Person 类的 Teacher 类:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
Teacher 类为 Person 类的的方法和属性添加了以下内容:
- 一个 `section` 属性,表示该教师在教职员工中所属的教学班(大致每门学科对应一个教学班)
- 一个新的构造函数,用于初始化教师的所有属性
声明
Public Class enseignant
Inherits personne
表示 Teacher 类是从 Person 类派生而来的。
3.2.2. 创建一个 Teacher 对象
Teacher类的构造函数如下:
' manufacturer
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
声明
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
声明该构造函数接收四个参数:P、N、age 和 section。它必须将其中三个(P、N、age)传递给其基类,在本例中即 Person 类。 我们知道该类有一个构造函数 Person(string, string, int),它允许我们使用传入的参数(P、N、age)创建 Person 对象。[Teacher] 类将参数(P、N、age)传递给其基类,具体如下:
MyBase.New(P, N, age)
一旦基类被构造完成,Teacher 对象的构造过程将继续执行构造函数的主体:
Me._section = section
总而言之,派生类的构造函数:
- 将自身构造所需的参数传递给基类
- 使用其余参数来初始化自身的属性
我们本可以选择这样写:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
这是不可能的。Person 类已将其三个字段——_firstName、_lastName 和 _age——声明为 private。只有同类的对象才能直接访问这些字段。所有其他对象(包括本例中的子类对象)都必须使用 public 方法来访问它们。如果 Person 类将这三个字段声明为 protected,情况就会不同:那样的话,派生类就可以直接访问这三个字段了。 因此,在本例中,使用父类的构造函数才是正确的解决方案,也是标准做法:在构造子对象时,我们首先调用父类的构造函数,然后完成子对象特有的初始化(参见本例中的相关部分)。
现在将 Person 和 Teacher 类编译成程序集:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
请注意,为了编译子类 teacher,我们必须引用包含 person 类的 person.dll 文件。让我们尝试编写第一个测试程序:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
该程序仅创建了一个 Teacher 对象(new)并获取其标识。Teacher 类本身没有 Identity 方法,但其父类拥有该方法,且该方法为 public 类型:通过继承,它成为了 Teacher 类的 public 方法。所得结果如下:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
我们可以看到:
- 在创建教师对象之前,先创建了一个人员对象
- 得到的标识是 person 对象的标识
3.2.3. 重载方法或属性
在上一个示例中,我们获得了教师对象中“人”部分的身份,但缺少了一些特定于 Teacher 类的信息(如所属年级)。因此,我们需要编写一个属性,以便能够识别该教师:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' overload property identity
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
Teacher 类的 identity 方法依赖于其父类的 identity 方法(MyBase.identity)来显示其“person”部分,然后使用 Teacher 类特有的 _section 字段对其进行补充。请注意 identity 属性的声明:
Public Shadows ReadOnly Property identite() As String
这表明 identite 属性“隐藏”了父类中可能存在的同名方法。考虑一个 Teacher 对象 E。该对象内部包含一个 Person 对象:
![]() |
“identity”属性在 Teacher 类及其父类 Person 类中均有定义。在子类 Teacher 中,必须在“identity”属性前添加“shadows”关键字,以表明我们正在为 Teacher 类重新定义一个新的“identity”属性。
Public Shadows ReadOnly Property identite() As String
现在 Teacher 类拥有两个 identity 属性:
- 一个是从父类 Person 继承而来的
- 以及它自己的
如果 E 是一个 Teacher 对象,则 E.identity 指向 Teacher 类的 identity 方法。我们说父类的 identity 属性被子类的 identity 属性“覆盖”了。一般而言,如果 O 是对象,M 是方法,要执行方法 O.M,系统将按以下顺序查找方法 M:
- 在对象 O 的类中
- 在其父类中(如果存在父类)
- 在其父类的父类中(如果存在)
- 等等……
因此,继承允许在子类中重定义父类中同名的方法/属性。这正是子类能够适应自身需求的关键。结合稍后将要讨论的多态性,方法/属性的重载是继承的主要优势。让我们以之前的例子为例:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
此次获得的结果如下:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. 多态性
考虑一个类层次结构:C0→ C1→ C2→ …→ Cn,其中 Ci→ Cj 表示类 Cj 继承自类 Ci。这意味着类 Cj 具有类 Ci 的所有特征以及额外的特征。设 Oi 是类型 Ci 的对象。以下写法是有效的:
事实上,根据继承关系,类 Cj 不仅具备类 Ci 的所有特征,还拥有额外的特征。因此,类型为 Cj 的对象 Oj 内部包含一个类型为 Ci 的对象。该运算
表示 Oi 是对象 Oj 内部所包含的 Ci 类型对象的一个引用。
类 Ci 的变量 Oi 实际上不仅可以引用类 Ci 的对象,还可以引用任何从类 Ci 派生的对象,这一事实被称为多态性:即变量能够引用不同类型的对象。让我们通过一个示例来考虑以下与任何类无关的函数:
Sub affiche(ByVal p As personne)
我们同样可以这样写
作为
在后一种情况下,display 函数中类型为 person 的形式参数将接收类型为 teacher 的值。由于类型 teacher 继承自类型 person,因此这是有效的。
3.2.5. 重新定义与多态性
让我们完成我们的 display 过程:
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
p.identity 方法返回一个字符串,用于标识 Person 对象。在处理 Teacher 对象时,我们之前的示例会发生什么情况:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
让我们来看以下示例:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
End Module
所得结果如下:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
执行结果表明,语句 p.identity 每次执行的都是某个人的身份属性:先是教师 e 所包含的人,然后是人 p 本身。它并未根据实际作为参数传递给 display 的对象进行适配。 我们更希望显示的是教师 e 的完整身份。要实现这一点,p.identity 这一写法需要引用 p 实际指向的对象的 identity 属性,而非 p 实际指向的对象中“person”部分的 identity 属性。可以通过在基类 Person 中将 identity 声明为可重写属性来实现这一效果:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
overridable 关键字使 *identite* 成为可重写或虚拟属性。该关键字也可应用于方法。子类若要重定义虚拟属性或方法,必须使用 overrides 关键字而非 shadows 来修饰其重定义的属性或方法。因此,在 enseignant 类中,identite 属性定义如下:
' overload property identity
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
测试程序:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
End Module
然后会产生以下结果:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
这次,我们成功获取了该教师的完整身份信息。现在,让我们重新定义一个方法,而不是属性。Object 类是所有 VB.NET 类的“父”类。因此,当我们编写:
这实际上等同于隐式地编写:
object 类定义了一个虚拟的 ToString 方法:

ToString 方法返回该对象所属类的名称,如下例所示:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test2
Sub Main()
' a teacher
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' a person
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
结果如下:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
请注意,尽管我们在 Person 和 Teacher 类中尚未重写 ToString 方法,但我们仍能看到 Object 类的 ToString 方法依然能够显示对象的实际类名。让我们在 Person 和 Teacher 类中重写 ToString 方法:
' ToString
Public Overrides Function ToString() As String
' we return the identity property
Return identite
End Function
这两个类的定义是相同的。请看以下测试程序:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
End Module
执行结果如下:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. 为类定义索引器
考虑 .NET 平台中预定义的 [ArrayList] 类。该类允许您将对象存储在列表中。它属于 [System.Collections] 命名空间。在下面的示例中,我们使用该类来存储一个人员列表(广义而言):
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' an indexer
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
下面介绍 [ArrayList] 类的某些属性和方法:
该属性返回列表中的元素个数 | |
用于将对象添加到列表中的方法 | |
返回列表中第 i 个元素的方法 |
需要注意的是,为了获取列表的第 i 个元素,我们没有写成 [list.Item(i)],而是直接写成了 [list(i)],乍看之下似乎不正确。但这确实是可行的,因为 [ArrayList] 类定义了一个默认属性 [Item],其语法类似于以下形式:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
当编译器遇到 [list(i)] 这种写法时,它会检查 [ArrayList] 类是否定义了具有以下签名的属性:
Default Public Property Proc(ByVal var As Integer) As Type
在此处,它将找到 [Item] 方法。随后,它会将 [list(i)] 这种写法转换为 [list.Item(i)]。 [Item] 属性是 [ArrayList] 类的默认索引属性。运行前面的程序将得到以下结果:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
让我们创建一个名为 [listOfPeople] 的类,它将是一个人员列表,因此这是一个很自然地从 [ArrayList] 类派生出来的特定列表:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' an indexer
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
该类具有以下方法和属性:
返回一个“表示”列表内容的字符串 | |
用于将人员添加到列表中的方法 | |
默认索引属性,返回列表中的第 i 个人员 |
让我们来看看这些新功能:
创建了一个新方法 [Add]。
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
父类 [ArrayList] 中已经存在一个具有相同签名的方法,因此使用 [Shadows] 关键字来表示新方法将替换父类中的方法。 子类的 [Add] 方法使用 [TypeOf] 函数检查要添加的对象是否确实是 [person] 类型或其派生类型。如果不是,则使用 [Throw] 语句抛出异常。如果要添加的对象确实是 [person] 类型,则使用基类的 [Add] 方法执行添加操作。
创建了一个索引属性:
' an indexer
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
基类中已经存在一个名为 [Item] 的默认属性,且其签名与之相同。因此,我们必须使用 [Shadows] 关键字来表明,新的索引属性 [Index] 将“隐藏”基类的属性 [Item]。请注意,即使这两个属性名称不同,此规则依然适用。 [Index] 属性允许您引用列表中索引为 i 的个人。它依赖于基类的 [Item] 属性来访问底层 [ArrayList] 对象中索引为 i 的元素。进行类型转换是为了适应以下事实:[Item] 属性处理 [Object] 类型的元素,而 [Index] 属性处理 [Person] 类型的元素。
最后,我们重写了 [ArrayList] 类的 [ToString] 方法:
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
此方法返回形式为“(e1,e2,...,en)”的字符串,其中 e1、e2、...、en 是列表中的元素。 请注意 [Me(i)] 这种写法,它指的是当前对象 [Me] 的第 i 个元素。这是默认使用的索引属性。因此,[Me(i)] 等同于 [Me.Index(i)]。
类代码放置在文件 [lstpersonnes2.vb] 中并编译:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
创建了一个测试程序:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Collections
Module test
Sub Main()
' create an empty list of people
Dim liste As listeDePersonnes = New listeDePersonnes
' creating people
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' list filling
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' list display
Console.Out.WriteLine(liste.ToString)
' add an object other than person
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
编译结果如下:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
然后执行:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
可能有人想写
其中 l 的类型为 [listOfPeople]。这里,我们希望通过人的名字而非元素编号来索引列表 l。为此,我们定义一个新的默认索引属性:
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
第一行
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
表明我们正在创建一个新的默认索引属性。所有默认属性必须具有相同的名称,在本例中为 [Index]。新的 [Index] 属性使用字符串 N 对 PeopleList 类进行索引。PeopleList(N) 的结果是一个整数。该整数表示列表中名称为 N 的人员的位置,如果该人员不在列表中,则返回 -1。 我们仅定义了 get 属性,从而阻止了 listOfPeople("name")=value 这种赋值操作——若允许该操作,则必须定义 set 属性。因此使用了 [ReadOnly] 关键字。而 [Shadows] 关键字则是为了隐藏基类的默认属性(尽管其签名并不相同)。
在 get 方法的实现中,我们遍历人员列表,查找作为参数传入的名称 N。若在第 i 个位置找到该名称,则返回 i;否则返回 -1。
一个新的测试程序可以如下所示:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a list of people
Dim l As New listeDePersonnes
' add people
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' display
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' change item 1
l(1) = New personne("sylvie", "cachan", 5)
' display element 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' display list l
Console.Out.WriteLine(("l=" + l.ToString))
' people search
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
执行结果如下:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. 结构
VB.NET 结构直接源自 C 语言的结构,与类非常相似。结构的定义如下:
Structure spersonne
' attributs
...
' propriétés
...
' constructeurs
...
' méthodes
End Structure
尽管语法相似,类和结构体之间存在显著差异。例如,结构体中不存在继承的概念。如果我们要编写一个不打算从其他类派生的类,结构体和类之间有哪些区别能帮助我们做出选择?让我们通过以下示例来了解:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' spersonne structure
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' class cperson
Class cpersonne
Public nom As String
Public age As Integer
End Class
' a test module
Public Module test
Sub Main()
' a person p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' a p2 person
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 is modified
sp2.nom = "nicole"
sp2.age = 30
' checking sp1 and sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' a cperson cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' a P2 person
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 is modified
cp2.nom = "nicole"
cp2.age = 30
' p1 and P2 verification
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
如果运行此程序,我们将得到以下结果:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
虽然在本章前面的页面中我们使用了 Person 类,但现在我们使用 PersonStructure:
' structure spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
声明
Dim sp1 As spersonne
创建了一个 (name, age) 结构,而 sp1 的值即为该结构本身。
声明
Dim cp1 As New cpersonne
创建了一个 [cpersonne] 对象(大致相当于我们的结构体),而 cp1 则是该对象的地址(即引用)。
总结
- 对于结构体而言,sp1 的值即为结构体本身
- 对于类而言,p1 的值是所创建对象的地址
![]() |
![]() |
在程序中,当我们编写
Dim sp2 As spersonne = sp1
将创建一个新的结构(name, age),并使用 sp1 的值(即该结构本身)对其进行初始化。
![]() |
因此,sp1 的结构被复制到了 sp2 中。这是一种值复制。
该语句
Dim cp2 As cpersonne = cp1
工作原理不同。cp1 的值被复制到 cp2 中,但由于该值实际上是对象的地址,因此对象本身并未被复制。只是有两个引用指向了它:
![]() |
对于结构体而言,如程序所示,修改 sp2 的值并不会改变 sp1 的值。对于对象而言,如程序结果所示,如果修改 cp2 所指向的对象,那么 cp1 所指向的对象也会被修改,因为它们指向的是同一个对象。
因此,根据上述解释,我们可以得出以下结论:
- 结构体类型的变量值即为该结构体本身
- 类型为对象的变量的值是其所指向的对象的地址
一旦理解了这一根本区别,结构体便会显得与类非常相似,如下面的新示例所示:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure person
Structure personne
' attributes
Private _nom As String
Private _age As Integer
' properties
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Manufacturer
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' a test module
Module test
Sub Main()
' one person p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' one person p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 is modified
p2.nom = "nicole"
p2.age = 30
' checking p1 and p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
得到以下执行结果:
这里结构与类之间唯一的显著区别在于:如果是类,程序结束时对象 p1 和 p2 将具有相同的值,即 p2 的值。
3.5. 接口
接口是一组构成契约的方法或属性原型。决定实现某个接口的类,即承诺提供该接口中定义的所有方法的实现。编译器会验证这一实现。例如,以下是 Istats 接口的定义:
任何实现此接口的类都将被声明为
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
[mean] 和 [standardDev] 方法必须在类 C 中定义。请看以下代码:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' class notes
Public Class notes
' attribute
Protected _matière As String
Protected _élèves() As élève
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' student & subject memorization
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' concatenate all the notes
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'final note
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' end
Return valeur
End Function
End Class
Notes 类用于收集某门课程中某个班级的成绩:
Public Class notes
' attribut
Protected _matière As String
Protected _élèves() As élève
这些属性被声明为受保护的,以便子类可以访问它们。student 类型是一个结构体,用于存储学生的姓名及其在该科目中的成绩:
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
我们决定将这个 notes 类派生为 notesStats 类,该类将额外包含两个属性:成绩的平均值和标准差:
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _moyenne As Double
Private _écartType As Double
notesStats 类实现了以下 Istats 接口:
这意味着 notesStats 类必须包含两个名为 average 和 standardDeviation 的方法,其签名需符合 Istats 接口中指定的要求。notesStats 类的定义如下:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _moyenne As Double
Private _écartType As Double
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' average score calculation
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' standard deviation
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' istats interface methods
Public Function moyenne() As Double Implements Istats.moyenne
' makes the average score
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' makes the standard deviation
Return _écartType
End Function
End Class
平均值 _mean 和标准差 _standardDev 在对象创建时即已计算完成。因此,mean 和 standardDev 方法仅返回 _mean 和 _standardDev 属性的值。如果学生数组为空,这两个方法均返回 -1。
以下是测试类:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' some students & notes
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' recorded in a notes object
Dim anglais As New notes("anglais", ELEVES)
' and display
Console.Out.WriteLine((anglais.ToString))
' idem with mean and standard deviation
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
结果如下:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
notesStats 类完全可以自行实现 average 和 standardDev 方法,而无需指定它实现了 Istats 接口。那么接口的意义何在?答案是:一个函数可以将类型为 I 的值作为形式参数。任何实现接口 I 的类 C 的对象,都可以作为该函数的实际参数。请看以下示例:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' an Iexample interface
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' a 1st class
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'a 2nd class
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
Iexample 接口定义了两个方法:add 和 subtract。Class1 和 Class2 类实现了该接口。请注意,为了简化示例,这些类没有执行其他操作。现在请看以下示例:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test class
Module test
'calculate
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' the Hand function
Sub Main()
' creation of two objects class1 and class2
Dim c1 As New classe1
Dim c2 As New classe2
' static function calls calculate
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
calculate 函数接受一个 Iexample 类型的元素作为参数。因此,该参数可以接收 class1 或 class2 类型的对象。Main 过程就是这样操作的,结果如下:
由此可见,这一特性与我们之前讨论的类多态性相似。如果一组互不相关的类 Ci(因此无法使用基于继承的多态性)共享了一组具有相同签名的方法,那么将这些方法归入一个接口 I 中,并让所有相关类都从该接口继承,可能会很有用。 这样,这些类 Ci 的实例就可以作为接受类型为 I 的参数的函数的参数,即这些函数仅使用接口 I 中定义的 Ci 对象的方法,而不使用各个 Ci 类的特定属性和方法。最后,请注意接口继承可以是多重的,即我们可以编写
Public Class classe
Implements I1,I2,...
其中 Ij 均为接口。
3.6. 命名空间
要在屏幕上输出一行,我们使用以下语句
如果我们查看 Console 类的定义
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
我们可以看到它属于 System 命名空间。这意味着 Console 类应称为 System.Console,因此我们实际上应写为:
我们可以通过使用 imports 子句来避免这种情况:
我们说使用 imports 子句导入 System 命名空间。当编译器遇到一个类名(此处为 Console)时,它会在 imports 子句导入的各个命名空间中查找该类。在此处,它将在 System 命名空间中找到 Console 类。现在,让我们注意与 Console 类相关的第二条信息:
Assembly: Mscorlib (in Mscorlib.dll)
这一行指明了 Console 类的定义位于哪个“程序集”中。当在 Visual Studio.NET 外部进行编译,且需要指定包含所需类的各种 DLL 的引用时,此信息会非常有用。请记住,要引用编译类所需的 DLL,应编写如下代码:
创建类时,可以在命名空间内进行定义。这些命名空间的目的是避免类之间发生命名冲突,例如在类被出售时。假设两家公司 E1 和 E2 分别将类打包在 DLL 文件 E1.dll 和 E2.dll 中进行分发。假设客户 C 购买了这两套类,其中两家公司都定义了一个 Person 类。客户 C 编译程序如下:
如果源文件 prog.vb 使用了 Person 类,编译器将无法确定应使用 E1.dll 中的 Person 类还是 E2.dll 中的 Person 类,并会报告错误。 如果 E1 公司将类创建在名为 E1 的命名空间中,而 E2 公司将类创建在名为 E2 的命名空间中,那么这两个 Person 类将分别命名为 E1.Person 和 E2.Person。客户端在其类中必须使用 E1.Person 或 E2.Person,而不能直接使用 Person。命名空间解决了这种歧义。要在命名空间中创建类,请编写:
Namespace istia.st
Public Class personne
' définition de la classe
...
end Class
end Namespace
在此示例中,让我们在命名空间中创建之前学习过的 Person 类。我们将选择 istia.st 作为命名空间。Person 类变为:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' creation of istia.st namespace
Namespace istia.st
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
该类编译为 person.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
现在让我们在测试类中使用 Person 类:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports istia.st
' pg test
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
为避免编写
Dim p1 As New istia.st.personne
我们已通过 Imports 子句导入了 istia.st 命名空间:
Imports istia.st
现在让我们编译这个测试程序:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
这将生成一个 test.exe 文件,执行后会得到以下结果:
3.7. 税费计算示例
我们将重新审视上一章已介绍过的税费计算,并使用类来处理它。让我们回顾一下这个问题:
我们考虑一个简化的案例,即纳税人只需申报工资收入:
- 对于该员工,若其未婚,则税级数 nbParts = nbEnfants / 2 + 1;若已婚,则 nbParts = nbEnfants / 2 + 2,其中 nbEnfants 表示子女数量。
- 如果其子女数量至少为三名,则可额外获得半份
- 我们计算其应税收入 R = 0.72 * S,其中 S 为其年薪
- 我们计算其家庭系数 QF = R / nbParts
- 我们计算其应纳税额 I。请看下表:
12620.0 | 0 | 0 |
13,190 | 0.05 | 631 |
15,640 | 0.1 | 1,290.5 |
24,740 | 0.15 | 2,072.5 |
31,810 | 0.2 | 3,309.5 |
39,970 | 0.25 | 4,900 |
48,360 | 0.3 | 6,898.5 |
55,790 | 0.35 | 9,316.5 |
92,970 | 0.4 | 12,106 |
127,860 | 0.45 | 16,754.5 |
151,250 | 0.50 | 23,147.5 |
172,040 | 0.55 | 30,710 |
195,000 | 0.60 | 39,312 |
0 | 0.65 | 49,062 |
每行有 3 个字段。要计算税款 I,请查找满足 QF <= 字段1 的第一行。例如,如果 QF = 23,000,则找到的行将是
此时,Tax I 等于 0.15*R - 2072.5*nbParts。如果 QF 使得条件 QF<=field1 永远不成立,则使用最后一行中的系数。这里:
由此可得税额 I = 0.65 * R - 49062 * nbParts。
**impot** 类将定义如下:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class impot
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' check that the 3 arrays have the same size
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' it's good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calculating the number of shares
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
创建一个税务对象,其中包含计算纳税人应纳税额所需的数据。这是该对象的稳定部分。一旦创建了该对象,就可以反复调用其 **calculate** 方法,根据纳税人的婚姻状况(已婚或未婚)、子女数量和年薪来计算其应纳税额。一个测试程序可能如下所示:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' data tables required for tax calculation
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' tax object creation
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' infinite loop
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' tax calculation parameters are requested
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' anything to do?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' checking parameter validity
If Not erreur Then
' married
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' salary
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' if the parameters are correct - the tax is calculated
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
以下是上述程序的运行示例:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





