WEO啦

首页 » 正文内容 » VisualC#07-1面向对象和类

VisualC#07-1面向对象和类

时间:2023-03-20 02:18:42  热度:0°C

1、第7章 面向对象和类/本章要点 面向对象程序设计的概念 掌握命名空间的意义 掌握类的声明方法 掌握构造器和析构器 掌握方法/私有方法/共有方法的声明 掌握域、属性、索引等概念/第7章 面向对象和类/7/1 面向对象编程概述 7/1/1结构化程序设计 结构化程序设计是为了解决早期计算机程序的难于阅读、理解和调试,难于维护和扩充,以及开发周期长、不易控制程序的质量等问题而提出来的,它的产生和发展奠定了软件工程的基础。 结构化程序设计的基本思想是:自顶向下,逐步求精,将整个程序结构划分成若干个功能相对***的模块,模块之间的联系尽可能简单;每个模块用顺序、选择、循环三种基本结构来实现;每个模块只有一个

2、入口和一个出口/第7章 面向对象和类/7/1/1 结构化程序设计 结构化程序设计的优点: (1)各模块可以分别编程,使程序易于阅读、理解、调试和修改; (2)方便新功能模块的扩充; (3)功能***的模块可以组成子程序库,有利于实现软件复用等。 由于这些优点,结构化程序设计方法出现以后,很快被人们接受并得到广泛应用/第7章 面向对象和类/7/1/1结构化程序设计 结构程序设计的问题:结构化程序设计方法以解决问题的过程作为出发点,其方法是面向过程的。它把程序定义为/ “数据结构+算法” 程序中数据与处理这些数据的算法(过程)是分离的。这样,对不同的数据结构作相同的处理,或对相同的数据结构作不同的处

3、理,都要使用不同的模块,降低了程序的可维护性和可复用性。同时,由于这种分离,导致了数据可能被多个模块使用和修改,难于保证数据的安全性和一致性/第7章 面向对象和类/7/1/1结构化程序设计 对于小型程序和中等复杂程度的程序来说,结构化程序设计是一种较为有效的技术,但对于复杂的、大规模的软件的开发来说,它就不尽如人意了。 可以看出:结构化程序设计的核心思想是功能的分解,特点是将数据结构与过程分离,着重点在过程。用这种方法开发的软件可维护性和可复用性差/第7章 面向对象和类/7/1/2 面向对象程序设计 面向对象的程序设计(Object-Oriented Programming)是一种基于结构分析

4、的、以数据为中心的程序设计方法。它的主要思想是: 将数据及处理这些数据的操作都封装(Encapsulation)到一个称为类(Class)的数据结构中。使用这个类时,只需要定义一个类的变量即可,这个变量叫做对象(Object)。通过调用对象的数据成员完成对类的使用。在该方法下,编程人员不需要过分关注“如何做”,而重点关注“做什么/第7章 面向对象和类/7/1/2 面向对象程序设计 结构化程序设计方法与面向对象程序设计的区别/ (1)结构化程序设计方法是面向过程的结构化方法,程序 设计从算法入手,然后是数据结构。 (2)面向对象程序设计(OOP)首先考虑的是程序处理的数 据结构,即对象/第7章

