本文是对"铁道版"c++一书的知识点总结,如果侵权,请及时告知我,将尽快删去,本文禁止转载
派生类的声明
class 派生类名:[继承方式] 基类名
{
派生类新增的数据成员和成员函数
};
继承方式可以是:private,protected或public
默认为私有继承(private)
派生类:
- 可以增加新的数据成员
- 可以增加新的成员函数
- 可以对基类的成员进行重定义
- 可以改变基类成员在派生类中的访问属性
基类成员在派生类中的访问属性
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
派生类对基类成员的访问规则
私有继承访问规则
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
公有继承访问规则
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
保护继承访问规则
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
派生类的构造函数和析构函数
创建派生类对象时,首相调用基类的构造函数,随后再调用派生类的构造函数;当撤销派生类对象时,则先调用派生类的析构函数,随后再调用基类的析构函数
B类从A类派生,那么B类有A类的所有数据成员
派生类构造函数和析构函数的构造规则
当基类的构造函数没有参数,或没有显示定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数
派生类不能继承基类中的构造函数和析构函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径
派生类名(参数总表):基类名(参数表)
{
派生类新增数据成员的初始化语句
};
在派生类中是否要自定义析构函数与它所属基类的析构函数无关
在执行派生类的析构函数时,系统会自动调用基类的析构函数,对基类的对象进行清理
含有子对象的派生类构造函数
派生类名(参数总表):基类名(参数表0),子对象名1(参数表1)...子对象名n(参数表n)
{
派生类新增数据成员的初始化语句
};
构造函数的调用顺序如下:(无论基类和子对象排列顺序如何)
- 调用基类的构造函数
- 调用子对象的构造函数
- 调用派生类的构造函数
#include<iostream>
#include<string>
using namespace std;
class aa
{
public:
aa(string t)
{
cout << t << endl;
}
};
class bb:public aa
{
private:
aa a1,a2,a3;
public:
bb():a3("a3"),a2("a2"),a1("a1"),aa("aa")
{
cout << "b1" << endl;
}
};
int main()
{
bb b1;
return 0;
}
结果:
aa
a1
a2
a3
b1
- 当基类构造函数不带参数时,派生类不一定需要定义构造函数;然而基类构造函数带参数时,派生类必须要定义构造函数
- 若基类使用默认构造函数或不带参数的构造函数,则在派生类中定义构造函数可略去“:基类构造函数名(参数表)”,甚至可以不定义构造函数
- 如果派生类的基类也是一个派生类,每隔派生类只需负责其直接基类数据成员的初始化,依次上溯
调整基类成员在派生类中的访问属性的其他方法
派生类可以重新声明与基类成员同名的成员。在没有虚函数的情况下,如果派生类中定义了与基类成员同名的成员,则称派生类成员覆盖了基类的同名成员,在派生类中使用这个名字意味着访问在派生类中重新声明的成员。,为了在派生类中使用与基类同名的成员,必须在该成员名之前加上基类名和作用域标识符“::”
基类名::成员名
访问声明
访问声明机制可以个别调整私有派生类从基类继承下来的成员性质,从而使外界可以通过派生类的界面直接访问基类的某些成员,同时也不影响其他基类成员的访问属性
- 数据成员可以使用访问声明
- 访问声明中只含不带类型和参数的函数名或变量名
A::show; 正确
void A::show; 错误
void A::show(); 错误
A::show(); 错误
- 访问声明不能改变成员在基类中的访问属性,访问声明只能把原基类的保护成员调整为私有派生类的保护成员,把原基类的公有成员调整为私有派生类的公有成员。但对基类的私有成员不能使用访问声明
class A
{
public:
int x1;
protected:
int x2;
private:
int x3;
};
class B:private A
{
public:
A::x1;//正确
A::x2;//错误
A::x3;//错误
protected:
A::x1;//错误
A::x2;//正确
A::x3;//错误
private:
A::x3;//错误
}
- 对于基类中的重载函数名,访问声明将对基类中所有同名函数起作用。这意味着对于重载函数使用访问声明时要慎重
多继承和虚基类
class 派生类名:继承方式1 基类名1,...,继承方式i 基类名i,...,继承方式n 基类名n
{
派生类新增的数据成员和成员函数
};
默认的继承方式是private
class X
{
public:
int f();
};
class Y
{
public:
int f();
int g();
};
class Z:public X, public Y
{
public:
int g();
int h();
};
Z obj;
obj.f();
上述二义性错误不知调用的是类X的f(),还是类Y的f()
使用成员名限定可以消除二义性
obj.X::f();
obj.Y::f();
多继承派生类的构造函数与析构函数
派生类名(参数总表):基类名1(参数表1),...,基类名i(参数表i),...,基类名n(参数表n)
{
派生类新增成员的初始化语句
}
多继承构造函数的调用顺序与单继承构造函数的调用顺序相同,也是遵循先调用基类的构造函数,再调用对象成员的构造函数,最后调用派生类的构造函数的原则。处在同一层次的各个基类构造函数的调用顺序,取决于声明派生类时所指定的各个基类的顺序,与派生类构造函数中所定义的成员初始化列表的各项顺序没有关系。析构函数的调用顺序则刚好与构造函数的调用顺序相反
基类的析构函数不会因为派生类没有析构函数而得不到执行,它们各自是独立的
虚基类
如果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多份同名成员。在访问这些同名成员时,必须在派生类对象名后增加直接基类名,使其唯一地标识一个成员,以免产生二义性
虚基类的声明
class 派生类名:virtual 继承方式 类名
{
...
};
虚基类的初始化
如果虚基类中定义有带形参的构造函数,并且没有定义默认形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化列表中列出对虚基类构造函数的调用,以初始化虚基类中定义的数据成员
class Base
{
...
};
class Base1:virtual public Base
{
...
};
class Base2:virtual public Base
{
...
};
class Derived: public Base1,public Base2
{
public:
Derived(int sa):Base(sa),Base1(sa),Base2(sa)
{
...
}
...
};
建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是有最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略
若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数
对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下
对于非虚基类,构造函数的执行顺序仍然是先左后右,自上而下
若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数
赋值兼容规则
不同类型数据之间的自动转换与赋值,称为赋值兼容
基类和派生类对象之间的赋值兼容规则是指在需要基类对象的任何地方,都可以使用其子类对象来替代
class Base
{
…
};
class Derived :public Base
{
…
};
- 派生类对象可以赋值给基类对象,即用派生类对象中从基类继承来的数据成员,逐个赋值给基类对象的数据成员
Base b;
Derived d;
b=d;
- 派生类对象可以初始化基类对象的引用
Base b;
Derived d;
Base &br=d;
- 派生类对象的地址可以赋值给指向基类对象的指针
Derived d;
Base &bp=d;
- 如果函数的形参是基类对象或基类对象的引用,在调用函数时可以用派生类对象作为实参
class Base
{
public:
int i;
…
};
class Derived :public Base
{
…
};
void fun(Base &bb)
{
cout<<bb.i<<endl;
}
Derived d4;
fun(d4);
- 声明为指向基类对象的指针可以指向它的公有派生类的对象,但不允许指向它的私有派生类对象
- 允许将一个声明为指向基类的指针指向其公有派生类的对象,但是不能将一个声明为指向派生类对象的指针指向其基类的对象