构造函数和考构,重载的赋值运算符是不会派生的,派生类需要定义自己的版本。基类如果有给派生类中调用准备的特定构造函数,可以将这些构造函数声明为保护类型的。
派生类如果没有定义自己的构造函数,编译器会为其提供默认的,在其中会调用基类的默认构造函数来初始化派生类中基类的那一部分。
派生类对象初始化顺序为先调用基类的构造函数初始化基类那一部分,再初始化属于其自己的那一部分。
派生类的构造函数不能初始化其中的基类部分,应该调用基类的构造函数来为那一部分进行初始化。
class base{
public:
int pub_b;
protected:
int pro_b;
};
class derived:protected base{
public:
derived(int a,int b,int c):pub_b(a),pub_d(b),pro_b(c){}
int pub_d;
};
|
程序编译报错
class `derived' does not have any field named `pub_b'
class `derived' does not have any field named `pro_b'
class base{
public:
base(int a,int b):pub_b(a),pro_b(b){};
int pub_b;
protected:
int pro_b;
};
class derived:protected base{
public:
derived(int a,int b,int c):base(a,b),pub_d(c){}
int pub_d;
}; |
这样初始化即可。
但是将派生类的构造函数改为不用冒号语法,在构造函数体内初始化成员可以通过
class derived:public base{
public:
derived(int a,int b,int c){pub_b=a;pro_b=b;pub_d=c;}
int pub_d;
}; |
派生类只可初始化其直接基类的成员(调用该基类构造函数),间接基类的不可,须由直接基类调用间接基类的构造函数。
为了保持数据抽象与封装的思想,我们
应当使用冒号语法后调用基类的构造函数的方法初始化基类的那一部分,而不应该在构造函数体内用赋值语句初始化基类的那一部分。
派生类只能显式调用其最直接基类的构造函数。拷贝构造函数,派生类是否需要自己定义考构完全出于自身的考虑,完全可以基类定义了自己的考构,而派生类中使用默认的考构,反之亦可以。但是当类成员中含有指针变量时,需要定义自己的考构。
当基类与派生类都使用默认考构时,调用派生类的考构,函数执行时会先调用基类的考构复制基类的部分,然后复制派生类自己的部分。赋值运算符和析构函数的执行过程也类似。
当派生类定义了自己的考构,在其中应该显示调用基类的考构来复制基类的那一部分,赋值运算符也同理。
derived(const &derived d):base(d)//接下来派生类自己部分的赋值 { }
|
而不要写成
derived(const &derived d)//冒号语法赋值
{}
|
这样基类部分将由默认考构进行复制,容易错误。
在函数体内用赋值语句复制呢?是否是出于数据抽象与封装的考虑不这么书写。
重载的赋值运算符,与考构类似,派生类需要重载自己的赋值运算符
derived& derived operator=(const derived &d)
{
if(this!=d)
{
base::operator=(d);
//接下来复制派生类自己的部分
}
} |
重载赋值运算符时要特别注意避免自我复制。
析构函数,与考构和赋值运算符不同,析构函数不会负责析构基类的部分,编译器会自动调用基类的析构函数,派生类的析构函数只需负责析构自己的部分即可。注意,与对象建立的顺序不同,析构时是先析构派生类的部分,然后再析构基类的部分。
class base{
public:
~base(){cout<<"in base destructor\n";}
};
class derived:public base{
public:
~derived(){cout<<"in derived destructor\n";}
};
int main(void) {
derived obj_d;
cout<<"end in main\n";
return EXIT_SUCCESS;
} |
程序运行结果
end in main
in derived destructor
in base destructor |
但是析构函数方面有个问题,如果有一个基类型的指针指向派生类的对象,我们delete这个指针会发生什么情况
class base{
public:
~base(){cout<<"in base destructor\n";}
};
class derived:public base{
public:
~derived(){cout<<"in derived destructor\n";}
};
int main(void) {
base * pt_b=new derived(); delete pt_b;
cout<<"end in main\n";
return EXIT_SUCCESS;
} |
程序编译没有问题,运行结果:
in base destructor
end in main
|
只是释放了基类所占部分的内存,派生类部分的没有解决。
为了解决这样的问题,我们要将析构函数声明为虚函数。在代码中两个析构函数前都加上关键字virtual,编译运行结果为
in derived destructor
in base destructor
end in main |
这才是我们想要的结果。
像其它虚函数一样,析构函数的“虚”属性是可以继承的,上例中可以只在基类的析构函数前面加关键字virtual,运行结果同上。
在类的派生层次当中,root基类一定要有一个虚的析构函数,即使这个函数什么也不做,只有空的函数体。构造函数和重载的赋值运算符不要声明为虚的,构造函数在对象建立前调用,这时对象的动态类型还不完整。而重载赋值运算符的参数基类和派生类是不同的,不能成为虚的。
在一个对象的构造函数或析构函数正在运行时,我们称这个对象是不完整的。在基类的构造和析构函数中会将派生类对象当作基类对象对待。所以尽量不要在构造或析构内调用虚函数。
阅读(739) | 评论(0) | 转发(0) |