Использование шаблонов C в связанных списках с отображением нескольких разных типов в списке

#c #templates #linked-list

#c #шаблоны #связанный список

Вопрос:

(Во-первых, в качестве оговорки, это связано с назначением. Я не прошу никого выполнять мое задание за меня, просто чтобы попытаться помочь мне понять, как правильно реализовать шаблоны.)

Моя текущая настройка:

У меня есть класс A, который является базовым классом. Классы B, C и D — все дочерние элементы класса A.

Я пытаюсь создать связанный список, который в рамках одного списка может указывать на B, C или D.

В настоящее время у меня настроено что-то вроде этого:

 enum Types { TypeB, TypeC, TypeD }

struct Node
{
    void * pointerToElement;
    int type;
    Node * next;
};

struct Header
{
    int counter;
    Node * first;
};
  

Это действительно работает. Когда я просматриваю связанный список, чтобы распечатать все элементы, я использую оператор and if и int type , чтобы определить, какой это тип (на основе определенного перечисления), а затем использую static_cast для приведения указателя void к указателю класса B, C или D.

Теперь мне сказали, что вместо этого я должен использовать шаблоны, что вызывает сильную головную боль. Я мало что сделал с шаблонами, но мой опыт работы с ними не был таким уж приятным.

Мое понимание шаблонов заключается в том, что я мог бы использовать его для определения всего связанного списка с классом B, C или D, но было бы неправдоподобно, чтобы все B, C или D отображались в одном связанном списке?

Я попробовал следующее:

 enum Types { TypeB, TypeC, TypeD } // I realise that if templates work, I won't need this

template <class T>
struct Node
{
    T * pointerToElement;
    int type;
    Node<T> * next;    // Reason 1 I suspect I could only use one type
};

template <class T>
struct Header
{
    int counter;
    Node<T> * first;    // Reason 2 I suspect I could only use one type
};
  

Главный вопрос, который у меня есть, должны ли шаблоны быть способны это делать? При реализации этого в классе мне нужно было указать тип для заголовка, чего я не хотел делать, поэтому я также сделал этот класс шаблоном, и он продолжал следовать до конца моего кода, который не должен был быть шаблоном, и, наконец, добрался до main (), где мне пришлось бы определить либо класс B, C, либо D.

Комментарии и предложения приветствуются.

Спасибо.

Редактировать

Спасибо всем за комментарии, я, вероятно, узнал больше, попробовав это, чем из лекций.

То, что я сделал, в значительной степени отбросило шаблоны или, по крайней мере, то, как я пытался их использовать. Я использовал шаблоны (к сожалению, ради использования шаблонов), и это работает. Вот что я сейчас сделал (все разработано на основе всех полезных комментариев … спасибо!)

 template <class T>
struct Node
{
    T * pointerToElement;
    int type;   // I can get rid of this after I go through the code and remove all references to it, which I am doing now.
    Node<T> * next;    // Reason 1 I suspect I could only use one type
};

template <class T>
struct Header
{
    int counter;
    Node<T> * first;    // Reason 2 I suspect I could only use one type
};
  

все по-прежнему так, как было, но при объявлении заголовка я объявляю его как:

Заголовок * MyHeader;

(Я использую структуру классов, идентичную той, что приведена в решении ниже).

Итак, это указывает на базовый класс, тот, от которого происходят все остальные классы. Затем, из-за наследования, я могу хранить там классы B, C или D без каких-либо проблем, и при условии, что все функции, определенные в производных классах (B, C и D), определены в базовом классе, я могу вызывать его напрямую, без необходимости его приведения (например, у всех них есть своя функция print, и она вызывает правильную функцию, когда она определена в производном классе).

Я думаю, что идея, которую пытается донести присваивание, — это связанный список, который можно использовать с любым типом, я думаю, что произошло некоторое недопонимание (вероятно, в основном из-за меня), я думал, что шаблоны должны использоваться для определения разных типов классов в каждом узле, скорее их можно использовать для определения базового класса.

Комментарии:

1. Это звучит как задание для наследования

2. Немного запутано, но 1 за хороший вопрос для домашнего задания.

3. GWW: Все классы B, C и D унаследованы от класса A. В реальном назначении оно имитирует банк, поэтому класс A — это учетная запись, а B, C и D — сберегательные, чековые или кредитные счета. Идея заключается в том, что у каждого клиента есть связанный список учетных записей, и их учетная запись может быть любого времени, и, по-видимому, я обязан использовать шаблоны для удовлетворения критериев маркировки (где, поскольку я использую void *). Я думаю, что в конечном итоге я могу добавить шаблоны где-нибудь еще в назначении. Джон: Спасибо.

4. @joshendo: Вы уверены, что от вас требуется использовать шаблоны для этой части задания? Какова была фактическая формулировка вопроса?

5. Координатор предмета не лучший в написании заданий, а шаблоны — это всего лишь сарафанное радио с прошлой недели от другого академика (вот почему это, по-видимому, а не наверняка… половина работы по назначению заключается в том, чтобы решить, что делать.). Я думаю, что я буду использовать производные классы / наследование и полностью откажусь от шаблона, это не кажется возможным или хорошей идеей, и нарушает мою в остальном работающую программу. Спасибо за комментарии.

Ответ №1:

Вместо шаблонов вы должны использовать наследование. Шаблоны, как вы догадались, создают экземпляр для каждого типа T , а это не то, что вы хотите.

Ваш код должен быть примерно в этих строках:

 class A{...};
class B: public A{...};
class C: public A{...};
class D: public A{...};
struct Node{
    A *next;
}
  

вы можете назначить next указателям на A , B C или D . Не забудьте пометить функции-члены как virtual там, где это уместно.

Комментарии:

1. Я думаю, что это то, что я мог бы в конечном итоге сделать. Просто небольшой вопрос, если у класса B есть функция, которой нет у класса C, можно ли определить ее в классе A как виртуальную функцию, даже если она не отображается во всех производных функциях?

2. @joshhendo — если это чисто виртуальная функция (т. Е. В классе A у вас нет тела для этой функции, только объявление с =0 после него), то все производные классы должны ее реализовать. Если у A есть реализация для него, то C унаследует эту реализацию, если у него нет своей собственной. В любом случае, при повторном просмотре списка вы сможете получить доступ только к функциям-членам и переменным, которые определены для A , если вы не применяете что-то другое (используя dynamic_cast вместо static_cast , прочитайте о различиях и использовании).

3. @joshhendo: Взгляните на принципы SOLID и, в частности, на принцип подстановки Лискова . В хорошо разработанной программе, если вы пишете операцию, которая использует некоторый объект типа A, вы должны иметь возможность заменить его объектом типа B, C или D, и программа все равно должна быть корректной. (Короче говоря, нехорошо делать то, что вы говорите, хотя C позволяет вам это делать.)