Я начинаю изучать C++ и поскольку осуществление решает реализовать простое LinkedList
класс (Ниже существует часть кода). У меня есть вопрос относительно способа, которым конструктор копии должен быть реализован и лучший способ данные по оригиналу LinkedList
должен быть получен доступ.
template <typename T>
class LinkedList {
struct Node {
T data;
Node *next;
Node(T t, Node *n) : data(t), next(n) {};
};
public:
LinkedList();
LinkedList(const LinkedList&);
~LinkedList();
//member functions
int size() const; //done
bool empty() const; //done
void append(const T&); //done
void prepend(const T&); //done
void insert(const T&, int i);
bool contains(const T&) const; //done
bool removeOne(const T&); //done
int removeAll(const T&); //done
void clear(); //done
T& last(); //done
const T& last() const; //done
T& first(); //done
const T& first() const; //done
void removeFirst(); //done
T takeFirst(); //done
void removeLast();
T takeLast();
//delete when finished
void print();
//end delete
//operators
bool operator ==(const LinkedList<T> &other) const; //done
bool operator !=(const LinkedList<T> &other) const; //done
LinkedList<T>& operator =(const LinkedList<T> &other); //done
private:
Node* m_head;
Node* m_tail;
int m_size;
};
template<typename T>
LinkedList<T>::LinkedList() : m_head(0), m_tail(0), m_size(0) {
}
...
Если мой конструктор копии получает доступ к данным по каждому узлу оригинала LinkedList
непосредственно?
template<typename T>
LinkedList<T>::LinkedList(const LinkedList& l) {
m_head = 0;
m_tail = 0;
m_size = 0;
Node *n = l.m_head;
// construct list from given list
while(n) {
append(n->data);
n = n->next;
}
}
Или действительно ли я должен получить доступ к данным через соответствующее средство доступа? (Я знаю, что мне не определили средство (средства) доступа).
Кроме того, я намереваюсь создать пользовательский итератор так, чтобы могло быть возможно выполнить итерации по LinkedList
. Я должен использовать в конструкторе копии для доступа к данным по каждому узлу?
Другой вопрос (полностью вне темы, я знаю), когда и/или почему мы должны объявить указатель на a LinkedList
LinkedList<int> *l = new LinkedList<int>();
вместо
LinkedList<int> l;
Я полагаю, что append правильно обработает начальные детали головы / хвоста, да? Если это так, то то, что у вас есть сейчас, великолепно и просто: просмотрите другой список, возьмите его элемент и добавьте копию в мой список. Идеально.
Ну, почти. Используйте список инициализаторов для инициализации переменных-членов:
template<typename T>
LinkedList<T>::LinkedList(const LinkedList& l) :
m_head(0), m_tail(0), m_size(0)
{
// ...
}
Кроме того, возможно, из-за стиля, это работает вместо цикла while:
// construct list from given list
for (Node *n = l.m_head; n != 0; n = n->next)
append(m->data);
На самом деле, я бы рекомендовал это вместо этого. Когда у вас есть итераторы, вы бы сделали что-то вроде:
for (const_iterator iter = l.begin(); iter != l.end(); ++iter)
append(*iter);
Он просто лучше соответствует стилю цикла for. (Инициализировать что-то, что-то проверить, что-то сделать). Хотя для итераторов, вероятно, будет иначе. (Подробнее позже)
Или мне следует получить доступ к данным через соответствующий метод доступа? (Я знаю, что у меня нет определенных средств доступа).
Кроме того, я намерен создать собственный итератор, чтобы можно было выполнять итерацию по LinkedList. Следует ли использовать в конструкторе копирования для доступа к данным на каждом узле?
Эти итераторы являются вашими аксессорами. Вы не хотите раскрывать свои внутренние указатели "голова-хвост", это рецепт катастрофы. Целью класса является , а не раскрытие деталей. Тем не менее, итераторы - это абстрактная оболочка для этих деталей.
Когда у вас есть итераторы, вы можете использовать их для итерации по списку вместо арифметических операций с указателями. Это связано с недавно заданным вопросом . В общем, вы должны использовать свои абстракции для работы с вашими данными. Итак, если у вас есть итераторы , вы должны использовать их для итерации по данным.
Большинство классов, которые предоставляют итераторы, также предоставляют способ вставки данных с учетом начального и конечного итераторов. Обычно это называется insert
, например: insert (iterBegin, iterEnd)
. Это проходит через итераторы, добавляя данные в список.
Если бы у вас была такая функциональность, ваш конструктор копирования был бы просто:
insert(l.begin(), l.end()); // insert the other list's entire range
Где insert
реализован аналогично циклу for, который у нас был выше.
Другой вопрос (совершенно не по теме, я знаю), когда и / или почему мы должны объявлять указатель на LinkedList
LinkedList * l = new LinkedList (); вместо LinkedList l;
Первое - это динамическое размещение, второе - автоматическое (стековое) распределение. Вы должны предпочесть распределение стека. Это почти всегда быстрее и безопаснее (поскольку вам не нужно ничего удалять). Фактически, концепция под названием RAII полагается на автоматическое хранение, поэтому деструкторы гарантированно запускаются.
Используйте динамическое размещение только при необходимости.
Я думаю, что реализация вашего собственного связанного списка по-прежнему является очень ценным упражнением, поскольку оно помогает вам изучить детали указателей, структур данных и т. Д. Просто убедитесь, что вы не используете свой класс связанного списка в реальном коде, поскольку есть многие существующие библиотеки, которые уже написаны и протестированы. Лучший код - это код, который вам не нужно писать. См. std :: list .
Я не вижу проблем в том, как вы реализовали конструктор копирования. Возможно, вам стоит переместить код в специальную функцию копирования и вызвать ее из конструктора, чтобы уменьшить количество кода, который вам нужно поддерживать. Но в целом использование деталей реализации вашего класса из самого класса не только приемлемо, но и во многих случаях предпочтительнее, так как это будет как можно быстрее.
Что касается создания списка с новым и его создания в стеке, это решение, которое применимо не только к вашему классу, но и к структурам данных в целом. Чрезмерно упрощенное практическое правило: выделяйте в стеке, если можете, выделяйте в куче, если нужно. Вам может понадобиться, например, если вам нужно, чтобы список пережил функцию, в которой он создан.
И снова возвращаясь к тому, чтобы не изменять свой собственный код вручную, если вы все же решите использовать new
для выделения в куче, вам следует использовать интеллектуальные указатели вместо того, чтобы пытаться самостоятельно управлять памятью. Приобретите эту привычку прямо сейчас. Не ждите, пока вы начнете работать над «настоящим» кодом. Многие люди, с которыми вы столкнетесь, будут бесполезны в ваших поисках лучшего кода и будут настаивать на том, чтобы «просто использовать new
».