繁体   English   中英

如何使用具有多种数据类型的C ++堆栈?

[英]How to have a C++ stack with more than one data type?

这是问题所在:

我目前正在尝试创建一个简单的基于堆栈的编程语言(反向波兰表示法,FORTH样式)作为更大项目的组件。 不过,我遇到了麻烦。

使用包含一种类型元素的C ++(通过使用std::vector<> )创建堆栈没有问题(例如,我可以使用语法std::vector<double> Stack )。

但是,编程语言需要能够保存多种数据类型,例如整数,双精度,字符串和三维向量(如在具有X,Y和Z分量的物理向量中),仅举几个简单的事情。

那么,C ++中是否有一个构造可以用作堆栈,它可以存储多种基本类型/对象/结构?

当然,一种方法是使用标记的联合:

enum Type { INTEGER, DOUBLE, /* ... */ };

union Data {
    uint64_t as_integer;
    double as_double;
    // ...
};

struct Value {
    Type type;
    Data data;
};

as_integeras_double等的存储将重叠,因此Value结构将占用两个存储字,并且您的堆栈将具有类型std::vector<Value> 然后根据type的值访问data成员:

void sub(std::vector<Value>& stack) {
    // In reality you would probably factor this pattern into a function.
    auto b = stack.back();
    stack.pop_back();
    assert(b.type == INTEGER);

    auto a = stack.back();
    stack.pop_back();
    assert(a.type == INTEGER);

    Value result;
    result.type = INTEGER;
    result.data.as_integer = a.data.as_integer - b.data.as_integer;
    stack.push_back(result);
}

当然,Forth通常是无类型的,这意味着堆栈只包含单词( std::vector<uint64_t> ),并且数据值的解释取决于对其进行操作的单词。 在这种情况下,您可以通过union或reinterpret_cast在每个单词的实现中使用相应的类型:

void subDouble(std::vector<Data>& stack) {
    // Note that this has no type safety guarantees anymore.
    double b = stack.back().as_double;
    stack.pop_back();

    double a = stack.back().as_double;
    stack.pop_back();

    Data result;
    result.as_double = a - b;
    stack.push_back(result);
}

void subDouble(std::vector<uint64_t>& stack) {
    double b = reinterpret_cast<double&>(stack.back());
    stack.pop_back();

    double a = reinterpret_cast<double&>(stack.back());
    stack.pop_back();

    double result = a - b;
    stack.push_back(reinterpret_cast<uint64_t&>(result));
}

另外,您也不能值,但指针存储一个类的实例Value从其他值类型,如IntegerDouble会得到:

struct Value {};
struct Integer : Value { uint64_t value; };
struct Double : Value { double value; };
// ...

您的堆栈将具有类型std::vector<unique_ptr<Value>>std::vector<Value*> 然后您不必担心不同的值大小,代价是制作包装器结构并在运行时分配它们的实例。

我建议使用继承。 为需要存储的对象创建公共基类,并创建基类型的向量。 存储此向量中的所有继承对象。

由于c ++是面向对象的语言,因此您可能只使用继承。 以下是http://www.cplusplus.com/forum/general/17754/的一个快速示例,并进行了扩展:

#include <iostream>
#include <vector>
using namespace std;

// abstract base class
class Animal
{
public:
    // pure virtual method
    virtual void speak() = 0;
    // virtual destructor
    virtual ~Animal() {}
};

// derived class 1
class Dog : public Animal
{
public:
    // polymorphic implementation of speak
    virtual void speak() { cout << "Ruff!"; }
};

// derived class 2
class Cat : public Animal
{
public:
    // polymorphic implementation of speak
    virtual void speak() { cout << "Meow!"; }
};

int main( int argc, char* args[] )

    // container of base class pointers
    vector<Animal*> barn;

    // dynamically allocate an Animal instance and add it to the container
    barn.push_back( new Dog() );
    barn.push_back( new Cat() );

    // invoke the speak method of the first Animal in the container
    barn.front()->speak();

    // invoke all speak methods and free the allocated memory
    for( vector<Animal*>::iterator i = barn.begin(); i != barn.end(); ++i )
    {
        i->speak();
        delete *i;
    }
    // empty the container
    barn.clear();

    return 0;
}

存储不同类型的解决方案是标记联合

enum Type { INT, STRING, DOUBLE, POINT2D, VECTOR, OBJECT... };

union Data {
    int int_val;
    double double_val;
    struct point2D { int x, int y };
    struct { int v3, int v2, int v1, int v0 }; // you can even use unnamed structs
    // ...
};

struct StackElem {
    Type type;
    Data data;
};

在C ++中,最好使用std::variant (或旧的C ++标准中的boost::variant ),这可能会使用引擎盖下的标记联合

但是,当使用反向波兰表示法时, 不需要为所有人使用单个堆栈 您可以使用值堆栈和单独的运算符堆栈 对于操作员堆栈上的每个操作员,您可以从值堆栈中弹出相应数量的参数。 因为你可以为操作符使用一个小char数组(除非你需要超过255个运算符),并且没有浪费内存来保存type以及大于需要的data字段,这将使事情变得更容易并节省内存。结构如上。 这意味着您不需要Type枚举中的类型OPERATOR

您可以对所有数字类型使用double类型堆栈,因为double可以包含所有int类型的范围而不会损失精度。 这就是在Javascript和Lua中实现的。 如果操作符需要多于1个参数,则只需按下/弹出所有参数,就像编译器在评估函数时所做的那样。 除非有特定的int运算符,否则您不再需要担心int操作,只需将所有内容都设置为double。 但是您可能需要针对不同类型的不同运算符,例如+用于双重加法, p或类似用于向量加法的运算符。 但是,如果需要64位int,则需要单独的整数类型

例如,如果您需要添加2个3D矢量,请按第一个矢量的3个维度,然后推动另一个矢量。 从运算符堆栈中弹出向量运算符时,从值堆栈中弹出2个向量的3维。 在对其进行数学运算之后,将得到的3个维度推送到堆栈。 不需要矢量类型。

如果您不想将int存储为double那么您可以使用NaN-boxing (或nunboxing / punboxing ),如Firefox的JS引擎,如果值为int,则64位的高16位为1,否则为double (或指针,你可能不会使用)。 另一种方法是旧FFJS引擎中3个低位的类型标记 在这种情况下,它有点复杂,但您可以为每种类型使用相同的运算符。 有关此读取的更多信息,请使用64位指针中的额外16位

您甚至可以使用字节数组存储所有数据类型,并读取运算符指定的正确字节数。 例如,如果运算符指示下一个操作数必须是int,则只读取4个字节。 如果它是一个字符串,首先读取字符串长度的4个字节,然后从堆栈中读取字符串内容。 如果它是int点读取4个字节的x和4个字节的y。 如果它是双读8字节等,这是最节省空间的方式,但显然它必须以速度交易

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM