c++专题之继承与多态

写在前面:根据个人习惯将笔记进行颜色区分,为了方便大家阅读,做出简单说明

赠人玫瑰,手有余香.小羊伴你一路同行~~~~

补充说明部分,将会用绿色字体

重点将会用红色

正文黑色

面向对象程序设计有4个主要特点 抽象 封装 继承 多态

要更好地进行面向程序的设计,最重要的就是继承和多态 .本次主要给大家介绍继承和多态的相关知识.抽象和封装将在后续章节讲解.

继承是面向对象程序设计最主要的特征,可以说,没有掌握继承,就等于没有掌握类和对象的精华,就是没有掌握面向对象程序设计的真谛.

1.1类之间的关系

包含关系has-A:一个类中的数据成员已经是另外一个定义的类.一个类的属性由另外一个类组成

使用关系uses-A:一个类部分的使用另一个类.通过类之间成员函数的相互联系,定义友员或对象参数传递实现.

is-A 机制称为"继承".关系具有传递性,但不具有对称性.(这句话的意思就在说,c++程序员属于程序员,但是不是所有的程序员都是c++程序员)

继承是类之间定义的一种重要关系,一个B类继承A类,或称从A类派生B类,称A类为基类(父类),B类为派生类(子类)

c++专题之继承与多态

继承关系简单图示

万事万物当中,都有继承.例子有很多,这里不再赘述.

1.2继承的语法

class 子类名 : 继承方式1 基类(父类)1, 继承方式2 基类2, ... {

...

};

继承方式:

公有继承 - public - 最常用方式

私有继承 - private - 缺省方式

保护继承 - protected - 特殊的私有继承

公有继承

1.通过继承,在基类中定义的任何成员,也都成为了子类的成员,但是基类的私有成员,子类虽然拥有却不能直接访问。(可以比如是爸爸的配偶,子类不能继承父类的配偶)

2.基类中的保护成员,可以被子类直接访问,但不能在无关的类和全局域中被访问。(爸爸的银行密码)

3.任何一个子类对象中都包含着它的基类子对象。如果在子类的构造函数中没有明确指明其基类子对象如何被构造,系统将采用无参的方式构造该子对象。如果在初始化表中指明了基类子对象的构造方式,就调用相应的构造函数构造该子对象。

4.子类对象的构造和析构顺序

按照继承表的顺序依次构造每个基类子对象->按照声明的顺序依次构造每个成员变量->执行子类构造函数体中的代码

析构的过程与构造严格相反

5.一个子类对象在任何都可以被视为它的基类对象——IsA。

任何时候,一个子类对象的指针或者引用,都可以被隐式地转换为它的基类类型的指针或者引用,但是反过来,将基类类型的指针或者引用转换为它的子类类型必须显示地(static_cast)完成。

Student s (...);

Human* h = &s; // OK !

6.在子类中定义的任何和基类成员同名的标识符,都可以将基类中的该成员隐藏起来。通过作用域限定操作符“::”,可对该成员解隐藏。

私有继承和保护继承

用于防止或者限制基类中的公有接口被从子类中扩散。

class DCT {
public:
void codec (void) { ... }
};
class Jpeg : protected DCT {
public:
void render (void) {
codec (...);
}
};
Jpeg jpeg;
jpeg.codec (...); // ERROR !
//Jpeg Has A DCT,实现继承
class Jpeg2000 : public Jpeg {
public:
void render (void) {
codec (...); // OK !
}
};
八、多重继承

从多于一个基类中派生子类。

电话 媒体播放器 计算机

1.多重继承的语法和语义与单继承并没有本质的区别,只是子类对象中包含了更多的基类子对象。它们在内存中按照继承表的先后顺序从低地址到高地址依次排列。

2.子类对象的指针可以被隐式地转换为任何一个基类类型的指针。无论是隐式转换,还是静态转换,编译器都能保证特定类型的基类指针指向相应类型基类子对象。但是重解释类型转换,无法保证这一点。

3.尽量防止名字冲突。

继承方式对访控属性的影响

子类继承了父类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成员的访问属性,在派生的过程中是可以调整的

c++中继承方式对子类访问属性的影响

1)public继承:父类成员在子类中保持原有访问级别.

private继承;父类成员在子类中会变成private成员.

protected继承:父类中public成员会变成protected

父类中protected成员会变成protected

父类中private成员仍然为private

2)private成员在子类中依然存在,但是却无法访问到,不论哪种方式继承,派生类都不能直接使用基类的私有成员.

3)"三看"原则

1看调用语句,这句话写在子类的内部,还是外部

2看子类如何从父类中继承

3看父类中的访问级别

继承中的构造和析构

直接来一个例子吧

#include 
using namespace std;
class Human {
public:
Human (const string& name, int age) :
m_name (name), m_age (age) {} //构造函数
void who (void) const {
cout << m_name << "," << m_age << endl;
}
void eat (const string& food) const {
cout << "我在吃" << food << endl;
}
protected:
string m_name;
int m_age;
};


