Не могу понять, как заставить g ++ принять мой код

0

Я отлаживал этот код часами, пытаясь получить правильный результат и g++. Он работал раньше, но на выходе были логические ошибки, поэтому я вошел и добавил цикл и дополнительный параметр в выходной функции.

Теперь g++ дает мне следующую ошибку:

Student.cpp: В функции-члене void Student :: InputData (std :: string, int, std :: string &): Student.cpp: 81: 21: ошибка: неверное преобразование из 'std :: string * {aka std:: basic_string *} to 'char [-fpermissive]/usr/include/c++/4.6/bits/basic_string.h:560:7: ошибка: инициализирующий аргумент 1 из' std :: basic_string <_CharT, _Traits, _Alloc > & std :: basic_string <_CharT, _Traits, _Alloc> :: operator = (_ CharT) [с _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 data:" << endl;
  s1.OutputData();      // Output data for student 1

  cout << endl;

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

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

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

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

  cout << endl;
  return 0;
}
  • 1
    Что это, "для (int i = 0; i <nClasses; i ++) {names = new string [i];" ?? Ваша переменная «names» является ссылкой на строковый тип. Назначение не имеет смысла.
  • 0
    @OldProgrammer обнаружил проблему компиляции; Я бы посоветовал вам взглянуть почти на каждое использование classList и лучше понять его управление.
Теги:

1 ответ

0

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


Первая и главная проблема заключается в следующем:

string snames[] = "";

Это объявление массива строк, содержащих только один элемент. Это сделает следующий код неопределенным поведением, если snumClasses больше 1:

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

Вышеприведенный код вызовет неопределенное поведение, потому что вы будете получать доступ к адресам без привязки, если i вырасту больше одного. После того, как Undefined Behavior поражает вашу программу, вы не можете понять, какое поведение происходит из-за этого. Ваша программа может сломаться, ваш компьютер может закрыть или еще хуже - носовые демоны.

Чтобы исправить это, размер snames должен быть размером, указанным пользователем. В частности, snames должен быть указателем на динамически выделенный массив, размер которого - snumClasses:

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

snames = new std::string[snumClasses];

Примечание. Я объясню позже, почему это не должно быть сделано.

И когда данные будут закончены, сделайте это точкой для delete[] it:

delete[] snames;

Следующая проблема исходит из этой строки:

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

Объект Student состоит из трех членов данных, имени класса, количества классов и массива классов. Функция-член InputData используется для назначения этих данных членам аргументов. Вы указали правильные аргументы для первых двух параметров (потому что string может быть назначена для string а int может быть назначена int), но ваш третий аргумент не соответствует типу элемента данных. Было бы разумно, если бы вы отправляли snames массивов вместо элемента внутри него, который вы сделали, сделав snames[i].

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

Это также требует, чтобы InputData берет string* а не string& и что указатель classList просто присваивается новому массиву snames:

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);
}

Еще одна ошибка, связанная с этим конструктором, заключается в том, что вместо инициализации используется назначение. Элементы данных вашего класса должны быть инициализированы с использованием списка инициаторов-членов:

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;
}

Параметр был изменен для принятия по значению, так что, когда передается lvalue, мы можем скопировать его и поменять его элементы данных. Функция 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);
}

Дополнительные сведения см. В разделе Что такое идиома копирования и свопинга?


Не используйте new:

Всякий раз, когда вы чувствуете, что используете new, рассмотрите альтернативы, приведенные в стандартной библиотеке. Они реализуют RAII и, как таковые, не требуют от пользователя освобождения памяти. Это очень выгодно, так как уменьшает потенциальную утечку памяти и сохраняет код чистым и эффективным. Мы можем использовать стандартный библиотечный контейнер std::vector<T> для списка классов и подставить его для string* classList данных string* classList:

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

Обратите внимание, как я также удалил numClasses данных numClasses. Нам это не нужно, поскольку вектор имеет функцию члена size(). Более того, поскольку нет необходимости в управлении памятью, мы можем реализовать правило Zero и не реализовывать по умолчанию-ctor, copy-ctor и destructor! Зачем? Поскольку компилятор будет генерировать эти функции-члены по умолчанию.


Используйте пользовательские вставки/экстракторы

Вы можете сократить много кода, указав свои собственные вставки/экстракторы. Вот реализация этих двух:

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();
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню