一、object
typedef struct{
float x;
float y;
float z;
}Point3d;
可以有以下方法列印上述型別欄位:
- 定義函式
void print_point3d(const Point3d* pd){
printf("(%g,%g,%g)", pd->x, pd->y, pd->z);
}
- 若要更有效率,可以定義一個宏函式
#define Point3d_print(pd) \
printf("(%g,%g,%g)", pd->x, pd->y, pd->z);
- 也可以由一個前置處理宏來處理
#define X(p,xval) (p.x)=(xval)
//...
Point3d pd;
X(pd, 0.0);
在C++中,Point3d
的實現方式如下:
- 可以採用獨立的抽象資料型別ADT實現
class Point3d{
public:
Point3d(float x=0.0, float y=0.0, float z=0.0):x_(x),y_(),z_(){}
float x(){ return x_; }
float y(){ return y_; }
float z(){ return z_; }
void x(float xval){ x_= xval; }
private:
float x_;
float y_;
float z_;
};
inline ostream& operator<<(ostream& os, const Point3d& pt){
os<<pt.x()<<pt.y()<<pt.z();
}
- 或使用多層的class層次結構實現
class Point{
public:
Point(float x=0.0):x_(x){}
float x(){ return x_; }
void x(float xval){ x_=xval; }
private:
float x_;
};
class Point2d:public Point{
public:
Point2d(float x=0.0, float y=0.0):Point(x), y_(y){}
float y(){ return y_; }
void y(float yval){ y_=yval; }
private:
float y_;
};
class Point3d:public Point2d{
public:
Point3d(float x=0.0, float y=0.0, float z=0.0):Point2d(x,y),z_(z){}
float z(){ return z_; }
void z(float zval){ z_=zval; }
private:
float z_;
};
- 更進一步,不管那種型別,都可以被引數化,座標型別引數化
template<class type>
class Point3d{};
// 或如下,座標型別和個數都引數化
/*
template<class type, int dim>
class Point{
public:
Point();
Point(type coords[dim]){
for(int index=0; index<dim; index++){
coords_[index]= coords[index]
}
}
type& operator[](int index){
assert(index<dim && index>=0);
return coords_[index];
}
type& operator[](int index)const{
}
private:
type coords_[dim];
};
inline template<class type, int dim>
ostream& operator<<(ostream& os, const Point<type,dim>& pt){
// print
}
*/
對於C++的封裝,是否有佈局上的成本
-
每個non-inline只會有一個函式例項,而每個擁有零個或一個定義的inline function則會在每個使用者中產生一個函式例項
-
C++佈局和效率的負擔主要在
virtual
關鍵字上
- 虛擬函式virtual function機制
用以支援執行期繫結runtime binding - 虛繼承virtual base class機制
用以實現多次出現在繼承體系中的基類,有單一且共享的例項
此外,還有一些多重繼承下的額外負擔,發生在“一個子類和其第二或後繼的父類間的轉換”之間
二、C++物件模型
class Point{
public:
Point(float xval);
virtual ~Point();
float x()const;
static int PointCount();
protected:
virtual std::ostream& print(std::ostream& os)const;
float x_;
static int point_count_;
};
簡單物件模型
一個object對應一系列的slots,每個slot指向一個member
表格驅動物件模型
C++物件模型
-
非靜態成員變數
-
靜態成員變數
-
靜態和非靜態成員函式
-
虛擬函式
類生成一系列指向虛擬函式的指標,放在虛擬函式表vtbl
中
每個物件有一個指向vtbl
的指標vptr
,vptr
的設定和重置由每一個類的建構函式,解構函式和賦值運算子自動完成
每個類所關聯的type_info object(用以支援RTTI),也由vtbl
指出,通常放在第一個slot
vptr
和vtbl
的使用沿用cfront編譯器 -
加上繼承
- 單一繼承
class Library_materials{}; class Book:public Library_materials{}; class Rental_book:public Book{};
- 多重繼承
class iostream: public istream, public ostream{};
繼承關係可以指定為虛繼承
class istream:virtual public ios{}; class ostream:virtual public ios{};
保證了在繼承關係中只會存在一個基類的例項subobject
C++最初採用的繼承模型並不運用任何間接性:基類的成員變數直接放在子類物件中
C++2.0引入的虛繼承,為每個關聯的虛基類加上指標
三、關鍵字差異
若沒有C的8種整數需要支援,overloaded function的解決方案會更簡單
若丟棄C的宣告語法,就無需花時間判斷下面到底是函式呼叫還是宣告
void (*pf)(1024);
void (*pq)();
struct
關鍵字實現了C的資料抽象觀念,class
關鍵字實現的是C++的ADT觀念
將單一陣列放在struct
的尾端,於是每個struct
物件將擁有可變大小的陣列
struct M{
char pc[1];
};
int main(){
const char* str="";
struct M* m=(struct M*)malloc( sizeof(struct M)+strlen(str)+1 );
strcpy(m->pc, str);
return 0;
}
四、物件差異
C++程式設計正規化programming paradigms
程式模型procedural model
抽象資料型別模型ADT model
物件導向模型object oriented model
完成多型polymorphic操作
class Library_materials{
public:
void check_in(){ std::cout<<"Library_materials"<<std::endl; }
};
class Book:public Library_materials{
public:
void check_in(){ std::cout<<"Book"<<std::endl; }
};
Library_materials thing1;
Book b;
thing1= b;
thing1.check_in(); // 實際呼叫 Library_materials::check_in()
透過pointer或reference的間接處理實現多型,前提:check_in()是虛擬函式
thing1
的定義反映的是ADT paradigm的行為
需要多少記憶體才能實現一個class object,一般而言包含如下:
- 非靜態成員變數大小
- 由於alignment的需要而填補padding的空間,即對齊
- 為了支援
virtual
而產生的額外負擔
class ZooAnimal{
public:
ZooAnimal(){}
virtual ~ZooAnimal(){}
virtual void rotate(){}
protected:
int loc;
char* name;
};
不同的指標型別,僅在於定址大小不同,void*
的定址大小是未知的,這也是為什麼空指標不能訪問物件
加上多型
class Bear:public ZooAnimal{
public:
Bear(){}
~Bear(){}
void rotate(){}
virtual void dance(){}
protected:
enum class Dances{};
Dances dances_known;
int cell_block;
};
定義變數
Bear b;
ZooAnimal* pz= &b;
Bear* pb= &b;
包含虛擬函式的類,在例項化前需要實現虛擬函式,否則提示undefined reference to vtable for Bear
指標pz
和pb
都指向物件b
的第一個位元組,差別是pb
涵蓋的地址包含整個Bear object,而pz
涵蓋的地址只包含Bear object中的ZooAnimal subobject
若pz
想訪問Bear
中的資料
經過一個顯式的轉換
(static_cast<Bear*>(pz))->dance();
或,更好的使用runtime operator,成本較高
if(Bear* pb2=dynamic_cast<Bear*>(pz)){
pb2->dance();
}
若呼叫
pz->rotate()
pz
的型別在編譯期決定,其型別決定rotate()
所呼叫的例項
型別資訊的封裝並不是維護在pz
中,而是維護在link中,此link存在於object的vptr
和vtbl
之間
Bear b;
ZooAnimal za= b;
za.rotate();
為什麼za.rotate()
呼叫的是ZooAnimal::rotate()
,而不是Bear::rotate()
za
只能是一個ZooAnimal
,多型的潛在力量不會出現在直接存取object這件事上
若初始化函式將一個object內容完整複製到另一個object中,那麼為什麼za
不指向Bear
的vtbl
因為編譯器會確保,若object含有一個或多個vptr
時,則那些vptr的內容不會被base class object初始化或改變
一個pointer或reference之所以支援多型,是因為他們並不引發記憶體中的任何與型別有關的記憶體委託操作,改變的只是其所指向的記憶體的大小和內容的解釋方式而已