Truyen2U.Net quay lại rồi đây! Các bạn truy cập Truyen2U.Com. Mong các bạn tiếp tục ủng hộ truy cập tên miền mới này nhé! Mãi yêu... ♥

chuong 7

CHƯƠNG 7: MỘT SỐ LỚP QUAN TRỌNG

GIỚI THIỆU

Chương này cung cấp cho  sinh viên một số lớp quan trọng và thường dùng sau:

-          Lớp vật chứa, danh sách liên kết đơn, ngăn xếp và hàng đợi

-          Lớp tập hợp

-          Lớp chuỗi động

-          Lớp Windows

7.1. LỚP VẬT CHỨA (CONTAINER)

            Một lớp vật chứa định nghĩa các đối tượng có khả năng chứa đựng các đối tượng khác, như các xâu và cây nhị phân. Kiểu vật chứa là những khối  xây dựng căn bản cho các chương trình máy tính. Vì C++ thiếu một bộ các lớp vật chứa chuẩn nên việc đưa thêm vào sẽ rất có ích. Ngoài ra, xây dựng một thư viện các lớp vật chứa giúp  cho việc minh hoạ rõ hơn nhiều khái niệm trong lập trình hướng đối tượng.

7.1.1. Khái niệm thiết kế chung

            Khi xây dựng các cấu trúc dữ liệu tổng quát trong C++, có ba vấn đề xảy ra. Thứ nhất, các kiểu dữ liệu có sẵn, như int và float thì không phải là lớp. Thứ hai, không phải tất cả các lớp C++ đều được dẫn xuất từ một lớp gốc đơn, do đó không thể có một phương tiện chung để gộp được tất cả các kiểu dữ liệu.

            Chúng ta mong muốn các lớp vật chứa có tính tương ứng bội. Ví dụ như phần chương trình cho một ngăn xếp hoặc một hàng đợi có thể hoán đổi được cho nhau. Do đó, vấn đề thứ ba là khi xây dựng các lớp vật chứa tổng quát, thì các hàm thành phần ảo phải có một kiểu dữ liệu đồng nhất cho mọi lớp mà trong đó nó được định nghĩa. Như vậy, cần tìm phương pháp có thể tham khảo tới đối tượng của tất cả các kiểu theo cùng một cách thức chung.

            Cách thực hiện tốt nhất là dùng con trỏ kiểu void. Một con trỏ kiểu void có thể trỏ tới mọi thứ, như là các kiểu bên trong hay là đối tượng của một lớp nào đó. Do vậy, các lớp vật chứa sẽ chỉ lưu trữ các con trỏ trỏ đến đối tượng thật sự. Điều này cho phép chúng ta không cần phải cấp phát bộ nhớ lưu trữ bản sao chép các đối tượng và dữ liệu thành phần. Nhưng nó cũng đòi hỏi chúng ta phải cẩn thận, không nên lưu trữ bất kỳ một con trỏ nào trong một vật chứa mà vùng nhớ của con trỏ này lại có thể huỷ trước khi bản thân vật chứa này bị xoá. Chúng ta không thể kiểm tra kiểu các đối tượng được gán vào hay lấy ra từ con trỏ void.

7.1.2. Lớp Container

            Tất cả các lớp vật chứa được dẫn xuất từ lớp Container. Sau đây là tệp tin chứa định nghĩa của lớp này (containr.hpp):

#if !defined(_CONTAINR_HPP)

#define _CONTAINR_HPP 1

class Container

            {          protected:

                                    unsigned long Count; //Số phần tử trong Container

                                    //Con trỏ trỏ đến hàm xử lý ngoại lệ

                                    void (*ErrorHandler)();

                        public:

                                    Container();

                                    Container(const Container &c);

                                    void  operator = (const Container &c);

                                    virtual int Store(void *item) =0; //Lưu một phần tử

                                    virtual void *Examine()=0; //Xem giá trị của một phần tử

                                    virtual void *Retrieve()=0; //Lấy một phần tử ra

                                    virtual void Empty()=0; //Huỷ bỏ nội dung

                                    unsigned long GetCount()// Trả lại số phần tử trong vật chứa

                                                {          return Count;               }

                                    //Định nghĩa hàm xử lý ngoại lệ

                                    void AssignHandler(void (*userHandler)())

                                                {          ErrorHandler=userHandler;    }

            };

#endif

            Sau đây là tệp tin cài đặt của Container (containr.cpp):

#include "Containr.hpp"

