C++笔记③继承与多态

23 阅读7分钟

一、继承

1.概念

  • 继承是利用已有的类定义新的类,被继承的为基类(父类),新类为派生类(子类),他们是is-a关系
  • 一个基类可以有多个派生类
  • 新类可以作为基类继续派生,形成类族
  • 直接基类、间接基类
  • 从一个类派生——单继承,有一个直接基类
  • 从多个类派生——多重继承,有多个直接基类

2.派生类

  • 继承基类的数据成员、成员函数
  • 产生新成员:新数据成员、新成员函数
  • 重新定义已有成员函数
  • 改变已有成员的访问属性

3.单继承

(1)定义

class 派生类名:继承方式 基类名 {派生类中的新成员}

(2)继承方式

public公有继承,protected保护继承,private私有继承(默认)

(3)成员构成

  • 继承基类的数据成员、成员函数,不包括构造和析构函数
  • 定义同名的成员来覆盖基类的数据成员和成员函数
  • 添加新的数据成员、成员函数

(4)访问属性

  • 基类的private和protected成员在派生类中不可访问
  • 公有继承:基类的public和protected成员在派生类中属性不变
  • 保护继承:基类的public和protected成员在派生类中有protected属性
  • 私有继承:基类的public和protected成员在派生类中有private属性

(5)派生类的构造函数

  • 定义:派生类构造函数名(总参数表):基类构造函数名(参数表) {派生类中新增数据成员初始化语句}。总参数表包括基类构造函数和新增数据成员初始化所需参数
  • 先调用基类的构造函数,初始化基类的数据成员,再执行派生类的构造函数初始化新增数据成员
  • 若无显式调用,则自动生成一个对基类的默认构造函数的调用
  • 显式调用只能在派生类构造函数的初始化成员列表中进行,可以调用基类中不带参数的默认构造函数,也可以调用合适的带参数的其他构造函数
  • 先执行派生类析构函数,再执行编译器自动调用的基类的析构函数
  • 在派生类的class中用using 基类名::基类名=基类定义的构造函数
  • 基类构造函数的参数默认值不会被派生类继承,但由默认参数导致的多个构造函数版本都会被派生类继承
  • 如果基类的某个构造函数被声明为私有成员函数,则不能在派生类中声明继承该构造函数
  • 如果派生类使用了继承基类构造函数,编译器就不会再为派生类生成默认构造函数
#include<iostream>
using namespace std;


class Shape {
	protected:
		int x, y;
	public:
		Shape();
		Shape(int x, int y);
		~Shape();
};
Shape::Shape() {
	x = y = 0;
	cout<<"Shape:constructor Shape()\n";
}
Shape::Shape(int x, int y) {
	this->x = x;
	this->y = y;
	cout<<"Shape:constructor Shape(int, int)\n";
}
Shape::~Shape() {
	cout<<"shape:destructor\n";
}

class Circle:public Shape {
	private:
		int r;
	public:
		Circle(int x);
		Circle(int x, int y, int r);
		void Show();
		~Circle();
};

Circle::Circle(int r) {
	this->r = r;
	cout<<"Circlr:constructor Circle(int)\n"; 
}
Circle::Circle(int x, int y, int r):Shape(x, y) {//调用 Shape 类的构造函数
	this->r = r;
	cout<<"Circlr:constructor Circle(int, int, int)\n"; 
}
Circle::~Circle() {
	cout<<"Circle:destructor\n";
}
void Circle::Show() {
	cout<<"A circle:";
	cout<<"("<<x<<","<<y<<")";
	cout<<","<<r<<endl;
}

int main() {
	Circle c1(2), c2(1, 1, 3);
	c1.Show();
	c2.Show();
	return 0;
}
//输出: 

//Shape:constructor Shape()
//Circlr:constructor Circle(int)
//Shape:constructor Shape(int, int)
//Circlr:constructor Circle(int, int, int)
//A circle:(0,0),2
//A circle:(1,1),3
//Circle:destructor
//shape:destructor
//Circle:destructor
//shape:destructor

(6)基类包含成员对象的派生类(代码、示例没看懂)

  • 派生类对象包含从基类继承来的数据成员,它们构成了“基类子对象
  • 基类中的私有成员,不允许在派生类成员函数中被访问,也不允许派生类的对象访问它们
  • 基类中的公有成员
  • 若是使用public继承方式,则成为派生类的公有成员,既可以在派生类成员函数中访问,也可以被派生类的对象访问
  • 若是使用private继承方式,则只能供派生类成员函数访问,不能被派生类的对象访问

(7)派生类重写基类成员函数(示例没看懂)

  • 重写发生时,基类中该成员函数的其他重载函数都将被屏蔽掉(就好像不存在一样),不能提供给派生类对象使用
  • 可以在派生类中通过using 类名::成员函数名;在派生类中“恢复”指定的基类成员函数(即去掉屏蔽),使之重新可用

