信息发布软件,b2b软件,广告发布软件

标题: 可能是最难的东东C++建构函式与解构函式会的人都学的头这眼花 [打印本页]

作者: 群发软件    时间: 2017-6-15 22:29
标题: 可能是最难的东东C++建构函式与解构函式会的人都学的头这眼花
本帖最后由 群发软件 于 2017-6-15 22:32 编辑

可能是最难的东东C++建构函式与解构函式会的人都学的头这眼花

建構函式會依此順序執行其工作:
下列範例顯示在衍生類別的建構函式中呼叫基底類別和成員建構函式的順序。 首先會呼叫基底建構函式,然後依其出現在類別宣告中的順序初始化基底類別成員,最後會呼叫衍生的建構函式。
C++



#include <iostream>  using namespace std;    class Contained1 {  public:      Contained1() {          cout << "Contained1 constructor." << endl;      }  };    class Contained2 {  public:      Contained2() {          cout << "Contained2 constructor." << endl;      }  };    class Contained3 {  public:      Contained3() {          cout << "Contained3 constructor." << endl;      }  };    class BaseContainer {  public:      BaseContainer() {          cout << "BaseContainer constructor." << endl;      }  private:      Contained1 c1;      Contained2 c2;  };    class DerivedContainer : public BaseContainer {  public:      DerivedContainer() : BaseContainer() {          cout << "DerivedContainer constructor." << endl;      }  private:      Contained3 c3;  };    int main() {      DerivedContainer dc;      int x = 3;  }   



輸出如下:



Contained1 constructor.  Contained2 constructor.  BaseContainer constructor.  Contained3 constructor.  DerivedContainer constructor.  



如果建構函式擲回例外狀況,解構順序是建構順序的相反:
成員清單




使用成員初始設定式清單,以從建構函式引數初始化類別成員。 此方法使用「直接初始化」(Direct Initialization),這比在建構函式主體內使用指派運算子更具效率。
C++



class Box {  public:      Box(int width, int length, int height)           : m_width(width), m_length(length), m_height(height) // member init list      {}      int Volume() {return m_width * m_length * m_height; }  private:      int m_width;      int m_length;      int m_height;    };   



建立 Box 物件:



Box b(42, 21, 12);  cout << "The volume is " << b.Volume();  



明確建構函式




如果類別的建構函式具有單一參數,或者,所有參數 (但其中一個除外) 都有預設值,則參數類型可以隱含地轉換為類別類型。 例如,如果 Box 類別具有建構函式,如下:



Box(int size): m_width(size), m_length(size), m_height(size){}  



Box 可能初始化如下:



Box b = 42;  



或將 int 傳遞給採用 Box 的函式:



class ShippingOrder  {  public:      ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage){}    private:      Box m_box;      double m_postage;  }  //elsewhere...      ShippingOrder so(42, 10.8);   



在某些情況下,這類轉換十分有用,但它們可能更常導致您程式碼中的細微但嚴重的錯誤。 一般規則是您應該在建構函式 (和使用者定義運算子) 上使用explicit,以防止這種隱含類型轉換:



  explicit Box(int size): m_width(size), m_length(size), m_height(size){}  



建構函式是明確建構函式時,此行會造成編譯器錯誤:ShippingOrder so(42, 10.8);。 如需詳細資訊,請參閱轉換。
預設建構函式




「預設建構函式」(Default Constructor) 沒有參數;它們遵循稍微不同的規則:
預設建構函式是其中一個「特殊成員函式」(Special Member Function);如果在類別中未宣告建構函式,編譯器會提供預設建構函式:
C++



