Я сделал этот код, который имеет 4 класса. Класс SpecialCharacter имеет 2 базы: Hero и Enemy, оба из которых имеют Person в качестве базового класса.
#include <iostream>
class Person {
private:
char* name;
int level;
public:
Person() {
name = nullptr;
level = 0;
}
Person(char* name, int level) {
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
this->level = level;
}
Person(const Person& p) {
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
level = p.level;
}
virtual ~Person() {
delete[] name;
}
Person& operator=(const Person& p) {
if (this != &p) {
delete[] name;
name = new char[strlen(p.name) + 1];
strcpy(name, p.name);
level = p.level;
}
return *this;
}
bool operator<(const Person& p) const {
return strcmp(this->name, p.name) < 0;
}
friend std::istream& operator>>(std::istream& is, Person& p) {
char nameTemp[100];
is >> nameTemp >> p.level;
delete[] p.name;
p.name = new char[strlen(nameTemp) + 1];
strcpy(p.name, nameTemp);
return is;
}
friend std::ostream& operator<<(std::ostream& os, const Person& p) {
os << "name " << p.name << "\n";
os << "level " << p.level << "\n";
return os;
}
};
class Hero : virtual public Person {
private:
char* ability;
public:
Hero() : Person() {
ability = nullptr;
}
Hero(char* name, int level, char* ability) : Person(name, level) {
this->ability = new char[strlen(ability) + 1];
strcpy(this->ability, ability);
}
Hero(const Hero& h) : Person(h) {
this->ability = new char[strlen(h.ability) + 1];
strcpy(this->ability, h.ability);
}
virtual ~Hero() {
delete[] ability;
}
Hero& operator=(const Hero& h) {
if (this != &h) {
Person::operator=(h);
delete[] ability;
this->ability = new char[strlen(h.ability) + 1];
strcpy(this->ability, h.ability);
}
return *this;
}
friend std::istream& operator>>(std::istream& is, Hero& h) {
is >> static_cast<Person&>(h);
char tempAbil[100];
is >> tempAbil;
delete[] h.ability;
h.ability = new char[strlen(tempAbil) + 1];
strcpy(h.ability, tempAbil);
return is;
}
friend std::ostream& operator<<(std::ostream& os, const Hero& h) {
os << static_cast<const Person&>(h);
os << "ability " << h.ability << "\n";
return os;
}
};
class Enemy : virtual public Person {
protected:
char* type;
public:
Enemy() : Person() {
type = nullptr;
}
Enemy(char* name, int level, char* type) : Person(name, level) {
this->type = new char[strlen(type) + 1];
strcpy(this->type, type);
}
Enemy(const Enemy& e) : Person(e) {
this->type = new char[strlen(e.type) + 1];
strcpy(this->type, e.type);
}
Enemy& operator=(const Enemy& e) {
if (this != &e) {
Person::operator=(e);
delete[] type;
type = new char[strlen(e.type) + 1];
strcpy(type, e.type);
}
return *this;
}
virtual ~Enemy() {
delete[] type;
}
friend std::istream& operator>>(std::istream& is, Enemy& e) {
is >> static_cast<Person&>(e);
char temptype[100];
is >> temptype;
e.type = new char[strlen(temptype) + 1];
strcpy(e.type, temptype);
return is;
}
friend std::ostream& operator<<(std::ostream& os, const Enemy& e) {
os << static_cast<const Person&>(e);
os << "type " << e.type << "\n";
return os;
}
};
class SpecialCharacter : public Hero, public Enemy {
private:
int team;
public:
SpecialCharacter() : Person(), Hero(), Enemy() {
team = 0;
}
SpecialCharacter(char* name, int level, char* ability, char* type, int team) : Person(name, level), Hero(name, level, ability), Enemy(name, level, type) {
this->team = team;
}
SpecialCharacter(const SpecialCharacter& sp) : Person(sp), Hero(sp), Enemy(sp) {
this->team = sp.team;
}
SpecialCharacter& operator=(const SpecialCharacter& sp) {
if (this != &sp) {
Hero::operator=(sp);
this->type = sp.type;
this->team = sp.team;
}
return *this;
}
friend std::istream& operator>>(std::istream& is, SpecialCharacter& sp) {
is >> static_cast<Hero&>(sp);
char temptype[100];
is >> temptype;
sp.type = new char[strlen(temptype) + 1];
strcpy(sp.type, temptype);
is >> sp.team;
return is;
}
friend std::ostream& operator<<(std::ostream& os, const SpecialCharacter& sp) {
os << static_cast<const Hero&>(sp);
os << "type " << sp.type << "\n";
os << "team number " << sp.team << "\n";
return os;
}
};
int main()
{
Person** pers = new Person * [5];
int i = 0, r;
while (i < 5) {
r = rand() % 3;
if (r == 0) {
std::cout << "Enter info for hero\n";
Hero* e = new Hero();
std::cin >> *e;
pers[i] = e;
}
else if (r == 1) {
std::cout << "Enter info for enemy\n";
Enemy* in = new Enemy();
std::cin >> *in;
pers[i] = in;
}
else {
std::cout << "Enter info for special character\n";
SpecialCharacter* ps = new SpecialCharacter();
std::cin >> *ps;
pers[i] = ps;
}
i++;
}
for (int j = 0; j < 5; j++) {
Hero* e = dynamic_cast<Hero*>(pers[j]);
if (e != nullptr) {
std::cout << *e;
continue;
}
Enemy* in = dynamic_cast<Enemy*>(pers[j]);
if (in != nullptr) {
std::cout << *in;
continue;
}
SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
if (ps != nullptr) {
std::cout << *ps;
continue;
}
}
}
Я попытался перегрузить операторы << и >> для SpecialCharacter, используя static_cast, в результате чего он сначала считывает/распечатывает объекты как тип Hero, после чего считывает/распечатывает значения членов Enemy
и SpecialCharacter.
Однако, когда я тестировал этот код в main(), он отображает только name, level и abilities для объектов SpecialCharacter, то есть членов классов Person и Hero.
Может ли кто-нибудь помочь мне понять, почему это происходит? Это из-за того, как я использовал dynamic_cast в цикле for из main()? Или я неправильно перегрузил операторы SpecialCharacter?
В Person не управляйте этой памятью вручную. std::string сделает код намного проще.
Если вы используете другой стандарт кодирования, в котором имена членов отличаются от имен параметров, вы можете исключить весь синтаксис this->. Дополнительный набор текста увеличивает вероятность появления проблем.
К вашему сведению, C++ отличается от других языков: вам не нужно размещать объекты в динамической памяти, чтобы их использовать. Относитесь к объектам как к целым числам: просто объявите их и используйте. Предпочитаю держаться подальше от динамического распределения памяти; используйте экономно и только при необходимости.
Примечания: используйте списки инициализации базовых членов в своих конструкторах. Они более эффективны, чем задания, которые вы используете. В качестве бонуса они позволяют опускать this->. Согласен с @Thomas Matthews относительно std::string. Есть ли причина, по которой вы его не используете?
Вы можете удалить SpecialCharacter класс. Рефакторинг члена team в класс Person. Присвойте ему значение «не в команде». Удаление класса SpecialCharacter также устраняет проблему множественного наследования.
К вашему сведению: использование std::string уменьшает объем написанного вами кода почти до вдвое.
Если вам не разрешено использовать std::string, то есть из-за ограничений проекта, вам следует написать свой собственный класс строк, чтобы исключить весь этот ненужный дублирующийся код.





