c#数据结构(1.3集合,递归,接口,泛型)

一,集合

1、集合的概念

      集合(Set)是由一些确定的彼此不同的成员(Member)或者元素(Element)构成的一个整体。

      成员取自一个更大的范围,称为基类型(Base Type)。集合中成员的个数称为集合的基数(Cardinality)。

      例如,集合 R 由整数 3、4、5 组成,写成 R={3,4,5}。此时 R 的成员是 3、4、5,R 的基类型是整型,R 的基数是 3。依赖于集合的基类型,它的成员经常有一个线性顺序。集合的每个成员或者是基类型的一个基本元素(Base Element),或者它本身也是一个集合。我们把是集合的成员叫做该集合的子集(Subset),子集中的每个成员都属于该集合。没有元素的集合称为空集(Empty Set,又称为 Null Set),记作Φ。如上例中,3 是 R 的成员,记为:3∈R,6 不是 R 的成员,记为:6∉R。{3,4}是 R 的子集。

2、 集合的表示法

1) 穷举法:S={2,4,6,8,10};

2) 描述法:S={x|x 是偶数,且 0≤x≤10}。

3、集合的特性

1) 确定性:任何一个对象都能被确切地判断是集合中的元素或不是;

2) 互异性:集合中的元素不能重复;

3) 无序性:集合中元素与顺序无关。

阶乘函数(Factorial Function):阶乘函数 n!是指从 1 到 n 之间所有整数的连乘,其中 n 为大于 0 的整数。因此,5!=1·2·3·4·5=120。特别地,0!=1。

二,递归

1,递归的概念:

      一个算法调用自己来完成它的部分工作,在解决某些问题时,一个算法需要调用自身。如果一个算法直接调用自己或间接地调用自己,就称这个算法是递归的(Recursive)。

     一个递归算法必须有两个部分:初始部分 (Base Case) 和 递归部分(Recursion Case)。初始部分只处理可以直接解决而不需要再次递归调用的简单输入。递归部分包含对算法的一次或多次递归调用,每一次的调用参数都在某种程度上比原始调用参数更接近初始情况。

例子


三,接口

1、 接口的定义

      接口(Interface)定义为一个约定,实现接口的类或结构必须遵守该约定。

      简单的说,接口是类之间交互时遵守的一个协议。最初接触“类与类之间通过接口交互”这个概念时,误以为接口就是类公开的方法,类之间通过类的方法进行交互。其实接口是独立于类的一个定义,定义了类之间交互的标准。 

      那么类与类之间直接交互就好了,为什么还要使用接口呢

     在程序设计过程中,如果将一个对象看作多个类型将是非常有用的,因为对象的类型描述了它的能力和行为。比如,设计一个 SortedList 类型来保存一个有序对象集合。只要对象的类型支持将它和其他类型的对象比较的能力,便可将任何继承自 System.Object 类型的对象添加到 SortedList 中。也就是说,希望SortedList 接受的类型继承自某个假想的 System.Comparable 类型。但是,许多现存的类型并不是继承自 System.Comparable 类型。这样,便不能将这些类型的对象添加到 SortedList 中,SortedList 类型也将变得不是很有用。在理想情况下,一方面希望 SortedList 中对象的类型能够继承自现存的System.Object 类型,另一方面,又可以将 SortedList 中对象的类型看作是从System.Comparable 类型继承而来。这种将一个对象看成多个类型的能力通常称作多继承(Multiple Inheritance)。

    通用语言运行时(CLR)支持单实现继承和多接口继承。单实现继承(Single Implementation Inheritance)是指一个类型只能有一个基类型。所以,单实现继承无法实现上述 SortedList 的功能。多接口继承(Multiple Interface Inheritance)是指一个类型可以继承多个接口,而接口是类之间相互交互的一个抽象(Abstract),把类之间需要交互的内容抽象出来定义成接口,可以更好地控制类之间的逻辑交互。可见,接口内容的抽象好坏关系到整个程序的逻辑质量。另外,可以在任何时候通过开发附加接口和实现来添加新的功能。所以,多接口继承可以实现上述 SortedList 的功能。

    关于接口一个很重要的概念是接口只包含成员定义不包含成员的实现。接口不会继承自任何的 System.Object 派生类型。接口仅仅是一个包含着一组虚方法的抽象类型。成员的实现需要在继承的类或者结构中实现。接口的成员包括静态方法、索引器、常数、事件以及静态构造器等,不包含任何实例字段或实例构造器,所以,不能实例化一个接口。实现接口的类必须严格按其定义来实现接口的每个成员。接口本身一旦被发布就不能再更改,对已发布的接口进行更改会破坏现有的代码。根据约定,接口类型的名称要加一个大写的字母 I 前缀。接口定义允许使用修饰符,例如 public、protected、internal 以及 private 等,这和类与结构的定义一样。当然,大部分情况下使用 public。

