程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++中類的繼承特性

C++中類的繼承特性

編輯:關於C++

整個c++程序設計全面圍繞面向對象的方式進行,類的繼承特性是c++的一個非常非常重要的機制,繼承特性可以使一個新類獲得其父類的操作和數據結構,程序員只需在新類中增加原有類中沒有的成分。

可以說這一章節的內容是c++面向對象程序設計的關鍵。

下面我們簡單的來說一下繼承的概念,先看下圖:

上圖是一個抽象描述的特性繼承表

交通工具是一個基類(也稱做父類),通常情況下所有交通工具所共同具備的特性是速度與額定載人的數量,但按照生活常規,我們來繼續給交通工具來細分類的時候,我們會分別想到有汽車類和飛機類等等,汽車類和飛類同樣具備速度和額定載人數量這樣的特性,而這些特性是所有交通工具所共有的,那麼當建立汽車類和飛機類的時候我們無需再定義基類已經有的數據成員,而只需要描述汽車類和飛機類所特有的特性即可,飛機類和汽車類的特性是由在交通工具類原有特性基礎上增加而來的,那麼飛機類和汽車類就是交通工具類的派生類(也稱做子類)。以此類推,層層遞增,這種子類獲得父類特性的概念就是繼承。

下面我們根據上圖的理解,有如下的代碼:

#include <iostream>
using namespace std;
class Vehicle
{
public:
void EditSC(float speed,int total);
protected:
float speed;//速度
int total;//最大載人量
};
void Vehicle::EditSC(float speed,int total)
{
Vehicle::speed = speed;
Vehicle::total = total;
}
class Car:public Vehicle//Car類繼承Vehicle的特性,Car類是Vehicle的派生類
{
public:
Car()
{
aird=0;
}
protected:
int aird;//排量
};
class plane:public Vehicle
{
protected:
float wingspan;//翼展
};
void main()
{
Car a;
a.EditSC(150,4);
cin.get();
}

派生類的定義可以在類名稱後加冒號public空格加基類名稱進行定義,如上面代碼中的class Car:public Vehicle。

一旦成功定義派生類,那麼派生類就可以操作基類的所有數據成員包括是受保護型的,上面代碼中的a.EditSC(100,4); 就是例子,甚至我們可以在構造派生類對象的時候初始化他們,但我們是不推薦這麼做的,因為類於類之間的操作是通過接口進行勾通的,為了不破壞類的這種封專裝特性,即使是父類於子類的操作也應按遵循這個思想,這麼做的好處也是顯而易見的,當基類有錯的時候,只要不涉及接口,那麼基類的修改就不會影響到派生類的操作。

至於為什麼派生類能夠對基類成員進行操作,我們上圖可以簡單的說明基類與子類在內存中的排列狀態。

我們知道,類對象操作的時候在內部構造的時候會有一個隱的this指針,由於Car類是Vehicle的派生類,那麼當Car對象創建的時候,這個this指針就會覆蓋到Vehicle類的范圍,所以派生類能夠對基類成員進行操作。

筆者寫到這裡的時候不得不提一下,我有開發c#與java的經驗,就這兩種語言來說,學到這裡的時候很多人很難理解繼承這一部分的內容,或者是理解的模糊不清,其實正是缺少了與this指針相關的c++知識,多數高級語言的特性是不涉及內存狀態的操作,java與c#是接觸不到這些知識的,所以理解起這部分內容就更抽象更不具體。

下面我們來說一下,派生類對象(子類對象)的構造。

由上面的例程我們知道Car類是Vehicle類的派生類(子類),c++規定,創建派生類對象的時候首先調用基類的構造函數初始化基類成員,隨後才調用派生類構造函數。

但是要注意的是,在創建派生類對象之前,系統首先確定派生類對象的覆蓋范圍(也可以稱做大小尺寸),上面代碼的派生類對象a就覆蓋於Vehicle類和Car類上,至於派生類對象的創建是如何構造基類成員的,我們看如下代碼,隨後再進行分析:

#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(float speed=0,int total=0)
{
cout<<"載入Vehicle類構造函數"<<endl;
Vehicle::speed = speed;
Vehicle::total = total;
}
Vehicle(Vehicle &temp)
{
Vehicle::speed = temp.speed;
Vehicle::total = temp.total;
}
~Vehicle()
{
cout<<"載入Vehicle類析構函數"<<endl;
cin.get();
}
protected:
float speed;//速度
int total;//最大載人量
};
class Car:public Vehicle
{
public:
Car(float aird=0,float speed = 0,int total = 0):Vehicle(speed,total)
{
cout<<"載入Car類構造函數"<<endl;
Car::aird = aird;
}
Car(Car &temp):Vehicle(temp.speed,temp.total)
{
cout<<"載入Car類拷貝構造函數"<<endl;
Car::aird = temp.aird;
}
~Car()
{
cout<<"載入Car類析構函數"<<endl;
cin.get();
}
protected:
float aird;//排量
};
void main()
{
Car a(250,150,4);
Car b = a;
cin.get();
}

