亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

c#泛型學習詳解 創建線性鏈表

2020-01-24 02:54:29
字體:
來源:轉載
供稿:網友

術語表

generics:泛型
type-safe:類型安全
collection: 集合
compiler:編譯器
run time:程序運行時
object: 對象
.NET library:.Net類庫
value type: 值類型
box: 裝箱
unbox: 拆箱
implicity: 隱式
explicity: 顯式
linked list: 線性鏈表
node: 結點
indexer: 索引器

泛型是什么?

很多人覺得泛型很難理解。我相信這是因為他們通常在了解泛型是用來解決什么問題之前,就被灌輸了大量的理論和范例。結果就是你有了一個解決方案,但是卻沒有需要使用這個解決方案的問題。

這篇文章將嘗試著改變這種學習流程,我們將以一個簡單的問題作為開始:泛型是用來做什么的?答案是:沒有泛型,將會很難創建類型安全的集合。

C# 是一個類型安全的語言,類型安全允許編譯器(可信賴地)捕獲潛在的錯誤,而不是在程序運行時才發現(不可信賴地,往往發生在你將產品出售了以后!)。因 此,在C#中,所有的變量都有一個定義了的類型;當你將一個對象賦值給那個變量的時候,編譯器檢查這個賦值是否正確,如果有問題,將會給出錯誤信息。

在 .Net 1.1 版本(2003)中,當你在使用集合時,這種類型安全就失效了。由.Net 類庫提供的所有關于集合的類全是用來存儲基類型(Object)的,而.Net中所有的一切都是由Object基類繼承下來的,因此所有類型都可以放到一 個集合中。于是,相當于根本就沒有了類型檢測。

更糟的是,每一次你從集合中取出一個Object,你都必須將它強制轉換成正確的類型,這一轉換將對性能造成影響,并且產生冗長的代碼(如果你忘了 進行轉換,將會拋出異常)。更進一步地講,如果你給集合中添加一個值類型(比如,一個整型變量),這個整型變量就被隱式地裝箱了(再一次降低了性能),而 當你從集合中取出它的時候,又會進行一次顯式地拆箱(又一次性能的降低和類型轉換)。

關于裝箱、拆箱的更多內容,請訪問 陷阱4,警惕隱式的裝箱、拆箱。

創建一個簡單的線性鏈表

為了生動地感受一下這些問題,我們將創建一個盡可能簡單的線性鏈表。對于閱讀本文的那些從未創建過線性鏈表的人。你可以將線性鏈表想像成有一條鏈子 栓在一起的盒子(稱作一個結點),每個盒子里包含著一些數據 和 鏈接到這個鏈子上的下一個盒子的引用(當然,除了最后一個盒子,這個盒子對于下一個盒子的引用被設置成NULL)。

為了創建我們的簡單線性鏈表,我們需要下面三個類:

1、Node 類,包含數據以及下一個Node的引用。

2、LinkedList 類,包含鏈表中的第一個Node,以及關于鏈表的任何附加信息。

3、測試程序,用于測試 LinkedList 類。

為了查看鏈接表如何運作,我們添加Objects的兩種類型到鏈表中:整型 和 Employee類型。你可以將Employee類型想象成一個包含關于公司中某一個員工所有信息的類。出于演示的目的,Employee類非常的簡單。

復制代碼 代碼如下:

public class Employee{
  private string name;
  public Employee (string name){
    this.name = name;
  }

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

這個類僅包含一個表示員工名字的字符串類型,一個設置員工名字的構造函數,一個返回Employee名字的ToString()方法。

鏈接表本身是由很多的Node構成,這些Note,如上面所說,必須包含數據(整型 和 Employee)和鏈表中下一個Node的引用。

復制代碼 代碼如下:

public class Node{
    Object data;
    Node next;

