C++构造函数 & 拷贝构造函数 & 派生类的构造函数 & 虚继承的构造函数

构造函数 ,是一种特殊的方法 。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。(摘自百度百科构造函数)。

一、最基本的构造函数

1 class Base
2 {
3 public:
4     Base(int var) : m_Var(var)
5     {
6     }
7 private:
8     int m_Var;
9 };

以上构造函数的执行过程:

1)传参   2)给类数据成员开辟空间     3)执行冒号语法给数据成员初始化    4)执行构造函数括号里面的内容

这里需要说明的是:冒号语法后面的内容相当于int a = 10;(初始化),而构造函数括号里面则是相当于是int a; a = 10;(赋初值)

二、拷贝构造函数

 1 class Base
 2 {
 3 public:
 4     Base(int var) : m_Var(var)
 5     {
 6     }
 7     //拷贝构造函数
 8     Base(Base &ref) : m_Var(ref.m_Var)
 9     {
10     }
11 private:
12     int m_Var;
13 };

为什么拷贝构造函数的参数只能用引用呢?

这就要从拷贝构造函数式数码时候触发开始说起了,以下几种情况都会自动调用拷贝构造函数:

1)用一个已有的对象初始化一个新对象的时候

2)将一个对象以值传递的方式传给形参的时候

3)函数返回一个对象的时候

所以当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。(摘自百度百科拷贝构造函数)。

拷贝构造函数,一般不需要自己编写,系统默认的拷贝构造函数就能抗住了,但是有些情况需要在构造的时候开辟空间,这时候就需要拷贝构造函数了,如下代码是摘自林锐博士的高质量C++编程指南一文。

 1 class String
 2 {
 3 public:
 4     String(const char *str = NULL); // 普通构造函数
 5     String(const String &other);    // 拷贝构造函数
 6     ~ String(void);                 // 析构函数
 7 private:
 8     char *m_data; // 用于保存字符串
 9 };
10 // String 的析构函数
11 String::~String(void)
12 {
13     delete [] m_data;
14     // 由于m_data 是内部数据类型,也可以写成 delete m_data;
15 }
16
17 // String 的普通构造函数
18 String::String(const char *str)
19 {
20     if(str==NULL)
21     {
22         m_data = new char[1]; // 若能加 NULL 判断则更好
23         *m_data = ‘\0‘;
24     }
25     else
26     {
27         int length = strlen(str);
28         m_data = new char[length+1]; // 若能加 NULL 判断则更好
29         strcpy(m_data, str);
30     }
31 }
32 // 拷贝构造函数
33 String::String(const String &other)
34 {
35     int length = strlen(other.m_data);
36     m_data = new char[length+1]; // 若能加 NULL 判断则更好
37     strcpy(m_data, other.m_data);
38 }

 三、普通派生类构造函数的写法

定义派生类对象的时候,会按如下步骤执行构造操作:

1)传参     2)根据继承时的声明顺序构造基类    3)给类数据成员开辟空间    4)执行冒号语法后面的语句    5)执行构造函数函数体语句

 1 class Base
 2 {
 3 public:
 4     Base(int b) : m_b(b)
 5     {
 6     }
 7 private:
 8     int m_b;
 9 };
10
11 class Derived : public Base
12 {
13 public:
14     //普通派生类构造函数的写法
15     Derived(int b, int d) : Base(b), m_d(d)
16     {
17     }
18 private:
19     int m_d;
20 };

再写一个多继承的示例:

 1 class Base1
 2 {
 3 public:
 4     Base1(int b1) : m_b1(b1)
 5     {
 6     }
 7 private:
 8     int m_b1;
 9 };
10
11 class Base2
12 {
13 public:
14     Base2(int b2) : m_b2(b2)
15     {
16     }
17 private:
18     int m_b2;
19 };
20
21 class Derived : public Base1, public Base2
22 {
23 public:
24     Derived(int b1, int b2, int d) : Base1(b1), Base2(b2), m_d(d)
25     { //注意冒号语法后面的顺序无所谓,创造基类是按照上面的继承声明顺序来进行的...
26     }
27 private:
28     int m_d;
29 };

四、含有虚继承的派生类构造函数的写法

为何要用到虚继承?

虚继承主要是针对多继承时,出现二义性问题而提出的。比如,如下代码就需要用到虚继承,否则的话Derived类继承时,Base类就会不明确。

虚继承构造函数的执行按照如下步骤:

1)传参 2)创建基类,注意这时候需要显示创建所有“有参构造函数”的基类,包括直接基类,间接基类。 3)给类数据成员开辟空间  4)执行冒号语法  5)执行构造函数函数体

注:你可能会疑惑,如下代码不是将Base间接基类创建了3次吗?其实不是这样的,编译器是这样处理的,当最远的派生类Derived创建了基类Base之后,其直接基类创建Base类的语句将会被忽略掉。

 1 class Base
 2 {
 3 public:
 4     Base(int b) : m_b(b)
 5     {
 6     }
 7 private:
 8     int m_b;
 9 };
10
11 class Base1 : virtual public Base
12 {
13 public:
14     Base1(int b, int b1) : Base(b), m_b1(b1)
15     {
16     }
17 private:
18     int m_b1;
19 };
20
21 class Base2 : virtual public Base
22 {
23 public:
24     Base2(int b, int b2) : Base(b), m_b2(b2)
25     {
26     }
27 private:
28     int m_b2;
29 };
30 //虚继承,避免二义性
31 class Derived : public Base1, public Base2
32 {
33 public:
34     Derived(int b, int b1, int b2, int d) : Base(b), Base1(b, b1), Base2(b, b2), m_d(d)
35     { //注意冒号语法后面的顺序无所谓,创造基类是按照上面的继承声明顺序来进行的...
36     }
37 private:
38     int m_d;
39 };

