cpa怎么做网站,购物网站建设策划报告,可拖拽网站,seo1搬到哪里去了文章目录 十二、继承8. 继承和组合 十三、多态1. 多态的概念2. 多态的定义和实现虚函数重写的两个特殊情况override 和 final 3. 多态的原理1. 虚函数表 未完待续 十二、继承
8. 继承和组合
我们已经知道了什么是继承#xff0c;那组合又是什么#xff1f;下面这种情况就是… 文章目录 十二、继承8. 继承和组合 十三、多态1. 多态的概念2. 多态的定义和实现虚函数重写的两个特殊情况override 和 final 3. 多态的原理1. 虚函数表 未完待续 十二、继承
8. 继承和组合
我们已经知道了什么是继承那组合又是什么下面这种情况就是 组合 。
class A
{//
};class B
{
private:A _a;
};组合和继承都是让代码复用但是继承的复用是一种 白箱复用 父类的内部细节是对子类透明的根透明箱子一样。而组合的复用是一种 黑箱复用 因为对象的内部细节是不可见的。 继承一定程度破坏了父类的封装父类的改变对子类有很大的影响。子类和父类间的依赖关系很强耦合度高 。组合类之间没有很强的依赖关系耦合度低。优先使用对象组合有助于保持每个类被封装 。 优先使用对象组合而不是继承。 public继承是一种 is-a 的关系。也就是说每个子类对象都是一个父类对象。 组合是一种 has-a 的关系。假设B组合了A每个B对象中都有一个A对象。
十三、多态
1. 多态的概念
多态 通俗来说就是多种形态具体点就是去完成某个行为当不同的对象去完成某个行为时会产生出不同的状态 。举个栗子比如买票这个行为当普通人买票时是全价买票学生买票时是半价买票军人买票时是优先买票。
2. 多态的定义和实现
我们先实现一下多态来尝尝鲜
#includeiostream
using namespace std;class Person
{
public:virtual void BuyTicket(){cout 买票-全价 endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout 买票-半价 endl;}
};// 多态
void Func(Person p)
{p.BuyTicket();
}int main()
{Person ps;Student st;Func(ps);// 子类可以赋值给父类---切片Func(st);return 0;
}在继承中想要构成多态是有条件的。 1. 必须通过父类的指针或者引用调用虚函数。 2. 被调用的函数必须是 虚函数 且子类必须对父类的虚函数进行重写。 虚函数的重写(覆盖/隐藏)子类中有一个跟父类完全相同的虚函数(即子类虚函数与父类虚函数的 返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了父类的虚函数。实际上父类的虚函数可以被子类继承所以只要父类写上 virtual 子类即使不写 virtual 也能构成重写 关于重写重写是重写的 实现 仅仅会改变实现方式声明并不会改变 。 虚函数重写的两个特殊情况
协变 在虚函数重写时父类和子类的虚函数返回类型可以不同但要求返回类型必须是父子类关系的指针和引用则称为 协变 。
#includeiostream
using namespace std;class A {};
class B : public A {};class Person
{
public:// 虚函数重写返回类型是对应的指针或引用virtual A* f(){cout A::f() endl;return new A;}
};class Student : public Person
{
public:// 虚函数重写返回类型是对应的指针或引用virtual B* f(){cout B::f() endl;return new B;}
};int main()
{Person* p new Student;p-f();return 0;
}当返回类型是对应的指针或引用时成功实现多态当返回类型不是时
#includeiostream
using namespace std;class A {};
class B : public A {};class Person
{
public:// 返回类型不同且不说相应的指针或引用virtual A f(){cout A::f() endl;return *new A;}
};class Student : public Person
{
public:// 返回类型不同且不说相应的指针或引用virtual B f(){cout B::f() endl;return *new B;}
};int main()
{Person* p new Student;p-f();return 0;
}析构函数的重写 如果父类的析构函数为虚函数此时子类析构函数只要定义无论是否加 virtual 关键字都与父类的析构函数构成重写。原因是编译器对析构函数的名称做了特殊处理编译后所以析构函数的名称统一处理成 destructor 。
当父类的析构函数不是虚函数时如下情况则会
#includeiostream
using namespace std;class Person
{
public:~Person(){cout ~Person() endl;}
};class Student : public Person
{
public:~Student(){cout ~Student() endl;}
};int main()
{// 父类指针指向父类对象Person* p1 new Person;// 父类指针指向子类对象Person* p2 new Student;delete p1;cout endl;delete p2;return 0;
}没能成功进行多态调用访问的还是父类的析构函数。当父类的析构函数是虚函数时
#includeiostream
using namespace std;class Person
{
public:virtual ~Person(){cout ~Person() endl;}
};class Student : public Person
{
public:// 子类可以不写 virtual 自动构成虚函数重写~Student(){cout ~Student() endl;}
};
// 只有派生类Student的析构函数重写了Person的析构函数
//下面的delete对象调用析构函数才能构成多态
//才能保证p1和p2指向的对象正确的调用析构函数
int main()
{// 父类指针指向父类对象Person* p1 new Person;// 父类指针指向子类对象Person* p2 new Student;delete p1;cout endl;delete p2;return 0;
}成功构成多态调用。我们怎么分辨 普通调用 和 多态调用 呢 普通调用 看指针或引用或者对象的类型。 多态调用 看指针或引用指向的对象。 override 和 final
如果我们想实现一个类使其不能被继承应该怎么做方法一将父类的构造函数私有化由于子类的构造函数必须调用父类的构造函数所以父类的构造函数私有化会导致子类无法实例出对象。方法二使用关键字 final 。
// 父类增加关键词 final
class A final
{//
};class B : public A
{//
};final 还可以修饰虚函数表示该虚函数不能再被重写。
class Car
{
public:virtual void Drive() final{//}
};class Benz :public Car
{
public:virtual void Drive(){cout Benz-舒适 endl;}
};override 可以检查子类虚函数是否重写了父类某个虚函数如果没有重写则编译报错。
class Car
{
public:void Drive(){//}
};class Benz :public Car
{
public:// override 写在子类后面virtual void Drive() override{cout Benz-舒适 endl;}
};3. 多态的原理
1. 虚函数表
这里常考一道笔试题sizeof(Base)是多少
class Base
{
public:virtual void Func1(){cout Func1() endl;}
private:int _b 1;
};int main()
{Base bb;cout sizeof(Base) endl;return 0;
}答案是8原因是int 占 4 个字节而只要类里面有虚函数类就会在内部 额外生成一个指针 指针指向函数指针数组函数指针数组里存的都是虚函数的地址称为 虚函数表 。指针占 4 个字节故答案是 8 。 对于上面的代码我们再进行改造一下
#includeiostream
using namespace std;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 _d 2;
};int main()
{Base b;Derive d;return 0;
}我们发现父类b对象和子类d对象虚函数表是不一样的这里我们发现Func1完成了重写所以d的虚函数表中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚函数表中虚函数的覆盖。b对象的虚函数表先拷贝一份父类的虚函数表然后子类重写的函数覆盖进b对象的虚函数表。重写是语法的叫法覆盖是原理层的叫法。Func3由于不是虚函数所以没有进入虚函数表。 运行时是通过本身的父类虚函数表或者切片的父类虚函数表自己的找到相应的虚函数不同的对象虚函数表不同因此实现多态。 未完待续