无法弄清楚如何让g ++接受我的代码

时间:2014-01-31 04:10:05

标签: c++

我一直在调试这段代码几个小时试图让输出正确并且g ++没有错误。 它工作得比较早,但是输出中存在逻辑错误,所以我进入并在输出函数中添加了循环和一个额外的参数。

现在g ++给了我以下错误:

Student.cpp:在成员函数'void Student :: InputData(std :: string,int,std :: string&)'中: Student.cpp:81:21:错误:从'std :: string * {aka std :: basic_string *}'到'char'的无效转换[-fpermissive] /usr/include/c++/4.6/bits/basic_string.h:560:7:错误:初始化'std :: basic_string< _CharT,_Traits,_Alloc>&的参数1 std :: basic_string< _CharT,_Traits, _Alloc> :: operator =(_ CharT)[with _CharT = char,_Traits = std :: char_traits,_Alloc = std :: allocator,std :: basic_string< _CharT,_Traits,_Alloc> = std :: basic_string]'[-fpermissive]

如何修复此代码?:

//This program defines a class for storing the names of classes that a student has enrolled in.

#include <iostream>
#include <cstdlib>
#include <string>

using namespace std;

class Student
{
public:
    Student();
    Student(Student& obj); // copy constructor
    ~Student();
    void InputData(string,int,string&);     // Input all data from user
    void OutputData();      // Output class list to console
    void ResetClasses();        // Reset class list
    Student& operator =(const Student& rightSide){
    this->name=rightSide.name;
    this->numClasses=rightSide.numClasses;
    this->classList=rightSide.classList;
}
    // Assignment operator

private:
    string name;
    int numClasses;
    string *classList;
};

// --------------------------------
// ----- ENTER YOUR CODE HERE -----
// --------------------------------

// ======================
// Student::Student
// The default constructor initialized variables to empty.
// The dynamic array is intialized to NULL.
// ======================
Student::Student () {
        name="";
    numClasses=0;
    classList=NULL;


}
// ======================
// Student::Student
// The copy constructor creates a deep copy of the parameter object
// ======================
Student::Student (Student& obj) {
    obj.name=name;
    obj.numClasses=numClasses;
    obj.classList=classList;
}   
// ======================
// Student::~Student
// The destructor frees up any memory allocated to
// the dynamic array.
// ======================
Student::~Student () {
    delete classList;
}
// ======================
// Student::ResetClasses
// This method deletes the class list
// ======================
void Student::ResetClasses () {
    if(classList) { delete [] classList;}
}
// ======================
// Student::InputData
// This method inputs all data from the user. 
// It allows the user to enter an arbitrary number of classes
// using a dynamic array to store the strings.
void Student::InputData(string nm, int nClasses, string& names) {
    name=nm;
    numClasses=nClasses;
    delete classList;
    for (int i=0; i<nClasses; i++) {
        names=new string[i];
    }
}   

// Reset the class list before entering data just in case this method
// was called repeatedly and the dynamic array wasn't cleared
// ======================


// ======================
// Student::OutputData
// This method outputs the data entered by the user.
// ======================
void Student::OutputData() {
    cout << "Student name : " << name <<endl;
    cout << "Student number of classes : " << numClasses <<endl;
    cout << "Student class list : " <<classList<<endl;
}

// ======================
// Student::=

// operator, we would end up with two references to the same
// class list when the assignment operator is used.
// ======================
//
// --------------------------------
// --------- END USER CODE --------
// --------------------------------



// ======================
//     main function
// ======================
int main()
{
  // Test our code with two student classes
  Student s1, s2;

  string sname;
  int snumClasses;
  string snames[]="";

  cout << "Enter student name, number of classes, and names of classes for first student" << endl;
  cin >> sname; cin >> snumClasses; 

  int i; 
  for (i=0; i < snumClasses; i++) {
    cin >> snames[i];
  }

  s1.InputData(sname, snumClasses, snames[i]);      // Input data for student 1
  cout << "Student 1's data:" << endl;
  s1.OutputData();      // Output data for student 1

  cout << endl;

  s2 = s1;  
  cout << "Student 2's data after assignment from student 1:" << endl;
  s2.OutputData();      // Should output same data as for student 1

  s1.ResetClasses();
  cout << "Student 1's data after reset:" << endl;
  s1.OutputData();      // Should have no classes

  cout << "Student 2's data, should still have original classes:" << endl;
  s2.OutputData();      // Should still have original classes

  Student s3(s2);  // explicit copy constructor call
  cout << "Student 3's data after assignment from student 2:" << endl;
  s2.OutputData(); // should have the same classes as student 2

  cout << endl;
  return 0;
}

1 个答案:

答案 0 :(得分:0)

TL; DR - http://ideone.com/rTVQUo


首要问题是:

string snames[] = "";

这是声明一个只包含一个元素的字符串数组。如果snumClasses大于1:

,这将使以下代码处于未定义的行为
for (i = 0; i < snumClasses; i++) {
    cin >> snames[i];
}

上面的代码会调用未定义的行为,因为如果i增长大于1,您将访问越界地址。一旦Undefined Behavior命中您的程序,您就无法理解因此而产生的任何行为。您的程序可能会中断,您的计算机可能会关闭或更糟 - 鼻腔恶魔。

要解决此问题,snames的大小应为用户指定的大小。特别是,snames应该是指向动态分配的数组的指针,该数组的大小为snumClasses

std::string* snames;
std::cin >> snumClasses;

snames = new std::string[snumClasses];

注意:我稍后会解释为什么这应该

当数据使用完毕后,请指向delete[]它:

delete[] snames;

下一个问题来自这一行:

s1.InputData(sname, snumClasses, snames[i]);
//                               ^^^^^^^^^

