为什么可以修改函数返回的const对象?

guy_1 发布于 2016/06/14 11:26
阅读 199
收藏 0

之前总是认为如果一个函数返回const对象,那么这个对象是不能修改的,但下面的代码的确让我费解

// Vector3.h
class Vector3
{
public:
    float x;
    float y;
    float z;

    // 构造函数
    .....

    const Vector3 operator+(const Vector3 &v)
    {
        return Vector3(x + v.x, y + v.y, z + v.z);
    }
}

// Source.cpp
Vector3 v0(1, 2, 3);
Vector3 v1(3, 4, 5);

Vector3 v3 = v0 + v1;
v3.x = 100;

return 0;

上面的 v3 应该是const 对象才是,不过程序中并没有报错,而且也可以修改里面的变量的值,不知道为什么。
加载中
0
石头左边
石头左边

int a = 1 + 2;

1 是常量(字面常量,可以称为“常量中的常量”),

2也是,

+ 操作符在语义上就是常量操作,返回值也是常量。

但……人家 a 是变量! 把一个常量赋值给一个变量,通常意义上就是将右值(可以是常量,也可能是变量)的值“复制”一份给左值(变量)。

============

举个例子,小明有女朋友,很漂亮,五官标致,但这个女朋友是常量,动不得。小明的同桌二狗子依据小明女友的模子,找塑胶厂定制了一个你懂的,然后二狗子当然当然当然可以动这个你懂的。


guy_1
guy_1
我忽然又有疑问了,函数声明中的返回类型不是对接受它的返回值的变量的类型的一种限制吗?既然这样为什么接受重载的+操作的返回值的变量的类型不能限制为 const Vector3 呢?
guy_1
guy_1
我明白了,这里改变的是复本的值。谢谢解惑!
0
石头左边
石头左边

引用来自“石头左边”的评论

int a = 1 + 2;

1 是常量(字面常量,可以称为“常量中的常量”),

2也是,

+ 操作符在语义上就是常量操作,返回值也是常量。

但……人家 a 是变量! 把一个常量赋值给一个变量,通常意义上就是将右值(可以是常量,也可能是变量)的值“复制”一份给左值(变量)。

============

举个例子,小明有女朋友,很漂亮,五官标致,但这个女朋友是常量,动不得。小明的同桌二狗子依据小明女友的模子,找塑胶厂定制了一个你懂的,然后二狗子当然当然当然可以动这个你懂的。


1. 函数返回值类型的声明,就是声明函数返回值的类型,函数可没有“义务”去限制什么类型的变量能 ”接受“ 这个返回值。甚至连有没有变量去接受返回值,函数都不管。事实上函数调用并返回结果,是一个过程,然后某个变量接受了它,是另一个过程。只不过这两段过程由编译器为代码自动产生,紧密衔接。比如:
      const bool foo(); //声明,特意搞成 const的,其实没意义(见后)
      ...
      //第一次调用:
      foo(); //你看,完全可以没人要返回值 。
      //第二次调用:
      int i = foo();
     
2. 对于第二次调用,其实编译后分成以下两个过程:
     过程一:
     const bool TMP_MEM (foo());
     foo() 的返回值,被放到某处栈内存中,这个内存当然也没有名字(变量名)。我们为表达方便,就叫 TMP_MEM吧。这个内存的类型,和函数声明的类型,当然完全一致,如果认为 const bool 和 bool是不同类型的话(其实const不是类型的一部分,见第4点)。
       接着,第二个过程:
       int i = TMP_MEM;
       看,这时候哪有函数什么事? 函数就是个狠心的妈,生下娃娃,早就走了(本次调用过程结束),娃被放在一个临时的栈内存中,如果没人要,娃就立马就消失在茫茫的内存中。
       i 是 int 类型,并且没有const 修饰。但语法上存在 从  bool 转换 为 int 的规则,所以编译器就很痛快地转换了。

3.  不过这里涉及到一个重要原则: 编译器通常只痛快一次(帮忙做类型转换)。如果从类型a到类型c需要转换两次或两次以上,编译器立马撂摊子不干,这是为了避免太多”自动“的转换,最终坑到程序员。

4.  const 其实不是类型系统的一部分。 const 只是声明:我是一个不让修改内容的变量。 而 = (赋值)操作符只是一个目的:我要复制你的内容。我又没说要复制你的const修饰。