五、关于虚析构

虚析构一般伴随着多态而产生,多态主要方式就是用基类的指针或引用指向或引用派生类,而形成多态。

但是这样就会存在一个问题,当我们析构的时候,由于是基类的指针,就会调用的是基类的构造函数,从而造成派生内存溢出。为了解决这个问题,引入了虚析构的概念。将基类的构造函数声明为虚,从而使其在调用析构函数的时候能够准确的调用派生类的析构函数。

如下代码必须用到虚析构才能准确的析构派生类,并释放其占有内存。

 1 class Base
 2 {
 3 public:
 4     Base(int b) : m_b(b)
 5     {
 6     }
 7     //虚析构,使基类指针能准确的释放所指向的派生类里面的内容
 8     virtual ~Base()
 9     {
10     }
11 private:
12     int m_b;
13 };
14
15 class Derived : public Base
16 {
17 public:
18     Derived(int b, char *pStr) : Base(b)
19     {
20         m_pStr = new char[strlen(pStr)+1];
21         strcpy(m_pStr,pStr);
22     }
23     ~Derived()
24     {
25         delete m_pStr;
26         m_pStr = NULL;
27     }
28 private:
29     char *m_pStr;
30 };
31
32 int main(void)
33 {
34     char *pStr = "abcdefg";
35     Base *b = new Derived(1,pStr);
36     delete b;
37
38     return 0;
39 }

原文地址:http://www.cnblogs.com/nchar/p/3911427.html

C++构造函数 & 拷贝构造函数 & 派生类的构造函数 & 虚继承的构造函数,布布扣,bubuko.com

时间: 2024-10-11 12:30:36

C++构造函数 & 拷贝构造函数 & 派生类的构造函数 & 虚继承的构造函数的相关文章

多重继承,虚继承,MI继承中虚继承中构造函数的调用情况

先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: [cpp] view plain copy print? //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; } ~base()

记录:C++类内存分布(虚继承与虚函数)

工具:VS2013 先说一下VS环境下查看类内存分布的方法: 先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局. 编译后,输出如图的内存布局: 测试代码 #include <iostream> using namespace std; typedef void(*FUN)(); cl

C++:派生类的构造函数和析构函数的调用顺序

一.派生类 在C++编程中,我们在编写一个基类的派生类时,大致可以分为四步: ? 吸收基类的成员:不论是数据成员还是函数成员,派生类吸收除基类的构造函数和析构函数之外的全部成员. ? 改造基类函数:在派生类中声明一个或多个与其(某个)基类中的成员函数同名的成员函数,并将它(们)根据新的需求进行重写 ? 发展新的成员:在派生类中添加新的成员变量和成员函数,其中新添加的成员要求必须和基类中的成员不同名,并且应当保证新添加的成员会使派生类在功能上相比其基类有所发展 ? 重写派生类的构造函数和析构函数

继承的构造函数

在C++11新标准中,派生类能够重用其直接基类的构造函数 #include <iostream> using namespace std; struct Base { Base() { cout << "construct" << endl; } }; struct Derived : public Base { using Base::Base; //继承Base的构造函数 }; int main() { Derived d; return 0;

C++:派生类的默认构造函数和拷贝构造函数调用基类构造函数的机制(含程序验证)

1.如果基类定义了不带参数的默认构造函数,则编译器为派生类自动生成的默认构造函数会调用基类的默认构造函数. 2.如果基类定义了拷贝构造函数,则编译器为派生类自动生成的拷贝构造函数同样会调用基类的拷贝构造函数. 3.如果基类定义了带参数的构造函数,派生类没有定义任何带参数的构造函数,则不能直接调用基类的带参构造函数,程序编译不通过. 代码如下: #include<iostream> using namespace std; //基类Game,定义了两个构造函数和一个拷贝构造函数 class Ga

派生类的构造函数和析构函数和多继承中的二义性与虚函数

析构函数主要作用是对数据成员初始化. 1派生类的构造函数 派生类的构造函数定义的一般形式为: 派生类名::派生类名(基类所需形参,本类成员所需形参):基类1(基类1 参数表),基类2(基类2 参数表),···,基类n(基类n 参数表),对象成员1(对象1 参数表),对象成员2(对象2 参数表),···,对象成员m(对象m 参数表){ //本类基本类型数据成员初始化 } 如果使用基类无参构造函数,派生类构造函数形参表中不包含供给基类构造函数的参数.此时,系统会调用基类的默认构造函数.如果使用对象数

C++ Primer 学习笔记_31_面向对象编程(2)--继承(二):继承与构造函数、派生类到基类的转换 、基类到派生类的转换

C++ Primer 学习笔记_31_面向对象编程(2)--继承(二):继承与构造函数.派生类到基类的转换 .基类到派生类的转换 一.不能自动继承的成员函数 构造函数 拷贝构造函数 析构函数 =运算符 二.继承与构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数. 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化调用基类构造函数完成(如果没有给出则默认调用默认构造函数). 派生类的构造函数需要给基类的构造函数传递参数 #include <iostream

c++学习笔记5,多重继承中派生类的构造函数与析构函数的调用顺序(二)

现在来测试一下在多重继承,虚继承,MI继承中虚继承中构造函数的调用情况. 先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; }

C++:派生类的构造函数和析构函数

4.2 派生类的构造函数和析构函数4.2.1 派生类构造函数和析构函数的执行顺序 通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数:当撤销派生类对象时,则先执行派生类的派生类的析构函数,随后再执行基类的析构函数. //例4.5 派生类的构造函数和析构函的执行顺序 #include<iostream> using namespace std; class Base{ //声明基类Base public: Base() { cout<<"Co