class Box {      Box(int width, int length, int height)           : m_width(width), m_length(length), m_height(height){}  };    int main(){        Box box1{}; // call compiler-generated default ctor      Box box2;   // call compiler-generated default ctor  }  



當您呼叫預設建構函式和嘗試使用括號時,會發出警告:
C++



class myclass{};  int main(){  myclass mc();     // warning C4930: prototyped function not called (was a variable definition intended?)  }  



這是「最令人惱怒的語法解析」(Most Vexing Parse) 問題範例。 由於範例運算式可解譯為函式的宣告或做為預設建構函式的引動過程,而且由於 C++ 剖析器偏好宣告更勝於其他項目,因此運算式被視為函式宣告。 如需詳細資訊,請參閱最繁瑣的語法解析 (英文)。
如果已宣告任何非預設建構函式,編譯器不會提供預設建構函式:
C++



class Box {      Box(int width, int length, int height)           : m_width(width), m_length(length), m_height(height){}  };  private:      int m_width;      int m_length;      int m_height;    };    int main(){        Box box1(1, 2, 3);      Box box2{ 2, 3, 4 };      Box box4;     // compiler error C2512: no appropriate default constructor available  }   



如果類別沒有預設建構函式,該類別的物件陣列無法透過單獨使用方括號語法來建構。 例如,根據上述程式碼區塊,Boxes 陣列無法宣告如下:
C++



Box boxes[3];    // compiler error C2512: no appropriate default constructor available   



不過,您可以使用初始設定式清單集合來初始化 Boxes 陣列:
C++



Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };  



複製和移動建構函式




「複製建構函式」(Copy Constructor) 是一種特殊成員函式,採用相同類型之物件的參考做為輸入,並建立其複本。 如需詳細資訊,請參閱複製建構函式和複製指派運算子 (C++)。 移動也是特殊成員函式建構函式,會將現有物件的擁有權移至新的變數,而不複製原始資料。 如需詳細資訊,請參閱移動建構函式和移動指派運算子 (C++) 和移動建構函式和移動指派運算子 (C++)。

 若您继承了某个类别之後,当您在生成衍生类别的物件时若不指定参数,无参数的预设建构子会被执行,而基础类别的无参数预设建构子也会被执行,所以基於这种特性,通常预设建构子中会撰写一些通用的成员状态初始,例如设定一些预设值。

  如果继承之後,您要使用衍生类别生成物件,在生成物件时指定参数,并同时执行基底类别中的某个参数建构子,您可以使用 : 运算子

  例如:

// Point2D类别

  class Point2D {

  public:

  Point2D() {

  _x = 0;

  _y = 0;

  }

  Point2D(int x, int y) : _x(x), _y(y) {

  }

  private:

  int _x;

  int _y;

  };

  // Point3D类别

  class Point3D : public Point2D { // 继承Point2D类别

  public:

  Point3D() {

  _z = 0;

  }

  // 建构函式,同时指定呼叫父类别建构函式

  Point3D(int x, int y, int z) : Point2D(x, y), _z(z) {

  }

  private:

  int _z; // 新增私用资料

  };

  如果您使用衍生类别生成物件,则建构函式的执行顺序会从基底类别的建构函式开始执行起,这是可以理解的,因为基底类别是衍生类别的基础,一些基础的参数或初始状态必须先完成,再来再完成衍生类别中的建构函式。

  而在物件被消灭时,解构函式的执行顺序则正好相反,是从衍生类别的解构函式开始执行,再来才是基础类别的建构函式,因为若基底类别的解构函式如果先执行,则衍生类别相依於基底类别的一些状态也会被解构(例如指标),则此时再行衍生类别的解构函式,将存在着相依问题而造成错误。


  下面这个简单的程式可以告诉您建构函式与解构函式,在继承之後的执行顺序:

  #include <iostream> using namespace std; class Foo1 { public:

  Foo1() {

  cout << "Foo1建构函式" << endl;

  }

  ~Foo1()

  {

  cout << "Foo1解构函式" << endl;

  }

  };

  class Foo2 : public Foo1 { public:

  Foo2() {

  cout << "Foo2建构函式" << endl;

  }

  ~Foo2() {

  cout << "Foo2解构函式" << endl;

  }

  };

  int main()

  {

  Foo2 f;

  cout << endl;

  return 0;

  }

  执行结果:

  Foo1建构函式

  Foo2建构函式

  Foo2解构函式

  Foo1解构函式


1 虚拟函式表

       如果能够了解C++编译器对于虚拟函式的实现方式,我们就能够知道为什么虚拟函式可以做到动态连结。为了达到动态连结的目的,C++编译器透过某个表格,在执行时期间接呼叫实际上连结的函式。这样的表格称为虚拟函式表(vtable)。每一个内含虚拟函式的类别,编译器都会为它做出一个虚拟函式表,表中的每一个元素都指向一个虚拟函式的位址。此外,编译器当然也会为类别加上一向成员函数,是一个指向该虚拟函式表的指標(vptr)。


2 例子说明Class1基类[cpp] view plain copy



