天天看點

OOP(面想對象設計)實踐————第十五章心得

文章目錄

  • ​​1 簡化版本的字元圖案​​
  • ​​1.1 編碼​​
  • ​​1.2 缺點​​
  • ​​2 用oop解決字元圖形​​
  • ​​2.1 解決的問題​​
  • ​​2.2 總體設計​​
  • ​​2.2.1 思想​​
  • ​​2.2.2 總體設計架構編碼​​
  • ​​2.2.3 設計接口​​
  • ​​2.3 詳細設計​​
  • ​​2.3.1 基類(Pic_base類)​​
  • ​​2.3.2 派生類​​
  • ​​2.4 實作​​
  • ​​2.4.1 實作使用者接口​​
  • ​​2.4.2 實作派生類​​
  • ​​2.4.2.1 String_Pic​​
  • ​​2.4.2.2 VCat_Pic​​
  • ​​2.4.2.3 HCat_Pic​​
  • ​​2.4.2.4 Frame_Pic​​
  • ​​2.5 後續問題​​
  • ​​2.6 代碼彙總​​

1 簡化版本的字元圖案

字元圖案:一個矩形的可顯示的字元列陣。

即:把字元串用矩形邊框包圍起來。

示例:綠色為輸入的字元串

OOP(面想對象設計)實踐————第十五章心得

1.1 編碼

#include 
#include 
#include 
#include 
#include 
#include 

using  std::cin; using std::endl;
using std::cout; using std::string;
using std::vector; using std::max;

//分割字元串
vector<string> split(const string& s){
    vector<string> ret;
    typedef string::size_type string_size;
    string_size  i = 0;

    while(i != s.size()){
        //忽略前段的空白:[先前的i,i)中全部字元都是空格
        while(i != s.size() && isspace(s[i])){
            i++;
        }
        //找出下一個單詞的終結點
        string_size j = i;
        //[先前的j,j)中的任意字元都不是空格
        while(j != s.size() && !isspace(s[j])){
            j++;
        }
        //找到了一些非空白符
        if(i != j){
            ret.push_back(s.substr(i, j - i));
            i = j;
        }
    }
    return ret;
}

//找出向量中最長字元串的長度
string::size_type width(const vector<string>& v){
    string::size_type  maxlen = 0;
    for (vector<string>::size_type i = 0; i != v.size(); ++i) {
        maxlen = max(maxlen, v[i].size());
    }
    return maxlen;
}

vector<string> frame(const vector<string>& v){
    vector<string> ret;
    string::size_type maxlen = width(v);
    //輸出頂部邊框
    string border(maxlen + 4, '*');

    //輸出内部的行,每行都用一個星号和一個空格來框起來
    ret.push_back(border);
    for (vector<string>::size_type i = 0; i != v.size(); ++i) {
        ret.push_back("* "+ v[i]  + string(maxlen - v[i].size(), ' ') + " *");
    }
    //輸出底部邊框
    ret.push_back(border);
    return  ret;
}

//縱向連接配接
vector<string> vcat(const vector<string>& top, const vector<string>& bottom){
    vector<string> ret = top;
//    for (vector::const_iterator it = bottom.begin(); it != bottom.end() ; ++it) {
//       ret.push_back(*it);
//    }
    //作用同上
    ret.insert(ret.end(), bottom.begin(), bottom.end());
    return  ret;
}

//橫向連接配接
vector<string> hcat(const vector<string>& left, const vector<string>& right){
    vector<string>ret;
    //在兩幅圖案之間留白格
    string::size_type width1 = width(left) + 1;
    //用于周遊left和right的索引
    vector<string>::size_type i = 0, j = 0;
    while(i != left.size() || j != right.size()){
        string s;
        //如果左側圖案還有待複制的行,則複制一行
        if(i != left.size()){
            s = left[i++];
        }
        //填充至适當長度
        s += string(width1 - s.size(), ' ');
        //如果右側還有代複制的行,則複制一行
        if(j != right.size()){
            s += right[j++];
        }
        ret.push_back(s);
    }
    return  ret;
}

int main(int argc,const char *argv[]){
    string s;
    while(getline(cin, s)){
            vector<string> v = split(s);
//        for (vector::size_type i = 0; i != v.size(); ++i) {
//            cout << v[i] <
//        }
        vector<string> fra = frame(v);
        //列印邊框
        for (vector<string>::const_iterator i = fra.begin(); i != fra.end(); ++i) {
            cout << *i <<endl;
        }
//        vector col, row;
//        col = vcat(v, fra);
//        row = hcat(v, fra);
//        //列印豎着拼圖
//        for (vector::size_type i = 0; i != col.size(); ++i) {
//            cout << col[i] << endl;
//        }
        //列印橫向拼圖
//        for (vector::size_type j = 0; j != row.size(); ++j) {
//            cout << row[j] <
//        }
    }
    return 0;
}      