對象a創建的時候通過Car(float aird = 0,float speed = 0,int total = 0):Vehicle(speed,total),也就是Car類構造函數,來構造Car類對象成員,但按照c++的規定首先應該調用基類構造函數構造基成員,在這裡基類成員的構造是通過Vehicle(speed,total),來實現的。

但值得注意的是Vehicle(speed,total)的意義並不是對Vehicle類的個個成員的初始化,事實上是利用它創建了一個Vehicle類的無名對象,由於Car類對象的覆蓋范圍是覆蓋於Vehicle類和Car類上,所以系統在確定了派生類對象a的空間范圍後,確定了this指針位置,這個this指針指向了Vehicle類的那個無名對象,這個成員賦值過程就是,this->speed=無名對象.speed。

其實這裡概念比較模糊,筆者因為個人能力的原因暫時也無法說的更明確了,讀者對此處知識點的學習,應該靠自己多對比多練習,進行體會理解。

許多書籍對於派生類對象的復制這一知識點多是空缺的,為了教程的易讀性,我還是決定說一下在復制過程中容易出錯的地方,Car b=a;是派生類對象復制的語句,通過前面教程的學習我們我們知道,類對象的復制是通過拷貝構造函數來完成的,如果上面的例子我們沒有提供拷貝構造函數不完整如下:

Car(Car &temp)
{
cout<<"載入Car類拷貝構造函數"<<endl;
Car::aird = temp.aird;
}

那麼復制過程中就會丟失基類成員的數據了,所以Car類拷貝構造函數不能缺少Vehicle類無名對象的創建過程:Vehicle(temp.speed,temp.total),派生類對象的復制過程系統是不會再調用基類的拷貝構造函數的,this指針的問題再次在這裡體現出來,大家可以嘗試著把無名對象的創建去掉,觀察b.speed的變化。

類對象夠創建必然就有析構過程,派生類對象的析構過程首先是調用派生類的析構過程,再調用基類的構造函數,正好和創建過程相反,在這裡筆者已經在上面代碼中加入了過程顯示,讀者可以自行編譯後觀察。

最後我們說一下類的繼承與組合。

其實類的組合我們在早些的前面的教程就已經接觸過,只是在這裡換了個說法而已,當一個類的成員是另一個類的對象的時候就叫做組合,事實上就是類於類的組合。組合和繼承是有明顯分別的,為了充分理解繼承與組合的關系,我們在不破壞類的封裝特性的情況下,先看如下的代碼:

#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(float speed=0,int total=0)
{
Vehicle::speed = speed;
Vehicle::total = total;
}
protected:
float speed;//速度
int total;//最大載人量
};
class Motor
{
public:
Motor(char *motor)
{
Motor::motortype = motor;
}

char* SMT(Motor &temp);
protected:
char *motortype;//發動機型號
};
char* Motor::SMT(Motor &temp)
{
return temp.motortype;
}
class Car:public Vehicle//繼承的體現
{
public:
Car(float speed,int total,int aird,char *motortype):Vehicle(speed,total),motor(motortype)
{
Car::aird = aird;
}
Motor rm(Car &temp);
protected:
int aird;//排量
Motor motor;//類組合的體現
};
Motor Car::rm(Car &temp)
{
return temp.motor;
}
//--------------------------------------------------------------
void test1(Vehicle &temp)
{
//中間過程省略
};
void test2(Motor &temp)
{
cout<<temp.SMT(temp);//讀者這裡注意一下,temp既是對象也是對象方法的形參
}
//--------------------------------------------------------------
void main()
{
Car a(150,4,250,"奧地利AVL V8");
test1(a);
//test2(a);//錯誤,Car類與Motor類無任何繼承關系
test2(a.rm(a));//如果Car類成員是public的那麼可以使用test2(a.motor)
cin.get();
}

在上面的代碼中我們新增加了發動機類Motor,Car類增加了Motor類型的motor成員,這就是組合,擁有繼承特性的派生類可以操作基類的任何成員,但對於與派生類組合起來的普通類對象來說,它是不會和基類有任何聯系的。

函數調用:test1(a);,可以成功執行的原因就是因為Car類對象在系統是看來一個Vehicle類對象,即Car類是Vehicle類的一種,Car類的覆蓋范圍包含了Vehicle。

函數調用:test2(a);,執行錯誤的原因是因為Motor類並不認可Car類對象a與它有任何關系,但我們可以通過使用Car類對象a的Motor類成員motor,作為函數形參的方式來調用test2函數(test2(a.motor)),在這裡由於類的成員是受保護的所以我們利用a.rm(a)來返回受保護的motor,作為函數參數進行調用。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved