LinkedList复制构造函数实现细节
我开始学习 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
。我应该在复制构造函数中使用来访问每个节点上的数据吗?
另一个问题(我知道完全偏离主题),何时和/或为什么我们应该声明一个指向 LinkedList
的指针
LinkedList<int> *l = new LinkedList<int>();
而不是
LinkedList<int> l;
I'm starting to learn C++ and as an exercise decide to implement a simple LinkedList
class (Below there is part of the code). I have a question regarding the way the copy constructor should be implemented and the best way the data on the original LinkedList
should be accessed.
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) {
}
...
Should my copy constructor access the data on each node of the original LinkedList
directly?
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;
}
}
Or should I access the data through the corresponding accessor? (I know that I don't have the accessor(s) defined).
Also, I intend to create a custom iterator so that it can be possible to iterate over the LinkedList
. Should I use in the copy constructor to access the data on each node?
Another question (completely off-topic, I know), when and/or why should we declare a pointer to a LinkedList
LinkedList<int> *l = new LinkedList<int>();
instead of
LinkedList<int> l;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为追加将正确处理初始头/尾细节,是吗?如果是这样,那么您现在所拥有的就非常简单:浏览另一个列表,获取其项目并将副本添加到我的列表中。完美的。
嗯,差不多了。使用初始化列表来初始化成员变量:
另外,也许是风格问题,这可以代替 while 循环:
事实上,我建议这样做。当你有迭代器时,你会做类似的事情:
它只是更好地遵循 for 循环的风格。 (初始化某事、检查某事、执行某事)。但对于迭代器来说,情况可能会有所不同。 (稍后详细介绍)
这些迭代器是您的访问器。您不想暴露您的内部头尾指针,这会导致灾难。该类的目的是不暴露细节。也就是说,迭代器是这些细节的抽象包装器。
一旦有了迭代器,您就可以使用它们来迭代列表而不是指针算术。这与最近的有关提出问题。一般来说,您应该使用抽象来处理数据。所以是的,一旦你有了迭代器
在适当的位置,您应该使用它们来迭代数据。
大多数提供迭代器的类还提供了一种在给定开始和结束迭代器的情况下插入数据的方法。这通常称为
insert
,如下所示:insert(iterBegin, iterEnd)
。这会循环遍历迭代器,将其数据附加到列表中。如果您有这样的功能,您的复制构造函数将很简单:
其中
insert
的实现就像我们上面的 for 循环一样。第一个是动态分配,第二个是自动(堆栈)分配。您应该更喜欢堆栈分配。它几乎总是更快,也更安全(因为您不需要删除任何内容)。事实上,一个称为 RAII 的概念依赖于自动存储,因此保证析构函数能够运行。
仅在必要时才使用动态分配。
I assume append will properly handle the initial head/tail details, yes? If so, what you have now is great and simple: Go through the other list, and take its item and add a copy to my list. Perfect.
Well, almost. Use an initializer list to initialize member variables:
Also, maybe a matter of style, this woks instead of a while loop:
In fact, I'd recommend this instead. When you have iterators, you'd do something like:
It just follows the style of a for-loop better. (Initialize something, check something, do something). Though for iterators, it'll probably be different. (More later)
Those iterators are your accessors. You don't want to expose your internal head-tail pointers, that a recipe for disaster. The purpose of the class is to not expose the details. That said, iterators are the abstract wrapper around those details.
Once you have your iterators, then you could use them to iterate through the list instead of pointer arithmetic. This ties in to this recently asked question. In general, you should use your abstractions to deal with your data. So yes once you have your iterators
in place, you should use those to iterate across the data.
Most classes that provide iterators also provide a way to insert data given a beginning and ending iterator. This is usually called
insert
, like this:insert(iterBegin, iterEnd)
. This loops through the iterators, appending it's data to the list.If you had such functionality, your copy-constructor would simply be:
Where
insert
is implemented like the for-loop we had above.The first is dynamic allocation, the second is automatic (stack) allocation. You should prefer stack allocation. It's almost always faster, and safer too (since you don't need to delete anything). In fact, a concept called RAII relies on automatic storage, so destructors are guaranteed to run.
Only use dynamic allocation when you have to.
我认为实现自己的链表仍然是一个非常有价值的练习,因为它可以帮助您学习指针、数据结构等的详细信息。请确保不要在实际代码中使用链表类,因为有许多现有的库已经编写并测试过。最好的代码是您不必编写的代码。请参阅 std::list。
我认为您实现复制构造函数的方式没有问题。不过,您最好将代码移动到专用的复制函数并从构造函数中调用它,这样您需要维护的代码就会减少。但一般来说,从类本身内部使用类的实现细节不仅是可以接受的,而且在许多情况下是首选,因为它会尽可能快。
至于使用 new 创建列表与在堆栈上创建列表,这个决定不仅适用于您的类,而且适用于一般数据结构。过于简化的经验法则:如果可以的话在堆栈上分配,如果需要的话在堆上分配。例如,如果您需要列表比创建它的函数寿命更长,则需要这样做。
再次回到不手动滚动您自己的代码,如果您确实决定使用 new 在堆上分配,您应该使用智能指针,而不是尝试自己管理内存。现在就让自己养成这个习惯。不要等到你正在编写“真正的”代码。您遇到的许多人对您寻找编写更好的代码没有帮助,并且会坚持“只使用
new
”。I think it is still a very valuable exercise to implement your own linked list as it helps you to learn the details of pointers, data structures, etc. Just be sure not to use your linked list class in real code as there are many existing libraries that are already writtern and tested. The best code is the code you don't have to write. See std::list.
I see no problem in the way you have implemented your copy constructor. You might do well to move the code to a dedicated copy function and call that from the constructor however, so that there is less code you have to maintain. But in general using the implementation details of your class from within the class itself is not only acceptable, but in many cases preferred as it will be as fast as possible.
As for creating the list with new vs creating it on the stack, this is a decision that applies not only to your class but data structures in general. Over-simplified rule of thumb: allocate on the stack if you can, allocate on the heap if you need to. You would need to for instance if you need tohe list to outlive the function in which it is created.
And again getting back to not hand-rolling your own code, if you do decide to use
new
to allocate on the heap, you should be using smart pointers instead of trying to manage the memory yourself. Get yourself in to this habit now. Don't wait until you're working on 'real' code. Many people you encounter will be unhelpful to you in your search to write better code, and will insist on "just usingnew
".