7.1 定義抽象資料型別
抽象資料型別的最大特點是其具有很高的封裝性,我們無法直接訪問其內部的資料,甚至我們不清楚其內部都有哪些型別的資料,我們僅使用其提供的各種介面(api)來對其資料進行訪問和操作。
C++中的類就是一種抽象的資料型別,類的基本思想就是資料抽象和封裝。
僅由一組資料組成的結構體並不是一種抽象的資料型別,因為我們能直接訪問其內部資料,而不是通過介面訪問。如下的Sales_data
如果想要將書的交易記錄Sales_data定義為一個抽象資料型別,我們需要提供一組操作(介面),每次交易只呼叫這些操作,這樣就可以將資料封裝起來。
(1)設計Sale_data類
書的交易記錄Sale_data包含的資料有:書的標號 bookNo(定義為 string類物件),書的銷量 units_sold(定義為 unsigned型別)和交易總價 revenue(定義為 double型別),使用這三個資料型別就可以完整的描述每單的交易了;除了對每單交易的描述外,還應提供哪些操作呢?
我們可以設計了這樣一些操作:
a.每單交易的錄入read()和列印該單交易print()
b.獲取每單交易的引數:獲取書編號getBookNo(),獲取銷量getUnitsSold(),獲取銷售總價getRevenue()
c.實現同一本書的多單交易的彙總統計(將一單交易加到另一單上)combine()
程式碼如下:
先編寫標頭檔案Sales_data ,將Saes_data類定義在標頭檔案裡 //注意,標頭檔案名需要與類名一致,這是規範各檔名中類定義一致,非語法強制要求。
Sales_data類定義
#ifndef SALES_DATA_H
#define SALES_DATA_H //定義標頭檔案時必須加保護,防止其他.cpp重複引入此標頭檔案。
#include <iostream>
#include <string>
using namespace std;
//統計每單書的交易記錄
struct Sales_data {
//資料成員
string bookNo; //書的編號
unsigned units_sold = 0; //銷量
double revenue = 0; //銷售總價
//成員函式 必須在類或結構體內宣告,可以在外面定義,但需要在函式名前加 類名::(Sales_data::)
string getBookNo();
unsigned getUnitsSold();
double getRevenue();
void read();
void print();
void combine(Sales_data next);
};
string Sales_data::getBookNo() {
return bookNo;
}
unsigned Sales_data::getUnitsSold() {
return units_sold;
}
double Sales_data::getRevenue() {
return revenue;
}
void Sales_data::read() {
cout << "請輸入一條交易" << endl;
cout << "書編號:";
cin >> bookNo;
cout << "銷量:";
cin >> units_sold;
cout << "銷售總價";
cin >> revenue;
}
void Sales_data::print() {
cout << "編號:" << bookNo << " 賣出" << units_sold << "本" << " 總價:" << revenue << endl;
}
void Sales_data::combine(Sales_data next) {
this->units_sold += next.units_sold;
this->revenue += next.revenue;
}
#endif // !SALES_DATA_H
主程式(輸入一批交易)
int main() {
Sales_data toutal, next;
int k=1;
toutal.read();
while (k!=-1)
{
next.read();
if (toutal.getBookNo() == next.getBookNo()) {
toutal.combine(next);
}
else {
toutal.print();
toutal = next;
}
cout << "是否停止錄入? ";
cin >> k;
}
toutal.print();
return 0;
}
通過以上,定義了描述每單交易的必要引數,以及每單交易和多單交易之前可能會用的的操作,這樣,Sale_data類基本就設計好。但還需要打磨一下設計的細節:
需要先介紹兩個概念:
引入this指標:
通過物件.成員函式呼叫時,形參表裡會隱式的傳入一個指向該物件的常量指標this,實際上在成員函式內使用物件的資料成員時,是隱式的使用this.資料成員.
this指標始終都指向呼叫物件,所以this都是常量指標 (type * const型別)
如toutal.getBookNo();將隱式傳入 this常量指標,存放的是toutal物件的地址(Sales_data *const this=&toutal;)。
引入const成員函式
如果呼叫物件是一個常量物件時,預設的指標型別是不能指向一個常量物件的,所以需要指定this指標為指向常量的指標,只需要在定義和宣告成員函式時,在形參列表後新增關鍵字const,用以修飾this為指向常量的指標常量。
使用const的成員函式稱為常量成員函式,無論是常量物件還是非常量物件都可以呼叫它,但它只能讀取呼叫物件的資料成員,無權修改呼叫物件。 (常量物件以及其引用或指標只能呼叫它的常量成員函式)
知道以上兩個概念後,我們可以將getBookNo()等成員函式定義為常量成員函式,如 string Sales_data::getBookNo() const{...}
因為getBookNo()只讀取物件的資料成員,無修改物件的操作。
類的設計者負責思考描述一個類事物需要哪些引數,然後將這些引數進行封裝,並設計一些相關操作,從而得到這類事務的一個模板;如描述人類,需要性別,姓名,年齡等特徵引數,然後我們可以設計一個走路的操作,統計他一分鐘走多遠等等,這樣就設計了一個簡單的human模板,可以通過此模板建立一個個具體的人類物件張三或者李四。
而類的User(即類的呼叫者),不應該去過多思考類的實現過程,甚至不需要了解它都有哪些資料成員和成員函式,我們僅需思考這個型別可以做些什麼?然後直接用其提供的API(成員函式)。