C++ 错误“左操作数必须是左值”
Posted
技术标签:
【中文标题】C++ 错误“左操作数必须是左值”【英文标题】:C++ Error "left operand must be l-value" 【发布时间】:2015-02-07 22:28:26 【问题描述】:我正在尝试编写一个 C++ 程序来解决魔方问题。我定义了四个类: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++ 新手,这个错误可能是由于我对某些概念缺乏了解造成的。我想知道我做错了什么,以便将来避免此类问题。提前致谢!
【问题讨论】:
什么是Cube::pieces
?请出示其声明。
发布Cube
的类定义以获得更好的帮助
Cube 的定义不是必须的,因为已经显示了 Cube::pieces 的定义。不幸的是,在所有无关紧要的混乱中很容易错过它。
如果Piece
实际上是一个类,则显示其定义以及WRG, WR, WBR
等的定义。
在你的构造函数中,Piece* pieces[3][3][3]
是一个新变量,它不引用类成员pieces
【参考方案1】:
不要使用原始指针,也不要在代码中的任何地方使用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++ 中,对象应该具有值语义。在这种情况下,这意味着复制对象应该执行“深度复制”,也就是。一个克隆。除了对象的构造函数之外,不应该有实现副本的代码。
因此,如果您的对象被设计为使用值语义,那么在 getChildren
函数中尝试 children[i].pieces = pieces
的问题就永远不会出现。
如果你的对象设计包含 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...
在这个版本中,至少没有内存泄漏,但是仍然存在默认生成的复制构造函数会将新立方体的 Piece 指针复制到旧立方体的 Pieces 的问题。
有三种方法可以解决这个问题:
-
编写一个复制构造函数(和一个复制赋值运算符)来检测旧多维数据集的指针指向哪一块,并使新多维数据集中的每个对应指针指向新多维数据集
将棋子设为
static
,这样就真的只有一组棋子了。将使用单独的变量记住方向。
按值而不是按指针保存片段。
1 是您目前正在尝试做的事情,实际上比看起来更难;即使您使用std::copy
或等效方法修复了编译错误,您仍然有指向旧多维数据集的指针。
2 是个好主意。如果只有一组片段,那么您可以复制指向片段的指针而不会造成任何麻烦。当然,那么你需要每个Cube
有一个新的数组来表示每块的当前方向。这仍然比 #1 简单!
这个选项还有一个很大的好处是可以减少每个状态的内存占用。
(有关 #3 和内存使用的更多 cmets,请参见下文)。
以下是策略 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
文件中,您将有一个查找表,您可以使用它来获取与该索引对应的片段。
查看我的帖子的编辑历史,大致了解它的外观。 (我最初是这么写的,但后来决定现在的版本会更容易理解!)
【讨论】:
【参考方案2】:在 C++ 中,您不能将另一个数组分配给一个固定长度的数组。您必须单独复制这些值。 (不是 100% 确定,但非常确定。)
在class Cube
的定义中,你的意思是指向这样一个数组的指针吗,即你的意思是
typedef Piece PieceArray [3][3][3];
PieceArray * pieces;
在您的代码中,您没有声明一个指向片段数组的指针,而是一个指向片段的指针数组。
帮自己一个忙,使用std::vector
。
【讨论】:
对此我不太确定。可能是,但不是必须的。无论如何,这不是问题。 @MikeNakis 整个事情需要彻底重新设计 "(不是 100% 肯定,但非常肯定。)"std::array
就是为了解决这个问题而发明的。所以它 100% 不再是真的了。
那是 C++:对于每一种语言特性,都可以通过内置方式或标准库函数或 boost 库函数来实现完全相反的效果。【参考方案3】:
您没有提供Cube::pieces
的外观与pieces
的外观以及类型为int[3][3][3]
。问题是它是C 风格的数组 并且C 的类型非常弱,所以C 和C++ 都无法区分int[3][3][3]
和int[2][2]
- 类型信息丢失。
考虑使用 C++ 数组类型 - 它们定义 复制构造函数 和 赋值运算符 并在内部保存它们的大小,因此它们将完成所有工作你。让我们搭车吧!
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 */
std::vector
为此,您需要定义向量的向量:
#include <vector>
int main()
std::vector<std::vector<int> > i = 0, -1, 1, 2 ;
std::vector<std::vector<int> > b;
b = i;
boost::multi_array
(这将需要外部库)
#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
可能比向量的向量更有效。
【讨论】:
以上是关于C++ 错误“左操作数必须是左值”的主要内容,如果未能解决你的问题,请参考以下文章