C#中泛型和单链表


泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险

泛型概述:
1.使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
2.泛型最常见的用途是创建集合类。
3..NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList。
4.您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
5.可以对泛型类进行约束以访问特定数据类型的方法。
6.关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。

泛型好处举例:
1.ArrayList 是一个使用起来非常方便的集合类,无需进行修改即可用来存储任何引用或值类型。但这种方便是需要付出代价的。添加到 ArrayList 中的任何引用或值类型都将隐式地向上强制转换为 Object。如果项是值类型,则必须在将其添加到列表中时进行装箱操作,在检索时进行取消装箱操作。强制转换以及装箱和取消装箱操作都会降低性能;在必须对大型集合进行循环访问的情况下,装箱和取消装箱的影响非常明显。
2.另一个限制是缺少编译时类型检查;因为 ArrayList 将把所有项都强制转换为 Object,所以在编译时无法防止客户端代码执行
3.这种做法更可能产生编程错误,并且直到运行时才能检测到此错误
4.对于客户端代码,与 ArrayList 相比,使用 List<T> 时添加的唯一语法是声明和实例化中的类型参数。虽然这稍微增加了些编码的复杂性,但好处是您可以创建一个比 ArrayList更安全并且速度更快的列表,特别适用于列表项是值类型的情况。

在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下表列出了六种类型的约束:

约束 说明

T:结构


类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)


T:类


类型参数必须是引用类型,包括任何类、接口、委托或数组类型。


T:new()


类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。


T:<基类名>


类型参数必须是指定的基类或派生自指定的基类。


T:<接口名称>


类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。


T:U


为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。

举例:

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}
class SuperKeyType<K, V, U>
    where U : System.IComparable<U>
    where V : new()
{ }

泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等,其中,像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。

一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项:

  • 将哪些类型通用化为类型参数。

    一般规则是,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。

  • 如果存在约束,应对类型参数应用什么约束

    一个有用的规则是,应用尽可能最多的约束,但仍使您能够处理需要处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。

  • 是否将泛型行为分解为基类和子类。

    由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。有关从泛型基类继承的规则,请参见下面的内容。

  • 是否实现一个或多个泛型接口。

    例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能需要实现一个接口,如 IComparable<T>,其中 T 是您的类的类型。

为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类,使用泛型接口十分可取,例如使用 IComparable<T> 而不使用 IComparable,这样可以避免值类型的装箱和取消装箱操作。.NET Framework 2.0 类库定义了若干新的泛型接口,以用于 System.Collections.Generic 命名空间中新的集合类。

举例:

//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
    protected Node head;
    protected Node current = null;

    // Nested class is also generic on T
    protected class Node
    {
        public Node next;
        private T data;  //T as private member datatype

        public Node(T t)  //T used in non-generic constructor
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data  //T as return type of property
        {
            get { return data; }
            set { data = value; }
        }
    }

    public GenericList()  //constructor
    {
        head = null;
    }

    public void AddHead(T t)  //T as method parameter type
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    // Implementation of the iterator
    public System.Collections.Generic.IEnumerator<T> GetEnumerator()
    {
        Node current = head;
        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    // IEnumerable<T> inherits from IEnumerable, therefore this class
    // must implement both the generic and non-generic versions of
    // GetEnumerator. In most cases, the non-generic method can
    // simply call the generic method.
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
    // A simple, unoptimized sort algorithm that
    // orders list elements from lowest to highest:

    public void BubbleSort()
    {
        if (null == head || null == head.Next)
        {
            return;
        }
        bool swapped;

        do
        {
            Node previous = null;
            Node current = head;
            swapped = false;

            while (current.next != null)
            {
                //  Because we need to call this method, the SortedList
                //  class is constrained on IEnumerable<T>
                if (current.Data.CompareTo(current.next.Data) > 0)
                {
                    Node tmp = current.next;
                    current.next = current.next.next;
                    tmp.next = current;

                    if (previous == null)
                    {
                        head = tmp;
                    }
                    else
                    {
                        previous.next = tmp;
                    }
                    previous = tmp;
                    swapped = true;
                }
                else
                {
                    previous = current;
                    current = current.next;
                }
            }
        } while (swapped);
    }
}

// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
    string name;
    int age;

    public Person(string s, int i)
    {
        name = s;
        age = i;
    }

    // This will cause list elements to be sorted on age values.
    public int CompareTo(Person p)
    {
        return age - p.age;
    }

    public override string ToString()
    {
        return name + ":" + age;
    }

    // Must implement Equals.
    public bool Equals(Person p)
    {
        return (this.age == p.age);
    }
}

class Program
{
    static void Main()
    {
        //Declare and instantiate a new generic SortedList class.
        //Person is the type argument.
        SortedList<Person> list = new SortedList<Person>();

        //Create name and age values to initialize Person objects.
        string[] names = new string[]
        {
            "Franscoise",
            "Bill",
            "Li",
            "Sandra",
            "Gunnar",
            "Alok",
            "Hiroyuki",
            "Maria",
            "Alessandro",
            "Raul"
        };

        int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };

        //Populate the list.
        for (int x = 0; x < 10; x++)
        {
            list.AddHead(new Person(names[x], ages[x]));
        }

        //Print out unsorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with unsorted list");

        //Sort the list.
        list.BubbleSort();

        //Print out sorted list.
        foreach (Person p in list)
        {
            System.Console.WriteLine(p.ToString());
        }
        System.Console.WriteLine("Done with sorted list");
    }
}

泛型方法是使用类型参数声明的方法,如下所示:

static void Swap<T>(ref T lhs, ref T rhs)
{
    T temp;
    temp = lhs;
    lhs = rhs;
    rhs = temp;
}

根据典型设计模式定义事件时,泛型委托尤其有用,因为发送方参数可以为强类型,不再需要强制转换成 Object,或反向强制转换。

delegate void StackEventHandler<T, U>(T sender, U eventArgs);

class Stack<T>
{
    public class StackEventArgs : System.EventArgs { }
    public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;

    protected virtual void OnStackChanged(StackEventArgs a)
    {
        stackEvent(this, a);
    }
}

class SampleClass
{
    public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}

public static void Test()
{
    Stack<double> s = new Stack<double>();
    SampleClass o = new SampleClass();
    s.stackEvent += o.HandleStackChange;
}

下面是链表

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/**
 * 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
 * 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
 * 每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
 * 相比于线性表顺序结构,操作复杂。
 * **/
namespace NETTest
{
    public class NodeTable
    {
    }

    /// <summary>
    /// 这个类仅包含一个表示员工名字的字符串类型,一个设置员工名字的构造函数,一个返回Employee名字的ToString()方法。
    /// </summary>
    public class Employee
    {
        private string name;
        public Employee(string name)
        {
            this.name = name;
        }

        public override string ToString()
        {
            return this.name;
        }
    }

    public class Node<T>
    {
        Object data;
        Node<T> next;

        //注意构造函数将私有的数据成员设置成传递进来的对象,并且将 next 字段设置成null。
        public Node(Object data)
        {
            this.data = data;
            this.next = null;
        }

        public Object Data
        {
            get { return this.data; }
            set { data = value; }
        }

        public Node<T> Next
        {
            get { return this.next; }
            set { this.next = value; }
        }

        /// <summary>
        /// 首先检测当前Node的next字段,看它是不是null。
        /// 如果是,那么当前Node就是最后一个Node,我们将当前Node的next属性指向传递进来的新结点,
        /// 这样,我们就把新Node插入到了链表的尾部。
        /// 如果当前Node的next字段不是null,说明当前node不是链表中的最后一个node。
        /// 因为next字段的类型也是node,所以我们调用next字段的Append方法(注:递归调用),再一次传递Node参数,这样继续下去,直到找到最后一个Node为止。
        /// </summary>
        /// <param name="newNode"></param>
        public void Append(Node<T> newNode)
        {
            if (this.next == null)
            {
                this.next = newNode;
            }
            else
            {
                next.Append(newNode);
            }
        }