1.2 缺點

上面的代碼有學多缺陷:

  • 1 講一個字元圖形描述為vector<string>,每生成一個圖形都要對字元進行複制,這造成了時間和空間上的浪費。
  • 如果要連接配接一個圖形的兩個副本,那麼就要儲存三個副本。(原始副本,新生成的圖形兩邊各需要一個副本)
  • 2 丢失了全部關于圖形結構的資訊。
  • 無法直到一個給出的圖形是如何生成的。有可能是客戶給出的,也可能是是某一原始圖形進行了一種或幾種特定的操作而生成的圖形。
  • 有些非常有用的操作需要直到一個圖形的結構資訊。
  • 例如修改一副圖的架構字元,就必須知道圖中那些部分加了架構,哪些部分沒有加架構。但是加了架構的字元極有可能是原始圖形中的一部分,而不是後來進行加框操作而形成的。

2 用oop解決字元圖形

2.1 解決的問題

oop解決的問題是:

  • 1儲存某個圖形是如何生成的結構資訊(總體設計);
  • 2 減少儲存資料的副本(細節設計)。

細節設計使用帶引用計數的句柄解決,總體設計用繼承來解決。

2.2 總體設計

2.2.1 思想

首先生成一幅圖形,前面調用了三個函數frame(加框)、hcat(水準)、vcat(垂直),加上輸入的字元串,也就是可以設計為四個類。

雖然四個類存在差異,但是都是圖形,可以設計一個基類,然後通過派生類來描述不同特征的圖形。

為了使派生類通過繼承聯系起來,可以通過虛拟函數寫不需要明确指定的生成圖形的代碼。

基類 Pic_base
派生類 String_Pic
派生類 Frame_Pic
派生類 HCat_Pic
派生類 VCat_Pic

繼承關系對客戶并不需要可見,因為任何操作都不是對某個特定的圖形進行的,而是通過一幅圖形的抽象概念。

在程式中隐藏繼承關系并使用關聯計數會使客戶在使用的中感到更加友善。

我們将定義一個接口類(Picture類),來讓使用者操作圖形。接口類中使用句柄類Ptr(管理指向對象的指針類)來管理記憶體。

接口類中Ptr類管理着基類Pic_base,但是将綁定到Ptr的對象始終是Pic_base的某個派生類,是以在建立或銷毀Pic_base對象時,通過一個Pic_base指針來完成,但是對象是派生類的對象,是以需要為Pic_base提供一個虛拟析構函數。

為了達到隐藏Pic_base和其關聯繼承的可見性,使用者隻能通過Picture類間接操作對象而無法直接通路的任何一個其他類。實作的方法是依賴與正常的保護機制。把這些類的public接口設定為空,使編譯其保證隻與圖形之間的任何操作都通過Picture類實作。

是以現在完整的類有6個;

基類 Pic_base
派生類 String_Pic
派生類 Frame_Pic
派生類 HCat_Pic
派生類 VCat_Pic
接口類(使用句柄類) Picture

2.2.2 總體設計架構編碼

設計結果

//基類
class Pic_base{};

//派生類
class String_Pic:public Pic_base{};
class Frame_Pic:public Pic_base{};
class Vcat_Pic:public Pic_base{};
class HCat_Pic:public Pic_base{};

//接口類
class Picture{
public:
    //從一個裝有string類型對象的容器中獲得資料
    Picture(const std::vector<std::string>& = std::vector<std::string>());

private:
    Ptr<Pic_base>p;
};      

Picture類的構造函數沒有被聲明為explicit(顯示),

是以可以這樣定義:執行時,自動将vs對象轉換為一個Picture類型對象。

vector<string> vs;
Picture p = vs;      

但是如果沒有,則隻能:

vector<string> vs;
Picture p(vs);      

2.2.3 設計接口

frame、hcat以及vcat三個操作函數不會改變正在作用于Picture類型對象的狀态,因為沒有必要把它們定義為成員函數。如果把他們那定義為非成員函數,可以允許左右操作數都可以使用自動轉換。

例如一個語句寫成​

​hcat(frame(p), p);​

​​會比​

​p.frame(p).hcat(p);​

​更明了,更有助于使用者寫一個表達式來生成圖形。

如果frame是一個成員函數,那麼使用者将無法把frame(vs)寫成vs.frame(),其中vs是Picture對象。因為成員函數左操作數不能是類型轉換後的結果。

