继承和多态

继承和多态

为什么我们要使用继承?

使用继承是为了减少代码的冗余,增强代码的可扩展性,并且当两个类具有相同的特征(属性)和行为(方法)时,可以相同的部分抽取出来放到一个类中作为父类,其他两个类继承这个父类。

1.继承和派生基本概念

(1)继承
  • 以现有的类为基础来构建新类

  • 新类(派生类)(子类)继承现有类(基类)(父类)的属性和行为

  • 派生类可以修改继承的行为

  • 派生类可以增加新的属性和行为

  • 派生类对象也是基类对象

  • 派生类对象和基类对象可以被统一管理

(4)对于基类和派生类的理解

基类就是父类,派生类就是子类

你可以理解为生活中的父子关系,

也就是说儿子继承了所有的父亲的样貌特征,但是儿子也有父亲所没有的一些特点

专业点讲,就是子类可以继承并使用父类的属性和方法,也可以有自己的属性和方法!

(3)例子

家具和柜子, 建筑和房子 ,道路和高速公路, 动物和猫 ,计算机和笔记本电脑

class Mammal {
public:
	Mammal();
	~Mammal();
	......
};
class Lion : public Mammal {
public:
	Lion();
	~Lion();
	......
}

2.继承的定义

在 C++ 中,当定义一个新的类 B 时,如果发现类 B 拥有某个已写好的类 A 的全部特点,此外还有类 A 没有的特点,那么就不必从头重写类 B,而是可以把类 A 作为一个“基类”(也称“父类”),把类 B 写为基类 A 的一个“派生类”(也称“子类”)。这样,就可以说从类 A “派生”出了类 B,也可以说类 B “继承”了类 A。

(1)继承的方式
  • 共有继承(public)
  • 受保护继承(protected)
  • 私有继承(private)
(2)派生类(子类)中重定义基类的函数
  • 派生类自动继承基类所有成员
  • 若基类成员函数不满足需要,在派生类中重新定义函数
  • 派生类对象可以直接访问自己定义的函数,也可以访问定义于基类被覆盖的函数
  • 调用方式:基类名 :: 函数名
(4)基类(父类)中的私有成员
  • 能被派生类继承
  • 不能被派生类的成员函数直接访问
  • 可通过定义于基类的共有和受保护成员函数访问
(5)在 C++ 中,从一个类派生出另一个类的写法
class  派生类名: 继承方式说明符  基类名{
   ...
};

继承方式说明符可以是 public(公有继承)、private(私有继承)或 protected(保护继承)。一般都使用 public。protected 或 private 方式很少用到。

class CBase
{
    int v1, v2;
};
class CDerived: public CBase
{
    int v3;
};
(6)基类的构造函数和析构函数不被继承,但会被派生类的构造函数和析构函数自动调用
  • 派生类的构造函数先调用基类的构造函数,再执行自己的函数体
  • 派生类的析构函数先执行自己的函数体,再调用基类的析构函数
(4)调用方式的区别
  • 调用基类函数:
    • 基类名 :: 函数名
  • 调用派生类函数:
    • 直接调用
#include <iostream>
#include <string>
using namespace std;
class CStudent
{
private:
    string name;
    string id;  //学号
    char gender;  //性别,F 代表女,M 代表男
    int age;
public:
    void PrintInfo();
    void SetInfo(const string & name_, const string & id_, int age_, char gender_);
    string GetName() { return name; }
};
class CUndergraduateStudent : public CStudent  //本科生类,继承了 CStudent 类
{
private:
    string department;  //学生所属的系的名称
public:
    void QulifiedForBaoyan() {  //给予保研资格
        cout << "qulified for baoyan" << endl;
    }
    void PrintInfo() {
        CStudent::PrintInfo();  //调用基类的 PrintInfo 函数
        cout << "Department: " << department << endl;
    }
    void SetInfo(const string & name_, const string & id_, int age_,
            char gender_, const string & department_) {
        CStudent::SetInfo(name_, id_, age_, gender_);  //调用基类的 SetInfo 函数
        department = department_;
    }
};
void CStudent::PrintInfo()
{
    cout << "Name: " << name << endl;
    cout << "ID: " << id << endl;
    cout << "Age: " << age << endl;
    cout << "Gender: " << gender << endl;
}
void CStudent::SetInfo(const string &name_, const string & id_, int age_, char gender_)
{
    name = name_;
    id = id_;
    age = age_;
    gender = gender_;
}
int main()
{
    CStudent s1;
    CUndergraduateStudent s2;
    s2.SetInfo("Harry Potter", "118829212", 19, 'M', "Computer Science");
    cout << s2.GetName() << " ";
    s2.QulifiedForBaoyan();
    s2.PrintInfo();
    cout << "sizeof(string) = " << sizeof(string) << endl;
    cout << "sizeof(CStudent) = " << sizeof(CStudent) << endl;
    cout << "sizeof(CUndergraduateStudent) = " << sizeof(CUndergraduateStudent) << endl;
    return 0;
}
运行结果:
Harry Potter qulified for baoyan
Name: Harry Potter
ID: 118829212
Age: 19
Gender: M
Department: Computer Science
sizeof(string) = 4
sizeof(CStudent) = 16
sizeof(CUndergraduateStudent) = 20

