【问题标题】:Need help with copy constructor for very basic implementation of singly linked lists需要复制构造函数的帮助以实现单链表的非常基本的实现
【发布时间】:2011-07-16 05:26:56
【问题描述】:

上周,我们创建了一个使用类和向量来管理字符串集的程序。我能够完成这个 100%。本周,我们必须用简单的单链表替换我们在类中用于存储字符串的向量。

该函数基本上允许用户声明一组空字符串,并且只包含一个元素。在主文件中,有一个vector,其元素是一个struct,其中包含setName和strSet(class)。

这是我的问题:它处理类的复制构造函数。当我删除/注释掉复制构造函数时,我可以声明任意数量的空集或单个集,并毫无问题地输出它们的值。但我知道当我实现程序的其余部分时,我显然需要复制构造函数。当我保留复制构造函数时,我可以声明一个集合,无论是单一的还是空的,并输出它的值。但是如果我声明第二组,并且我尝试输出前两组中的任何一个,我就会得到一个分段错误。此外,如果我尝试声明多于 2 组,则会出现分段错误。任何帮助将不胜感激!

这是我的代码,用于对所有内容进行非常基本的实现:

这里是 setcalc.cpp:(主文件)

#include <iostream>
#include <cctype>
#include <cstring>
#include <string>
#include "strset2.h"

using namespace std;

// Declares of structure to hold all the sets defined
struct setsOfStr {
    string nameOfSet;
    strSet stringSet;
};

// Checks if the set name inputted is unique
bool isSetNameUnique( vector<setsOfStr> strSetArr, string setName) {
    for(unsigned int i = 0; i < strSetArr.size(); i++) {
        if( strSetArr[i].nameOfSet == setName ) {
            return false;
        }
    }
    return true;
}

int main() {
    char commandChoice;
    // Declares a vector with our declared structure as the type
    vector<setsOfStr> strSetVec;

    string setName;
    string singleEle;
    // Sets a loop that will constantly ask for a command until 'q' is typed
    while (1) {
            cin >> commandChoice;
        // declaring a set to be empty
        if(commandChoice == 'd') {
            cin >> setName;
            // Check that the set name inputted is unique
            if (isSetNameUnique(strSetVec, setName)  == true) {
                strSet emptyStrSet;
                setsOfStr set1;
                set1.nameOfSet = setName;
                set1.stringSet = emptyStrSet;
                strSetVec.push_back(set1);
            }
            else {
                cerr << "ERROR: Re-declaration of set '" << setName << "'\n";
            }
        }
        // declaring a set to be a singleton
        else if(commandChoice == 's') {
            cin >> setName;
            cin >> singleEle;
            // Check that the set name inputted is unique
            if (isSetNameUnique(strSetVec, setName) == true) {
                strSet singleStrSet(singleEle);
                setsOfStr set2;
                set2.nameOfSet = setName;
                set2.stringSet = singleStrSet;
                strSetVec.push_back(set2);
            }
            else {
                cerr << "ERROR: Re-declaration of set '" << setName << "'\n";
            }
        }
        // using the output function
        else if(commandChoice == 'o') {
            cin >> setName;
            if(isSetNameUnique(strSetVec, setName) == false) {
                // loop through until the set name is matched and call output on its strSet
                for(unsigned int k = 0; k < strSetVec.size(); k++) {
                    if( strSetVec[k].nameOfSet == setName ) {
                            (strSetVec[k].stringSet).output();
                    }
                }
            }
            else {
                cerr << "ERROR: No such set '" << setName << "'\n";
            }
        }
        // quitting
        else if(commandChoice == 'q') {
            break;
        }
        else {
            cerr << "ERROR: Ignoring bad command: '" << commandChoice << "'\n";
        }
    }
    return 0;
}

这里是 strSet2.h:

#ifndef _STRSET_
#define _STRSET_

#include <iostream>
#include <vector>
#include <string>

struct node {
    std::string s1;
    node * next;
};

class strSet {

private:
    node * first;
public:
    strSet ();  // Create empty set
    strSet (std::string s); // Create singleton set
    strSet (const strSet &copy); // Copy constructor
    // will implement destructor and overloaded assignment operator later