5、面向对象和类/7/1/2 面向对象程序设计 在面向对象程序设计中/ (1)对象是构成软件系统的基本单元/ (2)从同类型的对象中抽象出一种新型的数据类型/即类/ 对象只是类的实例/ (3)类的成员中不仅包含有描述类对象属性的数据,还 包含有对这些数据进行处理的程序代码(这些程序 代码被称为对象的行为或操作/第7章 面向对象和类/7/1/2 面向对象程序设计 在面向对象程序设计中/ (1)对象是构成软件系统的基本单元/ (2)从同类型的对象中抽象出一种新型的数据类型/即类/ 对象只是类的实例/ (3)类的成员中不仅包含有描述类对象属性的数据,还 包含有对这些数据进行处理的程序代码(这些程序 代码

6、被称为对象的行为或操作/第7章 面向对象和类/7/1/2 面向对象程序设计 何为封装? 将对象的属性和行为放在一起作为一个整体的方法。封 装将对象的大部分行为的实现隐蔽起来,仅通过一个可 控的接口与外界交互。 类的派生: 面向对象程序设计提供了类的继承性,可通过对一个被 称为基类的类增添不同的特性来派生出多种被称为派生 类的特殊类,使得类与类之间建立了层次结构关系,为 软件复用提供了有效的途径/第7章 面向对象和类/7/1/2 面向对象程序设计 面向对象程序设计的基本思想: 数据的分解,即着重点放在***作的数据上而不是实现 操作的过程上。它把数据及其操作作为一个整体对待, 数据本身不能被外部过

7、程直接存取。 思想的特点是: 程序一般由类的定义和类的使用两部分组成,主程序中 定义各个对象并规定它们之间传递消息的规律,程序中 的一切操作都通过向对象发送消息来实现,对象接收到 消息后,调用有关对象的行为来完成相应的操作。 优点: 用这种方法开发的软件可维护性和可复用性高/第7章 面向对象和类/7/1/3 类和对象概述 在面向对象程序设计方法中,类是对自然现象或实体的程序语言描述,对象是对类的实例化。例如: 类: 类可以泛指某一批外形、价格、生产厂家、出厂日期完 全一致的笔; 对象: 用这个类声明的一个对象是这些笔中一支实实在在的笔。 而且单纯从这一支笔中可以得到这样的常识:它具有一定 的属

8、性(外形、价格、颜色、长短)、具有一定的行为 (写字、绘画),和“笔”这个类具有相同的属性和行为/第7章 面向对象和类/7/1/3 类和对象概述 例如,笔是类,钢笔、铅笔、毛笔可以是笔的子类,2B铅笔可以是铅笔的子类。张红买了1支2B铅笔,假设该铅笔叫APEN,则APEN就是类(2B铅笔类)的实例,称为对象,它可以有颜色、价格、长度、形状等属性/第7章 面向对象和类/面向对象程序设计的主要特性有: (1)封装 封装就是把相关的数据和代码结合成一个有机的整体,形成数据和操作代码的封装体,对外只提供一个可以控制的接口,内部大部分的实现细节对外隐蔽,达到对数据访问权的合理控制。 封装使程序中个部分之

9、间的相互联系达到最小,提高了程序的安全性,简化了程序代码的编写工作,是面向对象程序设计的重要原则/第7章 面向对象和类/2)继承(代码重用) 在面向对象程序设计中,继承表达的是对象类之间的关系,这种关系使得一类对象可以继承另一类对象的属性(数据)和行为(操作),从而,提供了通过现有的类创建新类的方法,也提高了软件复用的程度/第7章 面向对象和类/3)多态 多态是指不同的对象受到相同的消息时产生不同的操作行为,或者说同一个消息可以根据发送消息的对象的不同而采用多种不同的操作行为。多态性是面向对象程序设计的重要特性之一,例如: 当用鼠标单击不同的对象时,各对象就会根据自己的理解作出不同的动作,产生

