1,构造函数不能继承
这句开宗明义的话容易引起歧义,因为事实上基类的构造函数(一般为public)在派生类中是可见且可用的。如下例子所示:
class Base4
{
public:
Base4(int ii, int jj) :i(ii), j(jj) { cout << "调用基类的一般构造函数" << endl; }//基类的一般构造函数
Base4() :i(0), j(5203132) { cout << "调用基类的默认构造函数" << endl; }//基类的默认构造函数
void show_base() { cout << i << "\t" << j<<"\t"; }
public:
int i;
private:
int j;
};
class D4 :public Base4
{
public:
int k;
void show_d() { show_base(); cout << k << endl; }
};
int main()
{
//验证构造函数不能继承
Base4::Base4(1,2);
D4::Base4(1, 2); //这不就继承了基类的构造函数了么?
}
执行结果是:
![](https://i-blog.csdnimg.cn/blog_migrate/eeb1cfc2a2301c97969513466b81b1a0.png)
可见,D4继承了Base4的构造函数。
注:由于我们不能在对象上使用构造函数,所以使用了例子里的用法,生成了一个临时变量。
所以“构造函数不能继承” 这句话的含义是:派生类不能把继承得来的基类的构造函数当成是自己的构造函数,派生类需要有自己的构造函数,如果不定义则编译器自动创建合成默认构造函数。
事实上,由于构造函数总是与类重名,派生类与基类连名字都不一样,所以也没办法把基类的构造函数当成是自己的构造函数。
但是,在定义派生类的构造函数时却能调用(有的时候还是由编译器隐式调用)基类的构造函数,以初始化从基类继承来的那些成员。
2,合成的派生类默认构造函数
前面已经提到,如果派生类不自己定义任何构造函数,则编译器会合成一个默认构造函数——这和普通的类是一样的。这个合成的默认构造函数,除了要初始化派生类新增的成员,还要初始化派生了继承自基类的成员。
首先,初始化继承自基类的成员:编译器隐式调用基类的默认构造函数。试想,如果基类没有默认构造函数,不管是自己定义的还是合成的(定义了其他构造函数而又没有定义默认构造函数的情况),那么此时是不是就报错了?
然后,初始化派生类新增的成员:和普通类的合成默认构造函数一样,使用变量的初始化规则。类类型的成员调用默认构造函数来初始化,内置或复合类型(指针、数组)的成员的初始化依赖于对象的作用域(在什么地方定义这个类对象的):若处于局部作用域中,这些成员不被初始化,获得的值是随机的;若处于全局作用域中,则被初始化为0值。
例子:
class Base4
{
public:
Base4(int ii, int jj) :i(ii), j(jj) { cout << "调用基类的一般构造函数" << endl; }//基类的一般构造函数
//注释掉基类的默认构造函数
//Base4() :i(0), j(5203132) { cout << "调用基类的默认构造函数" << endl; }//基类的默认构造函数
void show_base() { cout << i << "\t" << j<<"\t"; }
public:
int i;
private:
int j;
};
class D4 :public Base4
{
public:
int k;
void show_d() { show_base(); cout << k << endl; }
};
int main()
{
//合成的派生类默认构造函数
D4 d;<pre name="code" class="cpp"> d.show_d()
}
编译报错:
error C2280: “D4::D4(void)”: 尝试引用已删除的函数
D4::D4就是编译器自己合成的派生类默认构造函数,在这里由于没办法调用基类的默认构造函数而出错。
D4::D4就是编译器自己合成的派生类默认构造函数,在这里由于没办法调用基类的默认构造函数而出错。
将基类的默认构造函数的注释去掉后编译通过,执行结果为:
![](https://i-blog.csdnimg.cn/blog_migrate/bde394c15f582acc78e628479519460e.png)
其中,由于派生类对象d是在局部作用域中,所以d.k未被初始化。
3,定义派生类的默认构造函数
由上面的例子可以看出,局部作用域中的对象的内置类型成员未被初始化,值是随机的。这显然不是我们想要的结果。我们应当认为给成员k赋默认值,这就需要定义自己的默认构造函数。
class Base4
{
public:
Base4(int ii, int jj) :i(ii), j(jj) { cout << "调用基类的一般构造函数" << endl; }//基类的一般构造函数
Base4() :i(0), j(5203132) { cout << "调用基类的默认构造函数" << endl; }//基类的默认构造函数
void show_base() { cout << i << "\t" << j<<"\t"; }
public:
int i;
private:
int j;
};
class D4 :public Base4
{
public:
D4():k(0) {}//派生类的默认构造函数
int k;
void show_d() { show_base(); cout << k << endl; }
};
int main()
{
D4 d;
d.show_d();
}
执行结果为:
![](https://i-blog.csdnimg.cn/blog_migrate/e25172e117de5693af9de5d5c569d1cf.png)
很明显,这里i、j、k都已经被初始化为0了。
派生类的默认构造函数只有这一句:D4():k(0) {}
这一句显式初始化了k,那么继承自基类的i和j是怎么被初始化的?从执行结果上看,我们看到有字符串“调用基类的默认构造函数”被打出来,可以推断出是编译器隐式调用了基类的默认构造函数——这一点与合成的派生类默认构造函数相同。同样,如果基类没有默认构造函数,这里就会报错。不再举例。
4,定义一般的派生类构造函数
一般的派生类构造函数,需要给对象的各个成员——包括派生类新增加的和继承自基类的,赋有效值。要给继承自基类的成员赋值,应当调用基类的构造函数。与以前不同的是,基类构造函数的调用方式。class Base4
{
public:
Base4(int ii, int jj) :i(ii), j(jj) { cout << "调用基类的一般构造函数" << endl; }//基类的一般构造函数
Base4() :i(0), j(5203132) { cout << "调用基类的默认构造函数" << endl; }//基类的默认构造函数
void show_base() { cout << i << "\t" << j<<"\t"; }
public:
int i;
private:
int j;
};
class D4 :public Base4
{
public:
D4():k(0) {}//派生类的默认构造函数
D4(int ii, int jj, int kk) :Base4(ii, jj), k(kk) { }//派生类的一般构造函数
int k;
void show_d() { show_base(); cout << k << endl; }
};
int main()
{
cout << "d1(520, 3132, 1314):" << endl;
D4 d1(520, 3132, 1314);
d1.show_d();
}
输出结果为:
![](https://i-blog.csdnimg.cn/blog_migrate/b53a5c0935f8e6c7e74020146546eb50.png)
这次不再调用基类的默认构造函数了。
注意派生类构造函数的定义方式:D4(int ii, int jj, int kk) :Base4(ii, jj), k(kk) { }
我们在派生类构造函数的初始化列表中使用了基类的构造函数,这是一种以前没有用过的方式。我们通过这种方式来初始化派生类中继承自基类的成员。
注意:现在的函数定义体为空,但事实上定义体内可以写任何语句,包括再次改变继承自基类的成员的值——只要访问权限允许。
试想1,如果我们不在初始化列表中调用基类的构造函数直接初始化基类的成员会怎样?
修改派生类为如下定义,其他不变,
class D4 :public Base4
{
public:
D4():k(0) {}//派生类的默认构造函数
D4(int ii, int jj, int kk) :Base4(ii, jj), k(kk) { }//派生类的一般构造函数
D4(int ii,int kk) :i(ii),k(kk) {}//error C2614: “D4”: 非法的成员初始化:“i”不是基或成员
int k;
void show_d() { show_base(); cout << k << endl; }
};
则编译出错:error C2614: “D4”: 非法的成员初始化:“i”不是基或成员。
虽然i是public成员,也依然不能用这种方法来初始化。一言以蔽之,要初始化基类的成员,必须调用基类的构造函数。
试想2,如果我们不在初始化列表中调用基类的构造函数,也不显示初始化基类的成员会怎样?
修改派生类定义及主函数部分
class D4 :public Base4
{
public:
D4():k(0) {}//派生类的默认构造函数
D4(int ii, int jj, int kk) :Base4(ii, jj), k(kk) { }//派生类的一般构造函数
D4(int kk) : k(kk) {}//派生类的一般构造函数,隐式调用基类的默认构造函数
int k;
void show_d() { show_base(); cout << k << endl; }
};
int main()
{
cout << "d1(1314):" << endl;
D4 d1(1314);
d1.show_d();
}
运行结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/b47c2d17403f82e46142a86e2d328f9d.png)
可见,当不在初始化列表中调用基类的构造函数时,编译器就隐式调用基类的默认构造函数。
试想3,如果我们不在初始化列表中调用基类的构造函数,而是在派生了构造函数的函数体中调用基类的构造函数会怎么样?
修改派生类定义及主函数部分
class D4 :public Base4
{
public:
D4():k(0) {}//派生类的默认构造函数
//D4(int ii, int jj, int kk) :Base4(ii, jj), k(kk) { }//派生类的一般构造函数
D4(int ii, int jj, int kk) :k(kk)
{
cout << "进入派生类构造函数的定义体" << endl;
Base4(ii, jj);
cout << "i=" << i <<"\tii="<<ii<< endl;
}
D4(int kk) : k(kk) {}//派生类的一般构造函数,隐式调用基类的默认构造函数
int k;
void show_d() { show_base(); cout << k << endl; }
};
int main()
{
cout << "d1(520, 3132, 1314):" << endl;
D4 d1(520, 3132, 1314);
d1.show_d();
}
![](https://i-blog.csdnimg.cn/blog_migrate/bbf93f9d62031fa0619c7c2a277453d9.png)
结果出人意料,一方面,编译并没有报错,另一方面函数隐式调用了基类的默认构造函数,计入派生类构造函数的定义体之后又调用了我们写的基类的一般构造函数。最离奇的是函数体里的那次调用并没有改变派生类里的成员的值!
请看,最终的执行结果,i=0,j=5203132,这是基类的默认构造函数的效果,而不是我们输入的i=520,j=3132!
我们把原因分析如下:
- 由于派生了构造函数的初始化列表中没有出现基类的任何构造函数,所以编译器直接隐式调用了基类的默认构造函数,至于接下来派生类构造函数的定义体中是否会调用基类的构造函数,不会影响这种隐式调用行为的发生;
- 隐式调用结束后,继续执行初始化列表,初始化派生类自己新增的成员k;
- 初始化列表执行结束后,进入派生类构造函数的定义体,按顺序执行语句;
- 执行到Base4(ii, jj);的时候,推测基类的构造函数并没有获得派生类的this指针,这个基类构造函数实际上是生成了一个基类的临时变量,所以无法改变派生类对象的任何成员的值
- 因此,在执行完Base4(ii, jj);之后this->i与局部变量ii的值并不相同。
5,派生类构造函数的执行顺序
前面已经说过了,构造函数的执行顺序是:- 执行初始化列表里的语句,调用基类的构造函数(显式或隐式),初始化继承自基类的成员;
- 执行初始化列表里的语句,初始化派生类新增的成员。这两条的先后顺序与初始化列表的书写顺序无关;
- 顺序执行函数定义体里的语句;
6,只能初始化直接基类
如果A派生出B,B再派生出C,则C的构造函数里只能使用B的构造函数,而不能直接使用A的构造函数。