【问题标题】:Is it possible to transfer type to C++ method or determine type in the method?是否可以将类型转移到 C++ 方法或确定方法中的类型?
【发布时间】:2021-03-04 06:38:55
【问题描述】:

我正在尝试自己实现任何类型的数据列表,并且我想根据数据类型打印此列表。至此,我已经测试了整数和字符。要打印这些类型的列表,有两种方法print_intprint_char,具体取决于所显示的数据类型。我的问题是,是否可以只定义一种方法print,根据我列表的数据类型打印而不创建类模板(不像List<type> ...)? 我的意思是没有这个类定义:

template <class T>
class List{
  T *data;
  List *next;
  ...

(我不使用类模板,因为在 void * 类型为 data 的情况下,可以混合整数、字符、字符串等)

或者我可以将数据类型作为参数传递或确定方法中的类型?

我已尝试使用C++ pass variable type to function 中接受的答案中的模板,仅用于以下形式的函数print

template <typename T>
void List::print()
{
    printf("\nlist %p: [", this);
    bool is_first = true;
    
    List* element = this;
    while(element)
    {
        if (is_first)
            is_first = !is_first;
        else
            printf(", ");
        printf("%i", *(T*)element->data);
        element = element->next;
    }
    printf("]\n");
}

但我在使用g++ 编译器时出错:

对 `void List::print()' 的未定义引用

collect2: 错误:ld 返回 1 个退出状态

这是我的文件: main.cpp

#include "list/list.h"
#include <stdio.h>
#include <iostream>
using namespace std;

int main()
{
    List myList(new int(10));
    myList.append(new int(120));
    myList.append(new int(56));
    
myList.print<int>(); //!
    myList.print_int();
    

    List myListChar(new char('f'));
    myListChar.append(new char(48));
    myListChar.append(new char('g'));

    myListChar.print_int();
    myListChar.print_char();

    return 0;
}

列表的实现(list.h):

#ifndef LIST_H
#define LIST_H

class List
{
    void *data;
    List *next;
public:
    List(void*);

    void append(void*);
    void print_int();
    void print_char();
    template <typename T> void print();
};

#endif // LIST_H

list.cpp:

#include "list.h"
#include <stdio.h>

List::List()
{
    data = NULL;
    next = NULL;
}

List::List(void *data)
{
    this->data = data;
    next = NULL;
}

void List::append(void *data)
{
    List *last_element, *element = this;
    while(element)
    {
        last_element = element;
        element = element->next;
    }

    List* next_element = new List(data);
    last_element->next = next_element;
}

void List::print_int()
{
    printf("\nlist %p: [", this);
    bool is_first = true;
    
    List* element = this;
    while(element)
    {
        if (is_first)
            is_first = !is_first;
        else
            printf(", ");
        printf("%i", *(int*)element->data);
        element = element->next;
    }
    printf("]\n");
}

void List::print_char()
{
    printf("\nlist %p: [", this);
    bool is_first = true;
    
    List* element = this;
    while(element)
    {
        if (is_first)
            is_first = !is_first;
        else
            printf(", ");
        printf("%c", *(char*)element->data);
        element = element->next;
    }
    printf("]\n");

template <typename T>
void List::print()
{
    printf("\nlist %p: [", this);
    bool is_first = true;
    
    List* element = this;
    while(element)
    {
        if (is_first)
            is_first = !is_first;
        else
            printf(", ");
        printf("%i", *(T*)element->data);
        element = element->next;
    }
    printf("]\n");

}

抱歉这个愚蠢的问题,提前谢谢你!

-------------- 更新 ----- ---------

感谢https://stackoverflow.com/users/7582247/ted-lyngmo (Why can templates only be implemented in the header file?) 的评论和https://stackoverflow.com/users/1312382/aconcagua 的回答,将代码template &lt;typename T&gt; void List::print() 移动到标题避免了错误undefined reference to void List::print() '。

不要忘记模板不能与标题分开。

-------------- 最后------ ---------

按照@Aconcagua 的回答,我的列表实现的工作(未优化)版本(不同类型的存储,考虑到它们的打印)如下所示:

list.h:

#include<iostream>
using namespace std;

class NodeBase
{
public:
//    virtual ~NodeBase();
    virtual void print_elem() = 0;

    template <typename T>
    void append(T t);

    void print();
protected:
    NodeBase* next;

    void appendNode(NodeBase* node);
};


template<typename T>
class List : public NodeBase
{
    T data;
public:
    List(T _data)
    {
        data = _data;
        next = nullptr;
    }

    void print_elem() override
    {
        cout << this->data;
    }
};

template <typename T>
void NodeBase::append(T t) // as being template, needs to remain in header!
{
    appendNode(new List<T>(t));
}


void NodeBase::appendNode(NodeBase* node)
{
    NodeBase *last_element, *element = this;
    while(element)
    {
        last_element = element;
        element = element->next;
    }

    last_element->next = node;
}

void NodeBase::print()
{
    cout << "list at " << static_cast<void*>(this) << ": [";
    bool is_first = true;
    NodeBase *element = this;
    while(element)
    {
        if (is_first)
            is_first = !is_first;
        else
            cout << ", ";
        element->print_elem();
        element = element->next;
    }
    cout << "]" << endl;
}

带有测试文件main.cpp:

#include "list/list.h"
int main()
{
    List myNode(10);

    myNode.append(20);
    myNode.append(std::string("sdfsdv"));
    myNode.append('f');
    myNode.append(2.4);
    
    myNode.print();
}

【问题讨论】:

  • 一般经验法则:当数据类型改变但算法没有改变时,考虑使用template
  • 是的,您使用模板。您需要一个接受任何类型的打印函数,它会自动打印该类型。它已经存在。 operator&lt;&lt;()
  • stackoverflow.com/users/225074/thomas-matthews 这是否意味着唯一的解决方案是避免void * 并使用类模板? stackoverflow.com/users/14065/martin-york - 再次operator&lt;&lt;() 需要转换void*
  • 关于“void List::print&lt;int&gt;()的未定义引用”:这可能会有所帮助:Why can templates only be implemented in the header file?
  • 可以使用void*,但很难做到正确和安全。如果您绝对必须在编译时知道类型并且在运行时知道类型,请考虑使用std::variantstd::any,而不是尝试手动完成所有操作。

标签: c++


【解决方案1】:

如果您想在列表中存储 任何 类型而不丢失类型信息,您也可以尝试使用多态方法:

class NodeBase
{
public:
    virtual ~NodeBase();
    virtual void print() = 0;

    template <typename T>
    void append(T t);

protected:
    NodeBase* next;

    void appendNode(NodeBase* node);
};

template<typename T>
class Node : public NodeBase
{
public:
    void print() override { ... }

private:
    T data;
};

template <typename T>
void NodeBase::append(T t) // as being template, needs to remain in header!
{
    appendNode(new Node<T>(t));
}

最后,print 实现可能会使用另一个函数的不同重载来实际打印 data 值。

旁注:我更愿意将当前的 List 类重命名为 Node 并使其成为新创建的 List 类的内部类,该类将在内部管理节点。如果将唯一指向 head 的指针移动到后继者,当前的列表实现很容易导致内存泄漏。

【讨论】:

  • 非常感谢!您的答案可能看起来像我想要的(尤其是对于标题中的其余模板)。您能否更准确地解释一下如何使用您的 Node 对象?
  • @AlexanderKorovin 没什么好说的。新节点将完全用作您的变体,NodeBase 类将保留您的原始变体帽子的所有实现两个细节:特定数据对象的存储和对象特定功能,如打印它。您可以通过虚函数(多态性的核心特性)使基类可以访问这种特定行为——就是这样……
  • 再次感谢!我最终实现了一个不同类型的列表。但我不确定这是否是最优的,并且我在析构函数的实现上遇到了问题(这就是我评论它的原因)。
  • @AlexanderKorovin 然后你可以打开一个聊天室并邀请我加入——我们可以在那里详细讨论你的实现。
【解决方案2】:

这是一个非常简单的列表!

template <class T>
class List{
   T*    data;
   List* next;

   void print() {
       std::cout << "[ ";
       print(this, "");
       std::cout << " ]\n";
   }
   void print(List* node, std::string const& sep) {
       if (node == nullptr) {
           return;
       }
       std::cout << sep << node->data;
       print(node->next, ", ");
   }
}

这应该适用于任何支持正常流输出的类型。现在这应该可以让你继续前进,但我会改变你实现列表的方式。将列表视为一组节点的所有者。节点保存数据但不是列表。

【讨论】:

  • 感谢您的回答。不幸的是,这种情况并非如此。我不想使用类模板,因为在void *类型为data的情况下,我们可以混合整数、字符、字符串等。
  • @AlexanderKorovin:在这种情况下,你已经删除了类型,因此不应该有任何类型特定的代码。
【解决方案3】:

让我们尝试一种不同的方法:打印函数的指针。
让我们为打印函数创建一个同义词:

typedef (void) (*(Ptr_Print_Function)(std::ostream& output, void * node_data));

由于每个节点可以是不同的类型,因此必须正确初始化打印函数。

struct Node
{
  void * p_data;
  Node * prev_node;
  Node * next_node;
  Ptr_Print_Function p_printer;
};

以下是一些附加的转发声明:

void Print_Integer_Node(std::ostream& output, void * p_data);
void Print_Char_Node(std::ostream& output, void * p_data);  

这是一个将节点打印到std::cout的代码片段:

Node * p_node;
//... node search / initialize
(p_node->p_printer)(std::cout, p_node->p_data);

编辑 1:数据继承
恕我直言,更简洁的方法是对数据使用继承和基(接口)类。

struct List_Data_Interface
{
    virtual void print_data(std::ostream& output) = 0;
    void * p_data;
};

struct Node_Version_2
{
    List_Data_Interface * p_data;
    Node_Version_2 *      p_prev;
    Node_Version_2 *      p_next;
};

在上述模型中,我们假设每个节点的数据都有一个打印方法和一个指向某些数据的指针。

一个样本整数数据:

struct Integer_Data : public List_Data_Interface
{
  void print_data(std::ostream& output)
  {
      int * p_int_data = (int *) p_data;
      output << *p_int_data;
  }
};

这是一些关于如何使用上述Node_Version_2 打印节点的代码:

//...
Node_Version_2 p_node;
//... initialize or find the node.
p_node->print_data(std::cout);

通过多态选择正确的打印函数。

【讨论】:

  • 恕我直言,你应该抛弃void * 的概念,除非作为最后的手段(比如失去你的脚是最后的手段)。请改用基接口类。基接口类更容易与多态一起使用。让子类定义接口的功能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-30
  • 1970-01-01
  • 2017-10-24
  • 2010-09-22
  • 2020-02-12
  • 2020-09-14
相关资源
最近更新 更多