        /// <summary>
        /// Node 类中的 ToString() 方法也被覆盖了,用于输出 data 中的值,并且调用下一个 Node 的 ToString()方法(译注:再一次递归调用)。
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            string output = data.ToString();

            if (next != null)
            {
                output += ", " + next.ToString();
            }

            return output;
        }
    }

    /**
     * LinkedList 类本身只包含对一个Node的引用,这个Node称作 HeadNode,是链表中的第一个Node,初始化为null。
     * **/
    public class LinkedList<T>
    {
        Node<T> headNode = null;

        /// <summary>
        /// LinkedList 类不需要构造函数(使用编译器创建的默认构造函数),
        /// 但是我们需要创建一个公共方法,Add(),这个方法把 data存储到线性链表中。
        /// 这个方法首先检查headNode是不是null,
        /// 如果是,它将使用data创建结点,并将这个结点作为headNode,
        /// 如果不是null,它将创建一个新的包含data的结点,并调用headNode的Append方法
        /// </summary>
        /// <param name="data"></param>
        public void Add(Object data)
        {
            if (headNode == null)
            {
                headNode = new Node<T>(data);
            }
            else
            {
                headNode.Append(new Node<T>(data));
            }
        }

        /// <summary>
        /// 线性链表创建一个索引器。
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public object this[int index]
        {
            get
            {
                int ctr = 0;
                Node<T> node = headNode;
                while (node != null && ctr <= index)
                {
                    if (ctr == index)
                    {
                        return node.Data;
                    }
                    else
                    {
                        node = node.Next;
                    }
                    ctr++;
                }
                return null;
            }
        }

        /// <summary>
        /// 用以调用headNode的ToString()方法。
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (this.headNode != null)
            {
                return this.headNode.ToString();
            }
            else
            {
                return string.Empty;
            }
        }
    }
}

static void Main(string[] args)
        {
            /**泛型**/
            LinkedList<int> ll = new LinkedList<int>();
            for (int i = 0; i < 10; i++)
            {
                ll.Add(i);
            }

            Console.Write(ll);
            Console.WriteLine("  Done.");

            LinkedList<Employee> employees = new LinkedList<Employee>();
            employees.Add(new Employee("Jason1"));
            employees.Add(new Employee("Jason2"));
            employees.Add(new Employee("Jason3"));
            employees.Add(new Employee("Jason4"));

            Console.Write(employees);
            Console.WriteLine("  Done.");

            Console.WriteLine("The fourth integer is {0}.", ll[3]);

            Employee d = (Employee)employees[1];
            Console.WriteLine("The second Employee is {0}.", d);

            Console.ReadLine();
        }
时间: 2024-10-24 02:15:07

C#中泛型和单链表的相关文章

python中栈的单链表实现

参考博客:https://www.cnblogs.com/stacklike/p/8284550.html 基于列表的简单实现 # 先进后出 # 以列表实现的简单栈 class SimpleStack: # 特殊属性,用以限制class可添加的属性 __slots__ = ('__items',) def __init__(self): self.__items = [] def is_empty(self): return self.__items == [] def peek(self):

单链表 之c代码

