Я отлаживал этот код часами, пытаясь получить правильный результат и 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;
}
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();
}
classList
и лучше понять его управление.