    public Node(Object data){
       this.data = data;
       this.next = null;
    }

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

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

注意構造函數將私有的數據成員設置成傳遞進來的對象,并且將 next 字段設置成null。

這個類還包括一個方法,Append,這個方法接受一個Node類型的參數,我們將把傳遞進來的Node添加到列表中的最后位置。這過程是這樣的: 首先檢測當前Node的next字段,看它是不是null。如果是,那么當前Node就是最后一個Node,我們將當前Node的next屬性指向傳遞進 來的新結點,這樣,我們就把新Node插入到了鏈表的尾部。

如果當前Node的next字段不是null,說明當前node不是鏈表中的最后一個node。因為next字段的類型也是node,所以我們調用next字段的Append方法(注:遞歸調用),再一次傳遞Node參數,這樣繼續下去,直到找到最后一個Node為止。

復制代碼 代碼如下:

public void Append(Node newNode){
    if ( this.next == null ){
       this.next = newNode;
    }else{
       next.Append(newNode);
    }
}

Node 類中的 ToString() 方法也被覆蓋了,用于輸出 data 中的值,并且調用下一個 Node 的 ToString()方法(譯注:再一次遞歸調用)。

復制代碼 代碼如下:

public override string ToString(){
    string output = data.ToString();

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

    return output;
}

這樣,當你調用第一個Node的ToString()方法時,將打印出所有鏈表上Node的值。

LinkedList 類本身只包含對一個Node的引用,這個Node稱作 HeadNode,是鏈表中的第一個Node,初始化為null。

復制代碼 代碼如下:

public class LinkedList{
    Node headNode = null;
}

LinkedList 類不需要構造函數(使用編譯器創建的默認構造函數),但是我們需要創建一個公共方法,Add(),這個方法把 data存儲到線性鏈表中。這個方法首先檢查headNode是不是null,如果是,它將使用data創建結點,并將這個結點作為headNode,如 果不是null,它將創建一個新的包含data的結點,并調用headNode的Append方法,如下面的代碼所示:
復制代碼 代碼如下:

public void Add(Object data){
    if ( headNode == null ){
       headNode = new Node(data);
    }else{
       headNode.Append(new Node(data));
    }
}

為了提供一點集合的感覺,我們為線性鏈表創建一個索引器。

復制代碼 代碼如下:

public object this[ int index ]{
    get{
       int ctr = 0;
       Node node = headNode;
       while ( node != null  && ctr <= index ){
           if ( ctr == index ){
              return node.Data;
           }else{
              node = node.Next;
           }
           ctr++;
        }
    return null;
    }
}

最后,ToString()方法再一次被覆蓋,用以調用headNode的ToString()方法。

復制代碼 代碼如下:

public override string ToString(){
    if ( this.headNode != null ){
       return this.headNode.ToString();
    }else{
       return string.Empty;
    }
}

測試線性鏈表

我們可以添加一些整型值到鏈表中進行測試:

復制代碼 代碼如下:

public void Run(){
    LinkedList ll = new LinkedList();
    for ( int i = 0; i < 10; i ++ ){
       ll.Add(i);
    }

    Console.WriteLine(ll);
    Console.WriteLine("  Done. Adding employees...");
}

如果你對這段代碼進行測試,它會如預計的那樣工作:

復制代碼 代碼如下:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Done. Adding employees...

然而,因為這是一個Object類型的集合,所以你同樣可以將Employee類型添加到集合中。

復制代碼 代碼如下:

ll.Add(new Employee("John"));
ll.Add(new Employee("Paul"));
ll.Add(new Employee("George"));
ll.Add(new Employee("Ringo"));

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

輸出的結果證實了,整型值和Employee類型都被存儲在了同一個集合中。

復制代碼 代碼如下:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9
  Done. Adding employees...
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, John, Paul, George, Ringo
Done.

雖然看上去這樣很方便,但是負面影響是,你失去了所有類型安全的特性。因為線性鏈表需要的是一個Object類型,每一個添加到集合中的整型值都被隱式裝箱了,如同 IL 代碼所示:

復制代碼 代碼如下:

IL_000c:  box        [mscorlib]System.Int32
IL_0011:  callvirt   instance void ObjectLinkedList.LinkedList::Add(object)

同樣,如果上面所說,當你從你的列表中取出項目的時候,這些整型必須被顯式地拆箱(強制轉換成整型),Employee類型必須被強制轉換成 Employee類型。

復制代碼 代碼如下:

Console.WriteLine("The fourth integer is " + Convert.ToInt32(ll[3]));
Employee d = (Employee) ll[11];
Console.WriteLine("The second Employee is " + d);

這些問題的解決方案是創建一個類型安全的集合。一個 Employee 線性鏈表將不能接受 Object 類型;它只接受 Employee類的實例(或者繼承自Employee類的實例)。這樣將會是類型安全的,并且不再需要類型轉換。一個 整型的 線性鏈表,這個鏈表將不再需要裝箱和拆箱的操作(因為它只能接受整型值)。

作為示例,你將創建一個 EmployeeNode,該結點知道它的data的類型是Employee。

復制代碼 代碼如下:

public class EmployeeNode {
    Employee employeedata;
    EmployeeNode employeeNext;
}

Append 方法現在接受一個 EmployeeNode 類型的參數。你同樣需要創建一個新的 EmployeeLinkedList ,這個鏈表接受一個新的 EmployeeNode:

復制代碼 代碼如下:

public class EmployeeLinkedList{
    EmployeeNode headNode = null;
}

EmployeeLinkedList.Add()方法不再接受一個 Object,而是接受一個Employee:

復制代碼 代碼如下:

public void Add(Employee data){
    if ( headNode == null ){
       headNode = new EmployeeNode(data);}
    else{
       headNode.Append(new EmployeeNode(data));
    }
}

類似的,索引器必須被修改成接受 EmployeeNode 類型,等等。這樣確實解決了裝箱、拆箱的問題,并且加入了類型安全的特性。你現在可以添加Employee(但不是整型)到你新的線性鏈表中了,并且當你從中取出Employee的時候,不再需要類型轉換了。

復制代碼 代碼如下:

EmployeeLinkedList employees = new EmployeeLinkedList();
employees.Add(new Employee("Stephen King"));
employees.Add(new Employee("James Joyce"));
employees.Add(new Employee("William Faulkner"));
/* employees.Add(5);  // try to add an integer - won't compile */
Console.WriteLine(employees);
Employee e = employees[1];
Console.WriteLine("The second Employee is " + e);

這樣多好啊,當有一個整型試圖隱式地轉換到Employee類型時,代碼甚至連編譯器都不能通過!

但它不好的地方是:每次你需要創建一個類型安全的列表時,你都需要做很多的復制/粘貼 。一點也不夠好,一點也沒有代碼重用。同時,如果你是這個類的作者,你甚至不能提前欲知這個鏈接列表所應該接受的類型是什么,所以,你不得不將添加類型安 全這一機制的工作交給類的使用者---你的用戶。

使用泛型來達到代碼重用

解決方案,如同你所猜想的那樣,就是使用泛型。通過泛型,你重新獲得了鏈接列表的   代碼通用(對于所有類型只用實現一次),而當你初始化鏈表的時候你告訴鏈表所能接受的類型。這個實現是非常簡單的,讓我們重新回到Node類:

復制代碼 代碼如下:

public class Node{
    Object data;
    ...

注意到 data 的類型是Object,(在EmployeeNode中,它是Employee)。我們將把它變成一個泛型(通常,由一個大寫的T代表)。我們同樣定義Node類,表示它可以被泛型化,以接受一個T類型。
復制代碼 代碼如下:

public class Node <T>{
    T data;
    ...

讀作:T類型的Node。T代表了當Node被初始化時,Node所接受的類型。T可以是Object,也可能是整型或者是Employee。這個在Node被初始化的時候才能確定。

注意:使用T作為標識只是一種約定俗成,你可以使用其他的字母組合來代替,比如這樣:

復制代碼 代碼如下:

public class Node <UnknownType>{
    UnknownType data;
    ...

通過使用T作為未知類型,next字段(下一個結點的引用)必須被聲明為T類型的Node(意思是說接受一個T類型的泛型化Node)。

Node<T> next;

構造函數接受一個T類型的簡單參數:

復制代碼 代碼如下:

public Node(T data)
{
    this.data = data;
    this.next = null;
}

Node 類的其余部分是很簡單的,所有你需要使用Object的地方,你現在都需要使用T。LinkedList 類現在接受一個 T類型的Node,而不是一個簡單的Node作為頭結點。

復制代碼 代碼如下:

public class LinkedList<T>{
Node<T> headNode = null;

再來一遍,轉換是很直白的。任何地方你需要使用Object的,現在改做T,任何需要使用Node的地方,現在改做 Node<T>。下面的代碼初始化了兩個鏈接表。一個是整型的。

復制代碼 代碼如下:

LinkedList<int> ll = new LinkedList<int>();

另一個是Employee類型的:

復制代碼 代碼如下:

LinkedList<Employee> employees = new LinkedList<Employee>();

剩下的代碼與第一個版本沒有區別,除了沒有裝箱、拆箱,而且也不可能將錯誤的類型保存到集合中。

復制代碼 代碼如下:

LinkedList<int> ll = new LinkedList<int>();
for ( int i = 0; i < 10; i ++ )
{
    ll.Add(i);
}

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

LinkedList<Employee> employees = new LinkedList<Employee>();
employees.Add(new Employee("John"));
employees.Add(new Employee("Paul"));
employees.Add(new Employee("George"));
employees.Add(new Employee("Ringo"));

Console.WriteLine(employees);
Console.WriteLine("  Done.");
Console.WriteLine("The fourth integer is " + ll[3]);
Employee d = employees[1];
Console.WriteLine("The second Employee is " + d);

泛型允許你不用復制/粘貼冗長的代碼就實現類型安全的集合。而且,因為泛型是在運行時才被擴展成特殊類型。Just In Time編譯器可以在不同的實例之間共享代碼,最后,它顯著地減少了你需要編寫的代碼。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩在线观看免费网站| 亚洲精品一区av在线播放| 精品久久久在线观看| 久久成年人免费电影| 精品露脸国产偷人在视频| 久久人人爽亚洲精品天堂| 成人性生交大片免费看小说| 日韩在线观看免费全集电视剧网站| 性欧美亚洲xxxx乳在线观看| 精品久久久av| 精品偷拍各种wc美女嘘嘘| 国产精品久久77777| 国产美女精品免费电影| 欧美激情网站在线观看| 一本色道久久综合狠狠躁篇怎么玩| 久久久噜久噜久久综合| 亚洲精品国产欧美| 国产91在线播放精品91| 精品免费在线观看| 97久久精品人人澡人人爽缅北| 久久人人爽人人爽人人片av高请| 久久人91精品久久久久久不卡| 亚洲国产一区二区三区在线观看| 亚洲精品久久久久久下一站| 久久夜精品香蕉| 日日狠狠久久偷偷四色综合免费| 亚洲自拍偷拍色图| 狠狠色噜噜狠狠狠狠97| www欧美xxxx| 最新国产成人av网站网址麻豆| 精品香蕉一区二区三区| 欧美做受高潮1| 中日韩美女免费视频网址在线观看| 狠狠躁天天躁日日躁欧美| 亚洲午夜av电影| 欧美午夜片欧美片在线观看| 日韩有码片在线观看| 久久久精品一区| 欧美日韩亚洲高清| 神马国产精品影院av| 一本色道久久综合狠狠躁篇的优点| 欧美理论电影网| 亚洲一区美女视频在线观看免费| 在线精品视频视频中文字幕| 欧美日韩亚洲系列| 日韩高清免费在线| 日韩高清电影好看的电视剧电影| 91久久精品视频| 91精品在线影院| 欧美与黑人午夜性猛交久久久| 日本国产一区二区三区| 色综合久久88色综合天天看泰| 亚洲国产日韩欧美综合久久| 日韩视频永久免费观看| 2019最新中文字幕| 久久久久久97| 久久成人亚洲精品| 动漫精品一区二区| 亚洲精品视频网上网址在线观看| 成人欧美一区二区三区黑人孕妇| 亚洲香蕉av在线一区二区三区| 中文字幕亚洲情99在线| 国产日本欧美一区二区三区在线| 日韩在线视频二区| 全亚洲最色的网站在线观看| 91精品国产91| 538国产精品视频一区二区| 欧美日韩一区二区免费视频| 国产欧美在线视频| 久久黄色av网站| 久久久久久久久久久网站| 亚洲字幕一区二区| 成人精品久久久| 欧美在线视频在线播放完整版免费观看| 日韩一中文字幕| 国产91av在线| 在线视频中文亚洲| 成年人精品视频| 久久久久久综合网天天| 成人久久久久久久| 国产伦精品免费视频| 亚洲一区999| 全亚洲最色的网站在线观看| 97在线视频免费看| 少妇高潮 亚洲精品| 青青久久av北条麻妃海外网| 欧美精品亚州精品| 精品国产一区久久久| 成人性生交大片免费看小说| 欧美区在线播放| 这里只有视频精品| 国产精品视频久久| 日韩欧美成人网| 中文字幕日韩欧美| 国产69精品久久久久99| 国产精品久久久久久网站| 日韩av一区在线观看| 亚洲第一福利视频| 精品久久久久久中文字幕大豆网| 中文字幕欧美专区| 国产盗摄xxxx视频xxx69| 亚洲欧美国产视频| 在线观看欧美视频| 欧美亚洲国产日韩2020| 国产欧美日韩中文字幕在线| 91精品在线观看视频| 国产成人午夜视频网址| 亚洲精品国产精品国自产观看浪潮| 国产精品久久久久久久app| 欧美在线激情视频| 国产一区二区日韩| 日韩电影中文字幕| 亚洲第一视频在线观看| 国内成人精品一区| 国产va免费精品高清在线观看| 国产精品女人网站| 日韩三级影视基地| 国产精品自拍偷拍视频| 日韩欧美有码在线| 精品丝袜一区二区三区| 亚洲国产精品福利| 精品magnet| 日韩美女毛茸茸| 色妞色视频一区二区三区四区| 国产一区二区在线免费视频| 国产精品直播网红| 欧美野外wwwxxx| 国产精品一区二区久久国产| 日韩欧美中文免费| 久久夜色精品亚洲噜噜国产mv| 福利视频第一区| 国产欧美精品在线播放| 日本高清+成人网在线观看| 亚洲第一区中文字幕| 亚洲综合精品伊人久久| 欧美裸体xxxx极品少妇软件| 亚洲综合社区网| 国产免费亚洲高清| 在线观看国产精品淫| 国产高清在线不卡| 久久中文字幕国产| 亚洲性猛交xxxxwww| 伊人伊成久久人综合网站| 国产一区二中文字幕在线看| 欧美激情2020午夜免费观看| 亚洲欧美一区二区三区四区| 成人精品在线观看| 日韩网站在线观看| 亚洲精品www| 欧美日本在线视频中文字字幕| 亚洲欧美日韩一区二区三区在线| 亚洲欧洲日产国产网站| 欧美性极品xxxx娇小| 亚洲午夜久久久影院| 国产精品一区二区女厕厕| xxxxx成人.com| 亚洲国产美女久久久久| 色狠狠av一区二区三区香蕉蜜桃| 亚洲国产成人精品女人久久久| 欧美精品成人91久久久久久久| 最近2019好看的中文字幕免费| 欧美日韩激情小视频| 久久亚洲综合国产精品99麻豆精品福利| 欧美日本高清一区|