Student对象有三个数据成员,类的名称,类的数量和类的数组。 InputData成员函数用于将这些数据成员分配给给定的参数。您已为前两个参数指定了正确的参数(因为string可以分配给stringint可以分配给int但是您的第三个参数与数据成员的类型不匹配。通过执行snames发送数组snames[i]而不是其中的元素是有意义的。

s1.InputData(sname, snumClasses, snames);
//                               ^^^^^^

这还需要InputData采用string*而不是string&,并且classList指针只需分配给新的snames数组:< / p>

void Student::InputData(string nm, int nClasses, string* names)
{
    name = nm;
    numClasses = nClasses;

    delete[] classList;
    classList = names;                                                         /*
    ^^^^^^^^^^^^^^^^^^                                                         */
}

现在我已经减少了大部分错误,让我们继续改进你的程序。

我将从主要的明显问题开始,并偏向更小的(但重要的)设计问题。

复制构造函数:

Student::Student(Student& obj)
{
    obj.name       = name;
    obj.numClasses = numClasses;
    obj.classList  = classList;
}

此构造函数不仅更像赋值运算符,而且还可以切换成员的赋值。您可能希望将*this的数据成员分配给要复制的对象,而不是相反。此外,您的复制构造函数必须引用const

Student::Student(const Student& obj)
{
    name       = obj.name;
    numClasses = obj.numClasses;
    classList  = obj.classList;
}

请注意,考虑到*this与被复制对象之间的生命依赖关系,需要执行深层复制。

Student::Student(const Student& obj)
{
    name       = obj.name;
    numClasses = obj.numClasses;
    std::copy(obj.classList, obj.classList + numClasses, classList);
}

此构造函数的另一个问题是它使用赋值而不是初始化。应使用 member-initializer list

初始化类的数据成员
Student::Student(const Student& obj)
    : name(obj.name),
      numClasses(obj.numClasses),
      classList(numClasses ? new std::string[numClasses]{} : nullptr)
{
    std::copy(obj.classList, obj.classList + numClasses, classList);
}

赋值运算符:

Student& operator=(const Student& rightSide)
{
    this->name       = rightSide.name;
    this->numClasses = rightSide.numClasses;
    this->classList  = rightSide.classList;
}

此运算符与复制构造函数具有相同的问题,因为它考虑了内存管理。如果我们从*this分配到rightSide,我们必须delete[]当前的classList对象,并复制要复制到其中的对象中的元素。如果我们不这样做,我们就有泄漏记忆的风险:

Student& operator=(const Student& rhs)
{
    name       = rhs.name;
    numClasses = rhs.numClasses;

    delete[] classList;  // delete classList
    classList = nullptr; // set to nullptr because if 'new' throws, we can still
                         // delete without causing undefined behavior

    classList = new std::string[numClasses]{};
    std::copy(rhs.classList, rhs.classList + numClasses, classList);

    return *this; // you forgot to return the object!
}

这看起来恰到好处,但也可以改进。目前,此代码不提供强例外保证。如果new抛出,*this的状态将与进入赋值运算符时的状态不同。为了避免这种情况,我们可以应用复制和交换习惯用语,以提供强大的异常保证:

Student& operator=(Student rhs)
{
    swap(*this, rhs);
    return *this;
}

参数已被更改为按值取值,因此当传入左值时,我们可以复制它并交换其数据成员。函数swap封装了交换语义。它在Student内定义如下:

friend void swap(Student& first, Student& second)
{
    using std::swap;
    swap(first.name, second.name);
    swap(first.numClasses, second.numClasses);
    swap(first.classList, second.classList);
}

有关详细信息,请参阅What is the copy-and-swap idiom?


不要使用new

每当您想使用new时,请考虑标准库中提供的替代方案。它们实现RAII,因此不要求用户自己解除内存。这非常有利,因为它可以减少潜在的内存泄漏并保持代码清洁和高效。我们可以将标准库容器std::vector<T>用于类列表,并将其替换为string* classList数据成员:

private:
    std::string name;
    std::vector<std::string> classList;

请注意我是如何删除numClasses数据成员的。我们不需要它,因为向量具有size()成员函数。而且,由于不需要内存管理,我们可以实现Zero规则而不实现default-ctor,copy-ctor和析构函数!为什么?因为编译器默认会生成这些成员函数。


使用用户定义的插入器/提取器

您可以通过定义自己的插入器/提取器来减少许多代码。这将是这两个的实现:

std::ostream& operator<<(std::ostream& os, const Student& s)
{
    s.OutputData(os);
    return os;
}

std::istream& operator>>(std::istream& is, Student& s)
{
    s.classList.assign(std::istream_iterator<std::string>{is >> s.name},
                       std::istream_iterator<std::string>{});

    return is;
}

这就是你在main()中使用它的方法:

int main()
{
    Student s1, s2;

    if (std::cin >> s1 >> s2)
         std::cout << s1 << s2;
}

这应该是你班级的结构:

class Student
{
public:
    void InputData(std::string, const std::vector<std::string>&);
    void OutputData(std::ostream&) const;
    void ResetClasses();

    friend std::ostream& operator<<(std::ostream&, const Student&);
    friend std::istream& operator>>(std::istream&,       Student&);
private:
    std::string name;
    std::vector<std::string> numClasses;
};

这是这些成员函数的实现:

void Student::InputData(std::string nm, const std::vector<std::string>& names)
{
    name = nm;
    numClasses = names;
}

void Student::OutputData(std::ostream& os) const
{
    os << "Students name: " << name << std::endl;
    os << "Number of classes: " << classList.size() << std::endl;
    os << "Classes: ";

    std::copy(classList.begin(), classList.end(),
        std::ostream_iterator<std::string>(os, "\n"));
}

void Student::ResetClasses()
{
    classList.clear();
}