10、不同的结果,这就是多态性。简单地说,多态性就是一个接口,多种方式。 C+语言支持两种多态性,即编译时的多态性和运行时的多态性。编译时的多态性是用函数的重载来实现的,运行时的多态性是用虚函数来实现的/第7章 面向对象和类/4)重载(overloading) 允许使用具有相同的方法名称的多个方法/区分方法的途径是方法具有不同的参数列表。如求立方的方法: int Cube(int number)/ float Cube(float number)/ double Cube(double number/第7章 面向对象和类/7/2 命名空间 在许多高级语言中,为了使编译系统内核尽量小,常常把每次编译中

11、肯定要用到的部分放入编译内核,而将其他部分生成***的单元放入到外围,用户在使用时,通过特定的命令将其调入。例如: C和C+语言中,系统将许多函数和预定义预存到一些称为头文件的文本文件中,用户在使用时,通过#include的命令就可以把这一部分调入并和源程序一起编译/第7章 面向对象和类/7/2 命名空间 在C#中,系统提供了命名空间来***程序。在/NET编程环境中,命名空间是一个很重要的概念,它提供了一个逻辑上的层次结构体系。 命名空间既可以作为程序的内部***系统,又可以作为程序的外部***系统。当作为外部***系统时,命名空间中的元素可以为其他程序所用/第7章 面向对象和类/7/2/1 命名空间

12、的声明 命名空间的声明方法为: namespace 命名空间名 命名空间定义体 / 命名空间前面不需要有任何修饰符,命名空间声明的最后一个分号可以省略/第7章 面向对象和类/7/2/1 命名空间的声明 一个C#程序可以包含一个或多个编译单元,每个编译单元包含在一个***的源文件中(编译单元就是能够被编译的最小单位)。 当C#程序被编译时,编译器会对程序的所有编译单元进行统一处理 。在一个编译单元中命名空间名的声明必须惟一,不能出现同名命名空间,否则系统会出现编译错误。 另外,虽然编译单元可能会相互依赖,但编译单元使用的命名空间调用语句只对本单元的属性和命名空间成员声明产生影响,不会影响到其他的编

13、译单元中/形式1: namespace NameSpaceOutside /命名空间NameSpaceOutside的定义体部分 namespace NameSpaceInside /命名空间NameSpaceInside的定义体部分/形式2: namespace NameSpaceOutside/NameSpaceInside /命名空间NameSpaceInside的定义体部分/命名空间中的各个成员,如果没有另外声明,默认的访问修饰符是public,在其他命名空间中可以直接访问。命名空间声明时可以嵌套。例如/如果在一个命名空间中使用其他已定义好的命名空间,只需要在这个命名空间使用using

14、语句便可对其他命名空间进行引用。例如/Namespace MyNameSpace using System; using NameSpaceOutside; /命名空间的其他定义部分/在该例中,命名空间MyNameSpace在定义体中引用了系统命名空间System和此前定义的命名空间NameSpaceOutside。注意,在一个命名空间体内,如果要引用其他命名空间,应该在声明其他任何类型之前引用。在该例中,如果把两行using命令放置到“命名空间的其他定义部分”之后就会出现编译错误/如果多个命名空间具有相同的完整合法名称,那么这几个命名空间属于一个命名空间/namespace NameSpac

15、e class A namespace NameSpace class B/该例中,两个命名空间声明具有相同的完整合法名NameSpace,所以它们属于同一个命名空间。此后对命名空间NameSpace中的类访问时,只需要直接使用NameSpace/A或NameSpace/B即可/第7章 面向对象和类/7/2/2 命名空间的成员 在一个命名空间中可以包含多个成员声明,这些声明共同组成了这个命名空间的成员。命名空间的成员类型多种多样,可以是一个类、结构、接口、枚举、委托等结构,也可以是另一个命名空间。无论是哪一种,命名空间成员类型声明都分别和各自类型声明要求一致/第7章 面向对象和类/7/2/2

16、命名空间的成员 类型声明可以出现在编译单元的第一行中作为顶层声明,也可以出现在命名空间、类或结构的内部作为成员声明。如果类型声明以顶层声明出现,新声明的类型作为一个***于其他编译单元的类型,此时,这个类型的名字本身就是它的完整合法名称。 如果一个类型声明在一个命名空间、类或结构内部,那么此类型就以它所在的命名空间、类或结构的成员身份出现,它的完整合法名应该与所在命名空间、类或结构关联起来表示,中间以“/”隔开。例如:用S代表要声明的类型,N代表命名空间、类或结构。如果S是顶层声明,则S的完整合法名就是简单的类型S,如果类型S声明在N内部,则S的完整合法名就是N/S/第7章 面向对象和类/7/2

17、/2 命名空间的成员 对类型声明的访问权限与该类型声明所处的位置有关: (1)在编译单元或者命名空间中声明的类型,可以具有公有(public)或内部(internal)两种访问权限。默认的访问权限是内部。 (2)在类中声明的类型,访问权限可以是公有(public)、保护(protected)、内部(internal)、私有(private),默认的访问方式是私有。 (3)在结构中声明的类型,访问权限可以是公有(public)、保护(protected)、私有(private),默认的访问方式是私有/第7章 面向对象和类/7/2/3 命名空间的使用 在C#中,命名空间的调用是通过using指令实

18、现的。using指令有两种用途:一种是别名使用指令,可以定义一个命名空间或类型的别名;另一种是命名空间使用指令,可以引入一个命名空间的类型成员。 using指令的有效范围是指该指令所属的编译单元或命名空间/第7章 面向对象和类/7/2/3 命名空间的使用 (1)别名使用指令 与C或C+中的#define命令类似,C#中可以使用别名使用指令为命名空间或类型定义别名,此后的程序就可以使用这个别名来代替定义的这个命名空间或类型。别名使用指令的格式为: using 别名=命名空间名或类型名/利用别名使用指令可以避免使用冗长的命名空间,例如下面代码/using LastMessage=MyProgram

19、/MessageManagement/AllMessage/NextMessage/再看下面的例子/namespace NameSpace1/NameSpace2 class A namespace NameSpace3 using R=NameSpace1/NameSpace2/A; class B/R/namespace NameSpace1/NameSpace2 class A namespace NameSpace3 using R=NameSpace1/NameSpace2; class B/R/A/利用别名使用指令所定义的别名的有效范围是该指令所属的编译单元或命名空间,因此这里的别

20、名在该编译单元或命名空间中应该是惟一的,不能与其它成员同名,否则就会出错。例如/namespace NameSpace1 namespace NameSpace2 class ClassA namespace NameSpace2 using ClassA=NameSpace1;/这里的三个命名空间中,后两个命名空间名一样,是同一个命名空间。这两个命名空间中,前一个声明了一个类ClassA,后一个引用了一个别名使用指示ClassA。二者虽然类型不同,但由于这两个成员同名,所以出错/在使用别名时,如果别名所定义的命名空间或命名空间中的成员不存在,同样会出现错误。例如/namespace Name

21、Space1 class ClassA namespace NameSpace2 using R=NameSpace1/ClassA/ class ClassB/ R/ClassA/与一般成员一样,别名使用指令定义的别名在整个命名空间或整个编译单元内都有效,该命名空间内其他成员都可直接使用。例如/using R=NameSpace1/ /假设此前已定义NameSpace1,并且有一个成员是类A namespace NameSpace2 class ClassB/R/A namespace NameSpace3 class ClassC/R/A/当在一个编译单元中同时声明多个别名时,别名的声明顺

22、序没有关系,并且每个别名的声明不会受到自身或别的别名的影响。例如/namespace NameSpace1/NameSpace2 namespace NameSpace3 using r1=NameSpace1/ using r2=NameSpace1/NameSpace2/ using r3=r1/NameSpace2//第7章 面向对象和类/7/2/3 命名空间的使用 (2)命名空间使用指令 命名空间使用指令可以将一个命名空间的类型整个导入到另一个编译单元或命名空间中,并且对这些类型的使用不需要使用完整合法名称,直接引用类型名称即可。命名空间使用指令的格式为: using 命名空间名/命名

23、空间名应该是由系统提供的或用户已定义的命名空间名。和别名不同的是:在包含命名空间的编译单元或命名空间定义体内部,别名在成员声明时需要引用别名的成员,而命名空间可以直接使用已定义的命名空间的类型,不需要指出命名空间名/例如对于大家比较熟悉的系统预定义的命名空间System,引用该命名空间后,可以直接使用其中的成员/using System/ class Test static void Main() Console/WriteLIne(“Hello/World!”)//对于用户自己定义的命名空间,先定义用户自己的命名空间后也可以直接使用,例如/namespace NameSpace1/NameS

24、pace2 class ClassA /命名空间NameSpace1/NameSpace2的类ClassA定义体 namespace NameSpace3 using NameSpace1/NameSpace2/ class ClassB/ClassA/注意:命名空间使用指令只能导入命名空间本身,而不会导入嵌套的命名空间,例如/namespace NameSpace1/NameSpace2 class ClassA /命名空间NameSpace1/NameSpace2的类ClassA定义体 namespace NameSpace3 using NameSpace1/ class ClassB/

25、NameSpace2/ClassA /出错/如果在编译单元或命名空间中定义的成员名与命名空间名相同,编译器将按成员名优先的原则。例如/namespace NameSpace1 class ClassA class ClassB namespace NameSpace2 using NameSpace1/ class ClassB/命名空间NameSpace2中使用了命名空间使用指令,但由于编译器在编译过程中,按在同一编译单元或命名空间中成员优先的原则,所以命名空间NameSpace2中的ClassB表示的是NameSpace2/ClassB,而不是NameSpace1/ClassB。 Name

26、Space1/ClassB是指命名空间NameSpace1中的那个类ClassB/如果在编译单元或命名空间中有多个命名空间使用指令使用,而这些命名空间中又包含了相同的类型名,用户定义和使用时要注意,不要引起二义性。例如/namespace NameSpace1 class ClassA namespace NameSpace2 class ClassA namespace NameSpace3 using NameSpace1/ using NameSpace2/ class ClassB/ClassA /出错/为避免上述错误的发生,在使用过程中,应该在程序中直接指明使用的是哪一个成员。例如/

27、namespace NameSpace1 class ClassA namespace NameSpace2 class ClassA namespace NameSpace3 using NameSpace1/ using NameSpace2/ using R=NameSpace2/ClassA/ class ClassB/R/第7章 面向对象和类/7/2/4 命名空间的举例 每个类或类库都定义在包含该库中所有类的命名空间中。可以以命名空间为单位,将类封装到给其它程序重用的动态链接库中,程序可以在执行时载入动态链接库来访问在很多程序间共享的公用功能。一个动态链接库代表一个程序集。项目使用类

28、库时,该项目必须包含对定义类库的程序集的引用。下面看一下如何将类装载到动态链接库中以及如何调用。 先编写如下程序代码/下面创建一个类AssemblyTest,在该类中引用程序集TimeLibrary/dll,并显示标准和通用的时间字符串格式/程序运行结果为: Standard time/ 1/27/06PM Universal time/13/27/06/第7章 面向对象和类/注意点: 命名空间使用指令在一定程度上能减少同一编译单元内或命名空间的程序代码量,增加可读性。但如果使用过多,反而会使程序关系复杂,减弱可读性,增加出错的可能。因此在使用时,特别是使用用户自己定义的别名使用指令或命名空间

29、使用指令时,应尽可能指明其隶属关系/第7章 面向对象和类/7/3 声明自己的类 在面向对象的程序设计中,静态的基本单位是类,动态的基本单位是对象,向对象发送消息可以激活对象的行为,所以面向对象的程序可以描述为:对象+消息传递。 7/3/1 类的声明 类的声明格式为: 类修饰符class 类名/基类类名 类的成员; C#支持的类修饰符有:new、public、protected、internal、private、abstract和sealed,其含义为/new:新建类,表明由基类中继承而来的、与基类中同名的成员。 public:公有类,表示外界可以不受***地访问。 protected:保护类,表

30、示可以访问该类或从该类派生的类型。 internal:内部类,仅能访问本程序。 private:私有类,只有该类才能访问。 abstract:抽象类,是一个不完整的类,只有声明而没有具体的实现。一般只能用来做其它类的基类,而不能单独使用。 sealed:密封类,不能作其它类的基类,不能再派生新的类。 使用以上类修饰符应注意以下几点: (1)在一个类声明中,同一类修饰符不能出现多次; (2)new类修饰符仅允许在嵌套类中表示类声明时使用,表明类中隐藏了由基类中继承而来的、与基类中同名的成员。 (3)在使用public、protected、internal和private修饰符时,它们不仅表示所定

31、义类的访问特性,而且还表明类中成员声明时的访问特性,并且它们的可用性也会对派生类造成影响/4)抽象类修饰符abstract和密封类修饰符sealed都是受限类修 饰符。抽象类修饰符只能作其它类的基类,不能直接使用。密封类修饰符不能作其它类的基类,可以由其它类继承而来但不能再派生其它类。一个类不能同时既使用抽象类修饰符又使用密封类修饰符。 (5)如果省略类修饰符,则默认为私有修饰符private。 (6)对于具有继承关系的类才有基类。如果一个类没有从任何类继承,就不需要基类类名选项。在C#中,一个类只能从另一个类中继承,而不能从多个类中继承;如果一个类想继承多个类的特点,可以采用接口的方法实现。

32、 在C#中,对象的定义很简单,其定义格式为: 类名 对象名/ 创建一个类的实例一般采用new修饰符。例如/using System/ class ClassA public string c=This is ClassA// class ClassB static void Fun() ClassA a/ a=new ClassA()/ Console/WriteLine(a/c)/ static void Main() Fun()//程序运行结果为: This is ClassA/类的继承也很简单,请看如下代码/程序运行结果为: This is ClassA/第7章 面向对象和类/7/3/2

33、 类的成员 在C#中,类的成员分为两大类,一种不以函数形式体现,称为成员变量;另一种是以函数形式体现,称为成员函数。类的具体成员有以下类型: 常量:代表与类相关的常量值。 变量:类中的变量。 方法:完成类中各种计算或功能的操作。 属性:定义类的值,并对它们提供读、写操作。 事件:由类产生的通知,用于说明发生了什么事情/索引器:允许编程人员在访问数组时,通过索引器访问类的多个实例。 索引器又称下标指示器。 运算符:定义类的实例能使用的运算符。 类 型:属于类的局部类型。 构造函数:在类被实例化时首先执行的函数,主要是完成对象的初始化 操作。 析构函数:在对象被销毁之前最后执行的函数,主要是完成对

34、象结束时 的收尾操作。 提示:以上各类型中,方法、属性、索引器、运算符、构造函数和析构函数是以函数形式体现,统称为成员函数。其它部分统称为成员变量/用户可以根据具体需要定义类的成员,但定义时需要注意以下几个问题: (1)由于构造函数规定为和类名相同,析构函数名规定为类名前加一个“”波浪线符号,所以其它成员名就不能命名为和类同名或是类名前加波浪线。 (2)类中的常量、变量、属性、事件或类型不能与其它类成员同名。 (3)类中的方法名不能和类中其它成员同名,既包括其它非方法成员,又包括其它方法成员。 (4)如果没有显示指定类成员访问修饰符,默认类型为私有类型修饰符/类的每个成员都需要设定访问修饰符,

