c++入门中,一道题展开的好多东西……权作记录
题目
程序分析:
`
#include <iostream>
#include <complex>
using namespace std;
class Base
{
public:
Base() {
cout<<"Base-ctor"<<endl;}
~Base() {
cout<<"Base-dtor"<<endl;}
virtual void f(int){
cout<<"Base::f(int)"<<endl;}
virtual void f(double){
cout<<"Base::f(double)"<<endl;}
virtual void g(int i=10){
cout<<"Base::g()"<<i<<endl;}
};
class Derived : public Base
{
public:
Derived() {
cout<<"Derived-ctor" <<endl;}
~Derived(){
cout<<"Derived-dtor"<<endl;}
void f(complex<double>) {
cout<<"Derived::f(complex)"<<endl;
}
void g(int i=20){
cout<<"Derived::g()"<<i<<endl;
}
};
int main()
{
cout<<sizeof(Base)<<endl;
cout<<sizeof(Derived)<<endl;
Base b;
Derived d;
cout<<endl;
Base *pb=new Derived;
cout<<endl;
b.f(1.0);
d.f(1.0);
pb->f(1.0);
cout<<endl;
b.g();
d.g();
pb->g();
cout<<endl;
delete pb;
return 0;
}
`
运行结果
分析
显然Base是基类,Derived是派生类,都定义了构造和析构函数以及各自成员函数,基类中使用的虚函数。
- sizeof()
-
看到主函数,sizeof(类)是什么?
sizeof 是一个关键字,它是一个编译时运算符,用于判断变量或数据类型的字节大小。 可用于获取类、结构、共用体和其他用户自定义数据类型的大小。 形如sizeof (data type)
所以,sizeof(类)是用来计算类的大小的。
-
再看运行结果,两个sizeof(type_name)都是8.为什么呢
成员函数(包括静态与非静态,虚函数也是成员函数)和静态数据成员都不占用类对象的存储空间 如有虚函数会生成虚函数表,并在该类型的每一个实例中添加指向虚函数表的指针(一个虚函数表(一到多个虚函数)只有一个指针) 32位机器中一个指针占4字节空间,64位机器中一个指针占8字节空间 普通继承的父类和子类共用一个虚函数表,但虚继承子类和父类各有自己的一个虚表指针
显然Base和Derived都只有一个虚表指针,因此均得到sizeof为8.
又根据以下博客第7点可知,
在普通继承下,不管是子类还是父类包含虚函数,子类的sizeof=sizeof(基类数据成员)+sizeof(派生类数据成员)+sizeof(指针)
https://blog.csdn.net/szchtx/article/details/10254007
-
其他
只要基类的成员函数是虚函数,派生类的同名函数(参数相同,返回值类型相同),函数体不同,不管有没有显式声明virtual都是虚函数。
-
其他
https://blog.csdn.net/e2788666/article/details/118070051
sizeof还涉及一个偏移量的问题,有兴趣可以从下面了解
https://baike.baidu.com/item/sizeof%28%29/10474327?fr=aladdin
-
-
继续看代码Base *pb=new Derived,由输出可知是先构造了基类,再构造的派生类。为什么呢,这和new关键字有关
new一个子类对象,其实还是调用子类的构造函数,调用子类的构造函数又还是先调用基类构造再调用派生类构造。 而调用派生类构造函数的顺序又是先调用基类再到派生类,所以如是输出。 new Derived等价于Derived x(指某实例名) 这个代码实质是new一个派生类对象赋给基类指针(基类指针指向派生类对象)
-
再看调用f()函数那一段代码,及其对应输出
这里涉及到同名函数的问题——重载、隐藏、覆盖
当基类Base::f()和派生类Derived::f()参数改为同类型(同为double或同为complex)时,构成同名函数覆盖,根据指针所指对象的类型来决定调用哪个函数体,这里pb指向派生类对象,所以调用派生类函数
同名不同参,构成隐藏,隐藏发生后,看指针类型,父类还是调用父类的函数,子类还是调用子类的函数
详见如下https://blog.csdn.net/qq_43435084/article/details/90115244
https://blog.csdn.net/yanguilaiwuwei/article/details/41347255
https://blog.csdn.net/choudan8888/article/details/98098204
https://blog.csdn.net/m0_62656812/article/details/121965526
-
到g()方法那段代码,及其对应输出。为什么pb调用的派生类的方法输出的形参i的值却是基类的呢?
//调用了派生类函数但输出基类里同名函数的形参默认值。
//预先设定的参数是静态设定的,而调用的函数型别则是动态确定。
//对于虚函数,编译系统将在编译时刻根据被调用函数的对象的类型决定默认形参的值。
//默认值不同,此时编译系统以先遇到的为准。
详见如下https://blog.csdn.net/qq_26501341/article/details/116264566
-
再看delete pb;这行代码及其输出
/用基类的指针去指向对象,在析构时就会出现问题,因为派生类对象在析构时是先释放派生类,再释放基类(正确的做法),
而用基类指针指向派生类对象,析构时是只析构基类(错误的做法),从而导致派生类的那一部分没有被释放掉,成为僵尸内存或称内存泄漏。
所以用基类指针指向派生类对象,要用virtual析构函数,使基类指针可以指向派生类中(virtual的作用:根据对象的实际类型,调用相应的类型函数)
,使得再手动释放内存delete 时,可以调用派生类的析构函数,从而自动调用基类的析构函数。
emmm,先到这,第一次写,有点麻爪