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&lt;Cube&gt;。您的示例函数可能如下所示:

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;

在此之前还需要进行其他更改(按照目前的情况,大量内存会泄漏,并且“副本”会相互影响)。


我猜你的CornerEdge 构造函数的最后一个参数是某种方向指示器,当部件旋转时你会改变它。因此变量WRGWGO 应该是可变的,并编码该部分的当前方向。

在 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 的实施方式。在EdgeCorner的定义中取出方向对应的字段。

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++ 错误“左操作数必须是左值”的主要内容,如果未能解决你的问题,请参考以下文章

C ++返回指针值不可更改

0

在 r 值概念上需要一些帮助

错误:链接:致命错误 LNK1561:入口点必须定义 C++

C++ 类查询错误:“”左侧必须指向类/结构/联合

OpenCV for iOS 框架错误“Base.hpp”标头必须编译为 C++