在面向对象里面有个非常重要的概念:继承与派生,这两个其实说的是一回事。
现在我引入一个新名词:“可重用性”。
而重用性好指的是有一部分代码实现了某个功能,而当你要新写一个新代码(新代码也要实现相同功能),这个时候你就不用重新再写,你把原来的代码拿来用就行了。
继承:
在你要定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),
那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称子类)。而A与B·重复的部分我们就不用再写一遍了。
派生类拥有基类的全部特点,并且派生类是通过对基类进行修改和扩充得到的。
“扩充”,就是在派生类中,可以扩充新的成员变量和成员函数。
“修改”,就是在派生类中,编写与基类函数名一样的函数,而这个函数的功能与原来的不同,这就是修改。
!
派生类一经定义后,可以独立使用,不依赖于基类。这句话什么意思呢?
它说的是:“当你用派生类定义对象的时候,可以放心大胆的使用,这个时候基类 ‘ 与我无关!’ 。”
注意事项:
(1) 派生类拥有基类的全部成员函数和成员变量,不论是pr ivate、protected、 public。
(2)在派生类的各个成员函数中,不能访问基类中的private成员。
派生类对象中的内存问题:
派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。(注意这个自己)在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。
!派生类的创造函数
在创建派生类的对象时:
(1) 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;
(2)再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。
(3)最后执行派生类自己的构造函数
!派生类的析构函数
在派生类对象消亡时:
1)先执行派生类自己的析构函数
2)再依次执行各成员对象类的析构函数
3)最后执行基类的析构函数
注意!!!----》析构函数的调用顺序与构造函数的调用顺序相反。
开始新的知识点:虚函数和多态
!多态可以说是面向对象里面一个非常重要的机制。它主要是用来提高程序的可扩充性。现在先不解释可扩充性什么意思,卖个关子,在此先“声明”,至于“定义”就留到后头。
敲黑板,重点来了!拿笔和本子开始记了
!!!虚函数:
(1)在类的定义中,前面有virtual 关键字的成员函数就是虚函数。
例如:
class base {
virtual string get();
//注意这个“virtual”
};
int base:get() { }
(2)virtual 关键字只用在类定义里的函数声明中,l类外写函数体时不需要用到关键字了。
(3)注意了(!!!):构造函数和静态成员函数一定不能是虚函数。
什么是多态?
首先我们要知道派生类的指针可以赋给基类指针。
那么通过基类指针调用基类和派生类中的同名虚函数时:
(1) 若该指针指向一个基类的对象时:
那么被调用是基类的虚函数;
(2) 若该指针指向一个派生类的对象时:
那么被调用的是派生类的虚函数。
这种机制就叫做“多态”。当然还有其他的方法表示多态,这个方式我称它为:
“指针多态大法”, 这么样?有没有种武功秘籍的感觉?
既然说是“武功秘籍”了,那么就一定有练武奇才,还有练武 “不太奇” 才,没关系啦!在我这那是包学包会,一个不也拉下,把你们从学海深处都给捞上来。
有没有同志觉得这个新知识点和我前面提到的不一样啊?它这么就这么“关谷神奇”呢?这么就能区别要调用同名里的哪一个?
我觉得解释再多也不如来场实战,举个例子吧。
class Human{
private:
略
public:
virtual void gorw_up(){年龄加一}
};
class Human_adults:public Human{
private:
略
public:
vietual void gorw_up(){年龄加一}
};
int main() {
Human_adults xiaoM;
Human *it = & xiaoM; // 基类指针指向派生类对象
p -> gorw_up(); // 用基类指针调用同名函数
// 重点!!! 调用哪个虚函数取决于p指向哪个类型的对象
return 0;
}
派生类的对象还可以赋给基类引用
通过基类引用调用基类和派生类中的同名虚函数时:
(1)若该引用引用的是一个基类的对象,那么被调用是基类的虚函数;
(2)若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制也叫做“多态”,同理,这个叫作:“引用多态大法”。至于两种大法哪个更强,看个人情况吧,就得自己适合哪个,就用哪个。(没有无敌的功法,只有无敌的人!)
接着实战:
class Xiaoshuo{
private:
略
public:
virtual void yanqing(){}
};
class SanGuoYy:public Xiaoshuo{
private:
略
public:
virtual void yanqing(){}
};
int main(){
sanguoYy a;
Xiaoshuo &it = a; // 基类引用派生类对象
it.yanqing(); // 调用哪个虚函数取决于it引用哪个类型的对象。
return 0;
}
!还有看不懂的吗?应该还是有滴。没事,不要气馁,郭靖不也是大器晚成类型的吗?咱最不怕不懂,就怕不肯努力!功夫不到家,没事,那就先不回家,他奈我何?
class A [
public :
virtual void Print( ) {cout << "A::Print"<<endl ;}
};
class B: public A[
public :
virtual void Print( ) { cout << "B::Print" <<endl; }
};
class D: publicA[
public:
virtual void Print( ) { cout «< "D::Print" << endl ;}
};
class E: public B
public:
virtual vỏid Print( ){ cout << "E::Print" << endl ;}
};
int main(){
A a;
B b;
E e;
D d;
A * pa= &a;
B * pb= &b;
D * pd= &d;
E * pe= &e;
pa->Print(); //a.Print()被调用,输出: A::Print
pa= pb;
pa -> Print(); // b.Print()被调用,输出: B::print
pa= pd;
pa -> Print(); // d.Print()被调用,输出:D::Print
pa = pe;
pa -> Print(); // e.Print()被调用,输出:E::Print
return 0;
}
说了这么久多态,那么多态的作用是什么?读书为了金榜题名,练武为了冲锋陷阵,那么学习多态为了啥啊?
在面向对象的程序设计中使用多态,能够增强程序的 “ 可扩充性 ” ,即程序需要修改或增加功能的时候,只需要改动和增加较少的代码即可(课本是这么说的,虽然很枯燥无味!)
用句通俗的话来说,多态就是为了偷懒而生滴。我们只需要知道:多态可以让一个函数可以分情况实现多种功能就行了。
!学了虚函数,就有比较特殊的虚函数等着你哦。现在我介绍这个虚函数里的奇葩之一:“虚析构函数”。
------什么是虚析构函数?从名字就能看出来:虚函数+析构函数。就这么简单!
!学习它之前,我先说下什么情况下用它,别学了半天发现:“学这个有啥用?”
在通过基类的指针删除派生类对象时,通常情况下只(没错,你发现了吧,“只”)调用基类的祈构函数。
(这个析构不完整)
但是,(注意这个但是!)删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。
解决办法 ---->> 把基类的析构函数声明为virtual,而派生类的析构函数可以virtual不进行声明。
(你知道为什么吗?)
因为基类的析构函数已经声明为:virtual,那么派生类的析构函数就自动被声明为:virtual
通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数.
也许有好奇宝宝问了:既然有虚析构函数,那么有没有虚构造函数?
???很遗憾的告诉你:没有!而且不允许以虚函数作为构造函数!!!
我觉得多半有人对我上面那句:“这个析构不完整”感到疑惑。所以分享给大家我看的面向对象网课里的一个例子,保准一看就懂!
class son{
public:
~son() {cout < <"bye from son"< <endl; }
};
class grandson :public son {
public:
~grandson() {cout< <"bye from grandson"<<endl; }
};
int main () [
son *pson;
pson=new grandson() ;
delete pson;
return 0;
}
输出:bye from son 没有执行 grandson::~grandson() !!!
解决办法:
class son{
public:
virtaul ~son() {cout < <"bye from son"< <endl; }
};
// 只需要把基类的析构函数声明为虚函数就OK了。
~~~~~其他的不变
输出结果:
bye from grandson
bye from son
从输出结果看,你就知道函数被调用的先后了:
执行: grandson::~grandson() , 引起执行: son::~son() ! ! !
趁热打铁,再介绍虚函数世界里的另一个奇葩:纯虚函数
还有一个名词:抽象类
纯虚函数:没有函数体的虚函数。
既然不存在函数体,那你也就不需要再类外定义函数体了。
纯虚函数也可以有成员变量和成员函数,不是只有纯虚函数。
class A{
pivate:
int a;
public:
virtual void A_B_C() = 0 ; // “ = 0 ” , 纯虚函数 !!!
void D_E_F() {} // 函数体为空,但不是纯虚函数
};
有人也许纳闷了,函数体都没有,那纯虚函数有什么用?
它是有用的,在说之前先把一些概念说完。
!包含 纯虚函数 的类叫 抽象类。
抽象类与一般类的区别:
(1)抽象类“只能”作为 / 基类 / 来 / 派生新类使用,不能“单独”创建抽象类的独立对象。
而抽象类的对象也存在,但它一定被包含在一个派生类对象里。
抽象类的指针和引用可以指向由抽象类派生出来的类的对象.。
A a; //错,A是抽象类,不能创建对象
A *pa; //ok,可以定义抽象类的指针和引用
pa = new A; //错误,A是抽象类,不能创建对象
以下是要注意的重点,认真记好,不然小心练功走火入魔!!!(编译出错)
!!!在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部
不能调用纯虚函数。
解释:
在成员函数内部调用虚函数是多态,而在构造函数或析构函数内部调用虚函数不是多态;
是多态就可以调用纯虚函数,不是多态就不能调用纯虚函数。
!!!如果一个类从抽象类派生而来,那么 / 当且仅当 / 它 / 实现了基类中的 / 所有 / 纯虚函
数,它才能成为非抽象类。
解释:
在派生类里写一个和基类同名同参数表的函数,并且这个函数是有函数体的,不再是纯虚函数。
而基类的所以纯虚函数都在派生类内实现了,这个派生类就不是抽象类了。
上面这几句话确实有点难懂,说不定有人看到这直接不想看跳过了,我觉得还是接着举例子吧,简单明了点。
class A {
public :
virtual void f() = o ; // 纯虚函数
void g( ) { this->f( ) ; //ok
}
A() { // f(); // 错误
}
};
// calss B的对象可以包含class A的对象,但是独立的class A对象是不存在的。
// 所以---- 当程序执行到: this -> f()时,它是通过哪个类走这一步的呢?
// 结合上上句,我们可以知道:那一步是通过A的派生类实现的。而这时的f()就不是基类里的,是派生类里的f();
class B:public A{
public :
void f() { cout<<"B:f() "<<endl; }
};
int main () {
B b;
b.g() ;
return 0;
}
输出结果:
B:f()
总结一下这个知识点:
在成员函数里定义纯虚函数是没有问题的,因为这样的调用语句是多态,真正被执行的语句是派生类的那个对应的虚函数,不可能是基类那个没有函数体的纯虚函数。
而如果实在函数里的构造函数或者析构函数里调用纯虚函数,编译的时候就会出错。
因为在构造(析构)函数里调用虚函数不是多态,而既然不是多态,调用的虚函数只能是基类的那个纯虚函数,但是它没有函数体,所以编译出错。
感谢各位老板坚持看到这,虽然这份博客有许多问题,漏洞,但它确实是我花费很长时间写出来的。
如果有大佬发现这份博客的错误,可以在评论区指出来,我一定会及时修改!麻烦了!!!
希望各位读者可以为这篇博客点个赞!!!它将会激励我的学习动力!!!再次感谢!
也希望我的博客对大家的学习有帮助!
再见了。