c++对象切割(Object Slicing)

以你之姓@ 2021-06-24 15:57 659阅读 0赞

当把一个派生类对象赋给一个基类对象时,会发生对象切割。(另外用基类对象强制转换派生类对象也会)

对象切割会发生什么呢?

  1. #include "stdafx.h"
  2. #include <iostream>
  3. using namespace std;
  4. class CShape
  5. {
  6. public:
  7. CShape ()
  8. {
  9. m_color=0;
  10. }
  11. ~CShape(){}
  12. virtual void draw()
  13. {
  14. cout<<"This is a shape!"<<endl;
  15. cout<<m_color<<endl;
  16. }
  17. double m_color;
  18. };
  19. class CRect: public CShape
  20. {
  21. public:
  22. CRect()
  23. {
  24. m_width=5;
  25. m_height=4;
  26. m_color=1;
  27. }
  28. ~CRect(){};
  29. double size()
  30. {
  31. return m_width*m_height;
  32. }
  33. virtual void draw()
  34. {
  35. cout<<"This is a rect!"<<endl;
  36. cout<<m_color<<endl;
  37. }
  38. double m_width;
  39. double m_height;
  40. };
  41. int main(int argc, char* argv[])
  42. {
  43. CShape shp;
  44. CRect rect;
  45. shp = rect;
  46. shp.draw();//对象切割
  47. ((CShape)rect).draw();//对象间的向上强制转型,造成对象切割
  48. CShape *pShape=new CShape();
  49. *pShape=rect;//对象切割
  50. pShape->draw();
  51. pShape=▭//多态实现,
  52. pShape->draw();
  53. ((CShape*)(&rect))->draw(); //派生类指针强制转化为基类指针,不会造成对象切割
  54. system("pause");
  55. return 0;
  56. }

输出结果为:

  1. This is a shape!
  2. 1
  3. This is a shape!
  4. 1
  5. This is a shape!
  6. 1
  7. This is a rect!
  8. 1
  9. This is a rect!
  10. 1
  11. 请按任意键继续……

shp=rect;会调用 CShape的默认赋值函数,shp的CShape属性值与rect相同,但其虚函数表指针指向基类CShape虚函数表。

((CShape)rect).draw();会调用CShape默认的拷贝构造函数,生成一个中间变量,其虚函数表指针指向基类CShape虚函数表。

多态的实现是通过指针和引用;而对象的转换只会造成对象切割,不能实现多态。

注意下面两句的不同

*pShape=rect;//对象切割
pShape=▭//多态

附基类和派生类对象间赋值的问题:

class A
{
}
Class B:public A
{
}
A a_object;
B b_object;

(1)a_boject=b_object;

(2)b_object=a_boject;

有关以上两句的说明:

(1)a_boject=b_object;调用default A::operator =,由编译器自动生成,它的函数声明大致类似于:A operator = (A rhs),反正=号右边要求是一个A的对象,bobject作为A的子类对象亦是可行的,只不过传递过程中会产生“截断”。

(2)b_object=a_boject;调用default B::operator =,也由编译器自动生成,它的函数声明大致类似于:B operator = (B rhs),这里=号右边要求是一个B的对象,aobject这时就不可行了。(编译器将报错)

这时,单单重载B::operator =也无济于事,因为你无法改变operator =函数参数必须是B对象的这个事实。要实现b_object=a_boject;可以重载强制类型转换函数,也可以利用编译器隐式类型转换的能力,如:

  1. class A
  2. {
  3. public:
  4. A(){}
  5. };
  6. class B:public A
  7. {
  8. public:
  9. B(){}
  10. B(A a){}//必须有
  11. };
  12. A aobject;
  13. B bobject;
  14. int main()
  15. {
  16. a_object=b_object;
  17. b_object=a_object;
  18. return 0;
  19. }

小结:Class 对象作参数时,用reference to const替换pass by value可避免对象切割。

基类指针与派生类指针的相互转换

1,直接用基类指针引用基类对象
2,直接用派生类指针引用派生类对象
3,用基类指针引用一个派生类对象,由于派生类对象也是基类的对象,所以这种引用是安全的,
但是只能引用基类成员。若试图通过基类指针引用那些只在派生类中才有的成员,编译器会报告语法错误。(解决该问题的答案是虚函数和多态性)
4,用派生类指针引用基类的对象。这种引用方式会导致语法错误。派生类指针必须先强制转换为基类指针,这种方法是安全的。

而在侯捷的深入浅出MFC中第二章C++重要性质中:
1、如果你以一个”基类之指针”指向一个”派生类之对象”,那么经由该指针你只能调用该基类所定义的函数
2、如果你以一个“派生类之指针”指向一个“基类之对象”,你必须先做明显的转型操作(explicit cast),这种作法很危险。
3、如果基类和派生类都定义了“相同名称之函数”,那么通过对象指针调用成员函数时,到底调用了那个函数,必须视该指针的原始类型而定,而不是视指针实际所指的对象的类型而定,这与第1点其实意义相通。

  1. #include <iostream>
  2. #include <stdlib.h>
  3. using namespace std;
  4. class A
  5. {
  6. public:
  7. char str[20];
  8. void f(){cout<<"class A"<<endl;}
  9. void fff(){cout<<"class A's str "<<str<<endl;}
  10. void add(){cout<<"class A address: "<<this<<endl;}
  11. };
  12. class B:public A
  13. {
  14. public:
  15. int i;
  16. char sb[20];
  17. B(){cout<<"class B constructor is run."<<endl;}
  18. ~B(){cout<<"class B destructor is run."<<endl;}
  19. void f(){cout<<"class B"<<endl;}
  20. void ff(){cout<<"class B "<<i<<str<<sb<<endl;}
  21. void add(){cout<<"class B address: "<<this<<endl;}
  22. };
  23. int main(int argc, char *argv[])
  24. {
  25. //声明父类对象
  26. A b;
  27. //声明父类对象的指针,指向父类对象
  28. A *pa=&b;
  29. pa->add();
  30. //这个写法正确,不过很危险,因为pa到底是指向B还是A是不确定的.如果能明确的知道
  31. //pa是指向B类对象的,就如现在写的这个,那么没问题,如果pa指向A类对象,就会存在
  32. //风险.改变指针的类型,并不会影响内存分配既不会调用构造函数
  33. B *pb=(B *)pa;
  34. //类型强制转化后,指向的地址相同. 但会按转化类型访问数据成员.
  35. //成员函数属于类而不属于对象.各对象共用类的成员函数.强制转换成 B 类型后,可
  36. //调用类的成员函数。编译pb->add();后的代码只是调用add函数的时候传入了pb的对
  37. //象指针this
  38. pb->add();
  39. //由于pa指向的是父类对象的地址,这个指针被强制转换为派生类指针后,会出现内存越
  40. //界访问的情况,是不安全的.
  41. pb->i=100;
  42. char dsd[100];
  43. strcpy(pb->sb, " class B's sb.");
  44. strcpy(pb->str, " class A's str.");
  45. //pb->f()调用的具体函数,视指针的原始类型而定,而不是视指针实际所指的对象的
  46. //类型而定.如果是虚函数,则视实际所指的对象的类型而定
  47. pb->f();
  48. pb->ff();
  49. pb->fff();
  50. system("PAUSE");
  51. return 0;
  52. }

基类代表着最少的确定的信息,而派生类则代表着较多的确定的信息。

  一个基类的指针可以指向一个派生类的对象,意味着一个指针可以指向一个容纳了多于自己期望的信息量的对象。那么当这样的一个操作发生后,这个指针就可以做出一些额外的操作,这些操作是它在被定义的时候并没有期望能够做到的,或者说并没有想到的。

  与此相对的是,一个派生类的指针则不能指向一个基类的对象。因为如果可以这样做的话,那么这个派生类的指针在被定义的时候所期望能做的所有事情中将有一部分不再能够做到,因为在它指向的那个基类的对象中根本就没有提供那些在派生类派生的时候新增加的方法。但是C++则保证一个指针定义之后,它至少要能够做和它被期望做的一样多的事情。那么,派生类指针指向基类对象的意图当然不能被允许。

附一段比较有趣的代码:

  1. // virtual Function.cpp : 定义控制台应用程序的入口点。
  2. #include "stdafx.h"
  3. #include <iostream>
  4. using namespace std;
  5. class IHello
  6. {
  7. public:
  8. virtual void hello1() = 0;
  9. virtual void hello2() = 0;
  10. };
  11. class IWorld
  12. {
  13. public:
  14. virtual void world1() = 0;
  15. virtual void world2() = 0;
  16. };
  17. class HelloWorld: public IHello,public IWorld
  18. {
  19. public:
  20. void hello1();
  21. void hello2();
  22. void world1();
  23. void world2();
  24. };
  25. void HelloWorld::hello1()
  26. {
  27. cout<<"hello1!"<<endl;
  28. }
  29. void HelloWorld::hello2()
  30. {
  31. cout<<"hello2!"<<endl;
  32. }
  33. void HelloWorld::world1()
  34. {
  35. cout<<"world1!"<<endl;
  36. }
  37. void HelloWorld::world2()
  38. {
  39. cout<<"world2!"<<endl;
  40. }
  41. int _tmain(int argc, _TCHAR* argv[])
  42. {
  43. IHello* pIHello = new HelloWorld;
  44. IWorld* pIWorld = (IWorld*)(void*)pIHello;
  45. cout<<"********************First test********************"<<endl;
  46. pIWorld->world1();
  47. pIWorld->world2();
  48. cout<<"********************Second test********************"<<endl;
  49. pIWorld = new HelloWorld;
  50. pIWorld->world1();
  51. pIWorld->world2();
  52. system("pause");
  53. return 0;
  54. }

发表评论

表情:
评论列表 (有 0 条评论,659人围观)

还没有评论,来说两句吧...

相关阅读

    相关 object-c对象的创建

    和C++不同的是,object-c中并没有规定说一定要实现一个构造函数和析构函数;所以,在object-c中,其实没有构造函数和析构函数这样的概念,取而代之的应该是对象的创造和

    相关 Objective-C实现对象复用

    思想 用两个`NSDictionary`分别记录对象的标识符与其类对象的关联和标识符与可用对象队列的关联。这样就可以使用标识符来从对象复用池里获取对象以及回收对象了。

    相关 对象切割与临时对象

    1、判断是copy构造还是copy赋值,不是看是否有赋值操作符=,而是看对象当前是否已经有值了。如果没有值,就是copy构造。如果已经有值了,就是copy赋值。 2、对象赋值