5. 真正容易让你有疑问的,其实是发生在:有些时候,= (赋值)操作符,并不是_复_制_ 源对象的内容,而只是复制源对象的内存地址,这时const就起作用了。
         目标对象  = 源对象;  //=操作虽然总是在复制,但有时也可以复制“地址”。 比如: 目标对象是一个指针:
           const int   a = 60; // 源对象a是一个常量
           int * pa  = &a; //不行不行的。

          为什么不行? 因为 pa 是一个指针,并不复制a的内容,而是复制a的地址(&a),这相当于直接指向 a所在内存,古代程序员称之为:这是指向a的真实肉体(鬼魂附体是也)。但是你pa这头鬼居然没有const限定,就意味着 *pa 就可以修改原本是常量的a的值嘛。   正确做法应是:  

          const int* pa = &a; // const int* pa 表示pa所指向的对象是常量,完美!

           ……还是继续小明女朋友的例子吧:
          小明女朋友今年19, 长得漂亮,但人家在某些方面是常量,坚定地认为在在24岁之前某些方面不能被改变!!!

          隔壁二狗子也喜欢小明女朋友这种外貌类型的的女生(当然,他不喜欢const这个修饰)。怎么办?

         两种做法:一种是二狗努力学习,终于成为一名生物学家,克隆出小明女朋友的复制品。带回家,爱怎样就怎样——小明虽然内心别扭,竟也无话可说。

         如果当不了科学家,那只能搞个塑胶复制品——你以为这是第二种方法吗?当然不是。

         第二种方法是使用“指针”:二狗子当众宣布,我的女朋友(指针pa)和小明的女朋友是同一肉身(内存地址),但当这个女孩子被视为小明的女朋友时,她是常量,视为我二狗的女朋友时,她不是常量!!!所以我今天晚上,哈哈哈……很快二狗子被编译器扭送到公安局。依据C++语法第N条第M款,宣布程序编译失效。

5,少年,忘记什么女朋友的事(我估计你也没有)。复习一下吧:       
            const int a = 100; //满分美女
            const int b = a; //当然合法
            int  c = a;   //也合法,以a为模子,全身倒模成复制品c。爱怎样就怎样,比如:
            c = 250; //
           int * const pa = &a; //也可以,其实*pa和a是同一肉身,a 和 *pa都是常量(因为就是同一个人!)。
           int* pb = &a; //不合法。

6、最后不要被我误导,现实生活中,你硬要声明某人的女友也是你的女友,管你要不要常量,都是不道德的!



7、真正的最后, 安利一下我的《白话c++》。
0
guy_1
guy_1

引用来自“石头左边”的评论

引用来自“石头左边”的评论

int a = 1 + 2;

1 是常量(字面常量,可以称为“常量中的常量”),

2也是,

+ 操作符在语义上就是常量操作,返回值也是常量。

但……人家 a 是变量! 把一个常量赋值给一个变量,通常意义上就是将右值(可以是常量,也可能是变量)的值“复制”一份给左值(变量)。

============

举个例子,小明有女朋友,很漂亮,五官标致,但这个女朋友是常量,动不得。小明的同桌二狗子依据小明女友的模子,找塑胶厂定制了一个你懂的,然后二狗子当然当然当然可以动这个你懂的。


1. 函数返回值类型的声明,就是声明函数返回值的类型,函数可没有“义务”去限制什么类型的变量能 ”接受“ 这个返回值。甚至连有没有变量去接受返回值,函数都不管。事实上函数调用并返回结果,是一个过程,然后某个变量接受了它,是另一个过程。只不过这两段过程由编译器为代码自动产生,紧密衔接。比如:
      const bool foo(); //声明,特意搞成 const的,其实没意义(见后)
      ...
      //第一次调用:
      foo(); //你看,完全可以没人要返回值 。
      //第二次调用:
      int i = foo();
     
2. 对于第二次调用,其实编译后分成以下两个过程:
     过程一:
     const bool TMP_MEM (foo());
     foo() 的返回值,被放到某处栈内存中,这个内存当然也没有名字(变量名)。我们为表达方便,就叫 TMP_MEM吧。这个内存的类型,和函数声明的类型,当然完全一致,如果认为 const bool 和 bool是不同类型的话(其实const不是类型的一部分,见第4点)。
       接着,第二个过程:
       int i = TMP_MEM;
       看,这时候哪有函数什么事? 函数就是个狠心的妈,生下娃娃,早就走了(本次调用过程结束),娃被放在一个临时的栈内存中,如果没人要,娃就立马就消失在茫茫的内存中。
       i 是 int 类型,并且没有const 修饰。但语法上存在 从  bool 转换 为 int 的规则,所以编译器就很痛快地转换了。

3.  不过这里涉及到一个重要原则: 编译器通常只痛快一次(帮忙做类型转换)。如果从类型a到类型c需要转换两次或两次以上,编译器立马撂摊子不干,这是为了避免太多”自动“的转换,最终坑到程序员。

4.  const 其实不是类型系统的一部分。 const 只是声明:我是一个不让修改内容的变量。 而 = (赋值)操作符只是一个目的:我要复制你的内容。我又没说要复制你的const修饰。

5. 真正容易让你有疑问的,其实是发生在:有些时候,= (赋值)操作符,并不是_复_制_ 源对象的内容,而只是复制源对象的内存地址,这时const就起作用了。
         目标对象  = 源对象;  //=操作虽然总是在复制,但有时也可以复制“地址”。 比如: 目标对象是一个指针:
           const int   a = 60; // 源对象a是一个常量
           int * pa  = &a; //不行不行的。

          为什么不行? 因为 pa 是一个指针,并不复制a的内容,而是复制a的地址(&a),这相当于直接指向 a所在内存,古代程序员称之为:这是指向a的真实肉体(鬼魂附体是也)。但是你pa这头鬼居然没有const限定,就意味着 *pa 就可以修改原本是常量的a的值嘛。   正确做法应是:  

          const int* pa = &a; // const int* pa 表示pa所指向的对象是常量,完美!

           ……还是继续小明女朋友的例子吧:
          小明女朋友今年19, 长得漂亮,但人家在某些方面是常量,坚定地认为在在24岁之前某些方面不能被改变!!!

          隔壁二狗子也喜欢小明女朋友这种外貌类型的的女生(当然,他不喜欢const这个修饰)。怎么办?

         两种做法:一种是二狗努力学习,终于成为一名生物学家,克隆出小明女朋友的复制品。带回家,爱怎样就怎样——小明虽然内心别扭,竟也无话可说。

         如果当不了科学家,那只能搞个塑胶复制品——你以为这是第二种方法吗?当然不是。

         第二种方法是使用“指针”:二狗子当众宣布,我的女朋友(指针pa)和小明的女朋友是同一肉身(内存地址),但当这个女孩子被视为小明的女朋友时,她是常量,视为我二狗的女朋友时,她不是常量!!!所以我今天晚上,哈哈哈……很快二狗子被编译器扭送到公安局。依据C++语法第N条第M款,宣布程序编译失效。

5,少年,忘记什么女朋友的事(我估计你也没有)。复习一下吧:       
            const int a = 100; //满分美女
            const int b = a; //当然合法
            int  c = a;   //也合法,以a为模子,全身倒模成复制品c。爱怎样就怎样,比如:
            c = 250; //
           int * const pa = &a; //也可以,其实*pa和a是同一肉身,a 和 *pa都是常量(因为就是同一个人!)。
           int* pb = &a; //不合法。

6、最后不要被我误导,现实生活中,你硬要声明某人的女友也是你的女友,管你要不要常量,都是不道德的!



7、真正的最后, 安利一下我的《白话c++》。

我真正明白我理解错什么地方了,混淆了返回值的类型和被赋值的类型之间的关系,正如您说,一个变量的内容被赋值给另外一个变量(二者在内存中有不同的地址),这个操作根本不受返回值类型的约束。
您上面提到了函数如何返回值的问题,我反汇编了一下代码,发现是函数将要返回的值放在了堆栈后面预留的一块儿空间里,ebp指向这个空间的首地址,之后将值 mov 到了一些寄存器中。不过对于对象的返回我也有疑问,等我表达清楚了再提问吧。
最后更正您的一个错误,第5点里的 int *const pa = &a; 应该是 const int *pa = &a; 要不然就能用指针 pa 修改变量 a 的值了。
谢谢您的指导! 

@石头左边

0
guy_1
guy_1

@石头左边

哪里才可以买到《白话c++》呢?之前看过<C++ Primer Plus>,不过我想通过您的书了解到更多程序执行的细节,我一直觉得这对写好C++程序很重要。

石头左边
石头左边
北航出版社今年内会出。
返回顶部
顶部