3.继承关系中类指针的使用

  • 类名可以用来声明变量(类的对象)、数组、指针等
  • 类的指针可以操作类的对象,也可以操作派生类的对象 (派生类对象也是基类对象)
  • 派生类对象和基类对象可以通过指针统一操作和管理
  • 类指针操作类对象的几种可能 :
    • 基类指针操作基类对象(自然)
    • 派生类指针操作派生类对象(自然)
    • 基类指针操作派生类对象——把派生类对象作为基类对象看(安全)
    • 派生类指针操作基类对象——把基类对象作为派生类对象看(危险)

4.构建继承关系的线性表

(1)用链表实现可变长(整型)数组
// 文件VLArray.h: 类VLArray的定义
#pragma once
class Node {
	friend class VLArray;
private:
	int data;
	Node* next;
};
class VLArray {
public:
	VLArray();
	~VLArray();
	void set(int i, int m); //修改数组元素的值
	int getLength(); //获取数组的长度
	int get(int i); //获取数组第i个元素的值
	void del(int i); //删除数组的第i个元素
private:
	Node* head;
	int len;
};
// VLArray.cpp: 类VLArray的实现
#include <stdlib.h>
#include "VLArray.h"
VLArray::VLArray() {
	head = NULL;
	len = 0;
}
VLArray::~VLArray() {
	Node* p = head;
	while (p) {
		head = head->next;
		delete p;
		p = head;
	}
}
int VLArray::getLength() {
	return len;
}
int VLArray::get(int i) {
	if (i >= 0 && i < len) {
		Node* p = head;
		for (int k = 0; k < i; k++)
			p = p->next;
		return p->data;
	}
	return 0;
}
void VLArray::set(int i, int m) {
	if (len == 0) {
		//在空数组中插入第一个元素
		head = new Node;
		head->data = m;
		head->next = NULL;
		len++;
	}
	else if (i >= 0 && i < len) {
		//修改数组第i个元素
		Node* p = head;
		for (int k = 0; k < i; k++)
			p = p->next;
		p->data = m;
	}
	else if (i < 0) { //在数组首部插入元素
		Node* p = new Node;
		p->data = m;
		p->next = head;
		head = p;
		len++;
	}
	else { //在数组尾部插入元素
		Node* q = new Node;
		q->data = m;
		q->next = NULL;
		Node* p = head;
		for (int k = 0; k < len - 1; k++)
			p = p->next;
		p->next = q;
		len++;
	}
}
void VLArray::del(int i) {
	if (len == 0)
		return;
	if (i == 0) {
		Node* p = head;
		head = head->next;
		delete p;
		len--;
	}
	else if (i > 0 && i < len) {
		Node* p = head;
		for (int k = 0; k < i - 1; k++)
			p = p->next;
		Node* q = p->next;
		p->next = q->next;
		delete q;
		len--;
	}
}
// 文件ex.cpp: 使用类VLArray
#include "VLArray.h"
#include <iostream>
using namespace std;
int main() {
	int m, i;
	VLArray a;
	for (i = 0; i < 10; i++) {
		cin >> m;
		a.set(a.getLength(), m);
	}
	for (i = 0; i < a.getLength(); i++) {
		m = a.get(i);
		cout << m << " ";
	}
	cout << endl;
	a.del(4);
	for (i = 0; i < a.getLength(); i++)
	{
		m = a.get(i);
		cout << m << " ";
	}
	cout << endl;
	return 0;
}
运行结果:
1 2 3 4 5 6 7 8 9 0
1 2 3 4 5 6 7 8 9 0
1 2 3 4 6 7 8 9 0
(2)使用可变长数组构建栈类Stack
  • push(k):将整数k压栈
  • isEmpty():判断是否栈空
  • pop():弹出栈顶元素并返回,栈空则返回0