    void output() const;


};  // End of strSet class

#endif  // _STRSET_

这里是strSet2.cpp(类的实现)

#include <iostream>
#include <vector>
#include <string>
#include "strset2.h"

using namespace std;

strSet::strSet() {
    first = NULL;
}

strSet::strSet(string s) {
    node *temp;
    temp = new node;
    temp->s1 = s;
    temp->next = NULL;
    first = temp;
}

strSet::strSet(const strSet& copy) {
    cout << "copy-cst\n";
    node *n = copy.first;
    node *prev = NULL;
    while (n) {
        node *newNode = new node;
        newNode->s1 = n->s1;
        newNode->next = NULL;
        if (prev) {
            prev->next = newNode;
        }
        else {
            first = newNode;
        }
        prev = newNode;
        n = n->next;
    }
}

void strSet::output() const {
    if(first == NULL) {
        cout << "Empty set\n";
    }
    else {
        node *temp;
        temp = first;
        while(1) {
            cout << temp->s1 << endl;
            if(temp->next == NULL) break;
            temp = temp->next;
        }
    }
}

【问题讨论】:

    标签: c++ class linked-list copy-constructor


    【解决方案1】:

    这看起来有点奇怪:

    strSet::strSet(string s) {
        node *temp;
        temp = new node;
        temp->s1 = s;
        temp->next = NULL;
        first = temp;
    }
    

    如果 'first' 已经指向某个东西怎么办?然后,您实际上是在杀死之前的列表并导致内存泄漏。

    【讨论】:

    • first 在调用这个函数之前没有指向某个东西,因为这个函数是一个构造函数。
    【解决方案2】:

    C++ 标准规定标准容器(例如 std::vector)中使用的类型必须是可复制构造和可赋值的。

    由于您没有在 strSet 类上实现自定义赋值运算符,编译器将为您生成一个执行简单的成员复制的操作符。在您的情况下,这意味着将直接复制“第一个”指针。显然,这意味着两个对象现在“拥有”集合中的节点,当它被释放两次时,您将遇到崩溃。

    一些提示:

    1. 实现一个自定义赋值运算符,其作用与复制构造函数相同

    2. 阅读有关通过引用传递对象的信息,并在可能的情况下通过 const 引用。当您按值传递时,您正在对容器和字符串进行大量不必要的复制。

    例如

    bool isSetNameUnique(const vector& strSetArr, const string& setName)

    祝你好运:)

    【讨论】:

      【解决方案3】:

      strSet 复制构造函数在其参数为空时不会分配成员 first。这会导致未定义的行为。

      另外,编辑前显示的strSet赋值运算符(operator=)肯定是错误的;定义复制构造函数确实不是一个好主意,但允许编译器隐式定义析构函数和赋值运算符。请参阅Rule of Three

      当三巨头需要进行大量管理(如本例)时,实施三巨头的一种常见方法如下:

      class strSet {
      private:
          void cleanup();
          void create_from(const node* n);
      // ...
      };
      
      strSet::~strSet() { cleanup(); }
      
      strSet::strSet(const strSet& copy) : first(NULL) { create_from(copy.first); }
      
      strSet& strSet::operator=(const strSet& rtSide) {
          if (this != &rtSide) {
              cleanup(); // trash old contents of *this
              create_from(rtSide.first); // clone contents of rtSide
          }
          return *this;
      }
      

      【讨论】:

      • 当声明让我们说空集(所以如果输入命令只是'd'),我不使用重载的'='运算符对吗?我只是不确定为什么当我连续声明 3 个空集时我不断收到分段错误。如果我声明一个,它工作正常,我可以输出它的值。如果我声明两组,一旦我尝试输出值,我就会得到“分段错误”,如果我尝试声明 3 组,一旦我尝试,我就会得到“分段错误”。这不应该访问重载的“=”运算符。
      • @Jesus:找到了你的其他问题并编辑了我的答案。但是如果你自己不声明一个operator=,编译器会给你补一个,出来的不一定安全。
      • 好的,完美!感谢您的帮助,不胜感激!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-13
      • 1970-01-01
      • 1970-01-01
      • 2016-10-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多