4.多重继承(例子没看懂)

(1)定义:class 派生类名 :继承方式 基类名1,继承方式基类名2,{}; (2)派生类负责所有基类构造函数的调用,派生类名(总参数表):基类1名(参数表),基类2名(参数表) {派生类中新增的数据成员初始化语句}; (3)二义性:两种情况,①多个基类中有同名的成员,在派生类中访问同名成员时;②多个基类有一个共同的基类(祖先类),在派生类中访问祖先类的成员时。 (4)二义性解决方法:①使用限定名来访问基类的同名成员;②将祖先类设置为虚基类

5.赋值兼容原则

  • 在公有继承的情况下,一个派生类的对象可以作为基类对象来使用
  • 派生类的对象可以赋值给基类对象
  • 派生类的对象可以赋值给基类的引用
  • 派生类对象的地址可以赋值给指向基类对象的指针变量
void main() {
	Shape s(1, 1), &rs = s, *ps = &s;
	Circle c(3, 2, 2), &rc = c, *pc = &c;
	
	Shape sc = c, &rsc = c, *psc = &c;
}
#include <iostream>
using namespace std;

void MoveTo(Shape &s, int x, int y) {
	cout<<"Move "; s.Show();
	s.SetPos(x, y);
	cout<<"to (" <<x<<","<<y<<")\n";
} 
void main() {
	Shape s(1, 1);
	Circle c(3, 2, 2);
	
	MoveTo(s, 5, 5);
	cout<<"Now, "; s.Show(); cout<<endl;
	
	MoveTo(c, 8, 8);
	cout<<"Now, "; c.Show(); cout<<endl;	
}

6.联编

  • 在编译时或运行时确定函数调用所使用的函数体(代码)
  • 把函数名与其实现代码联系起来。把一个消息与一个方法联系起来
  • 根据实现阶段的不同分为:静态联编(编译时完成)早期联编、前联编;动态联编(运行时完成)晚期联编、后联编

(1)静态联编(没看懂

(2)虚函数(例子没

  • 声明:virtual 类型 函数名(参数表);虚函数声明只能出现在类声明中的成员函数原型声明中
  • 只有类的普通成员函数才能声明为虚函数,全局函数及静态成员函数不能声明为虚函数。
  • 虚函数可以在一个或多个派生类中被重复定义,因此它属于函数重载的情况。但是,这种函数重载与一般的函数重载是不同的
  • 虚函数在派生类中重新定义时必须与基类中的原型完全相同(函数名、参数个数、参数类型、参数顺序、返回值类型)

(3)多态性

  • 同样的消息(调用同名成员函数)被类的不同对象接收时导致完全不同的行为
  • 通过指向派生类对象的基类指针来调用虚函数的重载函数,实现运行时的多态性 1717913506956.png

7.纯虚函数

  • 声明:virtual 函数原型 =0;
  • 没有函数体

8.抽象类

  • 包含纯虚函数的类
  • 主要作用:为一个类族建立一个公共的接口,使它们能更有效的发挥多态性
  • 抽象类只能用作基类来派生新类,不能声明抽象类的对象,但可以声明抽象类的指针变量和引用变量
  • 抽象类中可以定义纯虚函数和普通函数,如果抽象类的派生类没有定义基类中的纯虚函数,则必须再将该函数声明为纯虚函数,那么此派生类也是一个抽象类。
#include <iostream>
using namespace std;

class Shape {
	public:
		virtual void Show() = 0;
		virtual double Area() = 0;
};

class Circle:public Shape {
	private:
		double r;
	public:
		Circle(double nr = 0) {
			r = nr;
		}
		void Show();
		double Area();
};
void Circle::Show() {
	cout<<"Circle("<<r<<")";
}
double Circle::Area() {
	return 3.14*r*r;
}
class Rectangle:public Shape {
	private:
		double l, w;
	public:
		Rectangle(double nl = 0, double nw = 0) {
			l = nl;
			w = nw;
		}
		void Show();
		double Area();
}; 
void Rectangle::Show() {
	cout<<"Rectangle("<<1<<","<<w<<")";
}
double Rectangle::Area() {
	return l*w;
}
void ShowShape(Shape *ps) {
	ps->Show();
}
double ShapeArea(Shape *ps) {
	return ps->Area();
}

int main() {
	Circle c(2.7);
	ShowShape(&c);
	cout<<"area is"<<ShapeArea(&c)<<endl;
	
	Rectangle r(5.3, 6.2);
	ShowShape(&r);
	cout<<"area is"<<ShapeArea(&r)<<endl; 
	return 0;
}
//输出
//Circle(2.7)area is22.8906
//Rectangle(1,6.2)area is32.86