繼續完善接口:

Picture frame(const Picture&);
Picture hcat(const Picture&, const Picture&);
Picture vcat(const Picture&, const Picture&);
std::ostream& operator<<(std::ostream&, const Picture&);      

2.3 詳細設計

2.3.1 基類(Pic_base類)

下面的編碼中:

純虛拟函數:不用為虛拟函數寫出具體的實作,但是這個類不能有相應的對象。可能會有派生類的對象(實作了該純虛拟函數)。

定義了純虛拟函數的類為抽象基類,該類隻在繼承樹中用于提供對象的接口。編譯器會禁止為這個類生成相應的對象。

class Pic_base {
    typedef std::vector<std::string>::size_type ht_sz;//高
    typedef std::vector<std::string>::size_type wd_sz;//寬

    virtual wd_sz width() const = 0;//純虛拟函數
    virtual ht_sz height() const = 0;

    virtual void display(std::ostream &, ht_sz, bool) const = 0;//輸出内容};
    public:
    virtual ~Pic_base(){}
};      

2.3.2 派生類

一個函數的純虛拟特性也會被繼承。如果在派生類中定義了全部繼承而來的虛拟函數,那麼它就是一個具體的類。

例如為了計算Vcat_Pic類型對象,需要将兩個用于構成該對象的Picture類型對象的height相加。

如果在派生類中儲存Picture類型對象,那麼就違背在Picture類中隻定義接口而沒有具體實作的設計思想。是以在派生類中存儲Ptr<Pic_base>類型對象。

寫出相應的派生類編碼:

除了在String_Pic類中從向量參數v中将底層的字元複制到data資料成員中,其他的地方都隻複制類Ptr<Pic_base>對象,即複制了指針,使引用計數器加1。

類中沒有定義複制構造函數、複制運算符函數和析構函數,原編譯器會自動為我們生成相應的預設函數。原因是Ptr句柄類會負責管理複制、指派和删除操作。

//派生類
class String_Pic:public Pic_base{
    std::vector<std::string> data;

    String_Pic(const std::vector<std::string>& v):data(v){}

    wd_sz width() const;
    ht_sz height() const;
    void display(std::ostream&, ht_sz, bool) const;
};

class Frame_Pic:public Pic_base{
    Ptr<Pic_base> p;
    //構造函數
    Frame_Pic(const Ptr<Pic_base>& pic):p(pic){}

    wd_sz width() const;
    ht_sz height() const;
    void display(std::ostream&, ht_sz, bool) const;
};

class VCat_Pic:public Pic_base{
    Ptr<Pic_base> top, bottom;
    //構造
    VCat_Pic(const Ptr<Pic_base>& t, const Ptr<Pic_base>& b):top(t),bottom(b){}
    wd_sz width() const;
    ht_sz height() const;
    void display(std::ostream&, ht_sz, bool) const;
};

class HCat_Pic:public Pic_base{
    Ptr<Pic_base>left, right;

    HCat_Pic(const Ptr<Pic_base>& l, const Ptr<Pic_base>& r):left(l),right(r){}

    wd_sz width() const;
    ht_sz height() const;
    void display(std::ostream&, ht_sz, bool) const;
};      

2.4 實作

2.4.1 實作使用者接口

這是其中一個操作函數的實作

Picture frame(const Picture& pic){
    return new Frame_Pic(pic.p);
    //等同于
      //  Pic_base* temp1 = new Frame_Pic(pic.p);//生成一個新Frame_Pic對象
//    Picture temp2(temp1);//使用一個Pic_base*指針構造一個Pictrue類型對象
//    return temp2;//傳回一個Picture類型對象,這将激活Picture類的複制構造函數
}      

由于Frame_Pic的構造函數是私有的,是以需要在Frame_Pic類中将該函數聲明為友元函數。同時也聲明Picture的友元函數,以便通路p成員。另外,由于生成的是一個Frame_Pic類型,但是需要的是Picture類型對象,可以通過Picture的構造函數實作這種類型轉換。

class Picture{
    friend Picture frame(const Picture&);
    //構造函數提供類型轉換
    Picture(Pic_base* ptr):p(ptr){}
};      

對于String_Pic類的中向量成員,可以通過定義Picture的構造函數實作轉換。

//為vector類型構造Picture類型對象
Picture::Picture(const std::vector<std::string>& v):p(new String_Pic(v)){}      

同理得:

Picture hcat(const Picture& l, const Picture& r){
    return new HCat_Pic(l.p, r.p);
}
Picture vcat(const Picture& t, const Picture& b){
    return new Vcat_Pic(t.p, b.p);
}      