class Student : public Human {
public:
Student (const string& name, int age, int no) :
Human (name, age), m_no (no) {} //正确的构造函数创建Student类的对象的同时先创建基类Huamn类,所以会调用Human类的构造函数,这里指定Human所调用的构造函数与所写的构造函数相匹配,所以不会出错。
/*
Student (const string& name, int age, int no) :
m_no (no) {
m_name=name;
m_age=age;
}
*/ 错误的构造函数会报错,原因是这里调用Human的构造函数时没有指定方式,默认使用无参构造,但是在基类Human类中没有无参构造,所以会报错



Student (const Student& that) :
Human (that), m_no (that.m_no) {} //拷贝构造,显式的指明了调用基类的拷贝构造函数

Student& operator= (const Student& that) { //操作符重载
if (&that != this) {
Human::operator= (that);//显式的调用基类的拷贝赋值
m_no = that.m_no;
}
return *this;
}
void learn (const string& lesson) const {
cout << "我(" << m_name << "," << m_age
<< "," << m_no << ")在学" << lesson
<< endl;
}
using Human::eat;//如果没有这句,则Human的eat与这里的eat作用域不再一起,则下面的eat不会与Human中的eat构成重载,而是构成隐藏关系
//但是有了这句之后,将Human的eat在这里可见,既作用域也被声明在这里,则两个eat构成了重载关系

void eat (void) const {
cout << "我绝食!" << endl;
}
// int eat;
private:
int m_no;
};
int main (void) {
Student s1 ("张飞", 25, 1001);
s1.who ();
s1.eat ("包子");
s1.learn ("C++");
Human* h1 = &s1;//子类的指针可以隐式转换为基类的指针,因为访问范围缩小了,是安全的
h1 -> who ();
h1 -> eat ("KFC");
// h1 -> learn ("C");//基类的指针或对象不可以访问子类中的成员
Student* ps = static_cast (h1);//基类的指针不可以隐式的转换为子类的指针,因为访问范围扩大,不安全,所以必须显式的进行转换,但是这样有风险
ps -> learn ("C");
Student s2 = s1;
s2.who (); //子类的指针或对象可以方位基类的成员
s2.learn ("英语");
Student s3 ("赵云", 20, 1002);
s3 = s2;
s3.who ();
s3.learn ("数学");
return 0;
}


虚函数与多态

如果将基类中的一个成员函数声明为虚函数,那么子类中的同型函数就也成为虚函数,并且对基类版本形成覆盖。这时,通过一个指向子类对象的基类指针,或者一个引用子类对象的基类引用,调用该虚函数时,实际被调用的函数不由该指针或引用的类型决定,而由它们的目标对象决定,最终导致子类中覆盖版本被执行。这种现象称为多态。

虚函数

1 通过虚函数表指针VPRT调用重写函数是在程序上运行的,因此需要通过寻址操作上才能确定真正应该调用的函数,而普通成员的函数是在编译的时候就确定了调用的函数,在效率上,虚函数的效率会低很多

2 这就是说,我们可以把所有成员函数声明成虚函数,但是没有必要,因为执行效率太低.

3 c++编译器,执行howtoprint函数,不需要区分是子类对象还是父类对象

1什么是多态?

调用同样的语句,有多种不同的表现形式.

2多态实现的三个条件

有继承

有virtual重写

有父类指针(引用)指向子类对象

3多态的c++实现

virtual关键字告诉编译器,这个函数要支持多态,不是根据指针类型判断如何调用,而是要根据指针所指向的实际对象类型来判断如何调用.

4多态的理论基础

动态联编, 静态联编.根据实际情况来判断重写函数的调用

5多态的意义

设计模式的基础

6实现多态的理论基础

函数指针做函数参数

下面就来一个多态的例子吧

#include"iostream"
using namespace std;
class Liberation
{
public:
	virtual int Power()
	{
		return 70;
	}
};
class Armed :public Liberation
{
public:
	int Power()
	{
		return 90;
	}
};
class  Pleice :public Liberation
{
public:
	int Power()
	{
		return 170;
	}
};
class Thief
{
public:
	int Attack()
	{
		return 112;
	}
};
void objPlay(Liberation &sun, Thief &di)
{
	if (sun.Power() < di.Attack())
	{
		cout << "小偷好厉害呀,好怕怕" << endl;
	}
	else
	{
		cout << "警察胜利啦,啦啦啦" << endl;
	}
}
void main()
{
	Liberation lib;
	Armed arm;
	Pleice ple;
	Thief thi;
	objPlay(lib, thi);
	objPlay(arm, thi);
	objPlay(ple, thi);

	system("pause");
	return;
}

纯虚函数、抽象类、纯抽象类

形如:

virtrual 返回类型 成员函数名 (形参表) = 0;

的虚函数被称为纯虚函数。

一个包含了纯虚函数类称为抽象类,抽象类不能实例化为对象。

如果一个类继承自抽象类,但是并没有为其抽象基类中的全部纯虚函数提供覆盖,那么该子类就也是一个抽象类。

class A { // 纯抽象类

virtual void foo (void) = 0;

virtual void bar (void) = 0;

virtual void fun (void) = 0;

};

class B : public A { // 抽象类

void foo (void) { ... }

};

class C : public B { // 抽象类

void bar (void) { ... }

};

class D : public C { // 具体类

void fun (void) { ... }

};

除了构造和析构函数以外,所有的成员函数都是纯虚函数的类称为纯抽象类.

当然关系继承和多态的知识,远远不止于此.以后会继续补充的,感谢观看.

展开阅读全文

页面更新:2024-03-12

标签:子类   编译器   重写   指针   程序设计   程序员   函数   顺序   属性   定义   对象   成员   类型   关系   方式   专题   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top