-
继承
-
示例
class Grad : public Core { ... }
-
继承方式
继承方式/基类成员 public成员 protected成员 private成员 public继承 public protected 不可见 protected继承 protected protected 不可见 private继承 private private 不可见 由于 protected 和 private 继承会改变基类成员在派生类中的访问权限,导致继承关系复杂,所以实际开发中我们一般使用 public
-
除了“三位一体”(构造函数、复制构造函数、析构函数、赋值运算符函数)以外,基类的每个成员也是派生类的成员
-
派生类中调用基类中的被覆盖方法
class Derived: public Base { public: void haha() { Base::haha(); ... } ... };
-
构造派生类对象的步骤
(1) 为整个对象分配内存空间(包括基类的数据和派生类的数据)
(2) 调用基类的构造函数,以初始化对象中的基类部分数据
(3) 用构造函数初始化器,对派生类部分数据进行初始化
(4) 如果有的话,执行派生类构造函数的函数体
注:如果没有指定使用哪一个基类构造函数,就使用基类的默认构造函数
-
-
多态和虚函数
-
virtual关键字
(1) 只在类的声明中使用
class Core { public: virtual double grade() const; }
(2) 如果基类中一个函数是虚函数,那么派生类中它的virtual特性会被继承
-
静态绑定
bool compare_grades(Core c1, Core c2) { ... return c1.grade() < c2.grade(); }
当函数为值类型时,在编译期就会确定对象类型,因此无论传递进来的是Core类型,还是派生的Grad类型,都会只复制Core的部分进入函数,从而调用Core::grade()函数
-
动态绑定
(1) 当函数为引用或者指针类型时,调用一个虚函数,这个时候会根据对象的实际类型,决定调用哪一个函数
(2) 动态绑定的原理是,对于virtual修饰的函数,会对对象生成一个虚函数表,表中各个函数的偏移量是确定的,虚函数表中对应着实际的函数地址;在运行时,如果使用地址来调用,可以根据偏移量决定调用哪个函数
(3) 要注意的是,使用 指针(引用) + 虚函数 是多态的必要条件,缺一不可!!
使用指针/引用才能根据地址绑定,否则编译期就会确定类型;
使用virtual修饰才能生成虚函数表,否则没有虚函数表的概念。
(4) 对于非虚函数,只要程序中没有调用它,可以只声明不定义;但是virtual修饰的函数,必须要又声明又定义才能编译通过,除非声明为纯虚函数
-
虚拟析构函数
(1) 调用 delete 一个指针的时候,干了2件事
1° 调用指针所指对象的析构函数 2° 释放对象占用的内存空间
(2) 当出现基类指针指向派生类对象的情况时,为了调用派生类对象的析构函数,也要把析构函数定义为virtual。由于virtual特性会被继承,因此派生类的析构函数也是virtual
class Core { public: virtual ~Core() {} ... }
-
-
句柄类
-
一个要分配空间的类,在外部使用时要考虑delete很累,解决的办法就是用一个类在外面套一层,内部包着一个private数据成员,这个数据成员的类型是基类指针,这样可以实现多态
-
示例
class Core { public: std::string name() const; [[nodiscard]] virtual double grade() const { ... } ... }; class Grad : public Core { public: [[nodiscard]] double grade() const override { ... } ... };
这两个父子类是要用到的,并且 grade() 是虚函数,经常要new,用到它们的多态特性,忘记delete很麻烦;
这时就可以使用句柄类的编程技巧,在外面套一层
class Student_info { public: // "三位一体"的几个函数,首先都把内部的Core*指针初始化为null Student_info() : cp(nullptr) {} explicit Student_info(std::istream &is) : cp(nullptr) { read(is); } Student_info(const Student_info &student_Info); Student_info &operator=(const Student_info &); // 析构函数,只需释放cp指向的空间,根据cp的类型决定使用哪个析构函数 ~Student_info() { delete cp; } std::istream &read(std::istream &is); [[nodiscard]] std::string name() const { if (cp != nullptr) { return cp->name(); } else { throw std::runtime_error("uninitialized student"); } } // grade()函数本质上调用了cp->grade(),实现了多态 double grade() const { if (cp != nullptr) { return cp->grade(); } else { throw std::runtime_error("uninitialized student"); } } // 对于 Core 来说,compare函数没有改变对象状态,应该实现为全局函数而非成员函数;而Student_Info句柄类在外面包了一层,所以实现为static函数比较合适,作为谓词使用时,传递Student_Info::compare即可 static bool compare(const Student_info &s1, const Student_info &s2) { return s1.name() < s2.name(); } private: Core *cp; // 运行时决定指向哪种类型 };
-
"三位一体"的几个函数,首先都把内部的Core*指针初始化为null
-
析构函数,只需释放cp指向的空间,根据cp的类型决定使用哪个析构函数,这样用户无需手动管理内存
-
grade()函数本质上调用了cp->grade(),实现了多态
-
对于 Core 来说,compare函数没有改变对象状态,应该实现为全局函数而非成员函数;而Student_Info句柄类在外面包了一层,所以实现为static函数比较合适,作为谓词使用时,传递Student_Info::compare即可
-
read函数决定了cp指向的对象类型
std::istream &read(std::istream &is) { if (cp!= nullptr) { delete cp; } char ch; is >> ch; if (ch == 'U') { cp = new Core(is); } else { cp = new Grad(is); } return is; }
-
Student_Info 类的复制构造函数和赋值运算符函数,由于不知道 cp指针的类型,所以要委托给Core和Grad的虚函数处理实现多态,方法就是Core类和Grad类各加一个clone的虚函数
-
有了 Student_Info 句柄类,使用的时候大大简化
原来要使用 vector<Core*> 结构保存基类指针的集合,并且要手动在退出前 delete;现在使用 vector<Student_Info>,局部变量退出时,自动调用Student_Info的析构函数
-
-
继承与容器
vector<Core> students; Grad grad = Grad(cin); students.push_back(grad);
这样做不会报错,但是像students容器放置时,只会复制grad对象的Core数据部分,Grad的特有部分会被丢弃
-
覆盖
基类和派生类的两个函数,必须同名、参数相同、都是或都不是const,才会发生
-
构造函数不能声明为virtual,因为在用到构造函数的时候,对象类型一定被确定了
chapter13_使用继承与动态绑定
猜你喜欢
转载自blog.csdn.net/captxb/article/details/103093000
今日推荐
周排行