• 基于面向对象的字符图像设计


    基于面向对象的字符图像设计

             ——《C++沉思录》第10章 一个课堂练习的分析(下)

             发表一下个人看法。面向对象的一大特点就是提供了句柄,句柄的的作用一是隐藏了具体的继承层次细节,二是实现自动管理内存,省去客户端管理内存的烦恼。

             之前《字符图像》介绍了一个字符图像的设计。面向对象具有数据抽象、封装、动态绑定等特性,下面我们采用面向对象的思想来重新设计字符图像。

             具体细节详见代码和注释

    // 基于面向对象的字符图像设计
    #include <iostream>
    using namespace std;
    
    
    // 前置声明,因为Picture中需要定义P_Node*指针
    class P_Node;
    
    // 定义句柄类(代理)
    class Picture
    {
    private:
        P_Node* p;
    
    public:
        Picture();
        Picture(const char* const*, int);
        Picture(const Picture&);
        ~Picture();
    
        Picture& operator = (const Picture&);
    
        // 增加一构造函数,用于由P_Node*到Picture的隐式类型转换
        Picture(P_Node*);
    
    private:
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const; // 输出
    
    
        // 友元
        friend ostream& operator << (ostream&, const Picture&);
    
        // 边框函数
        friend Picture frame(const Picture&);
        // 纵向拼接
        friend Picture operator & (const Picture&, const Picture&);
        // 横向拼接
        friend Picture operator | (const Picture&, const Picture&);
    
        // 友元类
        friend class String_Pic;
        friend class Frame_Pic;
        friend class HCat_Pic;
        friend class VCat_Pic;
    };
    
    
    // 定义实际图像抽象基类
    class P_Node
    {
    private:
    
    private:
        int use; // 引用计数,用来记录实际被代理个数
    
    protected:
        P_Node(); // 用来初始化引用计数
    
        // 由于Picture析构函数中用到了delete,所以这里需要一个虚析构函数
        virtual ~P_Node();
    
        virtual int height() const = 0;
        virtual int width()  const = 0;
        virtual void display(ostream&, int, int) const = 0;
    
    protected:
        // 求较大的数
        int max(int, int) const;
    
        // 友元
        friend class Picture;
    };
    
    P_Node::P_Node() : use(1) {}
    
    P_Node::~P_Node() {}
    
    int P_Node::max(int x, int y) const
    {
        return x > y ? x : y;
    }
    
    // 字符图像
    class String_Pic : public P_Node
    {
    private:
        char** data;
        int    size;
    
    private:
        String_Pic(const char* const*, int);
        ~String_Pic();
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
        friend class Picture;
    };
    
    String_Pic::String_Pic(const char* const* p, int n) : data(new char*[n]), size(n)
    {
        for (int i = 0; i < n; ++i)
        {
            data[i] = new char[strlen(p[i]) + 1];
            strcpy(data[i], p[i]);
        }
    }
    
    String_Pic::~String_Pic()
    {
        for (int i = 0; i < size; ++i)
        {
            delete [] data[i];
        }
        delete [] data;
    }
    
    int String_Pic::height() const
    {
        return size;
    }
    
    int String_Pic::width() const
    {
        int n = 0;
        for (int i = 0; i < size; ++i)
        {
            n = max(n, strlen(data[i]));
        }
        return n;
    }
    
    static void pad(ostream& os, int x, int y)
    {
        for (int i = x; i < y; ++i)
        {
            os << ' ';
        }
    }
    
    void String_Pic::display(ostream& os, int row, int width) const
    {
        int start = 0;
        if (row >= 0 && row < height())
        {
            os << data[row];
            start = strlen(data[row]);
        }
        pad(os, start, width);
    }
    
    // 边框图像
    class Frame_Pic : public P_Node
    {
    private:
        Picture p;
    
    private:
        Frame_Pic(const Picture&);
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
        // 该友元函数对该类进行一次封装
        friend Picture frame(const Picture&);
        friend class Picture;
    };
    
    Frame_Pic::Frame_Pic(const Picture& pic) : p(pic) {}
    
    int Frame_Pic::height() const
    {
        return p.height() + 2;
    }
    
    int Frame_Pic::width() const
    {
        return p.width() + 2;
    }
    
    void Frame_Pic::display(ostream& os, int row, int wd) const
    {
        if (row < 0 || row >= height())
        {
            // 越界
            pad(os, 0, wd);
        }
        else
        {
            if (row == 0 || row == height() - 1) // 上边或下边
            {
                os << '+';
                int i = p.width();
                while (--i >= 0)
                {
                    os << '-';
                }
                os << '+';
            }
            else // 中间行
            {
                os << '|';
                p.display(os, row - 1, p.width());
                os << '|';
            }
            // 打印width()到wd之间的空格
            // wd是入参
            pad(os, width(), wd);
        }
    }
    
    // 纵向拼接
    class VCat_Pic : public P_Node
    {
    private:
        Picture top;
        Picture bottom;
    
    private:
        VCat_Pic(const Picture&, const Picture&);
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
        // 该友元函数对该类进行封装
        friend Picture operator & (const Picture&, const Picture&);
        friend class Picture;
    };
    
    VCat_Pic::VCat_Pic(const Picture& t, const Picture& b) : top(t), bottom(b) {}
    
    int VCat_Pic::height() const
    {
        return top.height() + bottom.height();
    }
    
    int VCat_Pic::width() const
    {
        return max(top.width(), bottom.width());
    }
    
    void VCat_Pic::display(ostream& os, int row, int wd) const
    {
        if (row >= 0 && row < top.height()) // 打印上部分
        {
            top.display(os, row, wd);
        }
        else if (row < top.height() + bottom.height() && row >= top.height()) // 打印下部分
        {
            bottom.display(os, row - top.height(), wd);
        }
        else
        {
            pad(os, 0, wd);
        }
    }
    
    // 横向拼接
    class HCat_Pic : public P_Node
    {
    private:
        Picture left;
        Picture right;
    
    private:
        HCat_Pic(const Picture&, const Picture&);
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
        // 该友元函数对该类进行封装
        friend Picture operator | (const Picture&, const Picture&);
        friend class Picture;
    };
    
    HCat_Pic::HCat_Pic(const Picture& l, const Picture& r) : left(l), right(r) {}
    
    int HCat_Pic::height() const
    {
        return max(left.height(), right.height());
    }
    
    int HCat_Pic::width() const
    {
        return left.width() + right.width();
    }
    
    // 打印横向拼接
    void HCat_Pic::display(ostream& os, int row, int wd) const
    {
        left.display(os, row, left.width());
        right.display(os, row, right.width());
        pad(os, width(), wd);
    }
    
    
    // 定义Picture的成员函数,需要在P_Node等类定义之后,因为用到起内部成员use等
    Picture::Picture(const Picture& orig) : p(orig.p)
    {
        ++orig.p->use;
    }
    
    Picture::~Picture()
    {
        if (--p->use == 0)
        {
            delete p;
        }
    }
    
    Picture& Picture::operator = (const Picture& orig)
    {
        // 先拷贝再删除
        ++orig.p->use;
    
        if (--p->use == 0)
        {
            delete p;
        }
        p = orig.p;
    
        return *this;
    }
    
    // 构造字符图像
    Picture::Picture(const char* const* str, int n) : p(new String_Pic(str, n)) {}
    
    // 用于隐式类型转换
    Picture::Picture(P_Node* p_node) : p(p_node) {}
    
    // 定义Picture的几个私有成员函数
    int Picture::height() const
    {
        return p->height();
    }
    
    int Picture::width() const
    {
        return p->width();
    }
    
    void Picture::display(ostream& o, int x, int y) const
    {
        p->display(o, x, y);
    }
    
    
    // 加边框函数
    // 实质是对Frame_Pic类进行封装一下
    // 该函数的逻辑流程是:
    // 入参为Picture对象,通过该对象生成一个Frame_Pic对象
    // 得到Frame_Pic对象的地址,然后由该地址隐式转换为Picture对象
    // 所以流程为:Picture->Frame_Pic->Frame_Pic*->Picture
    Picture frame(const Picture& pic)
    {
        return new Frame_Pic(pic);
    }
    
    // 纵向拼接
    Picture operator & (const Picture& t, const Picture& b)
    {
        return new VCat_Pic(t, b);
    }
    
    // 横向拼接
    Picture operator | (const Picture& l, const Picture& r)
    {
        return new HCat_Pic(l, r);
    }
    
    // 输出
    // 按行打印,按行打印可以很好的处理拼接打印
    ostream& operator << (ostream& os, const Picture& picture)
    {
        int ht = picture.height();
        int wt = picture.width();
        for (int i = 0; i < ht;++i)
        {
            picture.display(os, i, wt);
            os << endl;
        }
        return os;
    }
    
    int main()
    {
        char* init[] = {"Paris", "in the", "Spring"};
        Picture p1(init, 3);
    
        cout << p1 << endl;
    
        Picture p2 = frame(p1);
        cout << p2 << endl;
    
        Picture p3 = p1 & p2;
        Picture p4 = p1 | p2;
        Picture p5 = frame(p1 & p4);
    
        cout << p3 << endl;
        cout << p4 << endl;
        cout << p5 << endl;
    
        return 0;
    }

           对边框进行修改

             上面的边框我们采用的是+、|、-等字符。下面我们根据用户自定义的字符随意的更改。

             详见代码和注释。

    // 对边框进行修改
    #include <iostream>
    using namespace std;
    
    
    // 前置声明,因为Picture中需要定义P_Node*指针
    class P_Node;
    
    // 定义句柄类(代理)
    class Picture
    {
    private:
        P_Node* p;
    
    public:
        Picture();
        Picture(const char* const*, int);
        Picture(const Picture&);
        ~Picture();
    
        Picture& operator = (const Picture&);
    
        // 增加一构造函数,用于由P_Node*到Picture的隐式类型转换
        Picture(P_Node*);
    
    private:
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const; // 输出
    
    
        // 友元
        friend ostream& operator << (ostream&, const Picture&);
    
        // 边框函数
        friend Picture frame(const Picture&);
        
        // 更改边框
        friend Picture reframe(const Picture&, char, char, char);
        
        // 纵向拼接
        friend Picture operator & (const Picture&, const Picture&);
        // 横向拼接
        friend Picture operator | (const Picture&, const Picture&);
    
        // 友元类
        friend class String_Pic;
        friend class Frame_Pic;
        friend class HCat_Pic;
        friend class VCat_Pic;
    };
    
    
    // 定义实际图像抽象基类
    class P_Node
    {
    private:
    
    protected: // 原来为private,由于派生类String_Pic的reframe成员函数需要修改use,所以更改访问权限
        int use; // 引用计数,用来记录实际被代理个数
    
    protected:
        P_Node(); // 用来初始化引用计数
    
        // 由于Picture析构函数中用到了delete,所以这里需要一个虚析构函数
        virtual ~P_Node();
    
        virtual int height() const = 0;
        virtual int width()  const = 0;
        virtual void display(ostream&, int, int) const = 0;
    
    protected:
        // 求较大的数
        int max(int, int) const;
    
        virtual Picture reframe(char, char, char) = 0;
    
        // 全局reframe函数
        friend Picture reframe(const Picture&, char, char, char);
    
        // 友元
        friend class Picture;
    };
    
    P_Node::P_Node() : use(1) {}
    
    P_Node::~P_Node() {}
    
    int P_Node::max(int x, int y) const
    {
        return x > y ? x : y;
    }
    
    // 字符图像
    class String_Pic : public P_Node
    {
    private:
        char** data;
        int    size;
    
    private:
        String_Pic(const char* const*, int);
        ~String_Pic();
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
    protected:
        Picture reframe(char, char, char);
    
        friend Picture reframe(const Picture&, char, char, char);
    
        friend class Picture;
    };
    
    String_Pic::String_Pic(const char* const* p, int n) : data(new char*[n]), size(n)
    {
        for (int i = 0; i < n; ++i)
        {
            data[i] = new char[strlen(p[i]) + 1];
            strcpy(data[i], p[i]);
        }
    }
    
    String_Pic::~String_Pic()
    {
        for (int i = 0; i < size; ++i)
        {
            delete [] data[i];
        }
        delete [] data;
    }
    
    int String_Pic::height() const
    {
        return size;
    }
    
    int String_Pic::width() const
    {
        int n = 0;
        for (int i = 0; i < size; ++i)
        {
            n = max(n, strlen(data[i]));
        }
        return n;
    }
    
    static void pad(ostream& os, int x, int y)
    {
        for (int i = x; i < y; ++i)
        {
            os << ' ';
        }
    }
    
    void String_Pic::display(ostream& os, int row, int width) const
    {
        int start = 0;
        if (row >= 0 && row < height())
        {
            os << data[row];
            start = strlen(data[row]);
        }
        pad(os, start, width);
    }
    
    // String_Pic的reframe函数
    Picture String_Pic::reframe(char, char, char)
    {
        ++use; // 这里没有new一个新的对象,还是代理原来的对象,所以需要自加use
        return this; // 隐式类型转换
    }
    
    // 边框图像
    class Frame_Pic : public P_Node
    {
    private:
        Picture p;
    
        char corner;
        char sideborder;
        char topborder;
    
    private:
        Frame_Pic(const Picture&, char = '+', char = '|', char = '-');
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
    protected:
        Picture reframe(char, char, char);
    
        friend Picture reframe(const Picture&, char, char, char);
    
        // 该友元函数对该类进行一次封装
        friend Picture frame(const Picture&);
        friend class Picture;
    };
    
    Frame_Pic::Frame_Pic(const Picture& pic, char c, char s, char t)
        : p(pic), corner(c), sideborder(s), topborder(t) {}
    
    int Frame_Pic::height() const
    {
        return p.height() + 2;
    }
    
    int Frame_Pic::width() const
    {
        return p.width() + 2;
    }
    
    // 修改display函数,将+、|、-改为corner、sideborder、topborder
    void Frame_Pic::display(ostream& os, int row, int wd) const
    {
        if (row < 0 || row >= height())
        {
            // 越界
            pad(os, 0, wd);
        }
        else
        {
            if (row == 0 || row == height() - 1) // 上边或下边
            {
                os << corner;
                int i = p.width();
                while (--i >= 0)
                {
                    os << topborder;
                }
                os << corner;
            }
            else // 中间行
            {
                os << sideborder;
                p.display(os, row - 1, p.width());
                os << sideborder;
            }
            // 打印width()到wd之间的空格
            // wd是入参
            pad(os, width(), wd);
        }
    }
    
    Picture Frame_Pic::reframe(char c, char s, char t)
    {
        return new Frame_Pic(::reframe(p, c, s, t), c, s, t);
    }
    
    // 纵向拼接
    class VCat_Pic : public P_Node
    {
    private:
        Picture top;
        Picture bottom;
    
    private:
        VCat_Pic(const Picture&, const Picture&);
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
    protected:
        Picture reframe(char, char, char);
    
        friend Picture reframe(const Picture&, char, char, char);
    
        // 该友元函数对该类进行封装
        friend Picture operator & (const Picture&, const Picture&);
        friend class Picture;
    };
    
    VCat_Pic::VCat_Pic(const Picture& t, const Picture& b) : top(t), bottom(b) {}
    
    int VCat_Pic::height() const
    {
        return top.height() + bottom.height();
    }
    
    int VCat_Pic::width() const
    {
        return max(top.width(), bottom.width());
    }
    
    void VCat_Pic::display(ostream& os, int row, int wd) const
    {
        if (row >= 0 && row < top.height()) // 打印上部分
        {
            top.display(os, row, wd);
        }
        else if (row < top.height() + bottom.height() && row >= top.height()) // 打印下部分
        {
            bottom.display(os, row - top.height(), wd);
        }
        else
        {
            pad(os, 0, wd);
        }
    }
    
    Picture VCat_Pic::reframe(char c, char s, char t)
    {
        // 调用全局reframe函数
        return new VCat_Pic(::reframe(top, c, s, t), ::reframe(bottom, c, s, t));
    }
    
    // 横向拼接
    class HCat_Pic : public P_Node
    {
    private:
        Picture left;
        Picture right;
    
    private:
        HCat_Pic(const Picture&, const Picture&);
    
        int height() const;
        int width()  const;
        void display(ostream&, int, int) const;
    
    protected:
        Picture reframe(char, char, char);
    
        friend Picture reframe(const Picture&, char, char, char);
    
        // 该友元函数对该类进行封装
        friend Picture operator | (const Picture&, const Picture&);
        friend class Picture;
    };
    
    HCat_Pic::HCat_Pic(const Picture& l, const Picture& r) : left(l), right(r) {}
    
    int HCat_Pic::height() const
    {
        return max(left.height(), right.height());
    }
    
    int HCat_Pic::width() const
    {
        return left.width() + right.width();
    }
    
    // 打印横向拼接
    void HCat_Pic::display(ostream& os, int row, int wd) const
    {
        left.display(os, row, left.width());
        right.display(os, row, right.width());
        pad(os, width(), wd);
    }
    
    Picture HCat_Pic::reframe(char c, char s, char t)
    {
        return new HCat_Pic(::reframe(left, c, s, t), ::reframe(right, c, s, t));
    }
    
    
    // 定义Picture的成员函数,需要在P_Node等类定义之后,因为用到起内部成员use等
    Picture::Picture(const Picture& orig) : p(orig.p)
    {
        ++orig.p->use;
    }
    
    Picture::~Picture()
    {
        if (--p->use == 0)
        {
            delete p;
        }
    }
    
    Picture& Picture::operator = (const Picture& orig)
    {
        // 先拷贝再删除
        ++orig.p->use;
    
        if (--p->use == 0)
        {
            delete p;
        }
        p = orig.p;
    
        return *this;
    }
    
    // 构造字符图像
    Picture::Picture(const char* const* str, int n) : p(new String_Pic(str, n)) {}
    
    // 用于隐式类型转换
    Picture::Picture(P_Node* p_node) : p(p_node) {}
    
    // 定义Picture的几个私有成员函数
    int Picture::height() const
    {
        return p->height();
    }
    
    int Picture::width() const
    {
        return p->width();
    }
    
    void Picture::display(ostream& o, int x, int y) const
    {
        p->display(o, x, y);
    }
    
    
    // 加边框函数
    // 实质是对Frame_Pic类进行封装一下
    // 该函数的逻辑流程是:
    // 入参为Picture对象,通过该对象生成一个Frame_Pic对象
    // 得到Frame_Pic对象的地址,然后由该地址隐式转换为Picture对象
    // 所以流程为:Picture->Frame_Pic->Frame_Pic*->Picture
    Picture frame(const Picture& pic)
    {
        return new Frame_Pic(pic);
        // 注意,这里返回操作是将new出来的P_Node指针隐式转换为Picture
        // new时,其内部的use已经置为1,不需要再对use自加
        // 这里对应于P_Node的更改边框成员函数的操作:
        // 更改边框是直接return this,并没有new一个新对象,所以需要对其内部use自加
    }
    
    // 更改边框,全局函数
    Picture reframe(const Picture& pic, char c, char s, char t)
    {
        return pic.p->reframe(c, s, t);
    }
    
    // 纵向拼接
    Picture operator & (const Picture& t, const Picture& b)
    {
        return new VCat_Pic(t, b);
    }
    
    // 横向拼接
    Picture operator | (const Picture& l, const Picture& r)
    {
        return new HCat_Pic(l, r);
    }
    
    // 输出
    // 按行打印,按行打印可以很好的处理拼接打印
    ostream& operator << (ostream& os, const Picture& picture)
    {
        int ht = picture.height();
        int wt = picture.width();
        for (int i = 0; i < ht;++i)
        {
            picture.display(os, i, wt);
            os << endl;
        }
        return os;
    }
    
    int main()
    {
        char* init[] = {"Paris", "in the", "Spring"};
        Picture p1(init, 3);
    
        cout << p1 << endl;
    
        Picture p2 = frame(p1);
        cout << p2 << endl;
    
        Picture p3 = p1 & p2;
        Picture p4 = p1 | p2;
        Picture p5 = frame(p1 & p4);
    
        cout << p3 << endl;
        cout << p4 << endl;
        cout << p5 << endl;
    
        Picture p6 = reframe(p5, '*', '*', '*');
        cout << p6 << endl;
    
        return 0;
    }

             更改边框添加的操作有:

             增加全局函数:reframe,该函数用于调用Picture对象中的P_Node指针指向的对象中的reframe成员函数。

             在P_Node继承层次中添加reframe成员函数,实现P_Node的几个派生类的reframe成员函数。

             修改Frame_Pic类的定义,几个表示边框的成员

             修改Frame_Pic的display成员函数。

            

             全局函数reframe和类成员函数reframe调用关系其实是一个递归操作,终止条件时String_Pic的reframe函数,Frame_Pic、VCat_Pic、HCat_Pic都会递归下去。

           其他扩展

             之前的《字符图像》中,我们进行了几个扩展:去边框、靠右拼接、靠下拼接等。同样我们可以在此基础上进行这几个扩展。

             去边框:设置一个标示量,用于记录边框的个数。对于Frame_Pic函数来说,直接返回其成员p,并需要对use自加,表示被代理个数增加1。

             靠右拼接,需要先打印左边的空格,然后再打印真正的图像字符行。为了先打印左边的空格,可以修改display函数,添加一个参数,用于表示每行先打印的空格数。对于靠右拼接(纵向拼接)时,对于宽度小的图像,每行先打印空格,之后再打印本行。这样不影响其他三种拼接,因为对于其他三种拼接,我们只需将display的打印空格参数置为0即可。

             靠下拼接,row参数的实参值为row-(max-width)即可。

  • 相关阅读:
    Table的基本操作
    MySQL数据库基本操作
    jmeter中服务器返回的cookies的查看
    jemeter的乱码问题
    cucumber的报告
    Cucumber的依赖
    idea里maven执行插件pom文件依赖设置
    Tomcat和jenkins的安装
    maven配置
    Ajax必知必会
  • 原文地址:https://www.cnblogs.com/unixfy/p/3463846.html
Copyright © 2020-2023  润新知