【C++学习笔记】类的继承
语法:
class 派生类名:[继承方式]基类名{
派生类新增加的成员
};
继承方式
public(公有的)、protected(受保护的)和private(私有的)。它是可选的,如果不写,那么默认为private。基类中的protected成员可以在派生类中访问,而基类中的 private成员无论如何不能在派生类中访问。
基类成员在派生类中的访问权限不会高于继承方式中指定的权限。继承方式是用来指明基类成员在派生类中的最高访问权限的。一般地,如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为 protected。实际开发一般使用public。
在派生类中,可以通过基类的公有成员函数间接访问基类的私有成员。
使用 using 关键字可以改变基类public&protected成员成员在派生类中的访问权限。
public:
using A::m_b; // 把m_b的权限修改为公有的。
private:
using A::m_a; // 把m_a的权限修改为私有的。
继承的对象模型
1.创建派生类对象时,先初始化基类对象,先调用基类的构造函数。销毁派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。
2.创建派生类对象时只会申请一次内存,派生类对象包含了基类对象的内存空间(包括私有成员),因此this指针相同,成员变量的地址也相同。
3.不同继承方式的访问权限只是语法上的处理,对派生类对象用memset()会清空基类私有成员。指针可以访问到基类中的私有成员。
以下例证很好地说明了这点,指针无视了访问权限。后面的修改属于奇巧淫技,几乎没啥用,实际中还得考虑内存对齐之类的东西,太麻烦而且多此一举。
Derived *ptr = new Derived;
memset(ptr,0,sizeof(Derived));
ptr->func();ptr->func1(); //基类对象的private也被清零了
Derived *change_private = new Derived;
*((int*)change_private+2) = 666; //跳过了private限制修改了private的值
change_private->func();change_private->func1();
delete ptr;
派生类构造函数的注意事项
- 可以用初始化列表指明要使用的基类构造函数。如不指定,则采用基类的默认构造函数
- 基类构造函数负责初始化被继承的数据成员;派生类构造函数主要用于初始化新增的数据成员
- 派生类的构造函数总会调用一个基类构造函数,这个函数可以是拷贝构造函数。
例如:基类有拷贝构造函数base(const base& a),派生类有一公有成员int c,那么派生类构造函数可以是
derived(const base& a,int c):base(a),c(c)
类作用域
通过派生类对象或者在派生类的成员函数中使用基类-派生类重名成员时,将使用派生类新增的成员,而不是基类的。基类成员被遮蔽了。函数同理,并不会构成重载。
**类也是一种作用域。**普通的成员只能通过对象(对象本身,对象指针或对象引用)访问,静态成员可以通过对象或通过类访问。
出现作用域重复冲突等问题时,在成员名前面加类名和域解析符。如果不存在继承关系,类名和域解析符可以省略不写。
对于函数而言,显然,写出对象即可。如:base.show();
特殊的继承关系
可以把派生类对象赋值给基类对象(包括私有成员),但是,会舍弃非基类的成员.
基类指针可以直接(在不进行显式转换的情况下)指向派生类对象,但不能调用派生类的方法。反之不能。因为派生类对象的方法包含了基类对象的方法,用基类方法操作派生类是安全的。引用也行。
函数的形参是基类时,实参也可以用派生类。
*多继承和虚继承
不提倡使用多继承
语法:
class 派生类名 : [继承方式1] 基类名1, [继承方式2] 基类名2,......
{
派生类新增加的成员
};
菱形继承:
class D的m_a具有二义性(可以通过作用域解析解决)和数据冗余的问题。
虚继承:
class B : virtual public A { };
class C : virtual public A { }; 解决了菱形继承的问题