the head of the list.
(item1
Element: student1
Next ------------> (item2
) Element: student2
Next ------------> (item3
) Element: student3
Next: null
)
the tail of the list.
Прежде всего, для вас, чтобы иметь возможность написать класс StudentList, вам нужно написать код клиента первым. Клиентский код - это код, который использует ваш список учеников. Кроме того, не просто напишите одну вещь за раз и выбросьте ее. Вместо этого напишите целую кучу [тестовых] случаев, в которых вам необходимо взаимодействовать с StudentList. Пишите также исключительные случаи. Но не стоит соблазнять писать швейцарский армейский нож класса, который делает все только потому, что может. Напишите минимальный объем кода, который выполняет задание.
Как вам нужно использовать класс, будет сильно диктовать, как будет построен класс. В этом суть TDD или Test Driven Design.
Ваша самая большая проблема, которую я вижу, - вы не представляете, как вы хотите использовать класс. Поэтому давайте сделаем это в первую очередь.
// create a list of students and print them back out.
StudentList list = new StudentList();
list.Add(new Student("Bob", 1234, 2, 'A'));
list.Add(new Student("Mary", 2345, 4, 'C'));
foreach(Student student in list)
{
Console.WriteLine(student.Name);
}
Я добавляю учащихся в список, а затем распечатываю их.
Мне не нужно, чтобы мой клиентский код отображался внутри StudentList. Поэтому StudentList скрывает, как он реализует связанный список. Давайте напишем основы StudentList.
public class StudentList
{
private ListNode _firstElement; // always need to keep track of the head.
private class ListNode
{
public Student Element { get; set; }
public ListNode Next { get; set; }
}
public void Add(Student student) { /* TODO */ }
}
StudentList довольно простой. Внутри он отслеживает первые или головные узлы. Очевидно, всегда требуется отслеживание первого узла.
Вы также можете задаться вопросом, почему ListNode объявлен внутри StudentList. Что происходит, класс ListNode доступен только для класса StudentList. Это делается потому, что StudentList не хочет выделять детали во внутреннюю реализацию, потому что он контролирует весь доступ к списку. StudentList никогда не показывает, как этот список реализован. Скрытие реализации - важная концепция ОО.
Если мы действительно разрешили клиентскому коду напрямую манипулировать списком, не было бы места, поскольку StudentList является первым местом.
Перейдем к реализации операции Add().
public void Add(Student student)
{
if (student == null)
throw new ArgumentNullException("student");
// create the new element
ListNode insert = new ListNode() { Element = student };
if(_firstElement == null)
{
_firstElement = insert;
return;
}
ListNode current = _firstElement;
while (current.Next != null)
{
current = current.Next;
}
current.Next = insert;
}
Операция Add должна найти последний элемент в списке, а затем положить новый ListNode в конец. Не ужасно эффективно. В настоящее время O (N), и добавление будет замедляться по мере увеличения списка.
Немного оптимизирован для вставок и перезаписи метода Add. Чтобы сделать добавление быстрее, все, что нам нужно сделать, это заставить StudentList отслеживать последний элемент в списке.
private ListNode _lastElement; // keep track of the last element: Adding is O(1) instead of O(n)
public void Add(Student student)
{
if(student == null)
throw new ArgumentNullException("student");
// create the new element
ListNode insert = new ListNode() { Element = student };
if (_firstElement == null)
{
_firstElement = insert;
_lastElement = insert;
return;
}
// fix up Next reference
ListNode last = _lastElement;
last.Next = insert;
_lastElement = insert;
}
Теперь, когда мы добавляем, мы не итерации. Нам просто нужно отслеживать ссылки на голову и хвост.
Далее: петля foreach. StudentList - это коллекция, и мы являемся коллекцией, которую мы хотим перечислить и использовать C# foreach
. Компилятор C# не может итератировать по волшебству. Чтобы использовать цикл foreach, нам нужно предоставить компилятору перечислитель для использования, даже если код, который мы пишем, явно не использует перечислитель.
Во-первых, давайте снова рассмотрим, как мы перебираем связанный список.
// don't add this to StudentList
void IterateOverList(ListNode current)
{
while (current != null)
{
current = current.Next;
}
}
Хорошо. поэтому перейдем к циклу foreach C# и вернем перечислитель. Для этого мы меняем StudentList для реализации IEnumerable. Это немного продвинулось, но вы должны понять, что происходит.
// StudentList now implements IEnumerable<Student>
public class StudentList : IEnumerable<Student>
{
// previous code omitted
#region IEnumerable<Student> Members
public IEnumerator<Student> GetEnumerator()
{
ListNode current = _firstElement;
while (current != null)
{
yield return current.Element;
current = current.Next;
}
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
Здесь вы можете увидеть итерацию связанного списка. Не добавляйте ключевое слово yield
. Вся доходность возвращает текущий ученик обратно в цикл foreach. Перечислитель прекращает возвращать учеников, когда он добирается до конца связанного списка.
И все! Код работает так, как мы этого хотим.
* Это далеко не единственный способ реализовать список.Я решил разместить логику списка в StudentList и сохранить ListNode очень простой. Но код делает только то, что мне нужно для моего самого первого модульного теста, и не более того. Вы можете сделать больше оптимизаций, и есть другие способы построения списка.
Перейти к началу страницы Что вам нужно сделать, это сначала создать [unit] тесты для выполнения кода, а затем добавить требуемую реализацию.
* fyi Я также переписал класс Студента. Плохое имя и странный корпус от C# persepctive, не говоря уже о том, что код, который вы предоставили, не компилируется. Я предпочитаю _
в качестве лидера для частных переменных-членов. Некоторым людям это не нравится, но вы новичок в этом, поэтому я оставлю их, потому что их легко заметить.
public class Student
{
private string _name;
private int _id;
private int _mark;
private char _letterGrade;
private Student() // hide default Constructor
{ }
public Student(string name, int id, int mark, char letterGrade) // Constructor
{
if(string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
if(id <= 0)
throw new ArgumentOutOfRangeException("id");
_name = name;
_id = id;
_mark = mark;
_letterGrade = letterGrade;
}
// read-only properties - compressed to 1 line for SO answer.
public string Name { get { return _name; } }
public int Id { get { return _id; } }
public int Mark { get { return _mark; } }
public char LetterGrade { get { return _letterGrade; } }
}
- проверки параметров
- обратить внимание на другую обшивке свойств, классов и переменных.
- скрыть конструктор по умолчанию. Почему я хочу создавать студентов без реальных данных?
- предоставляют некоторые свойства только для чтения.
- Этот класс является неизменным, как указано (т. Е. Как только вы создаете ученика, вы не можете его изменить).
«Связанный массив списков» для меня не имеет смысла. Это либо связанный список, либо массив (то, что вы описываете, является связанным списком). Я ничего не знаю об этом. – Herms 2008-10-29 19:42:19
Я попытался отредактировать вопрос, чтобы сделать немного больше смысла, но абзац в нижней части (начиная с «Содержать все») просто слишком запутан. – swilliams 2008-10-29 19:52:42