虚基类 、虚函数、 纯虚函数和抽象类
2015-03-18 12:28
211 查看
一:虚基类
在《多继承》中讲过的例子中,由类A,类B1和类B2以及类C组成了类继承的层次结构。在该结构中,类C的对象将包含两个类A的子对象。由于类A是派生类C两条继承路径上的一个公共基类,那么这个公共基类将在派生类的对象中产生多个基类子对象。如果要想使这个公共基类在派生类中只产生一个基类子对象,则必须将这个基类设定为虚基类。
虚基类的引入和说明
前面简单地介绍了要引进虚基类的原因。实际上,引进虚基类的真正目的是为了解决二义性问题。
虚基类说明格式如下:
virtual <继承方式><基类名>
其中,virtual是虚类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。例如:
class A
{
public:
void f();
protected:
int a;
};
class B : virtual public A
{
protected:
int b;
};
class C : virtual public A
{
protected:
int c:
};
class D : public B, public C
{
public:
int g();
private:
int d;
};
由于使用了虚基类,使得类A,类B,类C和类D之间关系用DAG图示法表示如下:
A{ f(), a }
/ /
B{b} C{c}
/ /
D{g(),d}
从该图中可见不同继承路径的虚基类子对象被合并成为一个对象。这便是虚基类的作用,这样将消除了合并之前可能出现的二义性。这时,在类D的对象中只存在一个类A的对象。因此,下面的引用都是正确的:
D n;
n.f(); //对f()引用是正确的。
void D::g()
{
f(); //对f()引用是正确的。
}
下面程序段是正确的。
D n;
A *pa;
pa = &n;
其中,pa是指向类A对象的指针,n是类D的一个对象,&n是n对象的地址。pa=&n是让pa指针指向类D的对象,这是正确的,并且也无二义性。
虚基类的构造函数
前面讲过,为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最派生类。C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。
从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数 的调用。但是,只有用于建立对象的那个最派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的对象只初始化一次。
C++又规定,在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。
下面举一例子说明具有虚基类的派生类的构造函数的用法。
#include <iostream.h>
class A
{
public:
A(const char *s) { cout<<s<<endl; }
~A() {}
};
class B : virtual public A
{
public:
B(const char *s1, const char *s2):A(s1)
{
cout<<s2<<endl;
}
};
class C : virtual public A
{
public:
C(const char *s1, const char *s2):A(s1)
{
cout<<s2<<endl;
}
};
class D : public B, public C
{
public:
D(const char *s1, const char *s2, const char *s3, const char *s4)
:B(s1, s2), C(s1, s3), A(s1)
{
cout<<s4<<endl;
}
};
void main()
{
D *ptr = new D("class A", "class B", "class C", "class D");
delete ptr;
}
该程序的输出结果为:
class A
class B
class C
class D
在派生类B和C中使用了虚基类,使得建立的D类对象只有一个虚基类子对象。
在派生类B,C,D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数。
在建立类D对象时,只有类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,并且仅调用一次,而类D基类的构造函数的成员初始化列表中列出的虚基类构造函数不被执行。这一点将从该程序的输出结果可以看出。
二:虚函数
1)虚函数主要是实现运行时多态。虚函数的声明智能出现在类定义中的函数原型声明中,2)而不能在成员函数实现的时候。
2)只有使用指针或者引用的方式调用虚函数时,虚函数才能起到运行时多态的作用。
3)在程序中如果派生类没有显式的给出虚函数的声明,系统会根据以下规则来判断派生类中的函数成员是否是虚函数:
*是否与基类的虚函数有相同的名称
*是否与基类的虚函数有相同的参数个数及相同的对应参数类型。
*相同的返回值
4)只有虚函数是动态绑定的,如果派生类需要修改基类的行为,就应该将基类中相应的函数声明为虚函数。
5)再重写继承来的虚函数时,如果函数有默认的形参值,千万不要重新定义不同的值。原因是:虽然虚函数是动态绑定的,但默认值是静态绑定的。
6)在c++中不能声明虚构造函数,但可以声明虚析构函数,如果一个类的析构函数声明为虚函数,那么他派生而来的所有子类的析构函数也是虚函数。保证使用基类类型指针就能够调用适当的析构函数针对不同的对象进行清理工作。
#include
using namespace std;
class Base
{
public:
virtual ~Base(){cout<<"base destruction"<<endl;}
};
class Derived:public Base
{
public:
Derived();
~Derived();
private:
int *i_pointer;
};
Derived::Derived()
{
i_pointer = new int(0);
};
Derived::~Derived()
{
cout<<"Derived destruction"<<endl;
delete i_pointer;
};
void fun(Base *b)
{
delete b;
}
int main()
{
Base *b = Derived();
fun(b);
return 0;
}
如果父类没声明为虚函数,则不会调用子类的析构函数,造成内存泄露。
三:纯虚函数
纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
…
};
在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。下面给出一个纯虚函数的例子。
#include <iostream.h>
class point
{
public:
point(int i=0, int j=0) { x0=i; y0=j; }
virtual void set() = 0;
virtual void draw() = 0;
protected:
int x0, y0;
};
class line : public point
{
public:
line(int i=0, int j=0, int m=0, int n=0):point(i, j)
{
x1=m; y1=n;
}
void set() { cout<<"line::set() called./n"; }
void draw() { cout<<"line::draw() called./n"; }
protected:
int x1, y1;
};
class ellipse : public point
{
public:
ellipse(int i=0, int j=0, int p=0, int q=0):point(i, j)
{
x2=p; y2=q;
}
void set() { cout<<"ellipse::set() called./n"; }
void draw() { cout<<"ellipse::draw() called./n"; }
protected:
int x2, y2;
};
void drawobj(point *p)
{
p->draw();
}
void setobj(point *p)
{
p->set();
}
void main()
{
line *lineobj = new line;
ellipse *elliobj = new ellipse;
drawobj(lineobj);
drawobj(elliobj);
cout<<endl;
setobj(lineobj);
setobj(elliobj);
cout<<"/nRedraw the object.../n";
drawobj(lineobj);
drawobj(elliobj);
}
四:抽象类
带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。
抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。
抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
抽象类是为了抽象和设计的目的建立的,建立抽象类是为了通过它多态的使用其中的成员函数。抽象类处于类层次的上层,抽象类无法实例化。
1) 纯虚函数:是一个在基类中声明的虚函数,在基类中没有定义具体的操作内容。
virtual 函数类型 函数名(参数表)=0;
2)带有纯虚函数的类为抽象类,抽象类不能实例化,但我们可以声明一个抽象类的指针和引用,通过指针和引用,我们就看就可以访问派生类对象,这种访问具有多态性特征。
在《多继承》中讲过的例子中,由类A,类B1和类B2以及类C组成了类继承的层次结构。在该结构中,类C的对象将包含两个类A的子对象。由于类A是派生类C两条继承路径上的一个公共基类,那么这个公共基类将在派生类的对象中产生多个基类子对象。如果要想使这个公共基类在派生类中只产生一个基类子对象,则必须将这个基类设定为虚基类。
虚基类的引入和说明
前面简单地介绍了要引进虚基类的原因。实际上,引进虚基类的真正目的是为了解决二义性问题。
虚基类说明格式如下:
virtual <继承方式><基类名>
其中,virtual是虚类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。例如:
class A
{
public:
void f();
protected:
int a;
};
class B : virtual public A
{
protected:
int b;
};
class C : virtual public A
{
protected:
int c:
};
class D : public B, public C
{
public:
int g();
private:
int d;
};
由于使用了虚基类,使得类A,类B,类C和类D之间关系用DAG图示法表示如下:
A{ f(), a }
/ /
B{b} C{c}
/ /
D{g(),d}
从该图中可见不同继承路径的虚基类子对象被合并成为一个对象。这便是虚基类的作用,这样将消除了合并之前可能出现的二义性。这时,在类D的对象中只存在一个类A的对象。因此,下面的引用都是正确的:
D n;
n.f(); //对f()引用是正确的。
void D::g()
{
f(); //对f()引用是正确的。
}
下面程序段是正确的。
D n;
A *pa;
pa = &n;
其中,pa是指向类A对象的指针,n是类D的一个对象,&n是n对象的地址。pa=&n是让pa指针指向类D的对象,这是正确的,并且也无二义性。
虚基类的构造函数
前面讲过,为了初始化基类的子对象,派生类的构造函数要调用基类的构造函数。对于虚基类来讲,由于派生类的对象中只有一个虚基类子对象。为保证虚基类子对象只被初始化一次,这个虚基类构造函数必须只被调用一次。由于继承结构的层次可能很深,规定将在建立对象时所指定的类称为最派生类。C++规定,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始列表中必须列出对虚基类构造函数的调用。如果未被列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。
从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数 的调用。但是,只有用于建立对象的那个最派生类的构造函数调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数调用在执行中被忽略,这样便保证了对虚基类的对象只初始化一次。
C++又规定,在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。
下面举一例子说明具有虚基类的派生类的构造函数的用法。
#include <iostream.h>
class A
{
public:
A(const char *s) { cout<<s<<endl; }
~A() {}
};
class B : virtual public A
{
public:
B(const char *s1, const char *s2):A(s1)
{
cout<<s2<<endl;
}
};
class C : virtual public A
{
public:
C(const char *s1, const char *s2):A(s1)
{
cout<<s2<<endl;
}
};
class D : public B, public C
{
public:
D(const char *s1, const char *s2, const char *s3, const char *s4)
:B(s1, s2), C(s1, s3), A(s1)
{
cout<<s4<<endl;
}
};
void main()
{
D *ptr = new D("class A", "class B", "class C", "class D");
delete ptr;
}
该程序的输出结果为:
class A
class B
class C
class D
在派生类B和C中使用了虚基类,使得建立的D类对象只有一个虚基类子对象。
在派生类B,C,D的构造函数的成员初始化列表中都包含了对虚基类A的构造函数。
在建立类D对象时,只有类D的构造函数的成员初始化列表中列出的虚基类构造函数被调用,并且仅调用一次,而类D基类的构造函数的成员初始化列表中列出的虚基类构造函数不被执行。这一点将从该程序的输出结果可以看出。
二:虚函数
1)虚函数主要是实现运行时多态。虚函数的声明智能出现在类定义中的函数原型声明中,2)而不能在成员函数实现的时候。
2)只有使用指针或者引用的方式调用虚函数时,虚函数才能起到运行时多态的作用。
3)在程序中如果派生类没有显式的给出虚函数的声明,系统会根据以下规则来判断派生类中的函数成员是否是虚函数:
*是否与基类的虚函数有相同的名称
*是否与基类的虚函数有相同的参数个数及相同的对应参数类型。
*相同的返回值
4)只有虚函数是动态绑定的,如果派生类需要修改基类的行为,就应该将基类中相应的函数声明为虚函数。
5)再重写继承来的虚函数时,如果函数有默认的形参值,千万不要重新定义不同的值。原因是:虽然虚函数是动态绑定的,但默认值是静态绑定的。
6)在c++中不能声明虚构造函数,但可以声明虚析构函数,如果一个类的析构函数声明为虚函数,那么他派生而来的所有子类的析构函数也是虚函数。保证使用基类类型指针就能够调用适当的析构函数针对不同的对象进行清理工作。
#include
using namespace std;
class Base
{
public:
virtual ~Base(){cout<<"base destruction"<<endl;}
};
class Derived:public Base
{
public:
Derived();
~Derived();
private:
int *i_pointer;
};
Derived::Derived()
{
i_pointer = new int(0);
};
Derived::~Derived()
{
cout<<"Derived destruction"<<endl;
delete i_pointer;
};
void fun(Base *b)
{
delete b;
}
int main()
{
Base *b = Derived();
fun(b);
return 0;
}
如果父类没声明为虚函数,则不会调用子类的析构函数,造成内存泄露。
三:纯虚函数
纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
…
};
在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。下面给出一个纯虚函数的例子。
#include <iostream.h>
class point
{
public:
point(int i=0, int j=0) { x0=i; y0=j; }
virtual void set() = 0;
virtual void draw() = 0;
protected:
int x0, y0;
};
class line : public point
{
public:
line(int i=0, int j=0, int m=0, int n=0):point(i, j)
{
x1=m; y1=n;
}
void set() { cout<<"line::set() called./n"; }
void draw() { cout<<"line::draw() called./n"; }
protected:
int x1, y1;
};
class ellipse : public point
{
public:
ellipse(int i=0, int j=0, int p=0, int q=0):point(i, j)
{
x2=p; y2=q;
}
void set() { cout<<"ellipse::set() called./n"; }
void draw() { cout<<"ellipse::draw() called./n"; }
protected:
int x2, y2;
};
void drawobj(point *p)
{
p->draw();
}
void setobj(point *p)
{
p->set();
}
void main()
{
line *lineobj = new line;
ellipse *elliobj = new ellipse;
drawobj(lineobj);
drawobj(elliobj);
cout<<endl;
setobj(lineobj);
setobj(elliobj);
cout<<"/nRedraw the object.../n";
drawobj(lineobj);
drawobj(elliobj);
}
四:抽象类
带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。
抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。
抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
抽象类是为了抽象和设计的目的建立的,建立抽象类是为了通过它多态的使用其中的成员函数。抽象类处于类层次的上层,抽象类无法实例化。
1) 纯虚函数:是一个在基类中声明的虚函数,在基类中没有定义具体的操作内容。
virtual 函数类型 函数名(参数表)=0;
2)带有纯虚函数的类为抽象类,抽象类不能实例化,但我们可以声明一个抽象类的指针和引用,通过指针和引用,我们就看就可以访问派生类对象,这种访问具有多态性特征。
相关文章推荐
- 虚函数、纯虚函数、虚基类、抽象类、虚函数继承、虚继承
- 虚函数、纯虚函数、虚基类、抽象类、虚函数继承、虚继承
- C++ 虚函数、纯虚函数、抽象类、接口、虚基类
- C++的虚基类,抽象类,虚函数,纯虚函数,virtual
- C++ 虚基类、虚函数、纯虚函数、抽象类
- C++的虚基类,抽象类,虚函数,纯虚函数,virtual
- 基类、派生类、虚基类、虚函数、虚析构、纯虚函数、抽象类
- 虚函数、纯虚函数、虚基类、抽象类、虚函数继承、虚继承
- 171228—虚继承&虚基类、虚函数、纯虚函数&抽象类 这一家人
- C++的虚基类,抽象类,虚函数,纯虚函数,virtual
- C++的虚基类,抽象类,虚函数,纯虚函数,virtual
- 虚基类,抽象类,虚函数,纯虚函数,virtual
- 关于基类、派生类、对象、指针和虚函数、多态、 静态绑定、 动态绑定 纯虚函数、抽象类
- C++的虚基类,抽象类,虚函数,纯虚函数,virtual
- 虚基类,虚函数,纯虚函数,抽象类的区别与联系
- C++的虚基类,抽象类,虚函数,纯虚函数,virtual
- 虚函数、纯虚函数、虚基类、抽象类、虚函数继承、虚继承
- 二义性、虚函数、纯虚函数、虚基类、抽象类、基类对象与派生类对象之间赋值兼容原则
- 理解虚基类、虚函数与纯虚函数的概念
- 虚基类 、 纯虚函数和抽象类