继承:
看看下面代码
class A
{
private:
int a;
};
class B :public A
{
int a;
};
int main()
{
B b;
b.A::a = 1;
b.B::a = 2;
std::cout << b.A::a << " " << b.B::a << std::endl; // 1 2
b.a = 3; //这个是b.B::a
std::cout << b.A::a << " " << b.B::a << std::endl; // 1 3
}
//【注意:B的大小是8;相当于B中有A::a,和B::a,二个a前面有类限定,是不同的变量;】
1.继承的本质和原理:
继承的本质:a.代码复用 b.通过多态实现虚函数调用写出不依赖继承层次类型的统一代码;
is a :表示继承;一种的关系,a kind of
has a :表示组合;一部分的关系,a part of
继承方式 | 基类的访问限定 | 派生类的访问限定 | (main)外部的访问限定 |
public 继承 | public | public | ok,可以访问 |
protected | protected | no,不可访问 | |
private | 不可访问 | 不可访问 | |
protected继承 | public | protected | 不可访问 |
protected | protected | 不可访问 | |
private | 不可访问 | 不可访问 | |
private继承 | public | private | 不可访问 |
protected | private | 不可访问 | |
private | 不可访问 | 不可访问 |
注:私有成员只能被自己或者友元访问;
基类成员的访问限定,在派生类里面是不可能超过继承方式的。
tips:1.外部只能访问对象public成员,protected和private的成员无法直接访问;
2.在继承结构中,派生类可以从基类继承private成员,但是派生类无法访问(不论是什么继承方式);
3.protected和private的区别?在基类中定义的成员,想被派生类访问,但是不想外部访问,那么在基类中,相关成员定义为protected保护的;
如果派生类和外部都不打算访问,那么在基类中将相关成员定义为private私有的;
默认的继承方式:要看派生类是class定义的,还是struct定义的?
class定义派生类,默认继承方式是private私有的,成员的默认修饰也是private;
struct定义派生类,默认继承方式是public共有的,成员的默认修饰也是public;
派生类的构造和析构过程:
1.【派生类可以继承基类所有的成员(变量和方法),除了构造函数和析构函数;】 (因为构造函数和析构函数只知道在它们的特殊层次的对象进行构造和析构)
派生类怎么初始化从基类继承来的成员呢?
在派生的构造函数成员初始化列表中调用基类的构造函数来初始化派生类中的基类部分;
派生类的构造函数和析构函数负责初始化和清理派生类部分;
派生类从基类继承而来的成员的初始化和清理由基类的构造和析构函数负责;
》》》派生类对象构造的过程是:
1.派生类在自己的构造函数成员初始化列表中调用基类的构造函数来初始化基类部分;
2.派生类在自己的构造函数中初始化派生类自己特有的成员
》》》派生类对象的作用域到期了?
3.派生类的析构函数负责释放派生类成员可能占用的外部资源(堆内存或者文件描述符,网络连接等)
4.派生类调用基类的析构函数,释放派生类中从基类继承来的成员可能占用的外部资源;
即:每个类负责自己成员的初始化和析构;
重载、覆盖和隐藏
1.重载:一组函数要重载,必须处在同一个作用域中;而且函数名字相同,参数列表不同;
注意:成员函数后面的const修饰的是this指针,会导致成员函数的隐式形参this指针发生变化,因此会导致重载关系。
(函数的返回值对重载是没有任何影响的,因为函数签名中是没有函数的返回类型的)
2.隐藏(作用域隐藏)的关系:在继承结构中,派生类的同名函数,把基类的同名函数给隐藏了;
- 如果基类和派生类的函数名字相同,但是参数列表(特征标)不同,无论是否有vitrual关键字,基类的所有同名函数都会被隐藏,而不会重载,因为不在同一类中。
- 如果基类和派生类的函数名相同,但是参数列表(特征标)相同,但基类函数没有被virtual声明,那么基类的所有同名函数都会被隐藏,而不会覆盖,因为基类没有声明为虚函数。
所以有两种隐藏关系,分别对应不能满足重载和覆盖的情况。
3.覆盖:多态行为,通过虚函数实现;二个函数分别位于基类和派生类中,函数名和参数列表要相同,然后基类的函数必须用virtual修饰,派生类可以不用vitrual修饰。
即:不同的类继承范围,函数特征相同,基类有vitrual声明,这就是覆盖;
tipes:当函数参数有默认值的时候,又发生多态行为,那么函数的参数默认值是静态行为,在编译期就已经确定了,将使用基类版本的函数参数默认值而不是子类的。
#include <iostream>
class Base
{
public:
Base(int data = 10):ma(data){}
void show() { std::cout << "Base::show()" << std::endl; }
void show(int) { std::cout << "Base::show(int)" << std::endl; }
private:
int ma;
};
class Derived :public Base
{
private:
int mb;
public:
Derived(int data = 20):Base(data),mb(data){}
void show() { std::cout << "Derived::show()" << std::endl; }
};
int main()
{
Derived d;
d.show(); //调用的是派生类的show方法:优先调用的是派生类自己作用域的show名字成员,没有的话才取基类里面找;
//d.show(10); //这里想调用基类的show方法,但是派生类的show方法把基类的show隐藏了
d.Base::show(10); //只能通过加作用域运算符强制调用基类的show方法
d.Base::show();
}
1.继承结构,也说成从上(基类)到下(派生类)的结构:
a:可以把派生类对象赋值给基类对象;是从下到上的转换,原因是派生类对象中有基类子对象,可以把派生类中的基类子对象赋值基类对象;
b:不能把基类对象赋值给派生类对象;(因为派生类对象中有基类对象中没有的)
c:可以让基类指针(引用)指向派生类对象;实际上是基类指针(引用)指向了派生类对象中的基类子对象;从下到上的转换;
由于指针是基类指针(引用),即便它指向了派生类对象,所以限制了指针的能力,只能访问派生类中基类部分的成员变量和方法;但是如果我们把这个指向派生类对象的基类指针(引用)强转成派生类指针(引用),那么他就可以访问派生类的成员变量和方法了;所以默认情况下,它只能访问基类部分的成员变量和方法;
d:不能把派生类指针(引用)指向基类对象;因为如果派生类指针(引用)指向了基类对象,而派生类指针(引用)可以访问的是派生类那么大的内存空间,这个尺寸超过了它指向的基类对象的空间,那么就可能发生越界访问;(如果一个基类指针指向派生类对象,然后把基类指针赋值给另一个派生类指针,使用dynamic_cast执行继承层次上的安全转换;如果基类指针实际指向的是派生类对象,那么从基类指针转换成派生类指针就是安全的,否则不安全,通dynamic_cast完成这个安全性的判断)
dynamic_cast返回值:如果对指针使用dynamic_cast,失败返回nullptr,成功返回正常cast之后的对象指针;如果对引用执行dynamic_cast,失败抛出异常,成功返回正常cast对象的引用。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
总结:在继承结构中进行上下的类型转换,默认只支持从下到上(从派生类到基类)的类型转换;