我试图编写一个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 ++的新手,而该错误可能是由于我对某些概念缺乏了解而导致的。我想知道自己在做什么错,以便将来避免此类问题。提前致谢!
最佳答案
不要在代码中的任何地方使用原始指针,也不要使用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
文件中,您将具有一个查找表,可用于获取与该索引相对应的片段。查看我的帖子的编辑历史记录,以大致了解其外观。 (我起初是这样写的,但后来决定将当前版本更容易理解!)
关于c++ - C++错误“左操作数必须为l值”,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28388203/