虛基類 、 純虛擬函式和抽象類
阿新 • • 發佈:2019-02-17
虛基類
在《多繼承》中講過的例子中,由類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基類的建構函式的成員初始化列表中列出的虛基類建構函式不被執行。這一點將從該程式的輸出結果可以看出。
2001-8-21 23:57
上一講 | 返回 | 下一講
--------------------------------------------------------------------------------
純虛擬函式和抽象類
純虛擬函式是一種特殊的虛擬函式,它的一般格式如下:
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);
}
抽象類
帶有純虛擬函式的類稱為抽象類。抽象類是一種特殊的類,它是為了抽象和設計的目的而建立的,它處於繼承層次結構的較上層。抽象類是不能定義物件的,在實際中為了強調一個類是抽象類,可將該類的建構函式說明為保護的訪問控制權限。
抽象類的主要作用是將有關的組織在一個繼承層次結構中,由它來為它們提供一個公共的根,相關的子類是從這個根派生出來的。
抽象類刻畫了一組子類的操作介面的通用語義,這些語義也傳給子類。一般而言,抽象類只描述這組子類共同的操作介面,而完整的實現留給子類。
抽象類只能作為基類來使用,其純虛擬函式的實現由派生類給出。如果派生類沒有重新定義純虛擬函式,而派生類只是繼承基類的純虛擬函式,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛擬函式的實現,則該派生類就不再是抽象類了,它是一個可以建立物件的具體類了。
2001-9-14 16:34
上一講 | 返回 | 下一講
--------------------------------------------------------------------------------
----摘自《C++面向物件程式設計基礎教程》
在《多繼承》中講過的例子中,由類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基類的建構函式的成員初始化列表中列出的虛基類建構函式不被執行。這一點將從該程式的輸出結果可以看出。
2001-8-21 23:57
上一講 | 返回 | 下一講
--------------------------------------------------------------------------------
純虛擬函式和抽象類
純虛擬函式是一種特殊的虛擬函式,它的一般格式如下:
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);
}
抽象類
帶有純虛擬函式的類稱為抽象類。抽象類是一種特殊的類,它是為了抽象和設計的目的而建立的,它處於繼承層次結構的較上層。抽象類是不能定義物件的,在實際中為了強調一個類是抽象類,可將該類的建構函式說明為保護的訪問控制權限。
抽象類的主要作用是將有關的組織在一個繼承層次結構中,由它來為它們提供一個公共的根,相關的子類是從這個根派生出來的。
抽象類刻畫了一組子類的操作介面的通用語義,這些語義也傳給子類。一般而言,抽象類只描述這組子類共同的操作介面,而完整的實現留給子類。
抽象類只能作為基類來使用,其純虛擬函式的實現由派生類給出。如果派生類沒有重新定義純虛擬函式,而派生類只是繼承基類的純虛擬函式,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛擬函式的實現,則該派生類就不再是抽象類了,它是一個可以建立物件的具體類了。
2001-9-14 16:34
上一講 | 返回 | 下一講
--------------------------------------------------------------------------------
----摘自《C++面向物件程式設計基礎教程》