【问题标题】:Linked List exercise, what am I doing wrong?链表练习,我做错了什么?
【发布时间】:2011-01-27 05:36:27
【问题描述】:

大家好。我正在做一个涉及动态内存分配、指针、类和异常的链表练习。有人愿意批评它并告诉我在风格和我上面列出的主题方面我做错了什么以及我应该做得更好吗?

/*

Linked List exercise

*/

#include <iostream>
#include <exception>
#include <string>
using namespace std;

class node{
public:
    node * next;
    int * data;
    node(const int i){
        data = new int;
        *data = i;
    }
    node& operator=(node n){
        *data = *(n.data);
    }
    ~node(){
        delete data;
    }
};

class linkedList{

public:

    node * head;
    node * tail;
    int nodeCount;

    linkedList(){
        head = NULL;
        tail = NULL;
    }

    ~linkedList(){
        while (head){
            node* t = head->next;
            delete head;
            if (t) head = t;
        }
    }

    void add(node * n){
        if (!head) {
            head = n;
            head->next = NULL;
            tail = head;
            nodeCount = 0;
        }else {
            node * t = head;
            while (t->next) t = t->next;
            t->next = n;
            n->next = NULL;
            nodeCount++;
        }
    }

    node * operator[](const int &i){
        if ((i >= 0) && (i < nodeCount)) throw new exception("ERROR:  Invalid index on linked list.", -1);
        node *t = head;
        for (int x = i; x < nodeCount; x++) t = t->next;
        return t;
    }

    void print(){
        if (!head) return;
        node * t = head;
        string collection;
        cout << "[";
        int c = 0;
        if (!t->next) cout << *(t->data);
        else while (t->next){
            cout << *(t->data);
            c++;
            if (t->next) t = t->next;
            if (c < nodeCount) cout << ", ";
        }
        cout << "]" << endl;
    }

};

int main (const int & argc, const char * argv[]){

    try{
        linkedList * myList = new linkedList;
        for (int x = 0; x < 10; x++) myList->add(new node(x));
        myList->print();
    }catch(exception &ex){
        cout << ex.what() << endl;
        return -1;
    }   
    return 0;
}

