我正在尝试编写一个C ++程序,以便解决Rubik的多维数据集。我定义了四个类:Piece,Edge,Corner和Cube,其中Corner和Edge是Piece的子类。
Cube类定义如下:
class Cube{
private:
Piece* pieces[3][3][3];
public:
Corner* WRG = new Corner(WHITE, RED, GREEN, WHITE);
Corner* WGO = new Corner(WHITE, GREEN, ORANGE, WHITE);
Corner* WOB = new Corner(WHITE, ORANGE, BLUE, WHITE);
Corner* WBR = new Corner(WHITE, BLUE, RED, WHITE);
Corner* YRB = new Corner(YELLOW, RED, BLUE, YELLOW);
Corner* YBO = new Corner(YELLOW, BLUE, ORANGE, YELLOW);
Corner* YOG = new Corner(YELLOW, ORANGE, GREEN, YELLOW);
Corner* YGR = new Corner(YELLOW, GREEN, RED, YELLOW);
Edge* WR = new Edge(WHITE, RED, WHITE);
Edge* WB = new Edge(WHITE, BLUE, WHITE);
Edge* WO = new Edge(WHITE, ORANGE, WHITE);
Edge* WG = new Edge(WHITE, GREEN, WHITE);
Edge* YR = new Edge(YELLOW, RED, YELLOW);
Edge* YB = new Edge(YELLOW, BLUE, YELLOW);
Edge* YO = new Edge(YELLOW, ORANGE, YELLOW);
Edge* YG = new Edge(YELLOW, GREEN, YELLOW);
Edge* GO = new Edge(GREEN, ORANGE, GREEN);
Edge* GR = new Edge(GREEN, RED, GREEN);
Edge* BO = new Edge(BLUE, ORANGE, BLUE);
Edge* BR = new Edge(BLUE, RED, BLUE);
Cube();
~Cube();
void rotateRedClock();
void rotateRedCounter();
void rotateOrangeClock();
void rotateOrangeCounter();
void rotateYellowClock();
void rotateYellowCounter();
void rotateGreenClock();
void rotateGreenCounter();
void rotateBlueClock();
void rotateBlueCounter();
void rotateWhiteClock();
void rotateWhiteCounter();
void doMove(int);
Piece getPieces();
Cube* getChildren();
};
Cube::Cube(){
Piece* pieces[3][3][3] = { { { WRG, WR, WBR }, { GR, NULL, BR }, { YGR, YR, YRB } }, //Red face
{ { WG, NULL, WB }, { NULL, NULL, NULL }, { YG, NULL, YB } }, //Middle section
{ { WGO, WO, WOB }, { GO, NULL, BO }, { YOG, YO, YBO } } }; //Orange face
}
此数组存储在Cube对象内部,该对象可以对数组中的指针进行随机化,并更改每个Piece的方向参数以处理旋转。据我所知,这一切都应该可以正常工作。
当我尝试返回包含当前状态下所有可能移动的Cube对象数组时,问题就开始了。
如果我用Java编程,它看起来像这样:
public Cube[] getChildren(){
Cube children = new Cube[12];
for (int i = 0; i < 12; i++){
children[i] = new Cube(this.getPieces()); //Effectively clone this
children[i].doMove(i); //Does one of the 12 available moves on the cube
}
return children;
}
然而,在C ++中,我似乎无法实现这一目标。我尝试了以下方法:
Cube* Cube::getChildren(){
Cube* children = new Cube[12];
for (int i = 0; i < 12; i++){
children[i] = Cube();
children[i].pieces = pieces;
children[i].doMove(i);
}
return children;
}
但我在这一行上收到错误:
children[i].pieces = pieces;
它说:
error C2106: '=' : left operand must be l-value
我是C ++的新手,这个错误可能是由于我对某些概念缺乏了解所致。我想知道我做错了什么,以便将来可以避免这种问题。提前谢谢!
答案 0 :(得分:3)
不要使用原始指针,也不要在代码中的任何位置使用new
。 Java的Cube[]
的C ++等价物是std::vector<Cube>
。您的示例函数可能如下所示:
std::vector<Cube> Cube::getChildren() const
{
// 12 copies of current state
std::vector<Cube> children(12, *this);
for (int i = 0; i < children.size(); i++)
{
children[i].doMove(i);
}
return children;
}
在此之前还有其他更改可行(事实上,大量内存将被泄露,“副本”会相互影响)。
我猜你的Corner
和Edge
构造函数的最后一个参数是某种方向指示符,当你的棋子旋转时你会改变它。所以变量WRG
,WGO
应该是可变的,并编码该片段的当前方向。
在C ++中,对象应具有值语义。在这种情况下,这意味着复制对象应该执行“深层复制”,也就是说。一个克隆。除了在对象的构造函数内部之外,不应该有用于实现副本的代码。
因此,如果您的对象被设计为使用值语义,那么在children[i].pieces = pieces
函数内尝试getChildren
的问题将永远不会出现。
如果您的对象设计包含27个指向该类的可变成员的指针,则默认生成的复制构造函数将执行错误的操作,因为:
所以,这在C ++中不是一个好的设计。
按价值按住件会更好。对原始代码的改进(但仍然不可行)将是:
Corner WRG {WHITE, RED, GREEN, WHITE};
Edge WR {WHITE, RED, WHITE};
Pieces *pieces[3][3][3] =
{ {&WRG, &WR, &WBR}, {&GR, nullptr, &BR}, // etc...
使用这个版本,至少没有内存泄漏,但是仍然存在这样的问题:默认生成的复制构造函数将使用旧多维数据集的Pieces复制新的多维数据集的Piece指针。
有三种方法可以解决这个问题:
static
,因此实际上只有一组。将使用单独的变量记住方向。 1 是您目前正在尝试做的事情,实际上比看起来更难;即使您使用std::copy
或等效项修复了编译错误,您仍然会指向旧多维数据集的指针。
2 是一个好主意。如果只有一组碎片,那么你可以将指针复制到碎片而不会造成任何麻烦。当然,您需要每个Cube
都有一个新数组来表示每个部分的当前方向。这仍然比#1更简单!
此选项还可以大大减少每个州的内存占用量。
(有关#3和内存使用情况的更多评论,请参阅下文。)
以下是策略2的实现方式。在Edge
和Corner
的定义中,取出与方向相对应的字段。
class Cube
{
// "static" means only one copy of each for the whole program
// The constructor arguments for each one are placed in the .cpp file
static constexpr Corner WRG, WGO, WOB, /*....*/ ;
static constexpr Edge WR, GR, /*.....*/ ;
Pieces const *pieces[3][3][3] =
{ {&WRG, &WR, &WBR}, {&GR, nullptr, &BR}, // etc...
typedef unsigned char Orientation;
Orientation orientation[3][3][3] = { };
public:
// no default constructor needed if you got the above lists right
// no destructor needed either way
// Cube();
void rotateRedClock();
void rotateRedCounter();
// etc. - you'll probably find it easier to roll all of these into
// a single function that takes the face and the direction as parameter
void doMove(int); // suggest using an enum to describe possible moves
// not necessary getPieces();
vector<Cube> getChildren() const;
};
如果您计划某种求解算法,则需要减少每个状态的内存占用量。所以 3 也是一个好主意。
如果你采用#2,那么做#3就不那么紧迫了 - 你可以通过方法#2使你的代码工作并在以后优化它。
要使用#3,您需要放弃使用Piece *
多态的想法。
但是你无论如何都不需要这个功能,因为你可以从数组索引中判断出这个部分应该是一个角还是一个边。我建议只使用Piece
基本上是Corner
现在但没有方向;当你期待边缘时忽略第三个字段。
我的建议是用27个字节的表替换27个指针的表。然后,在您的.cpp
文件中,您将拥有一个查找表,您可以使用该表来获取与该索引相对应的部分。
检查我的帖子的编辑记录,以查看其外观的大致轮廓。 (我最初写过,但后来决定当前版本更容易消化!)
答案 1 :(得分:0)
在C ++中,您不能将另一个数组分配给固定长度的数组。您必须单独复制值。 (不是100%肯定,但非常确定。)
在class Cube
的定义中,你的意思是指向这样一个数组的指针,即你的意思是
typedef Piece PieceArray [3][3][3];
PieceArray * pieces;
在你的代码中,你没有声明一个指向数组的指针,而是指向一个指向数组的指针数组。
帮自己一个忙,并使用std::vector
。
答案 2 :(得分:0)
您没有提供Cube::pieces
pieces
的样子并且int[3][3][3]
类型。问题是它是 C风格的数组和C非常弱的类型,所以C和C ++都无法区分int[3][3][3]
和int[2][2]
- 类型信息丢失。
考虑使用 C ++ 数组类型 - 它们定义复制构造函数和赋值运算符并在内部保存它们的大小,因此它们将完成所有工作您。让我们坐一趟!
正如我们已经知道的那样,这不起作用,这只是一个例子。
int main() {
int i[2][2] = { {0, -1}, {1, 2} };
int b[2][2];
b = i; /* cube.cpp:5:9: error: invalid array assignment */
}
为此,您需要定义向量向量的向量:
#include <vector>
int main() {
std::vector<std::vector<int> > i = { {0, -1}, {1, 2} };
std::vector<std::vector<int> > b;
b = i;
}
(这将需要外部库)
#include <boost/multi_array.hpp>
int main() {
boost::multi_array<int, 2> i(boost::extents[2][2]);
/* Note that for multi_array we need single-dimentional initializer */
auto _i = { /*{*/ 0, -1 /*}*/,
/*{*/ 1, 2 /*}*/ };
i.assign(_i.begin(), _i.end());
boost::multi_array<int, 2> b(boost::extents[2][2]);
b = i;
}
这似乎比其他解决方案更复杂,但multi_array
可能比矢量矢量更有效。