35、不同的修饰符会造成对成员访问能力不一样。C#中成员主要有以下几种: 公有成员。这种成员允许类的内部或外界直接访问,修饰符是public。这是***最少的一种访问方式,它的优点是使用灵活,缺点是外界可能会破坏对象成员值的合理性。 私有成员。外界不能直接访问该成员变量或成员函数。对该成员变量或成员函数的访问只能由该类中其它函数访问,其派生类也不能访问。 保护成员。对于外界该成员是隐藏的,但这个类的派生类则可以访问。 内部成员。只有本类的成员才能访问。 下面成员代码说明了成员访问修饰符的作用/class ClassA public int a/ private int b/ protected int

36、 c/ public void SetA() a=1/ /正确,类自身的公有成员 b=2/ /正确,类自身的私有成员 c=3/ /正确,类自身的保护成员 class ClassB/ClassA public void SetB() a=11/ /正确,允许访问基类的公有成员 b=22/ /错误,不允许访问基类私有成员 c=33/ /正确,允许访问基类的保护成员/class ClassC public void SetC() ClassA BaseA=new ClassA()/ BaseA/a=111/ /正确,允许访问类的其它公有成员 BaseA/b=222/ /错误,不允许访问类的其它私有成

37、员 BaseA/c=333/ /错误,不允许访问类的其它保护成员/成员又可分为静态成员和非静态成员。声明一个静态成员只需要在声明成员的指令前加上static保留字。如果没有这个保留字就默认为非静态成员。二者的区别是: 静态成员属于类所有,非静态成员则属于类的对象所有 访问时静态成员只能由类来访问,而非静态成员只能由对象访问。 请看下面的代码/class ClassA public int a/ static public int b/ void Fun1() /定义一个非静态成员函数 a=10/ /正确,直接访问非静态成员 b=20/ /正确,直接访问静态成员 static void Fun2

38、() a=10/ /错误,不允许访问非静态成员 b=20/ /正确,允许访问静态成员,相当于ClassA/b=20/class Test static void Main() ClassA A=new ClassA()/ A/a=111/ /正确,访问类ClassA的非静态公有成员变量a A/b=222/ /错误,不能直接访问类中静态公有成员 ClassA/a=20/ /错误,不能通过类访问类中非静态公有成员 ClassA/b=20/ /正确,可以通过类访问类ClassA的静态公有成员变量b/第7章 面向对象和类/7/3/3 声明对象 类只是个静态概念,要想使用类,还需要对类进行实例化,也就是声明对象,声明对象后,就可以通过对象访问类中的公有类型数据或成员函数,使用格式为: 对

温馨提示:
1. WEO啦仅展示《VisualC#07-1面向对象和类》的部分公开内容,版权归原著者或相关公司所有。
2. 文档内容来源于互联网免费公开的渠道,若文档所含内容侵犯了您的版权或隐私,请通知我们立即删除。
3. 当前页面地址:https://www.weo.la/doc/62aa8b4442f73a43.html 复制内容请保留相关链接。