问题在于:
我目前正在尝试创建一个简单的基于堆栈的编程语言(反向波兰表示法,FORTH样式)作为更大项目的组件。不过,我遇到了麻烦。
使用包含一种元素的c ++(使用std::vector<>
)创建堆栈没有问题(例如,我可以使用语法std::vector<double> Stack
。)
但是,编程语言需要能够保存多种数据类型,例如整数,双精度,字符串和三维向量(如在具有X,Y和Z分量的物理向量中),只是为了命名一些简单的东西
那么,C ++中是否有一个构造可以用作堆栈,它可以存储多种基本类型/对象/结构?
答案 0 :(得分:3)
当然,一种方法是使用标记联合:
enum Type { INTEGER, DOUBLE, /* ... */ };
union Data {
uint64_t as_integer;
double as_double;
// ...
};
struct Value {
Type type;
Data data;
};
as_integer
,as_double
等的存储空间将重叠,因此Value
结构将占用两个存储字,而您的堆栈将具有{{1}类型}。然后,您可以根据std::vector<Value>
:
data
的成员
type
当然,Forth通常是无类型的,这意味着堆栈只包含单词(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);
}
),并且数据值的解释取决于对其进行操作的单词。在这种情况下,您将通过联合或std::vector<uint64_t>
对每个单词的实现中的相应类型进行惩罚:
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
或Integer
等其他值类型将从中派生:
Double
您的堆栈的类型为struct Value {};
struct Integer : Value { uint64_t value; };
struct Double : Value { double value; };
// ...
或std::vector<unique_ptr<Value>>
。然后你不必担心不同的值大小,代价是制作包装器结构并在运行时分配它们的实例。
答案 1 :(得分:0)
我建议使用继承。为需要存储的对象创建公共基类,并创建基类型的向量。存储此向量中的所有继承对象。
答案 2 :(得分:0)
由于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;
}
答案 3 :(得分:0)
存储不同类型的解决方案是tagged union
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
),这可能会使用引擎标记的联合
然而,当使用反向波兰表示法时,不需要为所有使用单个堆栈。你可以use a value stack and a separate operator stack。对于操作员堆栈上的每个操作员,您可以从值堆栈中弹出相应数量的参数。因为你可以为运算符使用一个小的char
数组(除非你需要超过255个运算符),并且没有浪费内存来保存type
以及更大的运算符,这将使事情变得更容易并节省内存像上面那样在结构中需要data
字段。这意味着您不需要OPERATOR
枚举
Type
类型
您可以对所有数字类型使用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引擎中的type tag in 3 lower bits。在这种情况下,它有点复杂,但您可以为每种类型使用相同的运算符。有关此阅读Using the extra 16 bits in 64-bit pointers
您甚至可以使用字节数组存储所有数据类型,并读取运算符指定的正确字节数。例如,如果运算符指示下一个操作数必须是int,则只读取4个字节。如果它是一个字符串,首先读取字符串长度的4个字节,然后从堆栈中读取字符串内容。如果它是int
的2D点,则读取x的4个字节和y的4个字节。如果它是双读8字节等,这是最节省空间的方式,但显然它必须以速度交易