extern "C"

            {          #include <stdio.h>      }

//Nguyên mẫu

static void DefaultHandler();

//Hàm xử lý ngoại lệ mặc định

static void DefaultHandler()

            {          puts("

Container Error: Memory allocation failure!");          }

Container:: Container()

            {          Count=0;

                        ErrorHandler=DefaultHandler;

            }

Container:: Container(const Container &c)

            {          Count=c.Count;

                        ErrorHandler=c.ErrorHandler;

            }

void Container::operator=(const Container &c)

            {          Count=c.Count;

                        ErrorHandler=c.ErrorHandler;

            }

Chỉ có hai dữ liệu thành phần được định nghĩa trong Container, đó là biến đếm số lượng phần tử đang cất trong Container và một con trỏ trỏ tới hàm được gọi khi xảy ra lỗi trong Container, ví dụ như lỗi về cấp phát bộ nhớ. Trong trường hợp này, ErrorHandler trỏ tới hàm xử lý lỗi cho mỗi Container.

Hàm khởi tạo cho Container không có tham số nào. Nó chỉ đơn thuần gán 0 vào dữ liệu thành phần Count. Toán tử gán được định nghĩa để chép các giá trị của Count và ErrorHandler từ đối tượng nguồn vào đối tượng đích. Không cần có hàm huỷ bỏ cho Container vì nó không cần phải huỷ bỏ gì nữa.

Có vẻ đặc biệt khi định nghĩa một hàm khởi tạo và một toán tử gán cho một lớp đơn giản như Container. Mục đích của chúng là cung cấp một giao tiếp phục vụ yêu cầu chung cho tất cả các lớp vật chứa. Ví dụ, mọi đối tượng vật chứa đều phải gán 0 cho thành phần dữ liệu Count, thừa kế từ Container. Thay vì mỗi lớp lưu trữ đều phải thực hiện cùng một phép gán như vậy trong các hàm khởi tạo của chúng, tốt hơn nếu ta thực hiện điều đó trong hàm khởi tạo của lớp cơ sở. Có một ưu điểm nữa, là mọi thay đổi về yêu cầu cơ bản nào đó của một vật chứa có thể được phản ánh lên tất cả các lớp dẫn xuất từ Container, chỉ bằng cách thay đổi các hàm thành phần của Container mà thôi.

Không có bản cài đặt cho bốn hàm thành phần cuối cùng định nghĩa trong lớp Container, vì tất cả chúng đều là các hàm thành phần ảo. Store() cất tham số con trỏ của nó vào trong Container. Cả hai hàm thành phần Examine() và Retrieve() trả về con trỏ của phần tử hiện thời trong một Container. Điểm khác nhau giữa chúng là ở chỗ Retreve() xoá mọi sự tham khảo đến phần tử đó ra khỏi danh sách. Cuối cùng, hàm thành phần Empty() dùng để loại bỏ tất cả các con trỏ phần tử ra khỏi một Container.

Bốn hàm thành phần này là ảo vì chúng phụ thuộc vào kiểu vật chứa mà trên đó chúng được sử dụng. Ví dụ, Store() của ngăn xếp (Stack) làm việc khác với Store() của hàng đợi (Queue). Container là một lớp trừu tượng, nó định nghĩa các khả năng chung cho một nhóm các lớp. Vì không cần cài đặt bốn hàm thành phần này, nên nó định nghĩa chúng như các hàm thành phần thuần túy ảo.

Hai hàm thành phần cuối cùng định nghĩa cho Container là hàm tiện ích. GetCount() trả về giá trị của biến Count, và AssignHandler() dùng để gán con trỏ cho một hàm mà nó được dùng để thông báo lỗi. Nếu  AssignHandler() không được gọi, hàm thông báo lỗi mặc định sẽ được dùng, như đã định nghĩa trước trong tập tin cài đặt containr.cpp của lớp Container.

7.2. LỚP DANH SÁCH LIÊN KẾT ĐƠN (SINGLY LINKED-LIST)

            Danh sách liên kết đơn là một trong những khối xây dựng căn bản của các chương trình. Tập tin đầu định nghĩa lớp cho một lớp danh sách liên kết đơn  (singlist.hpp) như sau:

#if !defined(_SINGLIST_HPP)

#define _SINGLIST_HPP 1

#include "Containr.hpp"

class SinglyLinkedList: public Container

            {          protected:

                                    struct ListNode//Cấu trúc của một nút trong danh sách

                                                {          ListNode *Next;

                                                            void *DataPtr;

                                                };

                                    ListNode *Head; //Con trỏ tới nút đầu

                                    ListNode *Tail; //Con trỏ tới nút cuối

                        private:

                                    //Hàm thành phần sao chép

                                    void Copy(const SinglyLinkedList &s1);

                        public:

                                    SinglyLinkedList();

                                    SinglyLinkedList( const SinglyLinkedList &s1);

                                    void operator = (const SinglyLinkedList &s1);

                                    ~SinglyLinkedList();

                                    virtual int Store(void *item) = 0; //lưu một phần tử

                                    virtual void *Examine() = 0; //xem một phần tử

                                    virtual void *Retrieve() = 0; //lấy ra một phần tử

                                    virtual void Empty();//Huỷ bỏ tất cả các phần tử khỏi danh sách

            };

#endif

            SinglyLinkedList là lớp cơ sở cho các lớp danh sách liên kết đơn khác và nó dẫn xuất từ lớp trừu tượng Container. Trên thực tế, chính SinglyLinkedList lại là một lớp trừu tượng với ba hàm thành phần thuần túy ảo.

            Đóng góp chủ yếu của SinglyLinkedList trong cấu trúc cây các lớp vật chứa là việc đưa thêm các cấu trúc dữ liệu được dùng cho danh sách liên kết đơn.  Cấu trúc ListNode định nghĩa dạng của một mắt xích trong danh sách liên kết đơn. Mặc dầu ListNode được định nghĩa bên trong SinglyLinkedList, nó là một cấu trúc phạm vi toàn cục. Bất kỳ một định nghĩa kiểu nào trong các lớp (hoặc các struct hoặc union) cũng đều dùng chung được, bất kể chúng được định nghĩa dưới chỉ định truy xuất nào.

            Các thành phần dữ liệu Head và Tail là các con trỏ trỏ tới thành phần đầu và cuối của danh sách liên kết đơn.

            Copy() là hàm thành phần private đầu tiên, Copy() sao chép xâu các con trỏ trỏ tới ListNode từ một đối tượng SinglyLinkedList này sang một đối tượng khác. Nó được dùng cho cả cấu tử sao chép lẫn toán tử gán và chương trình sẽ đơn giản hơn khi chỉ dùng một phương thức chung cho quá trình này. Vì không muốn hàm thành phần Copy() được gọi trực tiếp ở bên ngoài phạm vi lớp, nên chúng ta đã cho nó được bảo vệ bằng cách chỉ định kiểu truy xuất là private.

            Sau đây là cài đặt các hàm thành phần của lớp SinglyLinkedList trong tệp tin singlist.cpp:

#include "SingList.hpp"

extern "C"

            {          #include <stddef.h>   }

//Hàm thành phần sao chép

void SinglyLinkedList::Copy(const SinglyLinkedList &s1)

            {          Head=NULL;

                        Tail=NULL;

                        ListNode *temp=s1.Head;

                        while (temp!=NULL)

                                    {          if (Tail==NULL)

                                                            {          Tail=new ListNode;

if (Tail==NULL) ErrorHandler();

Head=Tail;

Tail->Next=NULL;

Tail->DataPtr=temp->DataPtr;

                                                            }

                                                else

                                                            {          Tail->Next=new ListNode;

if (Tail->Next==NULL) ErrorHandler();

Tail->Next->Next =NULL;

Tail->Next->DataPtr=temp->DataPtr;

Tail=Tail->Next;

                                                            }

                                                temp=temp->Next;

                                    }

            }

SinglyLinkedList ::SinglyLinkedList():Container()

            {          Head=NULL;Tail=NULL;    }

SinglyLinkedList:: SinglyLinkedList ( const SinglyLinkedList &s1):Container(s1)

            {          Copy(s1);        }

void SinglyLinkedList::operator = (const SinglyLinkedList &s1) //Toán tử gán

            {          this->Empty();

                        Count=s1.Count;

                        Copy(s1);

            }

SinglyLinkedList ::~ SinglyLinkedList()

            {          this->Empty();            }

//Huỷ bỏ tất cả các phần tử khỏi danh sách

void SinglyLinkedList ::Empty()

            {          ListNode *temp, *hold;

                        temp=Head;

                        while (temp!=NULL)

                                    {          hold=temp->Next; delete temp; temp=hold;  }

            }

            Mặc dù SinglyLinkedList không cài đặt các hàm thành phần cất và đọc các phần tử từ một danh sách liên kết đơn, nó vẫn biết được cấu trúc của danh sách liên kết đơn và có thể cài đặt các Hàm khởi tạo , huỷ bỏ và hàm thành phần Empty(). SinglyLinkedList có thể là một lớp cơ sở trừu tượng, nhưng nó vẫn cung cấp những bản cài đặt cho các phương thức tiện ích để dùng cho các lớp dẫn  xuất từ nó.

7.3. LỚP NGĂN XẾP VÀ HÀNG ĐỢI (STACK VÀ QUEUE)

            Lớp ngăn xếp và hàng đợi có dạng tương tự với lớp danh sách liên kết đơn. Trong một ngăn xếp (stack), các phần tử được cất vào sao cho phần tử được cất sau thì được lấy ra trước. Còn hàng đợi (Queue) thì làm việc ngược lại, tức là phần tử được cất trước chính là phần tử được lấy ra trước. Sự khác nhau chính giữa Stack và Queue là cách cất các phần tử vào nó. Trong phép cất vào Stack , các phần tử mới được cất vào đầu của xâu, trong khi ở Queue, phần tử mới được cất vào cuối của xâu.

            Định nghĩa của lớp Stack dựa trên lớp SinglyLinkedList trong stack.hpp:

#if !defined (_STACK_HPP)

#define _STACK_HPP 1

#include "SingList.hpp"

class Stack: public SinglyLinkedList

            {          public:

                                    Stack();                                   

                                    Stack(const Stack &st);

                                    void operator = (const Stack &st);

                                    //Lưu một phần tử vào ngăn xếp

                                    virtual int Store(void *item);

                                    //Xem xét tiếp phần tử trên đỉnh ngăn xếp.

                                    virtual void *Examine();

                                    //Lấy phần tử trên đỉnh ngăn xếp và huỷ nó

                                    virtual void *Retrieve();

            };

#endif

Stack không định nghĩa bất kỳ phần tử dữ liệu nào. Mọi thứ cần thiết đều được thừa hưởng từ SinglyLinkedList, mà đến lượt nó lại thừa hưởng từ Container. Hàm khởi tạo của Stack cũng đơn giản. Chúng chỉ đơn thuần gửi các thông số tới các hàm khởi tạo của SinglyLinkedList. Toán tử gán của Stack cho ta thấy cách gọi một phương thức toán tử được kế thừa như thế nào. Sau đây là một ví dụ mà ở đó, con trỏ this trỏ tới đối tượng ẩn trở nên cần thiết.

Các hàm thành phần của Stack trong stack.cpp như sau:

#include "Stack.hpp"

extern "C"

            {          #include <stddef.h>   }

Stack:: Stack(): SinglyLinkedList()    {          }

Stack::Stack(const Stack &st): SinglyLinkedList(st){          }

void Stack::operator = (const Stack &st)

            {          this-> SinglyLinkedList::operator = (st);        }

//Thêm một phần tử

int Stack::Store(void *item)

            {          ListNode *new_node;

                        new_node = new ListNode;

                        if (new_node==NULL) return 1;

                        new_node->Next=Head;

                        new_node->DataPtr=item;

                        Head=new_node;

                        if (Tail==NULL) Tail=new_node;

                        Count++;

                        return 0;

            }

//Xem xét phần tử trên đỉnh ngăn xếp

void *Stack::Examine()

            {          if (Count==0)  return NULL;

                        return Head->DataPtr;

            }

//Lấy ra và huỷ bỏ phần tử trên đỉnh ngăn xếp.

void *Stack::Retrieve()

            {          ListNode *temp;

                        void  *value;

                        if (Count==0)  return NULL;

                        value=Head->DataPtr;

            temp=Head->Next;

            delete Head;

            Head=temp;

            --Count;

            return value;

}         

Store() đặt phần tử mới vào đầu của Stack, còn Retrieve() và Examine() trả lại phần tử con trỏ đang được giữ ở đầu của Stack.

Vì sự khác nhau duy nhất giữa Stack và Queue chỉ là ở cách thức lưu trữ các phần tử dữ liệu, nên Queue có thể được dẫn xuất từ Stack. Nội dung của nó trong tệp tin queue.hpp như sau:

#if !defined (_QUEUE_HPP)

#define _QUEUE_HPP 1

#include "Stack.hpp"

class Queue: public Stack

            {          public:

                                    Queue();

                                    Queue(const Queue &q);

                                    void operator = (const Queue &q);

                                    //Lưu một phần tử vào Queue

                                    virtual int Store(void *item);

            };

#endif

Queue chỉ định nghĩa những gì cần thiết: các hàm khởi tạo, toán tử gán và hàm thành phần Store() của riêng nó để đặt các phần tử mới ở cuối của xâu.

Các lớp dẫn xuất không thừa hưởng được cấu tử và toán tử gán đã định nghĩa trong một lớp cơ sở. Do đó, Queue phải định nghĩa các bản riêng cho các hàm thành phần này, bởi vì chúng không được thừa hưởng từ Stack. Các hàm thành phần của Queue trong queue.cpp như sau:

#include "Queue.hpp"

extern "C"

            {          #include <stddef.h>   }

Queue:: Queue(): Stack()

            {          }

Queue:: Queue(const Queue &q): Stack(q)    {  }

void Queue ::operator = (const Queue &q)

            {          this-> Stack::operator = (q);    }

//Thêm một phần tử mới

int Queue::Store(void *item)

            {          ListNode *new_node;

                        new_node = new ListNode;

                        if (new_node==NULL)          return 1;

                        new_node->Next=NULL;

                        new_node->DataPtr=item;

                        if (Count>0)

                                    {          Tail->Next=new_node;

                                                Tail=new_node;

                                    }

                        else

                                    {          Head=new_node;

                                                Tail=new_node;

                                    }

                        Count++;

                        return 0;

            }

Chương trình con sqdemo.cpp minh họa cách thức làm việc của các lớp Stack và Queue:

extern "C"

            {          #include <stddef.h>

                        #include <stdio.h>

            }

#include "Stack.cpp"

#include "Queue.cpp"

#include "SingList.cpp"

#include "Containr.cpp"

Stack s,s2;

Queue q,q2;

int i, a[10], *ip;

int main()

            {          for (i=1;i<10;++i)

                                    {          a[i]=i;

                                                s.Store(&(a[i]));

                                                q.Store(&(a[i]));

                                    }

                        s2=s;

                        q2=q;

                        printf("

Stack2= ");

                        while (NULL!=(ip=(int *)(s2.Retrieve())))     printf("%3d",*ip);

                        printf("

Queue2= ");

                        while (NULL!=(ip=(int *)(q2.Retrieve())))    printf("%3d",*ip);

                        printf("

Stack= ");

                        while (NULL!=(ip=(int *)(s.Retrieve())))       printf("%3d",*ip);

                        printf("

Queue= ");

                        while (NULL!=(ip=(int *)(q.Retrieve())))      printf("%3d",*ip);

                        printf("

");

                        return 0;

            }

Condemo.cpp tạo ra một mảng các số nguyên từ 1 đến 10 và sau đó gán các địa chỉ phần tử mảng vào các đối tượng lớp Stack và lớp Queue là s và q. Sau đó, toán tử gán được dùng để chép lại hai biến vật chứa này. Các phần tử đang cất trong  Stack và Queue sẽ được hiển thị.

7.4. LỚP DANH SÁCH LIÊN KẾT KÉP (DOUBLY LINKED LIST)

Trong một danh sách liên kết đơn, mỗi phần tử trong danh sách giữ một con trỏ trỏ tới phần tử sau nó. Trong một danh sách liên kết kép, mỗi phần tử vật chứa sẽ giữ thêm một con trỏ trỏ đến phần tử trước nó. Một danh sách liên kết đơn xử lý nhanh chóng hơn một danh sách liên kết kép, nhưng những danh sách liên kết kép thì có thể được dùng cho những công việc phức tạp hơn danh sách liên kết đơn.

Sau đây là định nghĩa lớp danh sách liên kết đúp trong dbllist.hpp:

#if !defined(_DBLLIST_HPP)

#define _DBLLIST_HPP 1

#include "Containr.hpp"

class DoublyLinkedList: public Container

            {          protected:

                                    //Cấu trúc các nút trong danh sách

                                    struct DListNode

                                                {          DListNode *Prev;

                                                            DListNode *Next;

                                                            void *DataPtr;

                                                };

                                    DListNode *Head; //Con trỏ tới nút đầu

                                    DListNode *Tail; //Con trỏ tới nút cuối

                        private:

                                    void Copy(const DoublyLinkedList &sl);

                        public:

                                    DoublyLinkedList();

                                    DoublyLinkedList( const DoublyLinkedList &sl);

                                    void operator = (const DoublyLinkedList &sl);

                                    ~DoublyLinkedList();

                                    //lưu một phần tử

                                    virtual int Store(void *item) = 0;

                                    //xem một phần tử

                                    virtual void *Examine() = 0;

                                    //lấy ra một phần tử

                                    virtual void *Retrieve() = 0;

                                    //Huỷ bỏ tất cả các phần tử khỏi danh sách

                                    virtual void Empty();

            };

#endif

Các kiểu của hàm thành phần và dữ liệu thành phần được định nghĩa bởi DoublyLinkedList tương ứng với định nghĩa đã có trong SinglyLikedList. Giống như lớp SinglyLikedList, DoublyLinkedList là một lớp trừu tượng, nó cung cấp một cơ sở cho các lớp danh sách liên kết đúp khác. DoublyLinkedList được cài đặt trong tệp tin dbllist.cpp như sau:

#include "DblList.hpp"

extern "C"

            {          #include <stddef.h>   }

void DoublyLinkedList::Copy(const DoublyLinkedList &dl)

            {          Head=NULL;

                        Tail=NULL;

                        DListNode *temp=dl.Head;

                        while (temp!=NULL)

                                    {

                                                if (Tail==NULL)

                                                            {          Tail=new DListNode;

if (Tail==NULL) ErrorHandler();

Head=Tail;

Tail->Next=NULL;

Tail->Prev=NULL;

Tail->DataPtr=temp->DataPtr;

                                                            }

                                                else

                                                            {          Tail->Next=new DListNode;

if (Tail->Next==NULL) ErrorHandler();

Tail->Next->Next =NULL;

Tail->Prev->Prev =Tail;

Tail->Next->DataPtr=temp->DataPtr;

Tail=Tail->Next;

                                                            }

                                                temp=temp->Next;

                                    }

            }

DoublyLinkedList :: DoublyLinkedList():Container()

            {          Head=NULL;

                        Tail=NULL;

            }

DoublyLinkedList::DoublyLinkedList ( const DoublyLinkedList &dl)

            {          Copy(dl);         }

void DoublyLinkedList::operator = (const DoublyLinkedList &dl)

            {          this->Empty();Count=dl.Count;Copy(dl);     }

DoublyLinkedList ::~ DoublyLinkedList()

            {          this->Empty();            }

//Huỷ bỏ tất cả các phần tử khỏi danh sách

void DoublyLinkedList ::Empty()

            {          DListNode *temp, *hold;

                        temp=Head;

                        while (temp!=NULL)

                                    {          hold=temp->Next;

                                                delete temp;   

temp=hold;

                                    }

            }

7.5. CÁC TẬP HỢP

7.5.1. Khái niệm tập hợp (set)

            Theo định nghĩa toán học, tập hợp là một nhóm các đối tượng có cùng kiểu. Các đối tượng này được gọi là phần tử của tập hợp. Nếu hai tập hợp chứa đựng chính xác một số phần tử như nhau, thì chúng được gọi là bằng nhau. Nếu tất cả các phần tử của tập hợp A là phần tử của tập hợp B thì A được gọi là tập con của B. Giao của hai tập hợp là một tập hợp được tạo ra bởi những phần tử chung của cả hai tập hợp gốc. Hợp của hai tập hợp A và B là một tập hợp mà nó chứa tất cả những phần tử có trong A hoặc B.

            Các ngôn ngữ lập trình tiếp cận khái niệm tập hợp từ một góc độ hơi khác. Trong lập trình, một tập hợp chứa đựng một dãy các bit, đặc trưng cho một dãy các khóa nhị phân, lưu trữ trong dãy từ 0 cho đến số lượng lớn nhất các thành phần có thể có trong tập hợp. Mỗi khóa đại diện cho sự có mặt của một phần tử trong tập hợp. Như vậy, nếu một khóa nào đó được bật, thì tập hợp đang xét coi như có chứa phần tử tương ứng với khóa này.

Kiểu dữ liệu dễ sử dụng nhất với một tập hợp là số nguyên. Các bit tương ứng với giá trị của số nguyên được cất trong tập hợp. Để xác định một giá trị nguyên  nào đó có được "lưu" trong tập hợp này không, chúng ta chỉ cần kiểm tra xem giá trị bit ở vị trí đúng của nó. Điều này có nghĩa là mọi thứ biểu diễn được bằng một giá trị nguyên đều có thể được lưu trữ trong một tập hợp.

7.5.2. Định nghĩa lớp tập hợp bit BitSet

Kiểu tập hợp trên được gọi là tập hợp bit. Mỗi Bitset chứa một mảng các bit. Các kiểu tập hợp khác nhau có số lượng phần tử khác nhau. Như vậy, kích thước của mảng này phải linh động. Số bit đòi hỏi cần được xác định khi khởi tạo một tập hợp. Mảng bit này sau đó được cấp phát động. Vì hầu hết các máy vi tính cấp phát vùng nhớ theo những byte 8 bit, nên chúng sẽ được cấp phát bộ nhớ theo đơn vị 8 bit.

BitSet cần có tất cả phép toán chuẩn về tập hợp. Đương nhiên phải có những hàm thành phần để thêm vào và loại ra các giá trị từ một tập hợp. Lớp này cũng cần có một hàm thành phần để xác định một giá trị nào đó có được cất trong tập hợp này hay không. Cần phải tính được tập hợp giao và hội của nhiều tập hợp, do đó, chúng ta  sẽ thiết lập hàm thành phần so sánh tập hợp. Lớp BitSet sẽ có cấu tử sao chép chuẩn, huỷ tử ( để hủy bỏ mảng các bit), và các toán tử gán. Lớp BitSet được định nghĩa sẽ là một lớp sơ sở cho các lớp tập hợp khác.

Việc thiết kế lớp BitSet phải có hiệu quả tốt. Những thao tác thông thường trên BitSet cần có tốc độ thực hiện càng nhanh càng tốt để cho lớp có ý nghĩa thực tiễn và hiệu quả cao.

            Định nghĩa lớp BitSet trong tập tin bitset.hpp như sau:

#if !defined (_BitSet_HPP)

#define _BitSet_HPP 1

extern "C"

            {          #include <stddef.h>

                        #include <string.h>

            }

class BitSet

            {          protected:

                                    unsigned long Length;

                                    unsigned char *Data;

                                    BitSet()

                                                {          Length=0L;Data=NULL;       }

                                    public:

                                    BitSet(unsigned long size);

                                    BitSet(BitSet &bs);

                                    ~BitSet(void);

                                    void operator = (BitSet & bs);

                                    //Lấy số lượng bit trong tập hợp

                                    unsigned long Size()

                                                {          return Length;             }

                                    void Include(unsigned long bit)

                                                {          if (bit<Length)

                                                                        Data[bit/8] |=(unsigned char)(1<<(bit &7));

                                                }                     

                                    void Exclude(unsigned long bit)

                                                {          if (bit<Length)

                                                              Data[bit/8] &= ~ (unsigned char)(1<<(bit &7));

                                                }                     

                                    //bật tất cả các bit trong tập hợp lên

                                    void AllOn()

                                                {          memset(Data,'\xFF',(Length+7)/8);

                                                }                     

                                    //Tắt tất cả các bit trong tập hợp xuống

                                    void AllOff()

                                                {          memset(Data,'\x00',(Length+7)/8);

                                                }                     

                                    //Phép hội

                                    BitSet operator & (BitSet & bs);

                        BitSet operator &= (BitSet & bs);

                                    //Đồng nghĩa với phép hội

                                    BitSet operator + (BitSet & bs);

                        BitSet operator += (BitSet & bs);

                                    //Phép giao

                                    BitSet operator | (BitSet & bs);

                        BitSet operator |= (BitSet & bs);

                                    //Phép trừ

                                    BitSet operator - (BitSet & bs);

                        BitSet operator -= (BitSet & bs);

                        //Phép bù

                        BitSet operator ~ ();

                                    //Phép so sánh

                                    int operator == (BitSet & bs);

                        int operator != (BitSet & bs);

                                    // Toán tử lấy giá trị

                                    int operator [] (unsigned long bit)

                                                {          if (bit<Length)

                                                                        return (Data[bit/8] & (1<<(bit &7)));

                                                            else

                                                                        return 0;

                                                }

            };

#endif

Lớp BitSet định nghĩa hai dữ liệu thành phần private. Length chứa số lượng bit lưu trữ trong tập hợp và Data là một con trỏ trỏ đến chính các bit đó. Các bit được cấp phát theo nhóm 8 bit, đó là số bit của một ký tự (char). Một số bit có thể không được dùng. Ví dụ, một BitSet với Length là 11 sẽ có hai char được cấp phát nên có tổng cộng 16 bit, bởi vì bộ nhớ chỉ có thể được cấp phát mỗi đơn vị 8 bit (một char). Một vài bit bị bỏ cũng không ảnh hưởng đến kích thước chương trình ( vì không có quá 7 bit bị bỏ trong bất kỳ một BitSet nào).

            BitSet định nghĩa cấu tử mặc định là một thành phần inline và private. Điều này đặc biệt, vì một cấu tử private sẽ không thể dùng để tạo các đối tượng bên ngoài phạm vi của lớp này. Theo thiết kế, BitSet phải xác định số lượng các bit trong tập hợp ngay khi một đối tượng BitSet được tạo ra. Còn cấu tử mặc định sẽ được dùng cho một số  hàm thành phần trong lớp, vì thế nó được khai báo là private.

Sau đây là tệp tin cài đặt của BitSet (bitset.cpp):

#include "BitSet.hpp"

extern "C"

            {          #include <stddef.h>

                        #include <string.h>

            }

BitSet::BitSet(unsigned long size)

            {          unsigned long alloc;

                        Length = size;

                        alloc=(size+7)/8;

                        Data = new unsigned char[(unsigned int)alloc];

                        memset(Data,'\x00', (unsigned int)alloc);

            }

BitSet:: BitSet(BitSet & bs)

            {          unsigned long alloc;

                        Length =bs.Length;

                        alloc=( bs.Length+7)/8;

                        Data = new unsigned char[(unsigned int)alloc];

                        memcpy(Data,bs.Data, (unsigned int)alloc);

            }

BitSet ::~ BitSet(void)

            {          if (Data!=NULL) delete Data;

            }

void BitSet ::operator = (BitSet & bs)

            {          unsigned long alloc;

                        if (Length!=bs.Length)

                                    {          Length = bs.Length;

                                                alloc=( bs.Length +7)/8;

                                                if (Data!=NULL)        delete Data;

                                                Data= new unsigned char[(unsigned int)alloc];

                                                memcpy(Data, bs.Data, (unsigned int)alloc);

                                    }

                        else

            memcpy(Data, bs.Data, (unsigned int)((Length+7)/8));

}

//Phép hội

BitSet BitSet::operator & (BitSet & bs)

            {          BitSet result;

                        unsigned long bit;

                        if (Length<bs.Length)

                                    {          result=bs;

                                                for (bit=0;bit<Length;++bit)

                                                            if ((*this)[bit]) result.Include(bit);

                                    }

                        else

                                    {          result=*this;

                                                for (bit=0;bit<bs.Length;++bit)

                                                            if (bs[bit])        result.Include(bit);

                                    }

return result;

            }

BitSet BitSet::operator &= (BitSet & bs)

            {          *this = *this &bs;

                        return *this;

            }

//Đồng nghĩa với phép hội

BitSet BitSet::operator + (BitSet & bs)

            {          BitSet  result= *this & bs;

                        return result;

            }

BitSet BitSet::operator += (BitSet & bs)

            {          BitSet  result= *this &= bs;

                        return result;

            }

//Phép giao của hai tập hợp

BitSet BitSet::operator | (BitSet & bs)

            {          BitSet result;

                        unsigned long max;

                        if (Length<bs.Length)

                                    {          result=BitSet(Length);

                                                max=bs.Length;

                                    }

                        else

                                    {          result=BitSet(bs.Length);

                                                max=Length;

                                    }

                                    for (unsigned long bit=0;bit<max;++bit)

                                                            if ((*this)[bit] & bs[bit]) result.Include(bit);

return result;

            }

BitSet BitSet::operator |= (BitSet & bs)

            {          *this = *this | bs;

                        return *this;

}

//Phép trừ

BitSet BitSet::operator - (BitSet & bs)

            {          BitSet  result= *this;

                        unsigned long stop = (Length <bs.Length) ? Length:bs.Length;

                        for (unsigned long bit=0;bit<stop;++bit)

                                                            if (bs[bit])        result.Exclude(bit);

return result;

            }

BitSet BitSet::operator -= (BitSet & bs)

            {          *this = *this - bs;

                        return *this;

            }

//Phép bù

BitSet BitSet ::operator ~ ()

            {          BitSet  result(Length);

                        for (unsigned long bit=0;bit<Length;++bit)

                                                            if ((*this)[bit])             result.Exclude(bit);

                                                            else                  result.Include(bit);

return result;

            }

//Phép so sánh

int BitSet::operator == (BitSet & bs)

            {          if (Length !=bs.Length)

                                    return 0;

                        for (unsigned long bit=0;bit<Length;++bit)

                                                            if ((*this)[bit]!=bs[bit])           return 0;

return 1;

            }

int BitSet::operator != (BitSet & bs)

            {          if (Length !=bs.Length)

                                    return 1;

                        unsigned long bit=0;

                        while (bit<Length)

                                    if ((*this)[bit]==bs[bit])          ++bit;

                                    else      return 1;

                        return 0;

            }

Có hai hàm huỷ bỏ cho BitSet được khai báo là public. Một hàm khởi tạo nhận tham số unsigned long định nghĩa số bit trong tập hợp mới được tạo. Còn hàm khởi tạo kia là hàm khởi tạo sao chép. Vì BitSet cấp phát bộ nhớ động, nên cần có một hàm huỷ bỏ để trả lại vùng nhớ khi có một đối tượng bị hủy.

Một BitSet có thể được gán bằng với một BitSet khác bằng cách dùng toán tử gán.

Size() là  một hàm thành phần inline trả về giá trị Length của một BitSet. Length có thể được khai báo là thành phần public của BitSet, lúc đó sẽ không cần thiết phải có một hàm riêng cho nó nữa. Tuy  nhiên, điều đó cũng cho phép Length của một BitSet có thể bị sửa đổi từ bên ngoài phạm  vi lớp. Định nghĩa hàm thành phần Size() là một hàm inline có hiệu quả vào lúc chạy tương đương với việc truy xuất trực tiếp Length.

Include() và Exclude() cũng là các hàm thành phần inline, tương ứng bật (set) và tắt (reset) các bit riêng rẽ trong một BitSet. Khi một bit được bật (cho giá trị 1), thì giá trị unsigned long tương ứng với vị trí đó được xem là đã đặt vào trong tập hợp. Ngược lại, việc tắt một bit (đưa nó về zero) sẽ xóa bỏ giá trị unsigned long tương ứng ra khỏi tập hợp.

AllOn() và AllOff() đặt tất cả các bit của một BitSet sang 1 hoặc 0.

Toán tử & để tính hội của hai BitSet. Cả hai dạng nhị phân và gán của toán tử này đều được định nghĩa. Toán tử + đồng nghĩa với &, và thực hiện cùng một chức năng.

Hiệu của hai tập hợp được tính bằng cách loại bỏ các thành phần của tập hợp này ra khỏi tập hợp kia. BitSet dùng toán tử trừ (-) để thực hiện phép toán này.

Phép so sánh giữa hai tập hợp được thực hiện bằng các toán tử == và !=. Không có khái niệm một tập hợp này lớn hơn tập hợp khác. Do đó các toán tử so sánh quan hệ là không cần thiết.

BitSet sử dụng toán tử [] để xác định một giá trị nào đó có phải là thành phần của một tập hợp hay không. Hàm thành phần inline này sử dụng các phép toán xử lý trên bit và định nghĩa inline để cho chạy nhanh.

BitSet đã minh họa một kỹ thuật hữu dụng của C++. Những dạng gán của các toán tử của BitSet được định nghĩa bằng cách gọi đến các phép toán hai toán hạng tương ứng. Các toán tử + được định nghĩa bằng cách gọi trực tiếp đến toán tử &. Cách làm này sẽ đặt tất cả các phần chương trình thật sự thực hiện thao tác về cùng một vị trí.

Các phép toán là những hàm thành phần duy nhất sử dụng cấu tử mặc định. Chúng định nghĩa một đối tượng mà không cần cấp phát bộ nhớ. Những toán tử này  gán giá trị của biến cục bộ kiểu Bitset theo giá trị của một trong các biến BitSet ở đối số trước khi bắt đầu tính toán.

Sau đây là chương trình minh họa làm việc của BitSet ( trong demo2.cpp):

#include "BitSet.cpp"

extern "C"

            {          #include <stdio.h>

                        #include <string.h>

            }

void ShowBitSet(BitSet & bs);

void ShowBitSet(BitSet & bs)

            {          for (int i=0;i<bs.Size();++i)

                                    if (bs[i])           putchar('1');

                                    else      putchar('0');

                        putchar('

');

            }

int main()

            {

                        unsigned int i;

                        BitSet bs1(76); BitSet bs2(13); BitSet bs3(8);

                        for (i=0;i<bs1.Size();++i)

                                    {          if (i%2)            bs1.Include(i);

                                                else                  bs1.Exclude(i);

                                    }

                        ShowBitSet(bs1);

                        bs3=bs1;

                        ShowBitSet(bs3);

                        for (i=0;i<bs2.Size();++i)

                                    {          if (i%2)            bs2.Exclude(i);

                                                else                  bs2.Include(i);

                                    }

                        for (i=4;i<bs3.Size();++i)        bs2.Exclude(i);

                        ShowBitSet(bs2);

                        bs3=bs2+bs1;  ShowBitSet(bs3);

                        bs3=~bs2; ShowBitSet(bs3);

                        bs2=bs1 | bs3;  ShowBitSet(bs2);

                        bs2 -= bs3; ShowBitSet(bs2);

                        bs2=bs1-bs3; ShowBitSet(bs2);

                        bs3 += bs1; ShowBitSet(bs3);

                        bs3.AllOn(); ShowBitSet(bs3);

                        bs3.AllOff();   ShowBitSet(bs3);

                        bs3 = bs2;

                        if (bs3==bs1)   puts("bs3==bs1");

                        if (bs3==bs2)   puts("bs3==bs2");

                        if (bs3!=bs1)    puts("bs3!=bs1");

                        if (bs3!=bs2)    puts("bs3!=bs2");

                        return 0;

            }

7.5.3. Tập hợp ký tự CharSet

Sau khi có lớp BitSet, chúng ta có thể xây dựng một số lớp khác. Một trong những kiểu dữ liệu được định nghĩa trong Pascal là tập hợp ký tự (set of char), đó là một tập hợp tất cả các mã ký tự cho một hệ thống. Tập hợp ký tự có thể cần thiết khi kiểm tra một phím trả lời có nằm trong một tập hợp ký tự hợp lệ hay không.

CharSet là một dạng đặc biệt của BitSet, sử dụng các giá trị char làm chỉ số định vị cho các bit riêng rẽ. Vì có 256 ký tự trong bộ ký tự ASCII, nên một CharSet được định nghĩa với một kích thước cố định, một BitSet 256 bit. Vì nó có thể kế thừa hầu hết tất cả các hàm trong BitSet, nên cài đặt CharSet rất đơn giản. Định nghĩa của CharSet trong tệp tin chrset.hpp như sau:

#if !defined (_CHARSET_HPP)

#define _CHARSET_HPP 1

#include "BitSet.hpp"

class CharSet:public BitSet

            {          public:

                                    CharSet():BitSet(256){ }

                                    CharSet(CharSet &cs):BitSet(cs){ }

                                    CharSet(char *Values);

                                    void operator = (CharSet & cs){         BitSet::operator = (cs);            }

            };

#endif

Cài đặt của CharSet (trong charset.cpp) chỉ chứa bản cài đặt cấu tử chuyển đổi một chuỗi ký tự có ký tự kết thúc theo kiểu của C thành một CharSet:

#include "CharSet.hpp"

CharSet:: CharSet(char *Values):BitSet(256)

            {          while (*Values!='\x00')

                                    {          Include(*Values);++Values;               }

            }

CharSet có định nghĩa cấu tử mặc định và cấu tử  sao chép của riêng nó, gọi tới hàm thành phần tương đương của BitSet. Chiều dài của một CharSet không cần xác định, vì đã có chiều dài cố định là 256 bit.

Chương trình csdemo.cpp minh hoạ cách dùng lớp CharSet:

#include "CharSet.cpp"

#include "BitSet.cpp"

extern "C"

            {          #include <stdio.h>      }

char ics[]="CharSetTestProgram*&!@0123456789";

CharSet cs1;

CharSet cs2;

CharSet cs3;

int main()

            {          unsigned int i;

                        cs1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

                        cs2 = ics;

                        cs3 = cs1;

                        cs2 -= cs1;

                        cs3 -= CharSet("DEFGHI");

                        cs3 += cs2;

                        printf("set 1=[");

                        for (i=0;i<255;++i)

                                    if (cs1[i])         printf("%c",i);

                        printf("]

set 2=[");

                        for (i=0;i<255;++i)

                                    if (cs2[i])         printf("%c",i);

                        printf("]

set 3=[");

                        for (i=0;i<255;++i)

                                    if (cs3[i])         printf("%c",i);

                        printf("]

");

                        return 0;

            }

7.6. LỚP CHUỖI ĐỘNG

Mục đích của lớp chuỗi động là tạo ra một lớp chuỗi được cấp phát động, cung cấp tất cả những chức năng mà các mảng ký tự của C ( kết thúc bằng ký tự NULL ), đã từng có. Tuy nhiên cần phải tránh những lỗi trong các chuỗi C. Chẳng hạn như, các lỗi xảy ra khi làm việc với chuỗi C do không kiểm tra giới hạn hoặc kiểm tra ký tự hợp lệ. Các hàm thư viện định nghĩa trong string.h còn thiếu một số tính năng quan trọng. Do đó, chúng ta có thể đưa thêm những hàm mà bình thường không có trong thư viện hàm của C, chẳng hạn như hàm chèn dữ liệu hoặc xoá bỏ dữ liệu khỏi chuỗi.

7.6.1. Định nghĩa lớp

Lớp String bắt đầu bằng việc định nghĩa ba kiểu liệt kê: StrCompVal, StrCompMode và StrError. StrCompVal là giá trị trả về của hàm thành phần Compare. StrCompMode được dùng để xác định việc so sánh chuỗi có phân biệt chữ in, chữ thường hay không. StrError được dùng cho các hàm xử lý lỗi. Có thể dùng kiểu liệt kê cho các giá trị này để kiểm soát tính hợp lệ của các giá trị được gửi đến hoặc trả về từ hàm thành phần. Các kiểu này phải khai báo là public để các giá trị liệt kê đó có thể sử dụng từ bên ngoài.

Định nghĩa lớp String trong str.hpp như sau:

#if !defined(_STRING_HPP)

#define _STRING_HPP 1

enum StrCompVal {SC_LESS, SC_EQUAL, SC_GREATER, SC_ERROR };

enum StrCompMode {SM_SENSITIVE, SM_IGNORE };

enum StrError {SC_ALLOC, SE_TOO_LONG };

class String

            {          private:

                                    // Các biến thể hiện

                                    unsigned int Siz; //Kích thước cấp phát

                                    unsigned int Len; //Chiều dài hiện thời

                                    char *Txt; // Con trỏ trỏ đến chuỗi ký tự

                                    //Hằng số của lớp

                                    static unsigned int AllocIncr;

                                    //Con trỏ trỏ đến hàm xử lý ngoại lệ

                                    static  void (*ErrorHandler)(StrError err);

                                    //Hàm thành phần private để co phần cấp phát chuỗi

//về kích thước tối thiểu

void Shrink();

                        public:

                                    String();

                                    String(const String &str);

                                    String(const char *cstr);

                                    String(char fillCh, unsigned int count);

                                    ~String();

                                    //Các hàm thành phần trả về giá trị

                                    unsigned int Length();

                                    unsigned int Size();

                                    //Gán hàm xử lý ngoại lệ

                                    static  void  SetErrorHandler (void (*userHandler)(StrError));

                                    // Tạo một chuỗi C từ một hàm thành phần String

                                    operator const char * ();

                                    //hàm thành phần gán

                                    void operator = (const String &str);

                                    //hàm thành phần nối chuỗi

                                    friend String operator + (const String &str1, const String &str2);

                                    void operator += (const String &str);

                                    //hàm thành phần so sánh

                                    int operator < (const String &str);

                                    int operator > (const String &str);

                                    int operator <= (const String &str);

                                    int operator >= (const String &str);

                                    int operator == (const String &str);

                                    int operator != (const String &str);

                                    StrCompVal Compare(const String &str, StrCompMode

caseChk = SM_IGNORE);

                                    //Hàm thành phần tìm chuỗi con

                                    int Find(const String &str, unsigned int & pos,

StrCompMode caseChk = SM_IGNORE);

                                    //Hàm thành phần xoá chuỗi

                                    void Delete(unsigned int pos, unsigned int count);

                                    //Hàm thành phần chèn chuỗi con

                                    void Insert(unsigned int pos, char ch);

                                    void Insert(unsigned int pos, const String &str);

                                    //Hàm thành phần trích một chuỗi con

                                    String SubStr(unsigned int start, unsigned int count);

                                    //Hàm thành phần đọc một ký tự

                                    char operator [] (unsigned int pos);

                                    //Hàm thành phần đổi kiểu chữ

                                    String ToUpper();

                                    String ToLower();

};

#endif

Lớp String định nghĩa ba biến private: Siz, Len và Txt.Siz chứa kích thước cấp phát hiện thời của con trỏ Txt. Len lưu số ký tự thật sự cất trong String. Con trỏ char *Txt trỏ tới một vùng đệm đang chứa đựng dữ liệu văn bản của String.

AllocIncr là thành phần tĩnh private. Các dữ liệu thành phần được định nghĩa  tĩnh chỉ có một phát sinh duy nhất dùng chung cho toàn bộ lớp. Ở đây, chỉ một AllocIncr, dùng chung cho tất cả các đối tượng String. Mục đích các thành phần tĩnh của lớp là để tránh các biến toàn cục. Các thành phần tĩnh private của lớp sẽ được dùng khi nào có một dữ liệu thành phần chỉ được truy xuất từ bên trong phạm vi lớp. Không nên để cho bên ngoài lớp String truy nhập AllocIncr.

Cài đặt của lớp String trong tệp tin str.cpp  có nội dung như sau:

#include "Str.hpp"

extern "C"

            {          #include <string.h>

                        #include <stdio.h>

                        #include <stdlib.h>

                        #include <ctype.h>

                        #include <limits.h>

            }

//Nguyên mẫu cho hàm xử lý ngoại lệ

static void DefaultHandler(StrError err);

//Khởi động cho giá trị hằng của lớp

unsigned int String::AllocIncr=8;

void (*(String::ErrorHandler))(StrError)=DefaultHandler;

// Hàm xử lý ngoại lệ mặc định

static void DefaultHandler(StrError err)

            {          printf ("

Lỗi trong đối tượng String: ");

                        switch (err)

                                    {          case SE_ALLOC:

                                                            printf(" Không thể cấp phát bộ nhớ");

                                                            break; 

                                                case SE_TOO_LONG:

                                                      printf(" Vượt quá giới hạn %d ký tự", UINT_MAX);

                                    }

                        printf("

");

                        exit(1);

            }

void  String::SetErrorHandler (void (*userHandler)(StrError))

            {          ErrorHandler = userHandler;              }

// Hàm thành phần private dùng để co phần cấp phát chuỗi về kích thước tối thiểu

void String::Shrink()

            {          char *temp;

                        if ((Siz-Len)>AllocIncr)

                                    {          Siz=((Len+AllocIncr)/AllocIncr)*AllocIncr;

                                                temp= new char[Siz];

                                                if (temp !=NULL)

                                                            {          strcpy(temp,Txt);

                                                                        delete Txt;

                                                                        Txt=temp;                              

                                                            }

                                                else

                                                            ErrorHandler(SE_ALLOC);

                                    }

            }

String:: String()

            {          Len=0;

                        Siz=AllocIncr;

                        Txt=new char[Siz];

                        if (Txt==NULL) ErrorHandler(SE_ALLOC);

                        Txt[0]='\x00';

            }

String:: String(const String &str)

            {          Len= str.Len;

                        Siz= str.Siz;

                        Txt=new char[Siz];

                        if (Txt==NULL)         ErrorHandler(SE_ALLOC);

                        strcpy(Txt,str.Txt);

            }

String:: String(const char *cstr)

            {          if ((cstr==NULL)||(cstr[0]=='\x00'))

                                    {

                                                Len=0;

                                                Siz=AllocIncr;

                                    }

                        else

                                    {          Len=strlen(cstr);         

                                                Siz=((Len+AllocIncr)/ AllocIncr)* AllocIncr;

                                    }

                        Txt= new char[Siz];

                        if (Txt==NULL)         ErrorHandler(SE_ALLOC);

                        if (Len>0)        strcpy(Txt,cstr);

                        else      Txt[0]='\x00';

            }

String:: String (char fillCh, unsigned int count)

{          Siz=((count+AllocIncr)/ AllocIncr)* AllocIncr;

            Len=Siz;

                        Txt= new char[Siz];

                        if (Txt==NULL)

                                    ErrorHandler(SE_ALLOC);

            memset(Txt,fillCh,count);

            Txt[count]='\x00';

}

String:: ~String()

            {          delete Txt;       }

//Các hàm thành phần trả về giá trị

inline unsigned int String::Length()

            {          return Len;      }

inline unsigned int String::Size()

            {          return Siz;        }

// Tạo một chuỗi C từ một hàm thành phần String

String::operator const char * ()

            {          return Txt;       }

//hàm thành phần gán

void String::operator = (const String &str)

            {          Len=str.Len;

                        Siz=str.Siz;

                        delete Txt;

                        Txt = new char[Siz];

                        if (Txt==NULL)         ErrorHandler(SE_ALLOC);

                        strcpy(Txt,str.Txt);

            }

//hàm thành phần nối chuỗi

String operator + (const String &str1, const String &str2)

            {          unsigned long totalLen;

                        unsigned int newLen,newSiz;

                        String tempStr;

                        char *temp;

                        totalLen=str1.Len+str2.Len;

                        if (totalLen>UINT_MAX)

                                    String::ErrorHandler(SE_TOO_LONG);

                        //newLen= (unsigned int) totalLen;

                        newSiz=str1.Siz+str2.Siz;

                        temp=new char[newSiz];

                        if (temp==NULL)

                                    String::ErrorHandler(SE_ALLOC);

                        strcpy(temp,str1.Txt);

                        strcpy(&temp[str1.Len],str2.Txt);

                        tempStr=temp;

                        return tempStr;

            }

void String::operator += (const String &str)

            {          unsigned long totalLen;

                        unsigned int newLen,newSiz;

                        char *temp;

                        totalLen=str.Len+Len;

                        if (totalLen>UINT_MAX)

                                    String::ErrorHandler(SE_TOO_LONG);

                        newLen= (unsigned int) totalLen;

                        newSiz=Siz+str.Siz;

                        temp=new char[newSiz];

                        if (temp==NULL)

                                    String::ErrorHandler(SE_ALLOC);

                        strcpy(temp,str.Txt);

                        delete Txt;

                        Txt=temp;

                        strcpy(&Txt[Len],str.Txt);

                        Len=newLen;

                        Siz=newSiz;

                        Shrink();

            }

//hàm thành phần so sánh

inline int String:: operator < (const String &str)

            {          return (Compare(str)==SC_LESS);    }

inline int String::operator > (const String &str)

            {          return (Compare(str)==SC_GREATER);       }

inline int String::operator <= (const String &str)

            {          return (Compare(str)!=SC_GREATER);        }

inline int String::operator >= (const String &str)

            {          return (Compare(str)!=SC_LESS);     }

inline int String::operator == (const String &str)

            {          return (Compare(str)==SC_EQUAL);            }

inline int String::operator != (const String &str)

            {          return (Compare(str)!=SC_EQUAL);            }

StrCompVal String::Compare(const String &str, StrCompMode caseChk)

            {          char *tempStr1, *tempStr2;

                        StrCompVal compVal;

                        tempStr1=strdup(Txt);

                        if (tempStr1==NULL)                        ErrorHandler(SE_ALLOC);

                        tempStr2=strdup(str.Txt);

                        if (tempStr2==NULL)                        ErrorHandler(SE_ALLOC);

                        if (caseChk==SM_IGNORE)

                                    {          strupr(tempStr1);

            strupr(tempStr2);

                        }                     

            switch (strcmp(tempStr1,tempStr2))

                        {          case -1:

                                                compVal=SC_LESS;

                                                break;

                                    case 0:

                                                compVal=SC_EQUAL;

                                                break;

                                    case 1:

                                                compVal=SC_GREATER;

                                                break;

                                    default:

                                                compVal=SC_ERROR;

                        }

            delete tempStr1;

            delete tempStr2;

            return compVal;

            }

//Hàm thành phần tìm chuỗi con

int String::Find(const String &str, unsigned int & pos, StrCompMode caseChk)

            {          char *tempStr1, *tempStr2;

                        unsigned int lastPos, searchLen, tempPos;

                        int found;

                        tempStr1=strdup(Txt);

                        if (tempStr1==NULL)

                                    ErrorHandler(SE_ALLOC);

                        tempStr2=strdup(str.Txt);

                        if (tempStr2==NULL)

                                    ErrorHandler(SE_ALLOC);

                        if (caseChk==SM_IGNORE)

                                    {          strupr(tempStr1);

            strupr(tempStr2);

                        }         

            pos=0;

            tempPos=0;

            found=0;

            searchLen=str.Len;

            lastPos=Len-searchLen;

            while ((tempPos<=lastPos)&&!found)

                        {   if (0==strncmp(&tempStr1[tempPos],tempStr2,searchLen))

                                    {          pos=tempPos;

                                                found=1;

                                    }

                             else++tempPos;

                        }

            delete tempStr1;

            delete tempStr2;

            return found;

            }

//Hàm thành phần xoá chuỗi

void String::Delete(unsigned int pos, unsigned int count)

            {          unsigned int copyPos;

                        if (pos>Len)    return;

                        copyPos= pos+count;

                        if (copyPos>=Len)      Txt[pos]=0;

                        else

                                    while (copyPos<=Len+1)

                                                {          Txt[pos]=Txt[copyPos];

                                                            ++pos;

                                                            ++copyPos;

                                                }                     

                        Len-=count;

                        Shrink();

            }

//Hàm thành phần chèn chuỗi con

void String::Insert(unsigned int pos, char ch)

            {          char *temp;

                        if (pos>Len)    return;

                        if (Len+1==Siz)

                                    {          Siz+=AllocIncr;

                                                temp=new char[Siz];

                                                if (temp==NULL)

                                                            ErrorHandler(SE_ALLOC);

                                    strcpy(temp,Txt);

                                                delete Txt;

                                                Txt=temp;

                                    }         

                        for (unsigned int col=Len+1;col>pos;--col)

                                    Txt[col]=Txt[col-1];

                        Txt[pos]=ch;

                        ++Len;

                        Txt[Len+1]='\x00';

            }

void String::Insert(unsigned int pos, const String &str)

            {          unsigned long totalLen=str.Len+Len;

                        if (totalLen>UINT_MAX)     ErrorHandler(SE_TOO_LONG);

                        unsigned int SLen=str.Len;

                        if (SLen>0)

                        for (unsigned int i=0;i<SLen;++i)

                                    {          Insert(pos,str.Txt[i]);++pos;    }

            }

//Hàm thành phần trích một chuỗi con

String String::SubStr(unsigned int start, unsigned int count)

            {          String tempStr;

                        char *temp;

                        if ((start<Len) && (count>0))

                                    {

                                                temp=new char[count+1];

                                    memcpy(temp,&Txt[start],count);

                                                temp[count]='\x00';

                                    }                     

                        else      temp="";

                        tempStr=temp;

                        delete temp;

                        return tempStr;           

            }

//Toán tử đọc một ký tự

inline char String::operator [] (unsigned int pos)

            {          if (pos>=Len) return '\x00';

                        else      return Txt[pos];

            }

//Hàm thành phần đổi kiểu chữ

String String ::ToUpper()

            {          String tempStr = *this;

                        strupr(tempStr.Txt);

                        return tempStr;

            }

String String:: ToLower()

            {          String tempStr = *this;

                        strlwr(tempStr.Txt);

                        return tempStr;

            }

7.6.2. Hàm xử lý lỗi

Hàm xử lý lỗi ErrorHandler() được dùng chung cho tất cả các đối tượng String. ErrorHandler() nhận một tham số đơn kiểu StrError. StrError có kiểu liệt kê , dùng phân biệt dạng lỗi nào đã làm cho ErrorHandler() được gọi. Có hai lỗi có thể xảy ra với một String: hoặc khi chương trình chạy không đủ bộ nhớ (SE_ALLOC), hoặc khi một hàm nào đó cố tạo ra một chuỗi dài hơn 65 535 ký tự (SE_TOO_LONG).

Hàm xử lý lỗi mặc định được định nghĩa cho ErrorHandler() bằng cách định nghĩa một hàm mà địa chỉ của nó được gán cho ErrorHandler() trong khi khởi động thành phần tĩnh.

Khi khởi động một chương trình có dùng lớp String, nó sẽ làm tất cả việc khởi tạo liên quan đến với các thành phần tĩnh của String. ErrorHandler() được xác định để chỉ đến một hàm do người lập trình định nghĩa bằng hàm SetErrorHandler():

7.6.3. Hàm thành phần

Nói chung các hàm thành phần của lớp được khai báo public để cho các đối tượng do người dùng định nghĩa có thể dễ dàng sử dụng chúng. Tuy nhiên, trong lớp String, hàm thành phần Shrink() chỉ được dùng bên trong lớp. Shrink() điều chỉnh lại vùng nhớ đệm đã được cấp phát cho một chuỗi để giảm thiểu những vùng bị bỏ phí chưa sử dụng. Shrink() không phải để gọi từ bên ngoài lớp, do đó nó được khai báo private trong định nghĩa lớp String

Khởi tạo mặc định String() tạo ra một chuỗi trống, chưa khởi động

Khởi tạo sao chép tạo ra một String mới từ một String đã có:

String(char *cstr) cho phép một String mới tạo được khởi động bằng nội dung của một chuỗi C.

Khởi tạo String(char FillCh, unsigned int Count), tạo ra một chuỗi mới chứa Count ký tự FillCh:

Lớp String có một huỷ tử để xoá vùng nhớ được cấp phát cho biến Txt.

Hàm thành phần Length() và Size() trả về chiều dài hiện tại và kích thước cấp phát của một chuỗi. Length() tương đương với hàm Strlen() trong String.h. Size() được tạo ra để phục vụ cho việc kiểm tra lớp. Cả hai hàm này đều có thể được định nghĩa inline

Có thể dùng String thay cho mảng char chuẩn. Phép chuyển đổi này được thực hiện bằng cách định nghĩa toán tử chuyển đổi "const char *" cho String. Toán tử trên cho phép một String được ép kiểu thành một con trỏ trỏ đến const char. Phép chuyển đổi được sử dụng như sau:

String;

const char *s= (const char *)Str;

Từ khóa const dùng để ngăn cản sự sửa đổi mảng char được chỉ bởi Txt. Nói cách khác, s có thể được dùng để tham khảo giá trị của Str, nhưng không thể thay đổi nó được.

Toán tử + và +=  dùng để nối hai chuỗi, thực hiện chức năng giống như hàm strcat() chuẩn.  Nếu một chuỗi C được gửi cho một trong những hàm nối chuỗi, thì cấu tử chuyển đổi sẽ chuyển đổi nó sang một đối tượng String tạm thời trước khi gọi hàm.

Nhiều toán tử so sánh được định nghĩa cho String. Các hàm toán tử so sánh có hiệu quả nếu được cài đặt là inline và tất cả chúng đều gọi hàm Compare()

Hàm Compare() so sánh từng chữ một của hai chuỗi và trả về một giá trị liệt kê kiểu StrCompVal, xác định quan hệ giữa hai chuỗi này. Cách làm này tương tự hàm chuẩn strcmp(), với một điểm khác là có tham số Case, xác định việc so sánh có phân biệt chữ in, chữ thường hay không.

Hàm Find() thực hiện tương tự hàm thư viện chuẩn strstr(), định vị một String đã cho trong một String khác. Hàm này trả về chỉ số xác định vị trí bắt đầu của chuỗi con tìm thấy. Cũng giống như Compare(), tham số Case mặc định là SM_IGNORE xác định việc so sánh là không phân biệt kiểu chữ. Phép tìm chuỗi  có thể thực hiện với phân biệt chữ in, chữ thường bằng cách xác định tham số Case là SM_SENSITIVE.

Hàm Delete() loại bỏ một số ký tự ra khỏi một chuỗi. Tham số pos xác định chỉ số của ký tự đầu tiên cần được xoá và tham số count chỉ định số lượng ký tự sẽ  xóa.

Các ký tự và chuỗi có thể được chèn vào trong một String ở vị trí bất kỳ bằng  các hàm Insert(). Hàm Insert() đầu tiên  là hàm chèn một ký tự đơn. Hàm thứ hai cho phép chèn toàn bộ một chuỗi String. Ở đây, cấu tử chuyển đổi cho phép dùng một chuỗi C tại vị trí của tham số String trong cả hai hàm Insert() ở trên.

Có thể trích ra một phần của chuỗi bằng hàm SubStr(). SubStr() chép lại count ký tự, bắt đầu từ vị trí start vào trong một String khác.

Toán tử chỉ số [] được định nghĩa cho String. Toán tử này lấy ra những ký tự riêng lẻ tại những vị trí xác định trong một String. Nếu vị trí được cho vượt quá ký tự cuối cùng của String, thì trả về  ký tự NULL

Hai hàm cuối cùng là ToUpper() và ToLower() chuyển tất cả những ký tự trong chuỗi sang chữ hoa hoặc chữ thường. Các hàm này thực hiện tương tự như hàm strupr() và strlwr() trong thư viện C chuẩn.

7.6.4. Ví dụ về String

Chương trình sau minh hoạ sử dụng đối tượng String (strdemo.cpp):

#include "Str.cpp"

extern "C"

            {          #include <stdio.h>      }

void print_string(String &S);

String s1;

String s2("Đây là chuỗi ký tự thứ hai");

int main()

            {          String Is;

String Is1("Một chuỗi được khai báo trong nữa ");

                        StrCompVal v;

                        unsigned int pos,i;

                        char ch;

                        s1=s2;

                        if (s1==s2)       printf("s1 bằng s2!

");

                        else      printf("s1 không bằng s2!

");

                        Is="Đây là một chuỗi được khai báo trong";

                        print_string(s1); print_string(s2); print_string(Is);      print_string(Is1);

                        printf("

");

                        s1=s2+Is;

                        print_string(Is1);

                        v=s1.Compare(s2);

                        switch (v)

                                    {          case SC_LESS:

                                                            printf("s1<s2

");

                                                            break;

                                                case SC_EQUAL:

                                                            printf("s1==s2

");

                                                            break;

                                                case SC_GREATER:

                                                            printf("s1>s2

");

                                                            break;

                                    }

                        s1="Chuỗi có một nội dung"; print_string(s1);

                        s2+=Is; print_string(s2);

                        printf("

");

                        if (s2.Find("Bufulgunk",pos)) printf("Lần tìm thứ nhất = %d

",pos);

                        if (s2.Find(Is,pos))      printf("Lần tìm thứ hai = %d

",pos);

                        String Is2="&&";

                        s1.Insert(10,'*');

                        s1.Insert(15,Is2);

                        print_string(s1);

                        s1.Insert(s1.Length(),'%');

                        print_string(s1);

                        for (i=0;0!=(ch=s1[i]);++i)      putchar(ch);

                        s1.Insert(2,"<><><><><>");

                        print_string(s1);

                        s1.Delete(2,10);

                        print_string(s1);

                        s2=s1.ToUpper();

                        print_string(s2);

                        s2=s1.ToLower();

                        print_string(s2);

                        s1=s2.SubStr(2,10);

                        print_string(s1);

                        return 0;

            }

void print_string(String &S)

            {          const char *buffer;

                        buffer=(const char *)S;

                        if (buffer!=NULL)      printf("%s",buffer);

                        printf("Len=%u Siz=%u

",S.Length(),S.Size());

            }

7.7. LỚP WINDOWS

            Mọi chương trình đều có hiển thị thông tin lên màn hình, và cửa sổ (Window) là một hình thức hiển thị thường dùng. Mỗi cửa sổ là một phần màn hình được giới hạn trong một khung. Một hoặc nhiều cửa sổ có thể xuất hiện đồng thời trên màn hình và người sử dụng có thể chọn lựa giữa những của sổ khác nhau để truy xuất đến những nội dung của chúng.

            Sau khi đã xây dựng một lớp cửa số tổng quát, có thể dẫn xuất các lớp sâu hơn như thực đơn (menu), hộp hội thoại (dialog box) và các dạng hiển thị có cửa sổ khác.

            Hệ thống cửa sổ được phân chia thành hai lớp: Screen và Window. Screen định nghĩa các thao tác cơ sở có thể xảy ra trên màn hình. Lớp Window sẽ truy xuất màn hình thông qua lớp Screen. Khi lớp Screen thay đổi thích hợp với môi trường mới thì lớp Window sẽ tự động thừa hưởng khả năng làm việc trong môi trường mới naỳ.

7.7.1. Lớp Screen

            Lớp Screen phải được triển khai trước lớp Window. Định nghĩa của Screen được lưu trong tệp tin Screen.hpp như sau:

#if !defined (_SCREEN_HPP)

#define _SCREEN_HPP 1

#include "Str.hpp"

extern "C"

            {

                        #include "peekpoke.h"

                        #include "stddef.h"    

            }

enum BoxType {BT_NONE,BT_SINGLE,BT_DOUBLE,BT_SOLID};

class Screen

            {

                        protected:

                                    //Chiều rộng màn hình tính bằng số ký tự

                                    static unsigned int Width;

                                    //Chiều cao màn hình tính bằng số ký tự

                                    static unsigned int Length;

                                    //Địa chỉ nền của vùng nhớ ký tự

                                    static unsigned int BaseAdr;

                                    //Dạng con trỏ màn hình (cursor)

                                    static unsigned int CursorShape;

                                    static int CursorHidden; //Khác 0 nếu cursor đang bị che

                                    //Số lượng Screen được cấp phát

                                    static unsigned int HowMany;

                        public:

                                    Screen();

                                    ~Screen();

                                    //Hàm đọc kích thước hàm hình

                                    static void Dimensions(unsigned int &wid, unsigned int & len);

                                    // Các hàm về con trỏ màn hình

                                    static void CursorHide();

                                    static void CursorUnhide();

                                    static void CursorSetPos(unsigned int line, unsigned int col);

                                    static void CursorGetPos(unsigned int &line,

 unsigned int &col);

                                    // Các hàm hiển thị dữ liệu

                                    static void PutChar(unsigned int line, unsigned int col,

unsigned char attr, char ch);

                                    static void PutStr(unsigned int line, unsigned int col,

unsigned char attr, String &str);

                                    // Các hàm lấy dữ liệu từ bộ nhớ màn hình

                                    static void GetChar(unsigned int line, unsigned int col,

unsigned char &attr, char &ch);

                                    //Hiển thị một khung chữ nhật

                                    static void DrawBox(unsigned int topLine, unsigned int leftCol,

                                                unsigned int btmLine, unsigned int rigntCol,

unsigned char attr,BoxType typeBox);

                                    //Các hàm xoá màn hình

                                    static void Clear();

                                    static void ClearLine(unsigned int line, unsigned int col=0);

            };

//Hàm hiển thị ký tự

inline void Screen::PutChar(unsigned int line, unsigned int col,

unsigned char attr, char ch)

            {

                        unsigned int v=(( unsigned int)attr<<8 | (unsigned char)ch);

                        Poke(BaseAdr, (line *Width*2)+(2*col),v);

            }

//hàm đọc lại dữ liệu từ bộ nhớ màn hình

inline void Screen::GetChar(unsigned int line, unsigned int col,

unsigned char &attr, char &ch)

            {          unsigned int v=Peek(BaseAdr,(line*Width*2)+(2*col));

                        attr=v>>8;

ch=v & 0xff;

            }

extern Screen Display;

#endif

            Tất cả các thành phần của Screen đều là tĩnh (trừ cấu tử và huỷ tử). Thông thường, mỗi máy tính chỉ có một màn hình.

            Lớp Screen có 6 dữ liệu thành phần tĩnh. Số cột và dòng được lưu trong Width và Length. Hàm cấu tử của Screen xác định kích thước hiện thời màn hình và lưu các giá trị đó vào trong Width và Length

            BaseAdr chứa giá trị segment của vùng nhớ màn hình (0xB000 với màn hình đơn sắc, 0xB800 với màn hình màu). Cờ CursorHidden dùng để ghi tình trạng con trỏ màn hình là bật hay tắt. CursorShape chứa hình dạng ban đầu của con trỏ màn hình trước khi bị che.

            HowMany cho số lần Screen được khởi động và nó được sử dụng bởi cấu tử và huỷ tử. Cấu tử của Screen đặt giá trị cho các dữ liệu thành phần tĩnh của lớp, theo kiểu màn hình và chỉ được tự động gọi một lần. Hàm khởi tạo sẽ tăng cờ HowMany lên  mỗi khi có đối tượng Screen được khởi tạo.

            Hàm huỷ bỏ của Screen kiểm tra giá trị HowMany trước khi hoạt động. Nếu HowMany lớn hơn 1, hàm huỷ bỏ này biết nó đang được gọi cho một đối tượng Screen khác đối tượng Screen ban đầu. Nếu HowMany bằng 1 thì hàm huỷ bỏ mới có thể xoá màn hình và trả về con trỏ màn hình tại vị trí ban đầu.

            Một đối tượng Screen là Display được khởi động trong phần định nghĩa và cài đặt của lớp Screen. Display gọi hàm khởi tạo và huỷ bỏ để khởi động cho lớp Screen. Khi dùng lớp Screen trong chương trình, Display gọi hàm khởi tạo và lúc kết thúc chương trình, Display được huỷ bỏ.

            Lớp Screen được cài đặt trong Screen.cpp với nội dung như sau:

#include "Screen.hpp"

#include "Str.hpp"

extern "C"

            {          #include <dos.h>        }

//Gán các giá trị cho các thành phần tĩnh của lớp

unsigned int Screen::Width=0;

unsigned int Screen::Length=0;

unsigned int Screen::BaseAdr=0;

unsigned int Screen::CursorShape=0;

int Screen::CursorHidden=0;

Screen Display;

Screen:: Screen()

            {          ++HowMany;

                        if (HowMany>1)

                                    return;

                        union REGS regs;

                        regs.h.ah=0x0f;

                        int86(0x10,&regs,&regs);

                        if (regs.h.al==0x07)

                                    BaseAdr=0xB000;

                        else

                                    BaseAdr=0xB800;

                        Width= (int) regs.h.ah;

                        regs.x.ax=0x1130;

                        regs.h.bh=0;

                        regs.x.dx=0;

                        int86(0x10,&regs,&regs);

                        Length=regs.x.dx+1;

                        if (Length==1)

                                    Length=25;

                        Clear();

                        CursorHidden=0;

            }

Screen::~ Screen()

            {          --HowMany;

                        if (HowMany>0)

                                    return;

                        Clear();

                        CursorSetPos(0,0);

            }

void Screen::Dimensions(unsigned int &wid, unsigned int & len)

            {          wid=Width;

                        len=Length;

            }

//Các hàm về con trỏ màn hình

 void Screen::CursorHide()

            {          if (CursorHidden)

                                    return;

                        union REGS regs;

                        regs.h.ah=3;

                        regs.h.bh=0;

                        int86(0x10,&regs,&regs);

                        CursorShape=regs.x.cx;

                        regs.h.ah=1;

                        regs.x.cx=0x2000;

                        int86(0x10,&regs,&regs);

                        CursorHidden=1;

            }

void Screen::CursorUnhide()

            {          if (! CursorHidden)

                                    return;

                        union REGS regs;

                        regs.h.ah=1;

                        regs.x.cx=CursorShape;

                        int86(0x10,&regs,&regs);

                        CursorHidden=0;

            }

void Screen::CursorSetPos(unsigned int line, unsigned int col)

            {          union REGS regs;

                        regs.h.ah=2;

                        regs.h.bh=0;

                        regs.h.dh=line;

                        regs.h.dl=col;

                        int86(0x10,&regs,&regs);

            }

void Screen::CursorGetPos(unsigned int &line, unsigned int &col)

            {          union REGS regs;

                        regs.h.ah=3;

                        regs.h.bh=0;

                        int86(0x10,&regs,&regs);

                        line=regs.h.dh;

                        col=regs.h.dl;

            }

void Screen::PutStr(unsigned int line, unsigned int col,

unsigned char attr, String &str)

            {          for (unsigned int i=0;str[i]!='\000';++i)

                                    PutChar(line,col+i,attr,str[i]);

            }

//Hiển thị một hình chữ nhật

void Screen::DrawBox(unsigned int topLine, unsigned int leftCol,

                                                unsigned int btmLine, unsigned int rightCol,

unsigned char attr,BoxType typeBox)

            {          if ((typeBox==BT_NONE)||(leftCol>=rightCol)||

(topLine>=btmLine))

                                    return;

                        char v,h;

                        switch (typeBox)

                                    {          case BT_SINGLE:

                                                            v='\xB3';

                                                            h='\xC4';

                                                            PutChar(topLine,leftCol,attr,'\xDA');

                                                            PutChar(topLine,rightCol,attr,'\xBF');

                                                            PutChar(btmLine,leftCol,attr,'\xC0');

                                                            PutChar(btmLine,rightCol,attr,'\xD9');

                                                            break;

                                                case BT_DOUBLE:

                                                            v='\xBA';

                                                            h='\xCD';

                                                            PutChar(topLine,leftCol,attr,'\xC9');

                                                            PutChar(topLine,rightCol,attr,'\xBB');

                                                            PutChar(btmLine,leftCol,attr,'\xC8');

                                                            PutChar(btmLine,rightCol,attr,'\xBC');

                                                            break;

                                                case BT_SOLID:

                                                            v='\xDB';

                                                            h='\xDB';

                                                            PutChar(topLine,leftCol,attr,'\xDB');

                                                            PutChar(topLine,rightCol,attr,'\xDB');

                                                            PutChar(btmLine,leftCol,attr,'\xDB');

                                                            PutChar(btmLine,rightCol,attr,'\xD9');

                                    }

                        for(int c=leftCol+1;c<rightCol;++c)

                                    {          PutChar(topLine,c,attr,h);

                                                PutChar(btmLine,c,attr,h);

                                    }

                        for(int l=topLine+1;l<btmLine;++l)

                                    {          PutChar(l,leftCol,attr,v);

                                                PutChar(l,rightCol,attr,v);

                                    }

            }

//Các hàm xoá màn hình

void Screen::Clear()

            {          for (int l=0;l<Length;++l)

                                    {          for (int c=0;c<Width;++c)

                                                            PutChar(l,c,7,' ');

                                    }

            }

void Screen::ClearLine(unsigned int line, unsigned int col=0)

            {          for (int c=col;c<Width;++c)

                                    PutChar(line,c,7,' ');

            }

  Chúng ta xem xét từng phần của Screen.cpp:

            Các thành phần tĩnh của Screen và Display được khởi tạo như sau:

//Gán các giá trị cho các thành phần tĩnh của lớp

unsigned int Screen::Width=0;

unsigned int Screen::Length=0;

unsigned int Screen::BaseAdr=0;

unsigned int Screen::CursorShape=0;

int Screen::CursorHidden=0;

Screen Display;

            Hàm khởi tạo xác định loại màn hình, số dòng, số cột, và xoá màn hình.

            Hàm huỷ bỏ sẽ giảm HowMany. Nếu HowMany=0 thì huỷ bỏ biết rằng nó được gọi cho Screen đầu tiên (chính là biến Display được cấp phát tự động lúc dầu tiên),và xoá màn hình.

            Hàm Dimensions() trả lại giá trị Width và Length của màn hình hiện tại.

            Các hàm xử lý con trỏ màn hình dùng các lệnh gọi đến các hàm con trỏ của BIOS. CursorHide() che con trỏ màn hình và CursorUnHide() phục hồi con trỏ màn hình trở lại (nếu đang bị che)

            CursorSetPos() chuyển con trỏ màn hình đến một vị trí xác định và CursorGetPos() trả lại vị trí con trỏ màn hình hiện tại:

            PutChar() và GetChar() dùng để hiển thị và lấy lại một ký tự từ bộ nhớ màn hình. Chúng chứa tham số dòng và cột chỉ vị trí ký tự. Cặp giá trị char chứa thông tin về hình và thuộc tính ký tự. PutChar() và GetChar() là hàm inline. PutChar() dùng hàm Poke() để cất giá trị ký tự và thuộc tính vào một vị trí trong vùng nhớ màn hình. GetChar() dùng hàm Peek() để lấy lại giá trị 16bit từ một vị trí cho trước trong vùng nhớ. Các hàm Poke() và Peek() có thể được viết bằng ngôn ngữ assembler 8086. Các tệp tin nguồn cho hàm này là peekpoke.h và peekpoke..asm:

/* peekpoke.h*/

void Poke(unsigned int segm, unsigned int offs, unsigned int value);

unsigned int Peek(unsigned int segm, unsigned int offs);

peekpoke.asm

.8086

.CODE

PUBLIC Peek

PUBLIC Poke

Peek                PROC             segm:WORD, offs:WORD

                        push                 es

                        push                 si

                        mov                 ax,segm

                        mov                 es,ax

                        mov                 si,offs

                        mov                 ax,es:[si]

Peek                ENDP

Poke                PROC             segm:WORD, offs:WORD, value:WORD

                        push                 es

                        push                 di

                        mov                 ax,segm

                        mov                 es,ax

                        mov                 di,offs

                        mov                 cx,value

                        mov                 es:[di], cx

                        pop      di

Poke                ENDP

            Cần chú ý là hàm PutChar() và GetChar() không kiểm tra các giới hạn của ký tự để không làm tăng phần đầu và sự phức tạp của chương trình.

            Hàm PutStr() dùng PutChar() để hiển thị một giá trị String.

            Hàm DrawBox() hiển thị một hình chữ nhật với một trong bốn dạng đường viền. Kiểu enum BoxType định nghĩa các kiểu đường viền.

            Hàm Clear() lấp đầy màn hình bằng các ký tự khoảng trắng. Hàm ClearLine() xoá một dòng bắt đầu từ cột xác định bởi tham số c đến cuối dòng.

7.7.2. Lớp Window

            Window là một lớp phức tạp. Nó chứa nhiều đối tượng thành phần, các mảng được cấp phát động chứa con trỏ trỏ đến đối tượng, và nhiều hàm thành phần private. Sau đây là định nghĩa lớp Window trong Window.hpp:

#if !defined (_WINDOW_HPP)

#define _WINDOW_HPP 1

#include "Screen.hpp"

#include "Str.hpp"

#include "WorkList.hpp"

extern "C"

            {          #include <stddef.h>   }

class Window

            {          protected:

                                    static WorkList WdwList;

static Window **ScrnOwner;

static int Initialized;

static Window *TopWindow;

static unsigned int MaxLength;

static unsigned int MaxWidth;

unsigned int HomeLine;

unsigned int HomeCol;

unsigned int Length;

unsigned int Width;

unsigned int CrsLine;

unsigned int CrsCol;

unsigned char InsideColor;

unsigned char BorderColor;

BoxType Border;

//Tiêu đề cửa sổ

String Header;

//Các biến cờ

int Wrapped;

int Concealed;

//Vùng đệm cho cửa sổ này

struct WindowWord

            {          unsigned char Attribute;

                        char Symbol;

            };

WindowWord *BufferSW;

//các hàm dùng riêng

static void FirstTime();

void Display(int line, int col, unsigned char attr, char ch);

static void Restack();

void PlotOwnership();

static void (*ErrorHandler)();

                        public:

                                    Window(unsigned int line, unsigned int col, unsigned int len,

                                                unsigned int wid, unsigned char iattr, unsigned char battr,

                                                BoxType bord, String & heading, int wrapMode = 0);

                                    Window(const Window &w);

                                    void operator = (const Window &w);

                                    ~Window();

                                    //Gán hàm xử lý ngoại lệ

                                    static void SetErrorHandler(void (*userHandler)());

                                    //Vẽ một cửa sổ

                                    void Paint();

                                    //Vẽ một dòng

                                    void PaintLine(unsigned int line);

                                    //đổi vị trí

                                    void Move(unsigned int line, unsigned int col);

                                    //đưa lên làm cửa số trên cùng

                                    void Hoist();

                                    //Che cửa sổ

                                    void Conceal();

                                    //Hiện lại cửa sổ

                                    void Reveal();

                                    //Cho phép cuộn dòng xuống

                                    void WrapOn();

                                    //Không cho phép cuộn dòng

                                    void WrapOff();

                                    //Đổi tiêu đề

                                    void SetHead(String & heading);

                                    //Định vị con trỏ màn hình

                                    void SetCursor(unsigned int line, unsigned int col);

                                    //Đọc vị trí hiện thời của con trỏ

                                    void GetCursor(unsigned int &line, unsigned int &col);

                                    //Lưu một ký tự

                                    void PutChar(unsigned int line, unsigned int col,

unsigned char attr, char ch);

                                    //Lưu một chuỗi

                                    void PutStr(unsigned int line, unsigned int col,

unsigned char attr, String &str);

                                    //Xoá toàn bộ cửa sổ

                                    void ClearAll();

                                    //Xoá một dòng trong cửa sổ

                                    void ClearLine(unsigned int line, unsigned int col=0);

            };

#endif

            Sáu thành phần static của Window định nghĩa các dữ liệu thành phần dùng chung:

            WdwList là một đối tượng kiểu WorkList, chứa thứ tự của một chồng các cửa sổ trên màn hình.

ScrnOwner là một mảng hai chiều các con trỏ trỏ đến Window được cấp phát động. Nó có cùng kích thước với toàn bộ màn hình. Khi một Window sở hữu màn hình, địa chỉ của nó được gán vào con trỏ tương ứng với ScrnOwner.

            Biến cờ báo Initialized báo lớp Window được cài đặt toàn cục. Cấu tử của Window sẽ gọi đến hàm private First() nếu Initialized=0. Sau đó nó đặt  Initialized=1 và không gọi đến First() nữa.

            Con trỏ TopWindow trỏ đến Window trên cùng của chồng cửa sổ. Một số thao tác chỉ có thể thực hiện tại Window trên cùng. Ví dụ như con trỏ màn hình chỉ hiển thị tại TopWindow.

            12 thành phần private tiếp theo định nghĩa các biến dùng riêng cho các đối tượng Window. HomeLine và HomeCol cất vị trí con trỏ màn hình góc trái trên, Length và Width cất chiều dài và rộng cửa sổ. CrsLine và CrsCol theo dõi vị trí con trỏ hiện thời. InsideColor là thuộc tính mặc định của ký tự hiển thị. BorderColor là đặc tính khung. Border chứa kiểu khung, Header kiểu String chứa tiêu đề.

            Các cờ Wrapped và Concealed xác định hình thức làm việc cửa sổ. Nếu Wrapped khác 0 thì các dòng chữ dài hiển thị bên trong của sổ được cuộn xuống dòng. Nếu Concealed khác 0 thì cửa sổ này bị che (không hiển thị)

            Cấu trúc WindowWord chứa một mã ký tự và thuộc tính. BufferSW chứa thông tin hiện tại đang hiển thị trong Window. Khi vẽ cửa sổ lên màn hình, dữ liệu được lấy từ mảng BufferSW.

            Sau đây là cài đặt của lớp Window:

1. wdw_main.cpp (Các hàm chính của lớp Window)

#include "Screen.hpp"

#include "Str.hpp"

#include "Window.hpp"

extern "C"

            {          #include <string.h>

                        #include <stddef.h>

                        #include <dos.h>

            }

//Nguyên mẫu

static void DefaultHandler();

// Khởi động các thành phần tĩnh 

WorkList Window::WdwList;

Window ** Window::ScrnOwner=NULL;

int Window::Initialized=0;

Window * Window::TopWindow=NULL;

unsigned int Window::MaxLength=0;

unsigned int Window::MaxWidth=0;

//Hiển thị ký tự lên màn hình

void Window::Display(int line, int col, unsigned char attr, char ch)

            {          unsigned int rl=line+HomeLine;

                        unsigned int rc=col+HomeCol;

                        if ((rl>=MaxLength)||(rc>=MaxWidth))         return;

                        if (this==ScrnOwner[rl*MaxWidth+rc])        Screen::PutChar(rl,rc,attr,ch);

            }

// Sắp xếp lại tất cả các cửa sổ

void Window::Restack()

            {          Screen::Clear();

//Xoá lưới sở hữu chủ màn hình

for (unsigned int l=0;l<MaxLength;++l)

            {          for(unsigned int c=0;c<MaxWidth;++c)

                                    ScrnOwner[l*MaxWidth+c]=NULL;

            }

//Bắt đầu từ cửa sổ nằm dưới cùng trong danh sách

WdwList.GoToTail();

Window*curWdw=( Window *)WdwList.Examine();

while (curWdw!=NULL)

            {          //ấn định lại vùng sở hữu cửa sổ hiện tại

curWdw->PlotOwnership();

//Vẽ nó ra

curWdw->Paint();

//Đến cửa sổ kế tiếp

WdwList.GoPrev();

curWdw=(Window *)WdwList.Examine();

                                    }

}

//ấn định vị trí màn hình nào thuộc cửa sổ hiện tại

void Window::PlotOwnership()

            {          unsigned int l,c;

                        if (Concealed)             return;

            //Đánh dấu các vị trí cửa sổ hiện tại sở hữu

            if ((Length==MaxLength)&&(Width==MaxWidth))

                        {          for(l=0;l<Length;++l)

                                                for(c=0;l<Width;++c)ScrnOwner[l*MaxWidth+c]=this;

                        }

            else

                        {          for(l=HomeLine;l< HomeLine +Length;++l)

                                                for(c=HomeCol;c< HomeCol +Width;++c)

                                                            if ((l<MaxLength)&&(c<MaxWidth))

                                                            ScrnOwner[l*MaxWidth+c]=this;

                        }

//Đánh dấu các vị trí phần khung nền

if (Border!=BT_NONE)

            {          if (HomeCol>0)

                                    {

                                                if (HomeLine>0)

                                                            ScrnOwner[(HomeLine-1)*

MaxWidth+(HomeCol-1)]=this;

                                                if (Length<MaxLength)

                                                    ScrnOwner[(Length+HomeLine)*

MaxWidth+(HomeCol-1)]=this;

                                    }

                        if (Width<MaxWidth)

                                    {    if (HomeLine>0)

                                                ScrnOwner[(HomeLine-1)* MaxWidth

                        +(Width+HomeCol)]=this;

                                         if (Length<MaxLength)

                                                 ScrnOwner[(Length+HomeLine)*

 MaxWidth+(Width+HomeCol)]=this;

                                    }

                        if ((HomeLine+Length)<MaxLength)

                                    {           for(c=HomeCol;c<= HomeCol +Width;++c)

                             ScrnOwner[(Length+HomeLine)

*MaxWidth+c]=this;

                                                            }

                        if (HomeLine>0)

                                    {          for(c=HomeCol;c<= HomeCol +Width;++c)

                                    ScrnOwner[(HomeLine-1)

*MaxWidth+c]=this;

                                                            }

                        if ((HomeCol+Width)<MaxWidth)

                                    {          for(l=HomeLine;l<=HomeLine+Length;++l)

                        ScrnOwner[l*MaxWidth+

Width+HomeCol]=this;

                                                            }

                        if (HomeCol>0)

                                    {          for(l=HomeLine;l<=HomeLine+Length;++l)

                        ScrnOwner[l*MaxWidth+

HomeCol-1]=this;

                                                            }

            }

            }

2. wdw_ctdt.cpp (Hàm khởi tạo  và huỷ bỏ trong lớp Window)

#include "Str.hpp"

#include "Window.hpp"

extern "C"

            {          #include <string.h>

                        #include <stddef.h>

            }

Window:: Window(unsigned int line, unsigned int col, unsigned int len,

                        unsigned int wid, unsigned char iattr, unsigned char battr,

                        BoxType bord, String & heading, int wrapMode):Header(heading)

            {          if (!Initialized)             FirstTime();

                        //đảm bảo Window đã được tạo trên màn hình

                        if ((line>MaxLength)||(col>MaxWidth))

                                    {          line=0;col=0;              }

                        //Gán giá trị các biến

                        HomeLine=line;

                        HomeCol=col;

                        Length=len;

                        Width=wid;

                        CrsLine=0;

                        CrsCol=0;

                        InsideColor=iattr;

                        BorderColor=battr;

                        Border=bord;

                        Wrapped=wrapMode;

                        Concealed=0;

                        //Không có khung thì không có tiêu đề

                        if (Border==BT_NONE)        Header="";

                        //Thêm cửa sổ này vào danh sách

                        WdwList.Store(this);

                        //cấp phát vùng đệm để lưu nội dung cửa sổ

                        BufferSW=new WindowWord[Length*Width];

                                    if (BufferSW==NULL)          ErrorHandler();

                        //Lấy cửa sổ này làm cửa sổ trên cùng

                        TopWindow=this;

                        PlotOwnership();

                        //Xoá cửa sổ

                        ClearAll();

                        //Vẽ nội dung vào đó

                        Paint();

            }

Window::~Window()

            {          //Lấy cửa sổ này ra khỏi danh sách

                        WdwList.Delete(this);

                        //Định lại cửa sổ trên cùng

                        WdwList.GoToHead();

                        TopWindow=(Window *)WdwList.Examine();

                        //Huỷ bỏ vùng đệm

                        delete BufferSW;

                        //Sắp xếp các cửa sổ còn lại

                        Restack();

                        return;

            }

3. wdw_copy.cpp (Hàm khởi tạo sao chép và toán tử gán trong lớp Window)

#include "Str.hpp"

#include "Window.hpp"

extern "C"

            {          #include <string.h>

                        #include <stdio.h>

                        #include <stddef.h>

                        #include <dos.h>

            }

Window:: Window(const Window &w):Header(w.Header)

            {          //Chép lại các dữ liệu thành phần

HomeLine=w.HomeLine;

HomeCol=w.HomeCol;

Length=w.Length;

Width=w.Width;

CrsLine=w.CrsLine;

CrsCol=w.CrsCol;

InsideColor=w.InsideColor;

BorderColor=w.BorderColor;

Border=w.Border;

Wrapped=w.Wrapped;

Concealed=w.Concealed;

//Cấp phát vùng đệm

BufferSW=new WindowWord[Length*Width];

if (BufferSW==NULL)          ErrorHandler();

//Chép lại vùng đệm hiện tại vào vùng đệm mới

for (unsigned int l=0;l<Length;++l)

            {          for (unsigned int c=0;l<Width;++c)

            BufferSW[l*Width+c]=w.BufferSW[l*Width+c];

            }

//Thêm cửa sổ này vào danh sách

WdwList.Store(this);

//Lấy cửa sổ này làm cửa sổ trên cùng

TopWindow=this;

//ấn định lại vùng sở hữu của nó

PlotOwnership();

//Vẽ nội dung của nó

Paint();

            }

void Window::operator = (const Window &w)

            {          //Trả lại vùng đệm cũ

delete BufferSW;

//Chép lại các dữ liệu thành phần

HomeLine=w.HomeLine;

HomeCol=w.HomeCol;

Length=w.Length;

Width=w.Wdth;

CrsLine=w.CrsLine;

CrsCol=w.CrsCol;

InsideColor=w.InsideColor;

BorderColor=w.BorderColor;

Border=w.Border;

Wrapped=w.Wrapped;

Concealed=w.Concealed;

//Cấp phát  lại vùng đệm

BufferSW=new WindowWord[Length*Width];

if (BufferSW==NULL)          ErrorHandler();

//Chép lại từ vùng đệm của cửa sổ đang có

for (unsigned int l=0;l<Length;++l)

            {          for (unsigned int c=0;l<Width;++c)

            BufferSW[l*Width+c]=w.BufferSW[l*Width+c];

            }

//ấn định lại vùng sở hữu của cửa sổ

PlotOwnership();

//Vẽ lại toàn bộ cửa sổ

Restack();

            }

4. wdw_disp.cpp ( Các hàm hiển thị cửa sổ trong lớp Window)

#include "Str.hpp"

#include "Window.hpp"

extern "C"

            {          #include <string.h>

                        #include <stdio.h>

                        #include <stddef.h>

                        #include <dos.h>

            }

//Vẽ lại toàn bộ cửa sổ

void Window::Paint()

            {          if (Concealed)             return;

                        unsigned int l,c,hpos;

//Vẽ khung

switch (Border)

 {         case BT_SINGLE:

                        Display(-1,-1,BorderColor,'\xDA');

                        Display(Length,-1,BorderColor,'\xC0');

                        Display(Length,Width,BorderColor,'\xD9');

                        Display(-1,Width,BorderColor,'\xBF');

                        if (Header.Length()==0)

                                    for(c=0;c<Width;++c)

                                                {          Display(-1,c,BorderColor,'\xC4');

                        Display(Length,c,BorderColor,'\xC4');

                                                }

                        else

                                    {          hpos=0;

                                    for(c=0;c<Width         ;++c)

                                                    {      if(hpos<Header.Length())

                                                               {       Display(-1,c,

   BorderColor,Header[hpos]);

                                                                        ++hpos;

                                                               }

                                                            else

                                         Display(-1,c,BorderColor,'\xC4');

                                 Display(Length,c,BorderColor,'\xC4');

                                                   }

                                    }

                        for (l=0;l<Length        ;++l)

                                                {          Display(l,-1,BorderColor,'\xB3');

                        Display(l,Width,BorderColor,'\xB3');

                                                }

                        break;

            case BT_DOUBLE:

                        Display(-1,-1,BorderColor,'\xC9');

                        Display(Length,-1,BorderColor,'\xC9');

                        Display(Length,Width,BorderColor,'\xBC');

                        Display(-1,Width,BorderColor,'\xBB');

                        if (Header.Length()==0)

                                    for(c=0;c<Width         ;++c)

                                                {          Display(-1,c,BorderColor,'\xCD');

                        Display(Length,c,BorderColor,'\xCD');

                                                }

                        else

                                    {          hpos=0;

                                    for(c=0;c<Width         ;++c)

                                                    {      if(hpos<Header.Length())

                                                               {       Display(-1,c,

   BorderColor,Header[hpos]);

                                                                        ++hpos;

                                                               }

                                                            else

                                       Display(-1,c,BorderColor,'\xCD');

                                 Display(Length,c,BorderColor,'\xCD');

                                                   }

                                    }

                        for (l=0;l<Length        ;++l)

                                                {          Display(l,-1,BorderColor,'\xBA');

                        Display(l,Width,BorderColor,'\xBA');

                                                }

                        }         

unsigned int pos;

//Vẽ từng ký tự có trong vùng đệm

for (l=0;l<Length        ;++l)

            for (c=0;c<Width        ;++c)

                        {          pos=l*Width+c;

Display(l,c,BufferSW[pos].Attribute,

 BufferSW[pos].Symbol);

                        }

//Định vị con trỏ màn hình

SetCursor(CrsLine,CrsCol);

            }

//Vẽ một dòng

void Window::PaintLine(unsigned int line)

            {          if ((Concealed)||(line>Length))           return;

                        unsigned int pos;

//Vẽ một dòng ký tự

for(unsigned int c=0;c<Width;++c)

            {          pos=line*Width+c;

Display(line,c,BufferSW[pos].Attribute,

 BufferSW[pos].Symbol);

            }

            }

//Cất một ký tự vào cửa sổ

void Window::PutChar(unsigned int line,unsigned int col,

unsigned char attr,char ch)

            {          if ((line>=Length)||(col>=Width))

                                    return;

unsigned int pos=line*Width+col;

//Cất ký tự....

BufferSW[pos].Symbol=ch;

//và thuộc tính màu của nó

if (attr!=0)       BufferSW[pos].Attribute=attr;

else      BufferSW[pos].Attribute=InsideColor;

            }

//Cất một chuỗi vào cửa sổ

void Window::PutStr(unsigned int line,unsigned int col,

unsigned char attr,String & s)

            {          if ((line>=Length)||(col>=Width))      return;

                        if (attr==0)                  attr=InsideColor;

                        unsigned char l=line;

                        unsigned char c=col;

                        unsigned int spos=0;

                        unsigned wpos;

                        //Hiển thị chuỗi văn bản

                        while (s[spos]!='\x00')

                                    {          wpos=l*Width+c;

                                                BufferSW[wpos].Symbol=s[spos];

                                                BufferSW[wpos].Attribute=attr;

                                                if (c==(Width-1))

                                                            {          if (!Wrapped)              return;

                                                                        if (l<Length)

                                                                                    {          c=0;++l;           }                                 

                                                                        else      return;

                                                            }

                                                else      ++c;

                                                ++spos;

                                    }

            }

5. wdw_misc.cpp ( Các hàm khác trong lớp Window)

#include "Screen.hpp"

#include "Str.hpp"

#include "Window.hpp"

extern "C"

            {          #include <string.h>

                        #include <stddef.h>

                        #include <dos.h>

            }

//Thay đổi vị trí

void Window::Move(unsigned int line,unsigned int col)

            {          if ((line>MaxLength)||(col>MaxWidth))        return;

//Định lại vị trí mới của cửa sổ

HomeLine=line;

HomeCol=col;

//Sắp xếp lại toàn bộ cửa sổ

Restack();

            }

//Đưa cửa sổ này thành cửa sổ trên cùng

void Window::Hoist()

            {          //Xoá cửa sổ này khỏi danh sách

WdwList.Delete(this);

//Thêm cửa sổ này vào danh sách (thành cửa sổ trên cùng)

WdwList.Store(this);

TopWindow=this;

//Hiển thị lại tất cả cửa sổ

Restack();

            }

//Che cửa sổ này

void Window::Conceal()

            {          Concealed=0;

                        Restack();

            }

//Hiển thị lại cửa sổ này

void Window::Reveal()

            {          Concealed=0;

                        Restack();

            }

//Bật cờ cho phép cuộn xuống dòng

void Window::WrapOn()

            {          Wrapped=1;    }

//Tắt cờ cho phép cuộn dòng

void Window::WrapOff()

            {          Wrapped=0;    }

//Định lại tiêu đề

void Window::SetHead(String & heading)

            {          if (Border==BT_NONE)        return;

                        unsigned int c, hpos;

                        char bordchar;

//Lấy ký tự viền khung

if (Border==BT_SINGLE)     bordchar=0xC4;

else      bordchar=0xCD;

Header=heading;

//Không có tiêu đề, chỉ vẽ khung trơn

if (Header.Length()==0)

            {          for (c=0;c<Width;++c)Display(-1,c,BorderColor,bordchar);

                        return;

            }

hpos=0;

//Hiển thị tiêu đề và một phần khung

for (c=0;c<Width;++c)

            if (hpos<Header.Length())

                        {          Display(-1,c,BorderColor,Header[hpos]);

                                    ++hpos;

                        }

            else      Display(-1,c,BorderColor,bordchar);

            }

//Định vị con trỏ màn hình

void Window::SetCursor(unsigned int line, unsigned int col)

            {          //Con trỏ màn hình chỉ được định vị bên trong cửa sổ trên cùng

if ((line>Length)||(col>Width)||(this!=TopWindow))            return;

unsigned char newl=line+HomeLine;

unsigned char newc=col+HomeCol;

if ((newl>MaxLength)||(newc>MaxWidth))   return;

CrsLine=line;

CrsCol=col;

//Định vị vị trí vật lý của con trỏ màn hình

Screen::CursorSetPos(newl,newc);

            }

//Đọc vị trí hiện thời của con trỏ màn hình

void Window::GetCursor(unsigned int &line, unsigned int &col)

            {          line=CrsLine;  col=CrsCol;     }

//Xoá toàn bộ cửa sổ

void Window::ClearAll()

            {          unsigned int pos=0;

                        for (unsigned char l=0;l<Length;++l)

                                    {          for (unsigned char c=0;c<Width;++c)

                                                            {          BufferSW[pos].Symbol=' ';

                                                                        BufferSW[pos].Attribute=InsideColor;

                                                                        ++pos;

                                                            }

                                    }

            }         

//Xoá một dòng của cửa sổ

void Window::ClearLine(unsigned int line, unsigned int col)

            {          unsigned int pos=0;

                        for (unsigned char c=col;c<Width;++c)

                                    {          pos=line*Width+c;

                                                BufferSW[pos].Symbol=' ';

                                                BufferSW[pos].Attribute=InsideColor;

                                    }

            }         

6. wdw_err.cpp (Hàm xử lý ngoại lệ và khởi động lần đầu trong lớp Window)

#include "Screen.hpp"

#include "Window.hpp"

extern "C"

            {          #include <stdio.h>

                        #include <stdlib.h>

            }

//Nguyên mẫu

static void DefaultHandler();

void (*Window::ErrorHandler)()=DefaultHandler;

//Hàm xử lý ngoại lệ mặc định

static void DefaultHandler()

            {          puts("\aLỗi Window: Không cấp phát được

");        }

//Hàm khởi động cho lần đầu tiên

void Window::FirstTime()

            {          Initialized=1;

//Lấy kích thước màn hình

Screen::Dimensions(MaxWidth,MaxLength);

unsigned int area= MaxWidth*MaxLength;

//Sử dụng malloc để cấp phát bộ nhớ

ScrnOwner=(Window **)malloc(area*sizeof(Window *));

if (ScrnOwner==NULL)        ErrorHandler();

//Xoá nội dung của các mảng đang sở hữu

for (unsigned int l=0;l<MaxLength;++l)

            {          for (unsigned int c=0;l<MaxWidth;++c)

                                    ScrnOwner[l*MaxWidth+c]=NULL;

            }

            }

//Gán hàm xử lý ngoại lệ

void Window::SetErrorHandler(void (*userHandler)())

            {          ErrorHandler=userHandler;    }

Các thành phần tĩnh của Window được khởi động trong tệp tin cài đặt wdw_main.cpp.

            Hàm khởi tạo chuẩn của Window (cài đặt trong wdw_ctdt.cpp) định nghĩa vị trí, kích thước, các thuộc tính mặc định và khung của Window mới. Trừ một số sửa đổi nhỏ, các giá trị này được sao chép thẳng vào trong các biến của đối tượng Window này. Window mới luôn được coi là TopWindow. Khi nó được thêm vào WdwList, vùng màn hình sở hữu bởi cửa sổ mới được ấn định bằng hàm PlotOwnership(), cửa sổ này chứa đầy các khoảng trắng, sau đó nó được vẽ lên màn hình.

            Hàm huỷ bỏ của Window được định nghĩa trong wdw_ctdt.cpp, xoá đối tượng Window đã bị huỷ khỏi WdwList và sắp xếp lại các cửa sổ còn lại. Nó cũng giải phóng bộ nhớ được dùng bởi mảng BufferSW của đối tượng.

            Hàm khởi tạo sao chép (trong wdw_copy.cpp) dùng để sao chép lại các nội dung và  đặc tính của một cửa sổ.

            Hàm Display() ( định nghĩa trong wdw_main.cpp) hiển thị các ký tự thông qua hàm PutChar() của lớp Screen. Một ký tự cất trong một Window được đặt ở một vị trí dòng và cột tương đối so với góc trên bên trái của Window này. Vị trí của một Window trên màn hình thật sẽ chi phối vị trí vật lý của góc trên bên trái của Window trên màn hình. Khi một ký tự được đặt ở dòng 5, cột 2 của một Window mà gốc của nó ở dòng 10, cột 20 của màn hình vật lý, thì ký tự này cần được hiển thị ở dòng 15, cột 22  trên màn hình vật lý. Hàm Display() xử lý việc chuyển đổi đó, và đồng thời xác định những vị trí mà ở đó các ký tự được hiển thị là thuộc về Window nào.

            Trong một số trường hợp, cần hiển thị  lại toàn bộ chồng cửa sổ, ví dụ như khi một Window bị huỷ hay di chuyển. Hàm Restack() (trong wdw_main.cpp) bắt đầu bằng việc xoá màn hình vật lý và mảng ScrnOwner. Sau đó nó duyệt qua danh sách các cửa sổ từ đáy lên đỉnh, tính toán lại quyền sở hữu các vị trí màn hình trong khi vẽ lại các Window này.

            Hàm PlotOwnership() (trong wdw_main.cpp) được gọi từ một Window khi nó cần đánh dấu các vị trí màn hình mà nó sở hữu. Window bị che sẽ không sở hữu bất kỳ phần nào trên màn hình. Hàm này được gọi từ Restack() và chỉ nên được gọi cho cửa sổ trên cùng (TopWindow). Trong hàm Restack(), nó bắt đầu ở Window "cấp thấp nhất" trong chồng cửa sổ, và gọi PlotOwnership() cho mỗi Window trong chồng trong lúc di chuyển dần lên đỉnh. Vị trí màn hình dành cho một cửa sổ cấp thấp hơn sẽ được xác định khi gọi đến PlotOwnership() cho các cửa sổ gối lên nó.

            ErrorHandler (trong wdw_err.cpp), một con trỏ trỏ đến hàm xử lý lỗi được gọi bất kỳ lúc nào mà việc cấp phát bộ nhớ xảy ra lỗi trong khi xử lý các Window. Nó được khởi động từ hàm FirstTime() (được gọi khi xây dựng Window đầu tiên), để chỉ tới hàm xử lý lỗi mặc định. Nhưng nó có thể bị thay đổi để trỏ tới một hàm do người dùng định nghĩa bằng cách dùng hàm tĩnh SetErrorHandler(). SetErrorHandler() và hàm xử lý lỗi mặc định được lưu trữ trong tệp wdw_err.cpp.

            Các hàm thay đổi và hiển thị nội dung của Window được cài đặt trong wdw_disp.cpp. Cần 2 bước để hiển thị thông tin lên màn hình thông qua một Window. Bước thứ nhất, thông tin được cất trong vùng đệm BufferSW của Window. Bước thứ hai, vùng đệm này được "vẽ" lên màn hình. Có thể thực hiện một số thay đổi nào đó trên nội dung một cửa sổ mà không làm xuất hiện những thay đổi đó trên màn hình. Nội dung hiện tại của Window chỉ được hiển thị khi "vẽ" cửa sổ này.

            Hàm Paint() (trong wdw_disp.cpp) hiển thị tất cả nội dung của một cửa sổ lên màn hình. Nếu một Window bị che bởi một Window khác, thì không phải tất cả những nội dung của nó đều có thể được hiển thị. Hàm Paint() cũng hiển thị lại khung và tựa đề của cửa sổ nếu có.

            Hàm PaintLine() (trong wdw_disp.cpp) đơn giản hơn Paint(). Nó chỉ hiển thị một dòng của cửa sổ hiện tại lên màn hình. Hàm này làm tăng tốc độ làm việc khi có một thay đổi chỉ xảy ra trên một dòng của Window.

            PutChar() và PutStr() (trong wdw_disp.cpp) cất các ký tự vào trong BufferSW. PutChar() cất một ký tự đơn vào vị trí được xác định, còn PutStr() thì cất toàn bộ giá trị chuỗi String. Các ký tự được cất vào trong một Window bằng hàm này sẽ không xuất hiện trên màn hình cho đến khi gọi tới hàm Paint().

            Có nhiều hàm làm thay đổi vị trí và các đặc tính của Window. Những hàm này được cài đặt trong wdw_misc.cpp. Vị trí một Window trên màn hình được thay đổi bởi hàm Move(), nhưng việc di chuyển một cửa sổ không làm thay đổi cấp của nó trong chồng cửa sổ. Ví dụ, nếu cửa sổ thứ hai kể từ đỉnh chồng được di chuyển, nó vẫn là cửa sổ thứ hai từ trên xuống.

            Hàm Hoist() (trong wdw_misc.cpp) thay đổi vị trí của một cửa sổ trong chồng cửa sổ để làm cho nó trở thành cửa sổ trên cùng.

            Một Window không nhất thiết phải được hiển thị. Hàm Conceal() (trong wdw_misc.cpp) dùng để che một Window. Khi đó, Window bị che sẽ không chiếm một vị trí nào trên màn hình, và cũng không che bất kỳ một cửa sổ nào bên cạnh nó. Một Window bị che vẫn có thể có những ký tự được cất trong vùng đệm. Nó cũng có thể được di chuyển hoặc huỷ bỏ. Hàm Reveal() (trong wdw_misc.cpp) hiển thị  một Window bị che.

            Khi cờ Wrapped của một Window được bật, bất kỳ chuỗi ký tự nào được cất trong vùng đệm qua hàm PutStr() kéo dài quá bên phải của Window sẽ được cuộn xuống dòng mới. Nếu tắt cờ Wrapped, các chuỗi dài sẽ bị chặt đứt ở bên phải khung cửa sổ. Hàm WrapOn() và WrapOff() (trong wdw_misc.cpp) thay đổi việc đặt bit Wrapped này.

            Hàm SetHead() (trong wdw_misc.cpp) thay đổi tiêu đề Window. Mỗi tiêu đề là một String được thể hiện gần phía bên trái của dòng trên của khung Window. Tiêu đề được khởi động khi xây dựng Window.

            Hàm SetCursor() (trong wdw_misc.cpp) đặt vị trí con trỏ màn hình trong một Window, và hàm GetCursor() lấy lại vị trí hiện tại của con trỏ màn hình. Hàm ClearAll()  xoá trống toàn bộ Window, và ClearLine() chỉ xoá một dòng.

            Sau đây là một chương trình minh hoạ việc sử dụng lớp Window windemo.cpp:

#include "Window.hpp"

extern "C"

            {          #include <conio.h>

                        #include <string.h>

            }

void f1();

void f2(Window *Parent);

void WaitForKey();

static Window *Wmain;

int main()

            {          unsigned int l,c;

//Cấp phát cửa sổ chính

Wmain=new Window(1,1,23,78,7,7,BT_DOUBLE,"",0);   

//Điền vào cửa sổ chính những dấu sao

for (l=0;l<25;++l)

for (c=0;c<80;++c)      Wmain->PutChar(l,c,0,'*');

//Vẽ cửa sổ chính

Wmain->Paint();

WaitForKey();

//Thực hiện với các cửa sổ khác

f1();

WaitForKey();

delete Wmain;

            }

void f1()

            {          unsigned int l,c;

                        char buf[128];

                        Window W1(2,10,15,40,7,7,BT_DOUBLE,"Test Window 1",1);

                        W1.PutStr(0,0,0,"Đây là một chuỗi ký tự dài để "

                                                " xem xét cách cuộn xuống dòng làm việc ra sao");

                        W1.PutChar(14,10,0,'*');

                        W1.PutChar(5,29,0,'*');

                        W1.Paint();

WaitForKey();

Wmain->SetHead("MAIN WINDOW");

WaitForKey();

f2(&W1);

WaitForKey();

Wmain->PutStr(5,5,0," Đây cũng là một câu khá dài, và sẽ "

                        " bị cắt bớt ");

Wmain->Paint();

WaitForKey();

strcpy(buf,"Scott Ladd");

W1.PutStr(11,0,0,buf);

W1.Paint();

WaitForKey();

            }

void f2(Window *Parent)

            {          Window W2(10,25,8,20,7,7,BT_SINGLE,"Test Window 2",0);

                        W2.PutStr(3,4,15, "Câu này cũng sẽ bị che mất bớt "

                                                " bởi vì quá dài");

                        W2.Paint();

WaitForKey();

                        Parent->Hoist();

WaitForKey();

                        W2.Move(10,50);

WaitForKey();

            }

void WaitForKey()

            {          while (!kbhit())            if (!getch())      getch();            }

            Khi làm việc với lớp Screen và Window, chúng ta có thể định nghĩa các lớp cửa sổ đặc biệt, với các tính năng mới như có thanh cuốn (scroll bar), có nhận chuột, có thực đơn... Chúng ta có thể coi các lớp đã có như là các khối xây dựng, mỗi khối tạo nên một nền tảng cho những khối khác.

Bạn đang đọc truyện trên: Truyen2U.Com

Tags: