【问题标题】:Assignment of objects in C++C++中的对象赋值
【发布时间】:2013-11-24 00:38:41
【问题描述】:

为了具体化我的问题,我使用了具有以下定义的 Matrix 类:

Matrix(unsigned int, unsigned int); // matrix of the given dimension full of zeroes
Matrix(Matrix*); // creates a new matrix from another one
int &operator()(int, int);  // used to access the matrix
int **matrix; // the matrix

现在取这两个代码sn-ps:

第一:

Matrix test(4,4);
Matrix ptr = test;
ptr(0,0) = 95;

第二:

Matrix test(4,4);
Matrix *ptr = &test;
(*ptr)(0,0) = 95;

两个代码的效果一样,(0,0)位置的元素得到95(第一个sn-p和Java很像,所以才问这个问题的原因)。 问题是,这两种方法都正确地分配了对象吗?

【问题讨论】:

  • 我想你会发现它们并不相同......

标签: c++ object variable-assignment assign


【解决方案1】:

这有点复杂。

考虑这个简单的类:

class Thing1
{
public:
  int n;
}

现在我们尝试第一个实验:

Thing1 A;
A.n = 5;

Thing1 B = A;
B.n = 7;

cout << A.n << " " << B.n << endl;

结果是“5 7”。 AB 是两个独立的对象。改变一个不会改变另一个。

第二次实验:

Thing1 *p = &A;
p->n = 9;

cout << A.n << " " << p->n << endl;

结果是“9 9”; p 是指向A 的指针,所以A.np-&gt;n 是同一个东西。

现在事情变得复杂了:

class Thing2
{
public:
  int *p;
};

...
Thing2 A;
A.p = new int(2);

Thing2 B = A;
*(B.p) = 4;

cout << *(A.p) << " " << *(B.p) << endl;

现在结果是“4 4”。赋值B = A 复制了指针,所以虽然AB 是两个不同的对象,但它们的指针指向同一个int。这是浅拷贝。一般来说,如果你想制作一个深拷贝(也就是说,每个事物都指向它自己的一个int),你必须要么手工完成,要么给类一个赋值运算符 将处理它。由于您的 Matrix 类没有显式赋值运算符,因此编译器为其提供了默认值——即浅拷贝。这就是为什么在您的第一个 sn-p 中,两个矩阵似乎都发生了变化。

编辑:感谢@AlisherKassymov,指出Thing A=B; 形式的声明使用复制构造函数,而不是赋值运算符。 em> 因此,要使上述代码中的解决方案起作用,复制构造函数必须进行深层复制。 (请注意,如果复制构造函数这样做,您几乎肯定希望赋值运算符也这样做(参见Rule of Three))。另请注意,如果这些函数变得复杂,则只需让复制构造函数调用赋值运算符即可。)

【讨论】:

  • 惊人的答案!感谢您对所涉及概念的清晰解释(浅拷贝和深拷贝)。
  • 我不认为这是正确的。在这种情况下,使赋值运算符重载不会改变结果。这是一个复制构造函数。为了使赋值运算符起作用,首先需要构造对象,然后使用 = 复制另一个对象
  • @AlisherKassymov:你说得对,谢谢。 我忘了Thing A=B; 使用的是复制构造函数,而不是赋值运算符(尽管复制 ctor 通常在op=) 的条款,并且没有对我的解决方案进行足够的测试。我会编辑。
  • @Beta 所以假设我输入了Thing2 B; B=A; 两个对象的指针是否指向同一个内存地址?
  • @user148469:是的。如果你想要一个做深拷贝的赋值操作符,你必须自己写。
【解决方案2】:

在这种情况下,Java 行为的等价物使用 C++ references

表示
Matrix test(4,4);
Matrix &ptr = test;
ptr(0,0) = 95;

这段代码确实和指针版本做同样的事情,即它修改了原来的test对象。

您的第一个代码示例正式创建原始对象的副本,然后修改该副本。但是,您的 Matrix 类似乎是一个多级对象,它拥有一些较低级别的内存(matrix 指针)。如果您的Matrix 类复制构造函数实现了浅复制逻辑(即,它与原始对象共享较低级别的矩阵数据,而不是对其进行深度复制),那么修改副本似乎也会修改原始对象。这种行为是正确还是不正确取决于您的意图。

在您的 cmets 中,您提到第一个代码似乎也修改了原始对象。这立即意味着您的类实际上实现了浅拷贝逻辑。看起来它不是您设计的预期部分。显然,您在实现 Matrix 类时忘记关注 Rule of Three

【讨论】:

    【解决方案3】:

    第一个将test 的值复制到ptr。 第二个将ptr设置为指向test地址的指针

    这两个动作不一样。在第一种情况下,ptr 将具有与 test 相同的值,但它们本身是该数据的两个不同副本,因此您对 ptr(0,0) = 95; 的分配不会设置 test(0, 0)

    然而,在第二种情况下,ptr 指向test 的地址,因此ptr 的解引用 测试。因此,当您在此处设置值时,实际上也设置了 test(0, 0) 的值。

    这可以通过以下程序轻松验证:

    #include <iostream>
    class Test{
    public:
        int i;
    };
    
    int main(){
        Test c;
        c.i = 1;
    
        Test d = c;
        d.i = 2;
        std::cout << "Value: " << c.i << "\n";
    
        Test* e = &c;
        (*e).i = 2;
        std::cout << "Pointer: " << c.i << "\n";
    }
    

    当然,如果您动态分配矩阵(新),那么当您按照第一个示例复制值时,也会复制指向数据的指针,因此当您设置新数据时,它似乎等于第二个例子。

    【讨论】:

    • 我的错误是假设被复制的值是新值,但它是指向被复制数据的指针。感谢代码示例 Quirliom 的明确回答,非常有帮助!
    【解决方案4】:

    这两个不相等。

    第一个sn-p

    Matrix test,将完整内容复制到Matrix ptr。 当您稍后使用ptr 时,您只更改副本,而不是原始Matrix test

    第二个sn-p

    Matrix test的地址被放入指针Matrix *ptr。 指针现在保存test 的地址。 当您编写(*ptr) 时,您取消引用指针并使用原始test 的值。

    在 Java 中

    在 java 中,所有对象都是一种指针(int 等原语不是)。当您在那里将一个对象分配给另一个对象时,默认设置是仅覆盖指针值。像你的第二个例子一样工作。

    【讨论】:

    • 我觉得很奇怪,使用第一个 sn-p 两者都被更改,即位置 (0,0) 的元素设置为 95。
    • 您在 Matrix 类中是否有指向存储数据的指针?当你像这样复制一个对象时,类内的任何指针也会被复制,并将指向新类中的相同值。
    猜你喜欢
    • 2013-12-07
    • 2014-12-04
    • 1970-01-01
    • 1970-01-01
    • 2017-11-07
    • 1970-01-01
    • 1970-01-01
    • 2012-08-04
    • 1970-01-01
    相关资源
    最近更新 更多