我们知道数据结构就是数据及其相互关系,包括逻辑结构和物理结构.单链表的逻辑结构是一种一对一的线性关系,物理结构是利用节点把数据结合起来,在计算机中体现这种一对一的数据关系.单链表节点包括包含数据本身信息的数据域和体现数据一对一关系的指针域.因为单链表只有一个指向后一节点的单一指针域next 所以单链表只能从前往后遍历,而不能从后向前遍历,这就意味着一旦单链表的某一节点丢失 ,后面所有的数据信息都会丢失,并且单链表有头指针唯一确定,要想查找单链表的 某一数据只能从头开始遍历,最坏时间复杂度为O(n

java单链表常用操作

总结提高,与君共勉 概述. 数据结构与算法亘古不变的主题,链表也是面试常考的问题,特别是手写代码常常出现,将从以下方面做个小结 [链表个数] [反转链表-循环] [反转链表-递归] [查找链表倒数第K个节点] [查找链表中间节点] [判断链表是否有环] [从尾到头打印单链表-递归] [从尾到头打印单链表-栈] [由小到大合并有序单链表-循环] [由小到大合并有序单链表-递归] 通常在java中这样定义单链表结构 <span style="font-family:Microsoft YaHe

面试题 - 无序单链表,排序

有一个单链表,无序,给定一个值,将链表中小于这个值的节点放置于链表前面,节点之间相对顺序不变. 这个题目我是这样想的,我们遍历单链表,当遇到大于指定指的节点群后,再其后面查找小于指定值的节点群,然后交换两个节点群的位置. 思路有了,大致的代码: function LinkNode(data){ this.data = data; this.next = null; } function LinkList(data){ this.head = new LinkNode(data); } LinkL

用Java实现单链表的基本操作

笔试题中经常遇到单链表的考题,下面用java总结一下单链表的基本操作,包括添加删除节点,以及链表转置. package mars; //单链表添加,删除节点 public class ListNode { private Node head; public ListNode(){ head=null; } //在链表前添加节点 public void addpre(int dvalue){ Node n=new Node(dvalue); if(head==null){ head=n; }els

1.3单链表的设计与实现

实现单链表的基本操作,包括链表的建立与释放,查找,求长度,查找后继,插入,删除,输出等函数. //调试环境:DevC++ //库文件和预设定义 #include <stdio.h> #include <stdlib.h> #define NULL 0 typedef int ElemType;//指定单链表中的数据类型 //单链表存储结构定义 typedef struct LNode { ElemType data;//数据域 struct LNode *next;//指针域 }L

数据结构单链表,双链表,数组的底层实现原理

单链表: 链表是动态分配内存在内存中不连续,单链表只有一个向下的指针,指向下一个节点,单链表的定位时间复杂度是O(n),插入删除的时间复杂度是O(1) 双链表: 链表是动态分配内容在内存中不连续,单双链表一致,双链表有两个指针prov,next ,prov指向上一个节点,next指向下一个节点,理论上同样的数据量双向链表的查询速度比单链表快,双向链表可以使用二分查找法,最多查找一半的元素就可得到目标,单链表需要遍历整个链表对象. 数组: 数组静态分配内存,在内存中连续 数组的优点 随机访问性强(

【 C# 数据结构】(一) -------------------------- 泛型带头节点的单链表,双向链表实现

在编程领域,数据结构与算法向来都是提升编程能力的重点.而一般常见的数据结构是链表,栈,队列,树等.事实上C#也已经封装好了这些数据结构,在头文件 System.Collections.Generic 中,直接创建并调用其成员方法就行.不过我们学习当然要知其然,亦知其所以然. 本文实现的是链表中的单链表和双向链表,并且实现了一些基本方法 一. 定义一个链表接口 MyList 接口里声明了我们要实现的方法: interface MyList<T> { int GetLength(); //获取链表

[算法]向有序的环形单链表中插入新节点

题目: 一个环形单链表从头节点head开始不降序,同时由最后的节点指回头节点.给定这样一个环形单链表的头节点head和整数num,请生成节点值为num的新节点,并插入到这个环形链表中,保证调整后的链表依然有序. 要求时间复杂度为O(N),额外空间复杂度为O(1). 分析: 本题不算是很难,但是要注意最后返回头节点时要考虑到不同的情况. 有可能要插入的节点的值比头节点的值还小,此时返回的是要插入的节点,否则返回头节点. 程序: public static Node insertNum(Node h