继承
// 学习内容:
// 1. 继承的概念以及定义
// 2. 基类和派生类对象赋值转换
// 3. 继承中的作用域
// 4. 派生类的默认成员函数
// 5. 继承与友元
// 6. 继承与静态成员
// 7. 复杂的菱形继承及虚拟继承
// 8. 继承的总结和反思
// 1. 继承的概念以及定义
// 继承是面向对象程序设计使设计代码可以复用的最重要手段,它允许程序元在保持原有类特性的基础上
// 进行扩展,增加功能,这样产生新的类,称派生类. 继承呈现了面向对象程序设计的层次结构;
#include
#include
using std::cout;
using std::endl;
#if 0
class Person {
public:
void print() {
cout << "I am person " << endl;
cout << "name: " << name << endl;
cout << "age: " << age << endl;
}
protected:
std::string name;
int age;
};
// 继承后父类的成员(成员函数 + 成员变量) 都会变成子类的一部分.
class Student : public Person {
protected:
int stuid;
};
class Teacher : public Person {
protected:
int jobid;
};
int main() {
Student s;
Teacher t;
s.print();
t.print();
return 0;
}
#endif
// 继承定义:
// 定义格式 : class 派生类 : 继承方式 (public,private,protected) 基类
// 继承方式分类:
// 类成员/继承方式 public继承 protected 继承 private 继承
// 基类的public成员 可见 不可见 不可见
// 基类的protected成员 可见 可见 不可见
// 基类的private成员 不可见 不可见 不可见
// 总结 :
// 1.基类private成员在派生类中,无论以什么方式继承都是不可见的.这里的不可见是指基类的私有成员还是被继承到了派生类对象中
// 但是语法上限制派生类对象不管在类里面还是类外面都不能访问;
// 2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外被直接访问,但需要在派生类中能访问,就定义为protected.
// 3.使用关键字calss 时,默认的继承方式时private ,使用struct 时默认继承方式时public;
// 基类和派生类对象赋值转换
// 派生类对象可以赋值给基类对象/基类的指针/基类的引用;
// 基类对象不能赋值给派生类对象
// 基类的指针可以通过强制类型转换赋值给派生类的指针. 但是必须时积累的指针是只想派生类对象时才是安全的;
#if 0
class Person {
protected:
std::string name;
std::string sex;
int age;
};
class Student : public Person {
public:
int no;
};
void Test() {
Student sobj;
// 派生类对象可以赋值给基类对象/基类的指针/基类的引用;
Person pobj = sobj;
Person *pp = &sobj;
Person& rp = sobj;
// 基类对象不能赋值给派生类对象
// sobj = pobj;
// 基类的指针可以通过强制类型转换赋值派生类指针
pp = &sobj;
Student *psob = (Student*)pp;
psob->no = 10;
pp = &pobj;
Student *psob2 = (Student*)pp;
psob2->no = 10;
}
#endif
// 继承中的作用域
// 1.在继承体系中基类和派生类都有独立的作用域
// 2.子类和父类有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义!
// 3.需要注意的时如果成员函数的隐藏,只需要函数名相同就能构成隐藏!
#if 0
class Person {
protected:
std::string name = "张龙";
int age = 20;
};
class Student : public Person {
public:
void print() {
cout << "name : " << name << endl;
cout << "age : " << Person::age << endl; // 继承的
cout << "age : " << age << endl; // 自己的
}
protected:
int age = 100;
};
void TestInh() {
Student s1;
s1.print();
}
class A {
public:
void fun() {
cout << "A fun" << endl;
}
};
class B : public A {
public:
void fun(int i) {
A::fun();
cout << " B fun i = " << i <
B b;
b.fun(1);
}
int main() {
//TestInh();
T();
return 0;
}
#endif
// 派生类的默认成员函数
// 1. 派生类的构造函数必须调用基类的构造函数,初始化基类的那一部分成员. 如果基类没有默认的构造函数,
// 则必须在派生类的构造函数的初始话列表阶段显示调用;
// 2. 派生类的拷贝构造函数必须调用基类的拷贝构造函数,完成基类的拷贝初始化!
// 3. 派生类的operator= 必须要调用街垒的operator= 完成基类的复制;
// 4. 派生类的析构函数会在被调用完成后,自动调用积累的析构函数清理基类成员.因为这样才能保证先清理派生类成员,再清理派生类构造!
// 5. 派生类对象初始化先调用基类构造函再调派生类构造函数;
// 6. 派生类对象析构先调用派生类析构再调用基类析构;
// 继承与友元:
// 友元关系不能继承,也就是说基类友元不能访问子类private ,protected成员;
#if 0
class Student;
class Person {
public:
friend void display(const Person& p, const Student& s);
protected:
std::string name;
};
class Student : public Person {
protected:
std::string num = " 001";
};
void display(const Person& p, const Student& s) {
cout << p.name << endl;
// cout << s.num << endl; // private ,protected 没办法访问
}
#endif
// 继承与静态成员:
// 基类定义类static成员,则整个继承体系中只有这样一个成员.无论派生多少个子类,都只有一个static成员实例
#if 0
class Person {
public:
Person() {
count++;
}
protected:
std::string name;
public:
static int count;
};
int Person::count = 0;
class Student : public Person {
protected:
int num;
};
class Teacher : public Person {
protected:
std::string sex;
};
void TestStatic() {
Student s1;
Student s2;
Student s3;
Student s4;
Teacher t1;
cout << "count = " << Person::count << endl;
cout << "cout = " << Student::count << endl;
Teacher::count = 0;
cout << "count = " << Person::count << endl;
cout << "cout = " << Student::count << endl;
}
int main() {
TestStatic();
return 0;
}
#endif
// 多继承 : 菱形继承
// 菱形继承的问题:
// 菱形继承有数据冗余和二义性问题,;
#if 0
class Person {};
class Student : public Person{};
class Teacher : public Person{};
class Assiant : public Student,public Teacher{};
#endif
// 虚拟继承可以解决菱形继承的二义性和数据冗余的问题;
#if 0
class Person {
public:
std::string name;
};
class Student : public Person {
protected:
int num;
};
class Teacher : public Person {
protected:
int id;
};
class Assiant : public Student, public Teacher {
protected:
std::string majo;
};
void Testl() {
Assiant a;
// a.name = "张龙"; // 二义性,冗余问题
}
// 虚拟继承解决数据冗余和二义性问题
class A {
public:
int a;
};
class B : virtual public A{
public:
int b;
};
class C : virtual public A {
public:
int c;
};
class D : public B, public C {
public:
int d;
};
// B C 的两个指针,各自指向一张表,叫虚基表,虚基表中存储的偏移量,通过偏移量可以找到A;
int main() {
D d;
d.B::a = 1; // B::a 会去查找虚基表,找到A的a ,找到的是唯一的A
d.C::a = 2; // C::a 会去查找虚基表,找到A的a ,找到的是唯一的A
d.b = 3;
d.c = 4;
d.d = 5;
d.a = 6;
cout << d.B::a << endl;
cout << d.C::a << endl;
cout << d.a << endl;
return 0;
}
#endif
多态
#include
using namespace std;
// 多态
// 1. 多态的概念
// 2. 多态的定义以及实现
// 3. 抽象类
// 4. 多态的原理
// 5. 单继承和多继承关系中的虚函数表
// 多态是在不同继承关系的类对象,去调用同一函数,产生了不同行为.
// 在继承中要实现多态的两个条件
// 1. 必须通过基类的指针或者引用调用虚函数;
// 2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写;
#if 0
// 虚函数: 即被virtual 修饰的类成员函数称为虚函数;
class A {
public:
virtual void Buy() {
cout << "花钱 买东西 " << endl;
}
};
// 虚函数的重写: 派生类中有一个跟基类完全相同的虚函数(即派生类虚函数于基类虚函数的返回值类类型、参数列表完全相同),称子类的虚函数重写了
// 基类的虚函数;
class B : public A {
public:
virtual void Buy() { // vritual 可以省略,但是加上更加规范!
cout << "B extends A" << endl;
}
};
void Fun(A& a) {
a.Buy();
}
int main() {
A a;
B b;
Fun(a);
Fun(b);
return 0;
}
#endif
#if 0
// 虚函数重写的两个例外:
// 1. 协变(基类于派生类虚函数返回值类型不同),即基类虚函数返回基类对象的引用或者指针,派生类返回派生类对象的指针或者引用时,称为协变;
class A{};
class B :public A{};
class C {
public :
virtual A* f() { return new A; }
};
class D :public C {
public:
virtual B* f() { return new B; }
};
// 2. 析构函数的重写
// 如果基类的析构函数为虚函数,此时派生类析构函数只要定义无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数
// 名字不相同,看起来违背的重写的规则,其实不然,这里可以理解为编译器对析构函数名称做了特殊处理,编译后析构函的名称统一处理成destructor;
// C++11 override 和 final
//1. final 修饰虚函数,表示该虚函数不能在被继承;
class Car {
virtual void Drive() final{}
};
class Benz : public Car {
public:
// virtual void Drive() { cout << " DADA1" << endl; }
};
// 2.override: 检查派生类是否重写了某个类的某个虚函数,如果没有重写编译报错.
class Car {
virtual void Drive() {}
};
class Benz : public Car {
public:
virtual void Drive() override { cout << " DADA1" << endl; }
};
#endif
// 重载 : 两个函数在同一个作用域,函数名/参数相同;
// 重写 : 两个函数分别在基类和派生类的作用域, 函数名/参数/返回值 都必须相同(协变,析构例外),两个函数必须都是虚函数;
// 重定义: 两个函数分别在基类和派生类的作用域,函数名相同,两个基类和派生类的同名函数,不是重写就是重定义;
// 抽象类: 在虚函数的后面写=0,则这个函数就是纯虚函数; 包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化对象,
// 抽象类的派生类只有重写纯虚函数才能实例化对象. 纯虚函数规范了派生类必须重写.
#if 0
class Car {
public:
virtual void dirve() = 0;
};
class Bz : public Car {
public:
virtual void dirve() {
cout << "bz" << endl;
}
};
class BMW : public Car {
public:
virtual void dirve() {
cout << "BMW" << endl;
}
};
void TestCa() {
Car* pb = new Bz;
pb->dirve();
Car* pw = new BMW;
pw->dirve();
}
int main() {
TestCa();
return 0;
}
#endif
// 多态的原理:
// 虚表(虚函数表): 本质是一个存储虚函数指针的指针数组,这个数组的最后面放了一个nullptr;
// 派生类虚表的形成:
// 1. 先将基类中的虚表内容拷贝一份到派生类虚表中.
// 2. 如果派生类重写了基类的某个虚函数,那么在派生类对象的虚表中就会将自己的虚函数指针覆盖掉虚表数组中重写的虚函数指针所在的位置的的指针;
// 3. 派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后;
#if 0
class Base {
public:
virtual void Func1() { cout << "Base Func1()" << endl; }
virtual void Func2() { cout << "Base Func2()" << endl; }
void Func3() { cout << "Base Func3()" << endl; }
private:
int _b = 1;
};
class Derive : public Base {
public:
virtual void Func1() { cout << "Derive Func1()" << endl; }
private:
int id = 2;
};
int main() {
Base b;
Derive d;
return 0;
}
#endif
// 多态的条件: 虚函数的覆盖,对象的指针和引用调用虚函数;
// 动态绑定与静态绑定:
// 静态绑定(前期绑定/早绑定): 在程序编译期间确定了程序的行为,也称静态多态;eg: 函数重载;
// 动态绑定(后期绑定/晚绑定): 在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也成为动态多态;
// 单继承和多继承关系的虚函数表
// 1. 单进程中的的虚函数表:
#if 0
class Base {
public:
virtual void Func1() { cout << "Base Func1()" << endl; }
virtual void Func2() { cout << "Base Func2()" << endl; }
private:
int a;
};
class Derive : public Base {
public:
virtual void Func1() { cout << "Derive Func1()" << endl; }
virtual void Func3() { cout << "Derive Func3()" << endl; }
virtual void Func4() { cout << "Derive Func4()" << endl; }
private:
int b;
};
typedef void(*VFPTR)();
void printVTable(VFPTR vTable[]) {
// 依次取虚表中的函数指针打印并调用.
cout << "虚表地址 > " << vTable << endl;
for (int i = 0; vTable[i] != nullptr; i++) {
printf("第 %d 个虚函数地址: 0X%x,->", i,vTable[i] );
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main() {
Base b;
Derive d;
// 思路: 取出b,d对象的头4字节,就虚表的指针;虚表就是虚函数的指针数组,数组后面放了一个nullptr
// 1.先取 b的地址,强转成int*指针;
// 2.在解引用取值,就取到了b对象头4字节的值,这个值就是指向虚表的指针;
// 3.强转成VFPTR*,因为虚表中的函数与VFPTR类型一样;
// 4.打印虚表;
VFPTR* vTable = (VFPTR*)(*(int*)&b);
printVTable(vTable);
VFPTR* vTable1 = (VFPTR*)(*(int*)&d);
printVTable(vTable1);
return 0;
}
#endif
// 多继承中的虚函数表
// 可以发现继承几个就有几张虚表,重写那个就覆盖那个
#if 0
class Base1 {
public:
virtual void Func1() { cout << "Base1 Func1()" << endl; }
virtual void Func2() { cout << "Base1 Func2()" << endl; }
private:
int a1;
};
class Base2 {
public:
virtual void Func1() { cout << "Base2 Func1()" << endl; }
virtual void Func2() { cout << "Base2 Func2()" << endl; }
private:
int a2;
};
class Derive : public Base1,public Base2 {
public:
virtual void Func1() { cout << "Derive Func1()" << endl; }
virtual void Func3() { cout << "Derive Func3()" << endl; }
private:
int b;
};
int main() {
Derive d;
cout << sizeof(d) << endl;
return 0;
}
#endif
#if 0
//探寻一下虚继承的虚表问题
class Super {
public:
virtual void Func1() { cout << "Super Func1()" << endl; }
private:
int o1;
};
class Base1 :virtual public Super {
public:
virtual void Func1() { cout << "Base1 Func1()" << endl; }
virtual void Func2() { cout << "Base1 Func2()" << endl; }
private:
int a1;
};
class Base2 : virtual public Super {
public:
virtual void Func1() { cout << "Base2 Func1()" << endl; }
virtual void Func2() { cout << "Base2 Func2()" << endl; }
private:
int a2;
};
class Derive :public Base1,public Base2 {
public:
virtual void Func1() { cout << "Derive Func1()" << endl; }
virtual void Func3() { cout << "Derive Func3()" << endl; }
private:
int c = 1;
};
int main() {
Derive d;
return 0;
}
#endif