c语言怎样实现继承(专题之继承与多态)
c语言怎样实现继承(专题之继承与多态)1.1类之间的关系继承是面向对象程序设计最主要的特征 可以说 没有掌握继承 就等于没有掌握类和对象的精华 就是没有掌握面向对象程序设计的真谛.正文黑色面向对象程序设计有4个主要特点 抽象 封装 继承 多态要更好地进行面向程序的设计 最重要的就是继承和多态 .本次主要给大家介绍继承和多态的相关知识.抽象和封装将在后续章节讲解.
写在前面:根据个人习惯将笔记进行颜色区分 为了方便大家阅读 做出简单说明
赠人玫瑰 手有余香.小羊伴你一路同行~~~~
补充说明部分 将会用绿色字体
重点将会用红色
正文黑色
面向对象程序设计有4个主要特点 抽象 封装 继承 多态
要更好地进行面向程序的设计 最重要的就是继承和多态 .本次主要给大家介绍继承和多态的相关知识.抽象和封装将在后续章节讲解.
继承是面向对象程序设计最主要的特征 可以说 没有掌握继承 就等于没有掌握类和对象的精华 就是没有掌握面向对象程序设计的真谛.
1.1类之间的关系
包含关系has-A:一个类中的数据成员已经是另外一个定义的类.一个类的属性由另外一个类组成
使用关系uses-A:一个类部分的使用另一个类.通过类之间成员函数的相互联系 定义友员或对象参数传递实现.
is-A 机制称为"继承".关系具有传递性 但不具有对称性.(这句话的意思就在说 c 程序员属于程序员 但是不是所有的程序员都是c 程序员)
继承是类之间定义的一种重要关系 一个B类继承A类 或称从A类派生B类 称A类为基类(父类) B类为派生类(子类)
继承关系简单图示
万事万物当中 都有继承.例子有很多 这里不再赘述.
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 <iostream>
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<Student*> (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) { ... }
};
除了构造和析构函数以外,所有的成员函数都是纯虚函数的类称为纯抽象类.
当然关系继承和多态的知识 远远不止于此.以后会继续补充的 感谢观看.