继承方式的特点
- 子类对象内部包含基类子对象
子类的大小是一个基类对象+自身的成员:
class Base{
public:
int m_a;
void foo(){cout << "Base::foo()" << endl;}
protected:
int m_b;
void bar(){cout << "Base::bar()" << endl;}
private:
int m_c;
void hum(){cout << "Base::hum()" << endl;}
};
class Derived:public Base{
public:
void fun(){
m_a = 10;
foo();
m_b = 20;
bar();
// m_c = 30;
// hum();
}
private:
int m_d;
};
- 子类对象内部可以直接访问基类的非私有成员。基类的非私有成员在子类中仅仅为可见,而非子类拥有。
隐藏关系
不论哪种继承方式,子类隐藏关系 基类中同名的成员
可以借助 作用域限定符 使用 基类的函数
class Derived:protected Base{
public:
void fun(){
m_a = 10;
foo(); // 子类隐藏同名基类函数
m_b = 20;
bar();
Base::bar();
// m_c = 30;
// hum();
}
private:
int m_d;
void foo(){ cout << "Derived::foo()" << endl;}
void bar(){ cout << "Derived::bar()" << endl;}
};
三种继承方式的差别
子类内部访问基类的成员,编译器查看这些成员在基类中的原始标记。
class Derived:public Base{
public:
void fun(){
m_a = 10;
foo();
m_b = 20;
bar();
cout << "m_a = " << m_a << endl;
cout << "m_b = " << m_b << endl;
// m_c = 30; // 私有 不能访问
// hum(); //私有 不能访问
}
private:
int m_d;
};
当利用子类对象,在类外访问基类成员,编译器将查看这些成员在子类中的重新标记
类外访问代码:
int main(){
Derived d;
d.m_a = 100;
d.foo();
d.m_b = 200;
d.bar();
d.m_c = 300;
d.hum();
d.fun();
return 0;
}
编译结果:
可以看见,public继承时,按照子类中的重新标记进行限制,只有基类中public成员才能被使用。
public继承的独有特点
- 子类对象在类外可以访问基类共有成员(重新标记限制),其它继承方式不可以。
- 如果被子类同名标识符隐藏也可以借助作用域限定符
::
指定访问基类的共有成员。 - 子类类型的指针和引用 和 基类类型的指针和引用 可以相互转化。
#include <iostream>
using namespace std;
#pragma pack(1) // 按1字节补齐
class Human{
public:
private:
int m_age; // 4B
string m_name; // 32B
};
class Student:public Human{
public:
private:
int m_no;
};
int main(){
Human h;
cout << "基类对象h的大小:" << sizeof(h) << endl; //36B
Student s;
cout << "子类对象h的大小:" << sizeof(s) << endl; //40B
Human* ph = &s; // 基类类型指针 指向 子类对象
Human& ch = s;
return 0;
}
Human* ph = &s;
Human& ch = s;
编译器认为访问范围缩小 是安全的【向上转型】。能看见三个变成能看见两个,认为是安全的。
相反:基类类型的指针或者引用 不能 转成 子类类型的指针或者引用。
// Student* ps = &h; // 报错
// 强转 但有风险
Student* ps = static_cast<Student*>(&h);
虽然通过强制类型转换,但是存在越界风险。
Student* ps = ph; // 不看真实指向,只看指针的类型(Human* -> Student*)
编译器对类型安全的检查仅仅基于指针或引用本身;基类指针或引用的实际目标,究竟是不是子类对象,需要程序员自己判断。
Student* ps = static_cast<Student*>(ph);
Student* ps = static_cast<Student*>(ph);
如果成功的话,没有风险。ph实际是&s
,所以再强制转换成Student*,没有风险。
子类的构造和析构
class Student : public Human{
public:
Student(int age = 0, const char* name="匿名", float score=0.0, const char* remark="无"):
Human(age,name),m_score(score), m_remark(remark){
cout << "Student类的缺省构造函数被调用" << endl;
}
private:
float m_score;
string m_remark;
};
在子类的构造函数中指明基类的构造函数。
子类对象的构造过程:
构造基类子对象 -> 构造子类的成员变量->执行自己在子类构造函数中书写的代码
阻断继承:
子类的构造函数无论如何都会调用基类的构造函数;如果把基类的构造函数定义为私有,那么该类的子类就永远无法被实列化对象;c++中可以用这种方法阻断一个类被扩展。
虚继承
虚继承 – 钻石继承问题(公共基类子对象,在汇聚子类对象中,存在多个实例)的解决方法
(1)公共虚基类子对象在汇聚子类对象中仅存一份实例
(2)公共虚基类子对象被对个中间子类对象所共享
#include <iostream>
using namespace std;
#pragma pack(1)
class A{ // 人类
public:
int m_a; // 年龄
};
class X:virtual public A{ // 学生类
public:
int m_x;
void setAge(int age){
this->m_a = age; // (1)this(2)X中间子类子对象(3)指针1(4)偏移量(5)this+偏移量(6)公共虚基类子对象(7)m_a
}
};
class Y:virtual public A{ // 教师类
public:
int m_y;
int getAge(){
return this->m_a; // (1)this(2)Y中间子类子对象(3)指针2(4)偏移量(5)this+偏移量(6)公共虚基类子对象(7)m_a
}
};
class Z:public X,public Y{ // 汇聚子类 助教类
public:
int m_z;
void foo(){
m_a = 20;
}
};
int main( void ){
Z z;
cout << "汇聚子类对象z的大小: " << sizeof(z) << endl; // 32
z.setAge(35);
cout << z.getAge() << endl;
return 0;
}
虚函数
一定是类中的成员函数。
覆盖:如果子类的成员函数和基类的虚函数具有相同的函数签名,那么该成员函数也是虚函数。
#include <iostream>
using namespace std;
class Shape{
public:
void Draw(){ cout << "Shape::Draw()" << endl; }
private:
int m_x;
int m_y;
};
class Rect:public Shape{
public:
)
void Draw(){ cout << "Rect::Draw()" << endl; }
private:
int m_rx;
int m_ry;
};
class Circle:public Shape{
public:
void Draw(){ cout << "Circle::Draw()" << endl; }
void foo(){}
private:
int m_radius;
};
int main(void){
cout << "-------------利用对象调普通的成员函数--------------" << endl;
// 哪个类对象就调用哪个类的普通成员函数(对象的自恰性)
Shape s;
s.Draw(); // Shape::Draw
Rect r;
r.Draw(); // Rect::Draw
Circle c;
c.Draw(); // Circle::Draw
cout << "------------利用指针调用普通成员函数---------------" << endl;
// 编译器简单而粗暴的根据指针本身的类型来确定到底调用哪个类的普通成员函数
Shape* ps = &s;
ps->Draw(); // Shape::Draw (不是多态)
ps = &r;
ps->Draw(); // Shape::Draw (不是多态)
ps = &c;
ps->Draw(); // Shape::Draw (不是多态)
return 0;
}
使用虚函数时,如果基类类型指针指向基类对象,则调用基类的原始版本虚函数,如果基类对象指针指向子类对象,则调用子类的覆盖版本。
// 虚的世界(有虚函数的程序)
#include <iostream>
using namespace std;
class Shape{
public:
virtual void Draw(){ cout << "Shape::Draw()" << endl; } // 虚函数(原始版本)
private:
int m_x;
int m_y;
};
class Rect:public Shape{
public:
// 虚函数(编译器补virtual),与基类的Draw构成覆盖关系(覆盖版本)
void Draw(){ cout << "Rect::Draw()" << endl; }
private:
int m_rx;
int m_ry;
};
class Circle:public Shape{
public:
// 虚函数(编译器补virtual),与基类的Dra构成覆盖关系(覆盖版本)
void Draw(){ cout << "Circle::Draw()" << endl; }
void foo(){}
private:
int m_radius;
};
int main(void){
cout << "-------------利用对象调 虚 成员函数--------------" << endl;
// 哪个类对象就调用哪个类的普通成员函数(对象的自恰性)
Shape s;
s.Draw(); // Shape::Draw
Rect r;
r.Draw(); // Rect::Draw
Circle c;
c.Draw(); // Circle::Draw
cout << "------------利用指针调用虚函数成员函数---------------" << endl;
// 利用基类类型指针,调用成员虚函数,调用的具体是哪个类中的虚函数,由指针指向的对象的类型来决定
Shape* ps = &s;
ps->Draw(); // Shape::Draw (不是多态)
ps = &r;
ps->Draw(); // Rect::Draw (多态)
ps = &c;
ps->Draw(); // Circle::Draw (多态)
return 0;
}