SpecialCharacter происходит от Hero. В цикле печати ваша первая dynamic_cast проверка на Hero* будет успешной для SpecialCharacter объектов, а затем вы continue перейдете к следующей итерации, так что у вас никогда не будет возможности распечатать члены type и team этих объектов.
Ваш цикл печати должен проверять SpecialCharacter* первым, а не последним.
for (int j = 0; j < 5; j++) {
// DO THIS HERE!
SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
if (ps != nullptr) {
std::cout << *ps;
continue;
}
Hero* e = dynamic_cast<Hero*>(pers[j]);
if (e != nullptr) {
std::cout << *e;
continue;
}
Enemy* in = dynamic_cast<Enemy*>(pers[j]);
if (in != nullptr) {
std::cout << *in;
continue;
}
/* NOT HERE !
SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
if (ps != nullptr) {
std::cout << *ps;
continue;
}
*/
}
@Remy Lebeau правильно определил причину конкретной проблемы, о которой спрашивал ОП, и таким образом заслужил мой положительный голос. Обязательно прочтите его ответ.
Однако здесь есть более серьезная проблема. ОП имеет виртуальную иерархию, которая никогда не используется. Хотя существует множество приведения вверх и вниз, ни в одном коде OP нет виртуальных функций. Всего приведения можно избежать, правильно используя виртуальные методы.
В частности, виртуальная функция put решит проблему, поставленную в исходном вопросе. В каждом классе дружественная функция operator<< должна вызывать виртуальный метод put и позволить vtable разобраться, какая put функция вызывается.
Ошибка в ОП не в том, что даункаты проверялись не в том порядке (хотя они были). Ошибка в том, что в первую очередь нужно было использовать понижающие приведения.
class Person {
//...
friend std::ostream& operator<<(std::ostream& ost, const Person& p) {
p.put(ost);
return ost;
}
protected:
virtual void put(std::ostream& ost) const {
ost << "name " << name
<< "\nlevel " << level
<< '\n';
}
};
class Hero : virtual public Person {
//...
friend std::ostream& operator<<(std::ostream& ost, const Hero& h) {
h.put(ost);
return ost;
}
protected:
void put(std::ostream& ost) const override {
Person::put(ost);
ost << "ability " << ability << '\n';
}
};
class Enemy : virtual public Person {
//...
friend std::ostream& operator<<(std::ostream& ost, const Enemy& e) {
e.put(ost);
return ost;
}
protected:
void put(std::ostream& ost) const override {
Person::put(ost);
ost << "type " << type << '\n';
}
};
class SpecialCharacter : public Hero, public Enemy {
//...
friend std::ostream& operator<<(std::ostream& ost, const SpecialCharacter& sp) {
sp.put(ost);
return ost;
}
protected:
void put(std::ostream& ost) const override {
Hero::put(ost);
ost << "type " << type
<< "\nteam number " << team
<< '\n';
}
};
С этими изменениями порядок, в котором вы проверяете понижения, не имеет значения. Этот запуск был выполнен с использованием неисправленной версии функции main из ОП.
// from function `main` in the OP:
for (int j = 0; j < 5; j++) {
Hero* e = dynamic_cast<Hero*>(pers[j]); // <---------- Order does not matter
if (e != nullptr) {
std::cout
<< typeid(*pers[j]).name() << '\n' // <---------- Operator `typeid`
<< *e << '\n';
continue;
}
Enemy* in = dynamic_cast<Enemy*>(pers[j]);
if (in != nullptr) {
std::cout
<< typeid(*pers[j]).name() << '\n'
<< *in << '\n';
continue;
}
SpecialCharacter* ps = dynamic_cast<SpecialCharacter*>(pers[j]);
if (ps != nullptr) {
std::cout
<< typeid(*pers[j]).name() << '\n'
<< *ps << '\n';
continue;
}
}
Единственным существенным изменением функции main был вызов typeid(*pers[j]).name(), чтобы в выводе отображалось имя класса.
Каждый класс был напечатан с правильным набором переменных-членов. Таким образом, для каждого вызывался правильный метод put.
Enter info for special character
sp1 1 sp1 sp1 1
Enter info for hero
h1 1 h1
Enter info for hero
h2 2 h2
Enter info for special character
sp2 2 sp2 sp2 2
Enter info for enemy
e1 1 e1
class SpecialCharacter
name sp1
level 1
ability sp1
type sp1
team number 1
class Hero
name h1
level 1
ability h1
class Hero
name h2
level 2
ability h2
class SpecialCharacter
name sp2
level 2
ability sp2
type sp2
team number 2
class Enemy
name e1
level 1
type e1
Конечно, вся суть заключалась в том, чтобы вообще избегать использования каких-либо понижений.
Предыдущий цикл теперь можно заменить следующим:
// Corrected loop from function `main`:
for (int j = 0; j < 5; j++) {
std::cout
<< typeid(*pers[j]).name() << '\n'
<< *pers[j] << '\n';
}
ОП заслуживает похвалы за написание настоящих классов «Правила трех». Но затем в функции main он вызывает new и new[] без каких-либо соответствующих вызовов delete и delete[]. Это немного придирка, потому что операционная система восстановит утекшую память, как только программа завершится, но я бы предпочел, чтобы std::vector использовалось в функции main, возможно, с std::unique_ptr для управления указателями.
std::vector<std::unique_ptr<Person>> pers;
Кроме того, когда вам запрещено использовать std::string, рекомендация Реми Лебо написать свой собственный String класс будет хорошей. Это переместило бы элементы RAII в класс String и тем самым упростило бы остальную часть кода.
Переключитесь на
std::stringдля текста. Используйтеstd::string::c_str()для конвертации вchar *только при необходимости.