则Class1物件实体在记忆中占据这样的空间:

可能是最难的东东C++建构函式与解构函式会的人都学的头这眼花 b2b软件

图1.CMyClass1物件实体占据空间图

由图1.可知Class1物件实体内的数据分两个地方存储,第一个是存储vptr + m_data1 + m_data2成员变量之地;第二个数储存虚拟函数Class1::vfunc1、Class1::vfunc2、Class1::vfunc3和成员函数Class1::memfunc()之地,当然就单成员函数来说,衍生类中的成员函数跟基类所的成员函数都是一个地址的,衍生类使用父类的成员函数都是采用调用形式(this指標)。对于成员变量,在内存中会增加一个vptr变量,作用是指向虚拟函式表(vtable)的首地址,同时也是整个类物件实体的首地址。而虚拟函式表(vtable)中存的是指向每个虚拟函式的函数指针,指向存在其它地方的虚拟函式,虚拟函式跟成员函数存储在一起。


3 衍生类衍生类Class2

每一个由基类衍生出来的物件,都有这么一个vptr。当我们透过这个物件呼叫虚拟函式,事实上是透过vptr找到虚拟函式表,再找出虚拟函式的真正地址。奥妙自傲与这个虚拟函式表以及这种间接呼叫方式。虚拟函式表的内容是依据类别中的虚拟函式宣告次序,一一填入函数指標。衍生类别会继承基础类别的虚拟函式表(以及其他可以继承的成员),当我们在衍生类别中改写虚拟函式时,虚拟函式表就收到了影响,表中元素所指的函式位址将不再是基础类别中函数位址,而是衍生类别中中虚拟函式的位址。

[cpp] view plain copy



则CMyClass2物件实体在记忆中占据这样的空间:

可能是最难的东东C++建构函式与解构函式会的人都学的头这眼花 b2b软件

图2.衍生类CMyClass2物件实体记忆空间分配

由图2可知,在成员变量存储区多了m_data3变量,在虚拟函式表中,(*vfunc2)所指向的虚拟函式为Class2,成员函数menmfunc()也属于Class2物体实体的了。这跟指標只跟定义指標的类相同一致。


衍生类Class3[cpp] view plain copy



则Class3物件实体的记忆空间会区分Class1::m_data1和m_data1(Class3::m_data1),所以在数据变量存储区域存储的数据依次为:vptr, Class1::m_data1,m_data2, m_data3, m_data1, m_data4,虚拟函式表中所指向的虚拟函式跟Class2中一样都是指向Class3的虚拟函式,值得一提的是vfunc()实质是指Class1::vfunc1()而不是Class2::vfunc1()。成员函数是Class3型的。



作者: jzgsjt    时间: 2017-6-16 11:24
的很好,一看就很专业,技术也会耐心的解答问题~
作者: q1598188    时间: 2017-6-22 17:22
力很强大,必须好评!!!!
作者: 小白    时间: 2017-6-22 22:03
不错哦,功能很强大,解决了我不少疑惑,只有想不到的,没有他们做不到的,以后有朋友要开发微信公众平台,我会介绍过来的。
作者: meng00123    时间: 2017-6-22 22:08
很快,东西很多,不错哦
作者: yerface    时间: 2017-6-23 04:59
及时做出评价,系统默认好评!
作者: iiiiik    时间: 2017-7-1 20:49
售后服务很好,服务响应时间快,问题解决到位。
作者: java12005    时间: 2017-7-2 03:08
和设计直接沟通外其他都还可以,图片做的质量还不错,就是服务的客服月亮下午2点才上班,有时候还不在线!服务态
作者: loverun    时间: 2017-7-6 23:35
很好,很有耐心,修改了几次,都很配合
作者: yiyi2014    时间: 2017-7-9 13:17
蛋,一点都不笨!哈哈哈
作者: q117971371    时间: 2017-7-12 02:16
很多,非常明显。售前售后的小姑娘也很有责任心,态度温和有亲和力和柔和力给我留下了不可磨灭的印象
作者: meilifc001    时间: 2017-7-12 23:11
然中间有些波折,对设计结果还是比较满意的。好评。
作者: q1598188    时间: 2017-7-13 03:49
网络工程师专业到位,服务热情。




欢迎光临 信息发布软件,b2b软件,广告发布软件 (http://postbbs.com/) Powered by Discuz! X3.2