目录
一、基本概念
- 代码复用:继承的基本功能,封装起来的基类稳定性、安全性和效率都较高
1.1派生类
- 派生类:继承了基类的数据成员和成员函数,派生类中只需写新增的数据成员和新增的成员函数即可
- 派生类构造函数:
- a、先调用基类构造函数,然后调用派生类构造函数
- b、若未显式调用基类构造函数,则隐式调用
- 派生类析构函数:先调用派生类的析构函数,然后调用基类的析构函数
1.2 继承关系
- is-a关系
- 全称:is-a-kind-of
- 单向性:person包含所有singer,但singer不包含所有person
- 扩展性:派生类可在基类基础上添加属性,但不可删减
- has-a关系
- 例如:meal包含fruit,但是不能从meal派生出fruit
- 处理:将fruit类作为meal的一个数据成员
二、常规写法
- 合并写法:可以把以下三个放到一个文件里
- 头文件写法:分成三个文件,头文件、类实现文件、主程序文件,三者找地方放
2.1 头文件
- person.h
// 这个PERSON_H_是自己命名的,不必跟文件名一致 #ifndef PERSON_H_ #define PERSON_H_ // 以下内容会被原封不懂的插入#include "person.h"语句处 #include <iostream> #include <string> using namespace std; // 基类 class Person { private: string _name; int _age; // 静态数据成员:见下注释,记录类创建了多少个 static int count; public: // const静态数据成员:见下注释,记录创建对象的基类名字 static const string title; // 常规构造函数,接收0、1、2个参数 Person(const string &name = "未命名", int age = 0); ~Person(); void show() const; }; // 派生类:指明基类是Person,继承方式为public, // 可访问public,protected,不可访问private class Singer : public Person { private: string _style; public: Singer(const string &name = "未命名", int age = 0, const string &style = "无风格"); void show() const; }; #endif
- 静态数据成员:
- 功能:作为声明变量的类所创建的对象的共享变量
- 声明及初始化:类声明中声明,在类方法文件中初始化,防止多次引入头文件,导致的多次初始化
- 静态const数据成员
- 声明及初始化:同静态数据成员
- 功能:创建的常量生命周期跟类同长,避免频繁创建销毁
- 静态数据成员:
2.2 类实现
- person.cpp
// 注意路径的写法,同级目录可只写文件名 #include "./person.h" // 基类中静态成员初始化 int Person::count = 0; // 基类中const静态成员初始化 const string Person::title = "Person类"; // 基类的成员函数定义 // 类构造函数:分号后为成员初始化列表形式,只能用于构造函数 // 分号前括号内为形参,分号后_name(name),意义同_name=name; Person::Person(const string &name, int age) : _name(name), _age(age) { count += 1; } ~Person(){ count -= 1; }; // 基类的常规成员函数 void Person::show() const { cout << "姓名是:" << _name << endl; cout << "年龄是:" << _age << endl; cout << "当前Person个数:"<< count << endl; } // 派生类的成员函数定义 // 派生类构造函数:成员初始化列表,且调用了基类的构造函数 Singer::Singer(const string &name, int age, const string &style) : Person(name, age), _style(style){}; // 派生类的常规成员函数 void Singer::show() const { // 派生类中调用基类的成员函数方法 Person::show(); cout << "风格为:" << _style << endl; // 生成了15个*号 string s(15, '*'); cout << s << endl; }
2.3 主程序
- main.cpp
// 注意路径的写法,同级目录可只写文件名 #include "./person.h" int main() { // 类数组列表初始化 Singer singer[3] = {Singer("小明", 15, "hip-hop"), Singer("小李", 13, "blue")}; singer[0].show(); singer[1].show(); // 这里调用的是基类Person的show函数 singer[2].Person::show(); // 调用默认构造函数,生成一个对象,count增加1 Singer temp; temp.show(); }
2.4 编译及显示
- 编译命令
>> g++ main.cpp person.cpp person.h >> ./a.out
- 显示
姓名是:小明 年龄是:15 当前Person个数:3 风格为:hip-hop *************** 姓名是:小李 年龄是:13 当前Person个数:3 风格为:blue *************** 姓名是:未命名 年龄是:0 当前Person个数:3 姓名是:未命名 年龄是:0 当前Person个数:4 风格为:无风格
三、多态公有继承
-
名词释义
名称 释义 多态 即具有多种形态,同一方法,在派生类和基类中的行为是不同的 多态用途 函数虚实参传递时可用这一特性,实参为派生类对象(singer)或地址(&singer),虚参为基类绑定(&rp)或指针(*pp) 重载问题 若有基类有重载声明,派生类不可单独重载其中一个,需全部重写 派生类不可继承函数 构造、析构、=(用目标对象修改已初始化对象) 函数 能默认生成函数 构造(空的)、析构(空的)、=(默认逐数据成员项复制,遇到指针浅浅复制)、& 函数 -
多态调用指向(见2.3.1代码)
- 合法指向:基类的指针或绑定可以指向派生类,通过等号右侧(指针或绑定指向的对象)的对象决定调用哪个类的方法
- 限制指向:派生类指针或绑定不可以指向基类,除非显式的定义了构造函数
派生类(&基类)
3.1 虚方法
- 功能:若要在派生类中重新定义基类的方法,将它设置为虚方法,否则为非虚方法(效率高相对高)
- 头文件person.h
... // 基类 class Person { private: ... public: ... // 只在基类增加virtual关键字即可 virtual void show() const; ... // 虚析构函数:基类内声明,规范析构函数调用顺序, // 先调用派生类析构函数,再调用基类析构函数 // 若无此句,在main.cpp中,a、b完成后将只调用基类的析构函数 virtual ~Person(){} }; class Singer : public Person { ... } ... #endif
- 代码实现main.cpp
#include "person.h" int main() { Singer singer("小明", 15, "hip-hop"); // 以下三个都是调用派生类的show函数,若person.h中无virtual // 以下前两个调用的是基类的show函数,最后个还是派生类的show函数 // 注意体会将其应用在函数虚实参传递中的效果 // a、基类引用可以绑定派生类 Person &rp = singer; rp.show(); // b、基类指针可以指向派生类 Person *pp = &singer; pp->show(); singer.show(); }
3.2 抽象基类
- 定义:抽象基类(abstract base class,ABC),提取两个类的共性,形成抽象基类
- 纯虚函数:只可在ABC中创建,且ABC中至少包含一个
- 特点:不可创建ABC的对象,可应用虚函数技术指向派生类
- 代码示例
#include <iostream> #include <string> using namespace std; // 基类:构造、析构、复制函数均采用默认 class Person { public: // 纯虚函数:基类中,函数后加=0即可生成纯虚函数,同时将基类转换成ABC // 允许:纯虚函数是否定义都可,=0也可以应用在非虚函数上 virtual void show() const = 0; // 虚析构函数:规范析构函数调用顺序 virtual ~Person(){}; }; // 派生类Singer:构造、析构、复制函数均采用默认 class Singer : public Person { public: // 派生类必须实现纯虚函数,否则报错 void show() const { cout << "这是singer类" << endl; }; }; // 派生类Actor:构造、析构、复制函数均采用默认 class Actor : public Person { public: // 派生类必须实现纯虚函数,否则报错 void show() const { cout << "这是actor类" << endl; }; }; // 主函数 int main() { // ABC类指针数组:后续用于指向派生类 Person *p[3]; Singer singer; Actor actor; p[0] = &singer; // 输出:这是singer类 p[0]->show(); p[1] = &actor; // 输出:这是actor类 p[1]->show(); // 编译错误:ABC不可创建对象 // p[2]->show(); }
3.3 多重继承MI
-
定义:能够使用两个或多个基类派生出新的类,组合功能
-
虚基类:使得从多个类(他们基类相同)派生出的对象只继承一个基类对象,防止二义性
-
构造函数规则:对虚基类,除默认构造函数,否则必须显式调用虚基类的某个构造函数
-
继承关系示例
-
代码示例
#include <iostream> using namespace std; class B{ public: void show(){cout<<"B"<<endl;}; }; // 指定B为虚基类 class C:virtual public B{ public: void show(){cout<<"C"<<endl;}; void cal(){cout<<"C"<<endl;}; }; class D:public C{}; // 指定B为虚基类 class E:virtual public B{ public: void cal(){cout<<"E"<<endl;}; }; class F:public D,public E{}; int main() { F m; // 用的是类C的 m.show(); // m.cal()会产生二义性, // 最好在类F中重写此方法 m.D::cal(); return 0; }
四、动态内存分配
- 静态内存分配:程序启动瞬间,变量即被分配了内存空间,直到程序退出销毁
- 动态内存分配:程序运行期间,根据需要,动态分配内存空间,期间动态分配、销毁
4.1 头文件
- person.h
#ifndef PERSON_H_ #define PERSON_H_ #include <iostream> #include <string> using namespace std; class Person { private: // 动态内存分配的核心:此处只声明指针,未分配内存 char *_name; // 用于存储字符串长度,不包含结尾\0 int len; public: // 默认构造函数 Person(); // 重载默认构造函数 Person(const char *name); // 复制构造函数 Person(const Person &p); // 析构函数 ~Person(); // 常规函数:显示对象名 void show() const; // 重载赋值运算符= Person &operator=(const Person &p); }; #endif
4.2 类实现
- person.cpp
#include "./person.h" Person::Person() { // 赋值静态数据成员,见2.1节 static const char *s = "未命名"; // 字符串长度:传入字符串指针 len = strlen(s); // new分配内存空间,此处用[],对应于析构函数 _name = new char[len + 1]; // 字符串深拷贝 strcpy(_name, s); cout << "默认构造函数被调用" << endl; }; Person::Person(const char *name) { // 同构造函数 len = strlen(name); _name = new char[len + 1]; strcpy(_name, name); cout << _name << "的构造函数被调用" << endl; }; Person::Person(const Person &p) { this->len = p.len; _name = new char[len + 1]; strcpy(_name, p._name); cout << _name << "的复制构造函数被调用" << endl; }; Person::~Person() { // 析构函数对应与构造函数 delete[] _name; cout << _name << "的析构函数被调用" << endl; }; Person &Person::operator=(const Person &p) { // 检查是否为自我复制,若是且无此判断语句 // 则delete语句会先清空内存导致无值可赋 if (this == &p) return *this; // 若非自我复制,清空_name指向的内存 delete[] _name; // 以下同构造函数写法 len = strlen(p._name); _name = new char[len + 1]; strcpy(_name, p._name); cout << _name << "赋值运算符被调用" << endl; return *this; }; void Person::show() const { cout << "姓名是:" << _name << endl; };
4.3 主程序
- main.cpp
// 注意路径的写法,同级目录可只写文件名 #include "./person.h" int main() { // 复习下:在堆中创建对象,per?是指向对象Person的指针 // a、调用默认构造函数 Person *per1 = new Person; // b、调用重载的默认构造函数 Person *per2 = new Person("per2"); // c、调用复制构造函数:注意传入的是*per2即Person对象 Person *per3 = new Person(*per2); // 指针调用类方法的写法 per1->show(); per2->show(); per3->show(); // 此处调用重载的赋值运算符= *per3 = *per1; // 销毁类对象指针指向的内存空间, // 即调用Person对象的析构函数 delete per1; delete per2; delete per3; }
4.4 显示
- 显示结果
默认构造函数被调用 per2的构造函数被调用 per2的复制构造函数被调用 姓名是:未命名 姓名是:per2 姓名是:per2 未命名赋值运算符被调用 未命名的析构函数被调用 per2的析构函数被调用 未命名的析构函数被调用