// 文件Stack.h: 类Stack的定义
#pragma once
#include "..\8_2\VLArray.h"
class Stack :protected
	VLArray {
public:
	Stack() {};
	~Stack() {};
	void push(int k);
	bool isEmpty();
	int pop();
};
// 文件Stack.cpp: 类Stack的实现
#include "Stack.h"
void Stack::push(int k) {
	set(-1, k);
}
bool Stack::isEmpty() {
	if (getLength() == 0)
		return true;
	return false;
}
int Stack::pop() {
	if (getLength() == 0)
		return 0;
	int m = get(0);
	del(0);
	return m;
}

// 文件8_3.cpp: 使用类Stack
#include "Stack.h "
#include <iostream>
using namespace std;
int main() {
	Stack s;
	int i, m;
	for (i = 0; i < 10; i++) {
		cin >> m;
		s.push(m);
	}
	for (i = 0; i < 10; i++) {
		m = s.pop();
		cout << m << " ";
	}
	cout << endl;
	return 0;
}
程序运行结果
1 2 3 4 5 6 7 8 9 0
0 9 8 7 6 5 4 3 2 1

5.多态性

多态性:具有继承关系的类,其对象对同一个函数调用可 以作出不同的响应

  • 静态类型与动态类型
    • 对象的静态类型:
      • 对象在声明时候的类型,是在编译时期确定的
    • 对象的动态类型:
      • 目标所指向的对象,是在运行期决定的。对象的动态类型可以更改,但是静态类型无法更改。
  • 静态绑定与动态绑定
    • 静态绑定:
      • 绑定的是静态类型,比如函数依赖于对象的静态类型,发生在编译期
      • 在编译时确定函数或方法的调用方式。
      • 适用于静态方法和非虚函数
      • 编译器会根据函数或方法的名称和参数类型来确定调用方式
    • 动态绑定:
      • 绑定的是动态类型,比如函数依赖于对象的动态类型,发生在运行期
      • 在运行时确定函数或方法的调用方式。
      • 适用于虚函数和多态。
      • 根据对象的类型来确定调用哪个函数或方法。
#include <iostream>
using namespace std;

//基类People
class People{
public:
    People(char *name, int age);
    void display();
protected:
    char *m_name;
    int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}

//派生类Teacher
class Teacher: public People{
public:
    Teacher(char *name, int age, int salary);
    void display();
private:
    int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}

int main(){
    People *p = new People("王志刚", 23);
    p -> display();

    p = new Teacher("赵宏佳", 45, 8200);
    p -> display();

    return 0;
}
运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是个无业游民。

我们直观上认为,如果指针指向了派生类对象,那么就应该使用派生类的成员变量和成员函数,这符合人们的思维习惯。但是本例的运行结果却告诉我们,当基类指针 p 指向派生类 Teacher 的对象时,虽然使用了 Teacher 的成员变量,但是却没有使用它的成员函数,导致输出结果不伦不类(赵宏佳本来是一名老师,输出结果却显示人家是个无业游民),不符合我们的预期。

换句话说,通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数


6.虚函数

为了上面代码的这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。

  • 虚函数的定义:

    • 函数原型前加上关键字virtual
    • 如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数(包括重定义函数) virtual void show();
  • 只有通过基类指针或引用调用虚函数才能引发动态绑定

#include <iostream>
using namespace std;

//基类People
class People{
public:
    People(char *name, int age);
    virtual void display();  //声明为虚函数
protected:
    char *m_name;
    int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}

//派生类Teacher
class Teacher: public People{
public:
    Teacher(char *name, int age, int salary);
    virtual void display();  //声明为虚函数
private:
    int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
    cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}

int main(){
    People *p = new People("王志刚", 23);
    p -> display();

    p = new Teacher("赵宏佳", 45, 8200);
    p -> display();

    return 0;
}
运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入。

有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)

Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
cout<<m_name<<“今年”<<m_age<<“岁了,是一名教师,每月有”<<m_salary<<“元的收入。”<<endl;
}

int main(){
People *p = new People(“王志刚”, 23);
p -> display();

p = new Teacher("赵宏佳", 45, 8200);
p -> display();

return 0;

}


```cpp
运行结果:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入。

有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值