C++是一款面向对象的语言,对象是可以物化的实体,拥有自己的特性与行为,像车子有外形,内核,可以加速减速。而车子又分为保时捷与宝马、奔驰等等。我们想要描述一个车子对象的特征,就需要创建一个类,若为每一种车创建一个类,会使效率下降,而不同车子之间既有共性又有特性,因此,便产生了派生类。
一、派生类的继承方式
派生类(子类)继承了基类(父类)的特性,同时它可以添加自己的诸多特点。继承的方式有以下三种
既:派生类对基类的继承小于等于基类的数据(包括成员变量与成员函数)
无论用那种方式继承,派生类对基类数据的访问都只限于public和protected区域,而不能访问private区域。如果要访问基类的private区域,可以在基类的public或protectd区域里面创建接口
#inlcude<iostream>
using namespace std;
class base
{
public:
int func()
{return m_a}
private:
int m_a;
};
class derived:public base
{
......
};
派生类derived正常情况下访问不到m_a,但添加了func这个接口,即可通过func来访问。
二、构造派生类对象
构造一个对象,首先调用的是构造函数,派生类继承了除构造函数和析构函数的其他非私有部分,但派生类仍是属于基类的,所以如果创建一个派生类对象,那么构造函数和析构函数的调用顺序如下
#include <iostream>
using namespace std;
class Base
{
public:
Base(int a)
{
ma = a;
cout<<"Base::base() ";
}
~Base()
{
cout<<"Base::~base() ";
}
private:
int ma;
Test t;
};
class Derive : public Base
{
public:
Derive(int b):Base(b)
{
mb = b;
cout<<"Derive::derive() ";
}
~Derive()
{
cout<<"Derive::~derive() ";
}
private:
int mb;
};
int main()
{
Derive d(2);
return 0;
}
Base::base() Derive::derive() Derive::~derive() Base::~base()
可知,派生类的创建先调用了基类的构造函数和其部分成员变量,再调用派生类构造函数及其部分成员变量,如果基类中还有对象,则应先调用成员对象的构造函数,其中,析构函数的调用顺序与构造函数相反。
三、多态的初步形成
如果派生类中有和基类中重命名的成员,则有三种可能性:重载,隐藏和覆盖。
1、重载
void Swap1(int* a, int* b);
void Swap2(float* a, float* b);
void Swap3(char* a, char* b);
void Swap4(double* a, double* b);
以上是重载的实现,C++允许在同一作用域中声明几个类似的同名函数,通过函数的参数个数,种类与排列顺序来区分。函数的返回值类型可相同可不同(函数的返回值类型不是区分函数的条件)。C++的函数重载是在编译时完成的,既C++编译器为根据参数的种类,数量或者排列顺序不同将函数区分开来。
2、函数隐藏
函数隐藏,是指C++中派生类覆盖基类同名成员函数(不考虑参数列表)
class Base
{
public:
Base(int a) :ma(a){}
void Show()
{
std::cout << "Base::ma:" << ma << std::endl;
}
protected:
int ma;
};
class Derived :public Base
{
public:
Derived(int b) :mb(b), Base(b){}
void Show()
{
std::cout << "Derived::mb:" << mb << std::endl;
}
protected:
int mb;
};
int main()
{
Derived a(2);
a.Show();
Base* pb = new Derived(10);
pb->Show();
return 0;
}
运行结果:Derived::mb:2
Base::ma:10
即:即使派生类继承了基类的show函数,但派生类中的show函数将基类中的show函数替换掉了,但如果用Base类的指针指向show函数,还是能找到Base类的原函数的,即使它指向的是一个派生类对象(派生类对象本就属于基类,基类指针优先在基类中寻找函数,找到所需函数即停止寻找)。
三、函数覆盖
class Base
{
public:
Base(int a) :ma(a){}
virtual void Show()
{
std::cout << "Base::ma:" << ma << std::endl;
}
protected:
int ma;
};
class Derived :public Base
{
public:
Derived(int b) :mb(b), Base(b){}
void Show()
{
std::cout << "Derived::mb:" << mb << std::endl;
}
protected:
int mb;
};
int main()
{
Base* pb = new Derived(10);
pb->Show();
return 0;
}
运行结果:Derived::mb:10
当父类函数show()为virtual函数时,调用它的对象类型为指针类型指向的类型,这是一个动态的过程,当类中出现virtual函数时,在编译时期会为对象产生一个虚函数表,将virtual的成员放在里面,我们已经知道,基类的构造早于派生类,所以在创建虚函数表时,将基类的Show函数先放入改表中,到派生类时,派生类中的Show函数(若基类存在virtual函数类型,则系统自认派生类函数为虚函数)将基类的Show函数覆盖,因此,我们用Base虚指针创建Derived对象,再调用其Show函数时,看到的是派生类中的Show函数。
在次,我对基类和派生类中一些指针和引用情况进行简述。
派生类对象可以赋值给基类对象,但基类对象就不可以赋值给派生类对象。好比保时捷肯定是一个车,但你不能见到一个车就说它是保时捷。
基类指针(引用)可以指向派生类对象,但只能访问派生类中基类部分的方法,不能访问派生类部分的方法,派生类指针(引用)不可以指向基类对象,解引用可能出错,因为派生类的一些方法可能基类没有。
四、虚函数
下面我们借助一段代码来说明虚函数的一些问题
class A
{
public:
virtual void foo() { cout << "A::foo() is called" << endl;}
};
class B: public A
{
public:
virtual void foo() { cout << "B::foo() is called" << endl;}
};
int main()
{A * a = new B();
a->foo(); }
我们运行程序,得到的会是B的foo函数,如果基类中foo函数不加virtual,那么A的指针将无法访问B中的foo,在将A中的foo定义为虚函数后,如果想用B创建的对象来访问A中的foo函数,在foo函数的前面加上A::作用域即可。
#include <iostream.h>
#include <string.h>
class Thing
{ public:
virtual void what_Am_I( ) {cout << "My name is Wangyibo./n";}
~Thing(){cout<<"Thing destructor"<<endl;}
};
class Person : public Thing
{
public:
virtual void what_Am_I( ) {cout << "I am Wangyibo./n";}
~Person(){cout<<"Person destructor"<<endl;}
};
void main( )
{
Thing *t =new Thing;
Person*x = new Person;
Thing* a[2];
a[0] = t;
a[1] = x;
for (int i=0; i<2; i++) array->what_Am_I( ) ;
delete a[0];
delete a[1];
return ;
}
该段代码中,派生类没有定义虚析构函数,会造成delete父类的指针时只调用父类的析构函数,而不调用子类的析构函数。(前面提到过,基类的构造函数和析构函数是不被派生类继承的,基类的指针也就无法指向派生类的析构函数),此时delete a[0],就不会调用虚析构函数,如果在派生类构造函数中存在内存释放,不被调用,就会造成内存泄漏。所以,我们需要在基类中的析构函数前加上virtual时,基类的指针就会先调用派生类的析构函数(存在于虚函数表中),再返回自己的基类中调用基类析构函数。也就是说虚函数的指针和引用可以找到对应函数,而不仅是执行定义类的函数。但,构造函数不能声明为虚函数,因为在执行构造函数前对象没有创建,因此编译时产生的虚函数表里没有构造函数,我们也无法得知调用那个构造函数了。
纯虚函数
纯虚函数的简单模板如下:
class base
{ public:
virtual base()=0;
};
base::base()
{}
纯虚函数可以是类中的任意函数,只要类中出现纯虚函数,则该类被称为抽象类,即不可以创建对象,只能作为基类模板使用。但即使是抽象类,编译器仍会编译改类,所以我们需要在类外对纯虚函数进行声明。
虚继承
虚继承主要解决的是一个派生类如果继承多个基类,同时基类中又有几类继承同一类的话,在创建对象时,会产生二义性。
class A{
protected:
int m_a;}
class B:virtual public A{
protected:
int m_b;
};
class C:virtual public A{
protected:
int m_c;
};
class D: public B, public C{
public:
void seta(int a){ m_a = a; }
void setb(int b){ m_b = b; }
void setc(int c){ m_c = c; }
void setd(int d){ m_d = d; }
private:
int m_d;
};
int main(){
D d;
return 0;
}
如本段代码,如果我们不在类B和类C继承A时加上virtual seta函数在运行时,将发生错误,因为它有两个可选的途径,这会造成二义性。添加virtual之后,D类创建对象时,则会选择最近的A类来创建,并且只产生一份m_a的数据。