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 data;
…
读作:T类型的Node。T代表了当Node被初始化时,Node所接受的类型。T可以是Object,也可能是整型或者是Employee。这个在Node被初始化的时候才能确定。
注意:使用T作为标识只是一种约定俗成,你可以使用其他的字母组合来代替,比如这样:
public class Node {
UnknownType data;
…
通过使用T作为未知类型,next字段(下一个结点的引用)必须被声明为T类型的Node(意思是说接受一个T类型的泛型化Node)。
Node next;
构造函数接受一个T类型的简单参数:
public Node(T data)
{
this.data = data;
this.next = null;
}
Node 类的其余部分是很简单的,所有你需要使用Object的地方,你现在都需要使用T。LinkedList 类现在接受一个 T类型的Node,而不是一个简单的Node作为头结点。
public class LinkedList{
Node headNode = null;
再来一遍,转换是很直白的。任何地方你需要使用Object的,现在改做T,任何需要使用Node的地方,现在改做 Node。下面的代码初始化了两个链接表。一个是整型的。
LinkedList<int> ll = new LinkedList<int>();
另一个是Employee类型的:
LinkedList employees = new LinkedList();
剩下的代码与第一个版本没有区别,除了没有装箱、拆箱,而且也不可能将错误的类型保存到集合中。
LinkedList<int> ll = new LinkedList<int>();
for ( int i = 0; i < 10; i ++ )
{
ll.Add(i);
}
Console.WriteLine(ll);
Console.WriteLine(" Done.");
LinkedList employees = new LinkedList();
employees.Add(new Employee(“John”));
employees.Add(new Employee(“Paul”));
employees.Add(new Employee(“George”));
employees.Add(new Employee(“Ringo”));
Console.WriteLine(employees);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。有需要面试题资料的朋友点击这里可以免费领取。
学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
[外链图片转存中…(img-UwwANLxU-1713471803846)]
最后
技术是没有终点的,也是学不完的,最重要的是活着、不秃。零基础入门的时候看书还是看视频,我觉得成年人,何必做选择题呢,两个都要。喜欢看书就看书,喜欢看视频就看视频。最重要的是在自学的过程中,一定不要眼高手低,要实战,把学到的技术投入到项目当中,解决问题,之后进一步锤炼自己的技术。
技术学到手后,就要开始准备面试了,找工作的时候一定要好好准备简历,毕竟简历是找工作的敲门砖,还有就是要多做面试题,复习巩固。有需要面试题资料的朋友点击这里可以免费领取。
[外链图片转存中…(img-7otELN9H-1713471803846)]