以及輸出運算符函數:

std::ostream& operator<<(std::ostream& os, const Picture& picture){
    const Pic_base::ht_sz  ht = picture.p->height();
    for (Pic_base::ht_sz i = 0; i != ht; ++i) {
        picture.p->display(os, i, false);//輸出目前行,不需要補齊每一行輸出
        os << std::endl;
    }
    return os;
}      

2.4.2 實作派生類

2.4.2.1 String_Pic

成員函數實作:

Pic_base::ht_sz String_Pic::height() const {
    return data.size();
}

Pic_base::wd_sz  String_Pic::width() const {
    Pic_base::wd_sz n = 0;
    for (Pic_base::ht_sz i = 0; i != data.size(); ++i) {
        n = std::max(n, data[i].size());
    }
    return n;
}

void String_Pic::display(std::ostream &os, Pic_base::ht_sz row, bool do_pad) const {
    Pic_base::wd_sz start = 0;
    //如果row沒有超出範圍,就輸出第row行
    if(row < height()){
        os << data[row];
        start = data[row].size();
    }
    //如果有必要,補齊輸出各行
    if(do_pad){
        pad(os, start, width());
    }
}      

do_pad函數實作:

class Pic_base {
protected:
    //靜态成員,不屬于類的某個對象
    //減少定義全局函數或變量
    //雖然抽象基類沒有相應的對象,但是派生類會繼承它的全部成員函數
    static void pad(std::ostream& os, wd_sz beg, wd_sz end){
        while(beg != end){
            os <<" ";
            ++beg;
        }
    }
    //。。。
  }      

2.4.2.2 VCat_Pic

成員函數實作

Pic_base::wd_sz VCat_Pic::width() const {
    return  std::max(top->width(), bottom->width());
}

Pic_base::ht_sz VCat_Pic::height() const {
    return top->height() + bottom->height();
}

void VCat_Pic::display(std::ostream &os, Pic_base::ht_sz row, bool do_pad) const {
    wd_sz  w = 0;
    if(row < top->height()){//處于上面子圖
        top->display(os, row, do_pad);
        w = top->width();
    }else if(row < height()){//如初下面子圖中
        bottom->display(os, row, do_pad);
        w = bottom->width();
    }
    if(do_pad){
        pad(os, w, width());
    }
}      

2.4.2.3 HCat_Pic

成員函數實作:

Pic_base::wd_sz HCat_Pic::width() const {
    return left->width() + right->width();
}

Pic_base::ht_sz HCat_Pic::height() const {
    return std::max(left->height(), right->height());
}

void HCat_Pic::display(std::ostream &os, Pic_base::ht_sz row, bool do_pad) const {
    left->display(os, row, do_pad|| row < right->height());
    right->display(os, row, do_pad);
}      

2.4.2.4 Frame_Pic

成員函數實作

Pic_base::wd_sz Frame_Pic::width() const {
    return p->width() + 4;
}

Pic_base::ht_sz Frame_Pic::height() const {
    return p->height() + 4;
}


void Frame_Pic::display(std::ostream& os, ht_sz row, bool do_pad) const {
    if(row >= height()){
        //超出範圍
        if(do_pad){
            pad(os, 0, width());
        }
    }else{
        if(row == 0 || row == height() -1){
            //最頂行或最低行
            os << std::string(width(), '*');
        }else if(row == 1 || row == height() - 2){
            //在第二行或倒數第二行
            os << "*";
            pad(os, 1, width() - 1);
            os << "*";
        }else{
            //在内部圖形
            os << "* ";
            p->display(os, row - 2, true);
            os << " *";
        }
    }
}      

2.5 後續問題

//基類
class Pic_base {
    friend class String_Pic;
    friend class Frame_Pic;
    friend class HCat_Pic;
    friend class Vcat_Pic;
    friend std::ostream& operator<<(std::ostream& , const Picture& );

    typedef std::vector<std::string>::size_type ht_sz;//高
    typedef std::vector<std::string>::size_type wd_sz;//寬

    //純虛拟函數
    virtual wd_sz width() const = 0;//純虛拟函數
    virtual ht_sz height() const = 0;
    virtual void display(std::ostream &, ht_sz, bool) const = 0;//輸出内容
protected:
    //靜态成員,不屬于類的某個對象
    //減少定義全局函數或變量
    static void pad(std::ostream& os, wd_sz beg, wd_sz end){
        while(beg != end){
            os <<" ";
            ++beg;
        }
    }
public:
    //虛拟函數
    virtual ~Pic_base(){}
};      

基類的派生類都聲明為友元,為什麼不可以通過繼承來獲得通路Pic_base基類成員的權利?