【问题讨论】:

    标签: c++ pointers linked-list dynamic-memory-allocation


    【解决方案1】:

    数据不需要是指针。

    使用 ctor-initializer 列表,它对 const 正确性和异常安全性更好。您的构造函数都不需要在正文中包含任何代码。

    您的linkedList 构造函数没有初始化nodeCount。

    您没有将列表的尾指针用于任何事情。在 add 的非空情况下,它可以节省您扫描整个列表的时间——如果您保持它是最新的。

    索引 (operator[]) 在链表上是一种不寻常的支持。 OTOH,您还没有进行删除功能。

    operator[] 不应通过引用获取其参数。只有大结构需要通过 const 引用传递,像 int 这样的小类型应该只通过值传递。

    现在,如果 add 失败,指向 new node() 的指针会泄漏。 (但我实际上并没有看到添加失败的方法,除非列表链接损坏。)

    您应该在节点上定义一个私有复制构造函数以防止数据的双重释放。任何时候定义 operator= 和析构函数时,您还应该定义或删除复制构造函数(三规则)。您还应该在linkedList上定义私有复制构造函数和赋值运算符以防止双重释放。

    未使用打印函数中的变量字符串集合。 print() 中的变量 t 应该是指向 const 的指针。 print 本身应该是一个 const 成员函数。

    【讨论】:

    • 非常感谢这里的 cmets。感谢您愿意提供帮助。
    • 如果这个“练习”是家庭作业,你应该将“家庭作业”添加到你的标签列表中
    • @Neeraj:然后解释为什么我编辑我的答案以解释代码中的另外五个问题,因为它被接受为正确答案。去别的地方当巨魔吧。
    • 大家好。这不是家庭作业。我已经大学毕业多年了。然而,我正在努力争取能够自信地申请 C++/算法开发职位。
    【解决方案2】:
    if ((i >= 0) && (i < nodeCount)) throw new exception("ERROR:  Invalid index on linked list.", -1);
    

    那一行应该是:

    if( i < 0 || i >= nodeCount ) throw...
    

    add方法也有问题:

    void add(node * n){
        if (!head) {
            head = n;
            head->next = NULL;
            tail = head;
            nodeCount = 0;
        }else {
            node * t = head;
            while (t->next) t = t->next;
            t->next = n;
            n->next = NULL;
            nodeCount++;
        }
    }
    

    对于一个添加第一个节点后的计数将为 0,对于另一个您正在搜索以插入到链表中...人们期望的操作是 O(1)。

    更典型的实现是:

    void add( node* n ) {
       node* old_head = head;
       head = n;
       head->next = old_head;
       ++nodeCount;
       if( old_head == NULL )
          tail = head
    }
    

    【讨论】:

      【解决方案3】:

      1) 您可以使用data = new int(i) 来初始化字段。

      2) 你在 operator[] if ((i &gt;= 0) &amp;&amp; (i &lt; nodeCount)) 中创建了错误的条件,它应该被反转,比如 if ((i &lt; 0) || (i &gt;= nodeCount))

      【讨论】:

      • (1) 形式仍然很差,应该在 ctor-initializer 列表中完成
      【解决方案4】:
      /*
      Linked List exercise
      */
      
      class node {
      public:
          node * next;
          int * data;
      

      data 并不需要成为指针(除非您的课堂作业表明必须如此)。您可以将其更改为int,然后将您的引用从*(t-&gt;data) 更改为只是t-&gt;data。同样,您不需要在 ctor/dtor 中使用 newdelete

          node(const int i) {
              data = new int;
              *data = i;
          } 
          node& operator=(node n) {
              *data = *(n.data);
          } 
          ~node() {
              delete data;
          } 
      };
      
      class linkedList {
      
      public:
          node * head;
          node * tail;
          int nodeCount;
      
          linkedList() {
              head = NULL;
              tail = NULL;
      

      您应该在此处初始化所有您的数据成员。 nodeCount 将有一些随机值,除非您在此处将其设置为零。

          } 
      
          ~linkedList() {
              while (head) {
                  node* t = head->next;
                  delete head;
                  if (t)
                      head = t;
      

      这看起来像一个错误。您需要始终分配head = t,即使t 是NULL,否则您的循环不会终止(但多次删除同一个节点可能会导致异常)。

              } 
          } 
      
          void add(node * n) {
              if (!head) {
                  head = n;
                  head->next = NULL;
                  tail = head;
                  nodeCount = 0;
      

      我认为整个if 声明是不必要的。当链表为空时,两个分支都做同样的事情,所以你总是可以使用第二个 (else) 分支。

      另外,最后将nodeCount 设置为0 似乎是一个错误;不应该是1吗?

              } else {
                  node * t = head;
      
                  while (t->next)
                      t = t->next;
      
                  t->next = n;
                  n->next = NULL;
                  nodeCount++;
              } 
          } 
      
          node * operator[](const int &i) {
      

      一个小问题,但 const int &amp; 参数实际上没有任何意义。仅仅传递 int 类型并没有获得任何好处。

              if ((i >= 0) && (i < nodeCount))
                  throw new exception("ERROR:  Invalid index on linked list.", -1);
      

      上面的条件在我看来是倒退的;你总是会抛出带有有效参数的异常。

              node *t = head;
      
              for (int x = i; x < nodeCount; x++)
                  t = t->next;
      

      上面的循环对我来说似乎有点可疑。如果i 是您想要的节点的从零开始的索引,您的计数器不应该从0 转到i-1 吗?

              return t;
          } 
      
          void print() {
              if (!head)
                  return;
      

      同样,您应该使此方法有效,而不必防范空列表。我希望一个空列表会打印出类似[] 的内容,但是使用上面的保护代码,您将不会打印任何内容。

              node * t = head;
              string collection;
              int c = 0;
      
              cout << "[";
      
              if (!t->next) {
                  cout << *(t->data);
      

      和上面的其他条件一样,我认为上面的分支是不必要的。第二个 (else) 分支应该可以很好地处理 NULL 的情况。

              } else {
                  while (t->next) {
      

      我想你想要上面的while (t) 而不是while (t-&gt;next)

                      cout << *(t->data);
                      c++;
      
                      if (t->next)
                          t = t->next;
      

      我认为这与之前的错误一样。您应该始终分配 `t = t->next',否则您的循环不会终止。

                      if (c < nodeCount)
                          cout << ", ";
                  } 
              } 
      
              cout << "]" << endl;
          } 
      };
      
      int main (const int & argc, const char * argv[]) { // const int& ?
      

      const int &amp;再次...

          try {
              linkedList * myList = new linkedList;
              for (int x = 0; x < 10; x++)
                  myList->add(new node(x));
              myList->print();
          } catch(exception &ex) {
              cout << ex.what() << endl;
              return -1;
          }  
      

      对于家庭作业来说可能没问题,但捕获所有可能的异常(正如您在上面所做的那样)可能不会在这里增加太多价值,并且通常被认为是不好的做法。

      如果您忽略上面的 try/catch 内容,我怀疑每当您的程序因异常终止时,编译器提供的默认顶级异常处理程序无论如何都会转储异常和堆栈跟踪信息(取决于您的编译器)。

          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 2021-06-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多