接口总结:比如写一个user类,既想用peploe类里的“玩手机”功能,又想用school里的“学习”功能。但是已有的继承,只能够单实现继承,也就是要么实现“玩手机”功能要么实现“学习”功能。所以开发人员定义出接口,以实现多接口继承这样两个功能就可以同时继承、实现。

2、接口与抽象类

       抽象类(Abstract Class)和接口在定义与功能上有很多相似的地方,在程序中选择使用抽象类还是接口需要比较抽象类和接口之间的具体差别。抽象类是一种不能实例化而必须从中继承的类,抽象类可以提供实现,也可以不提供实现。子类只能从一个抽象类继承。抽象类应主要用于关系密切的对象。如果要设计大的功能单元或创建组件的多个版本,则使用抽象类。接口是完全抽象的成员集合,不提供实现。类或者结构可以继承多个接口。接口最适合为不相关的类提供通用功能。如果要设计小而简练的功能块,则使用接口。接口一旦创建就不能更改,如果需要接口的新版本,必须创建一个全新的接口。

3、接口的实现

       接 口 的 实 现 分 为 隐 式 实 现 (Implicit Implementation) 和 显 式 实 现(Explicit Implementaton)。如果类或者结构要实现的是单个接口,可以使用隐式实现,如果类或者结构继承了多个接口,那么接口中相同名称成员就要显式实现。显式实现是通过使用接口的完全限定名来实现接口成员的。

知识点:

抽象类的特点:

抽象方法一定在抽象类中。
抽象方法和抽象类都必须使用abstract关键字修饰。
抽象类不可以创建对象,因为调用抽象类的函数没有意义。
抽象类的方法要被使用,必须由子类进行复写(override)抽象类方法后,建立子类对象调用。如果子类只是覆盖了部分抽象的方法,那么该子类一定是一个抽象类。

注意:抽象类中可以不定义抽象方法,这样做仅仅是为了不让别人建立对象。

继承抽象类的方法必须重写 继承普通不用 因为抽象类里的方法是没有方法块。

四、泛型编程

      泛型(Generic Type)是.NET Framework 2.0 最强大的功能。泛型的主要思想就是将算法与数据结构完全分离开来,使得一次定义的算法能够作用于多种数据结构,从而实现高度可重用的开发。通过泛型可以定义类型安全的数据结构,而没有必要使用实际的数据类型。这将显著提高性能并得到更高质量的代码,因为可以重用数据处理算法,而没有必要复制类型特定的代码。

     泛型编程的优点:

1、性能高

例:

非泛型集合

ArrayList list=new ArrayList();
list.Add(20);    //装箱,list存放的是object类型元素,须将值类型转化为引用类型

int i=(int)list[0];     //拆箱,list[0]的类型是object,要赋值就得把引用类型转化为值类型

泛型集合
 List<T> list=new List<int>();
 list.Add(20);    //因为指定了用int来实例化,因此不必装箱

 int i=list[0];    //同样地,访问时也不需要拆箱

泛型编程避免了装箱和拆箱操作,减少了性能损耗。

2、类型安全

例:

非泛型集合

 ArrayList list=new ArrayList();
 list.Add(20);
 list.Add("string");
 list.Add(new MyClass());
  foreach(int i in list)
 {
        Console.WriteLine(i);    //这里会有个异常,因为并不是集合中的所有元素都可以转化为int

  }

 如果使用泛型编程,则可以避免这种异常,让编译器检查出错误

 List<int> list=new List<int>();
 list.Add(20);
 lsit.Add("string");   //编译时报错,只能报整数类型添加到集合中

 list.Add(new MyClass());   //同上

如果使用非泛型编程,就有可能在某些情况下会发生异常。而泛型编程就会在编译时报错

3.二进制代码重用
    泛型可以定义一次,用许多种不同的类型实例化,泛型可以在一种语言中定义,在另一种.NET语言中使用。

4.代码的扩展。
    因为泛型类的定义会放在程序集中,值类型包含在实例化的泛型类的内存中。而每个值类型对内存的要求都不同,所以要为每个值类型实例化一个新类。

扩展:
    泛型类封装了不针对任何特定数据类型的操作。泛型类常用于容器类,如链表、哈希表、栈、队列、树等等。这些类中的操作,如对容器添加、删除元素,不论所存储的数据是何种类型,都执行几乎同样的操作。

猜你喜欢

转载自blog.csdn.net/qq_40138785/article/details/81025902