派生類确實可以通過繼承獲得通路Pic_base類的某些成員,但是僅限于通路保護成員的權利。

那麼為什麼不像定義pad函數,将這些成員函數定義為Pic_base類的保護成員呢?

因為派生類的成員隻能通路自己類對象的基類部分的保護成員,或者是派生的其他類,但是它們不能通路單獨的基類對象的保護成員,即不是派生類對象一部分的對象。

例如:類Frame_Pic可以通路Frame_Pic對象Pic_base部分的保護成員,或者派生自Frame_Pic的類成員,但是不能通路單獨的Pic_base對象的保護成員。

也就是派生類隻能通路類的對象自身擁有的保護成員,但是他們不能通路其他對象的保護成員的權利。

2.6 代碼彙總

#include 
#include 
#include 
#include "Picture.h"

using std::endl; using std::cin;
using std::cout;
using std::string; using std::vector;

//分割字元串
vector<string> split(const string& s){
    vector<string> ret;
    typedef string::size_type string_size;
    string_size  i = 0;

    while(i != s.size()){
        //忽略前段的空白:[先前的i,i)中全部字元都是空格
        while(i != s.size() && isspace(s[i])){
            i++;
        }
        //找出下一個單詞的終結點
        string_size j = i;
        //[先前的j,j)中的任意字元都不是空格
        while(j != s.size() && !isspace(s[j])){
            j++;
        }
        //找到了一些非空白符
        if(i != j){
            ret.push_back(s.substr(i, j - i));
            i = j;
        }
    }
    return ret;
}



int main(int argc, char** argv){
    string str;
    getline(cin, str);
    vector<string> vs = split(str);

    Picture p = vs;
    Picture q = frame(p);
    Picture r = hcat(p, q);//橫向
    Picture s = vcat(q, r);//縱向
    cout << frame(hcat(s, vcat(r, q))) << endl;
//    cout << q << endl;

    return 0;
}      
#ifndef ACM_PTR_H
#define ACM_PTR_H

#include "Refptr.h"
#include "Student_info.h"

template <class T>T* clone(const T* tp){
    return tp->clone();
}

template <class T>
class Ptr{
public:
    //在需要時有條件複制對象

    void make_unique(){
        if(*refptr != 1){
            --*refptr;
            refptr = new size_t(1);
            p = p?clone(p):0;
        }
    }
    Ptr():p(0),refptr(new size_t(1)){}
    Ptr(T* t):p(t),refptr(new size_t(1)){}
    Ptr(const Ptr& h):p(h.p),refptr(h.refptr){++*refptr;}

    Ptr& operator=(const Ptr&);
    ~Ptr();
    operator bool() const {return p;}
    T&  operator*() const{
        if(p) return *p;
        throw std::runtime_error("unbound Ptr");
    }
    T* operator->() const{
        if(p) return p;
        throw std::runtime_error("unbound Ptr");
    }
private:
    T* p;
    std::size_t * refptr;
};

template <class T>
Ptr<T>& Ptr<T>::operator=(const Ptr &rhs) {
    ++*rhs.refptr;
    if(--*refptr == 0){
        delete(refptr);
        delete(p);
    }

    refptr = rhs.refptr;
    p = rhs.p;
    return  *this;
}

template <class T>
Ptr<T>::~Ptr() {
    if(--*refptr==0){
        delete(refptr);
        delete(p);
    }
}

#endif //ACM_PTR_H      
#include 
#include 
#include 
#include "Picture.h"

using std::endl; using std::cin;
using std::cout;
using std::string; using std::vector;

//分割字元串
vector<string> split(const string& s){
    vector<string> ret;
    typedef string::size_type string_size;
    string_size  i = 0;

    while(i != s.size()){
        //忽略前段的空白:[先前的i,i)中全部字元都是空格
        while(i != s.size() && isspace(s[i])){
            i++;
        }
        //找出下一個單詞的終結點
        string_size j = i;
        //[先前的j,j)中的任意字元都不是空格
        while(j != s.size() && !isspace(s[j])){
            j++;
        }
        //找到了一些非空白符
        if(i != j){
            ret.push_back(s.substr(i, j - i));
            i = j;
        }
    }
    return ret;
}

int main(int argc, char** argv){
    string str;
    getline(cin, str);
    vector<string> vs = split(str);

    Picture p = vs;
    Picture q = frame(p);
    Picture r = hcat(p, q);//橫向
    Picture s = vcat(q, r);//縱向
    cout << frame(hcat(s, vcat(r, q))) << endl;
//    cout << q << endl;

    return 0;
}