| Não há um modo simples de se modificar a solução de
matriz para uma outra estrutura sem re-escrever praticamente todo o código. O código que
implementa o programa - que contém portanto a solução do problema - não tem porque se
preocupar com a organização física da lista em uma matriz. O código para tratar a
organização da lista na matriz não deveria estar embutido no programa. Está no lugar
errado. A idéia subjacente à abstração de dados é proteger variáveis globais, tais como uma matriz global, da manipulação direta pelos programas de aplicação. Isolando, através de chamadas de funções, as variáveis que implementam fisicamente a lista do restante do programa, nós podemos obter três benefícios:
Em C, você poderia fazer esse programa da seguinte forma: |
| #include <iostream.h> #include <string.h> typedef struct { char name[20]; char city [20]; char state[20]; } addrStruct; //-------- data and functions for the list ------- const int MAX=10; addrStruct list[MAX]; int numInList; void listInit() { numInList=0; } void listTerminate() { } int listFull() { if (numInList >=MAX) return 1; else return 0; } int listEmpty() { if (numInList==0) return 1; else return 0; } int listSize() { return numInList; } int listAdd(addrStruct addr) { if (!listFull()) { list[numInList++]=addr; return 0; // returns 0 if OK } return 1; } int listGet(addrStruct& addr, int i) { if (i < listSize()) { addr=list[i]; return 0; // returns 0 if OK } return 1; } //------------------------------------------------ void addName() { addrStruct a; if (!listFull()) { cout << "Enter Name: "; cin >> a.name; cout << "Enter City: "; cin >> a.city; cout << "enter State: "; cin >> a.state; listAdd(a); } else cout << "List full\n"; } void printOneName(addrStruct a) { cout << endl; cout << a.name << endl; cout << a.city << endl; cout << a.state << endl; } void printNames() { int i; addrStruct a; for (i=0; i < listSize(); i++) { listGet(a,i); printOneName(a); } cout << endl; } void findName() { char s[20]; int i; int found=0; addrStruct a; if (listSize==0) cout << "List empty\n"; else { cout << "Enter name to find: "; cin >> s; for (i=0; i < listSize(); i++) { listGet(a, i); if (strcmp(s,a.name)==0) { printOneName(a); found=1; } } if (!found) cout << "No match\n"; } } void paintMenu() { cout << "Address list Main Menu\n"; cout << " 1 - add to list\n"; cout << " 2 - print list\n"; cout << " 3 - find name\n"; cout << " 4 - quit\n"; cout << "Enter choice: "; } void main() { char choice[10]; int done=0; listInit(); while (!done) { paintMenu(); cin >> choice; switch(choice[0]) { case '1': addName(); break; case '2': printNames(); break; case '3': findName(); break; case '4': done=1; break; default: cout << "invalid choice.\n"; } } listTerminate(); } |
| A classe lista está definida próximo ao topo do programa
e começa com as palavras
class
List. Isso é como uma declaração de tipo: a
instância real de lista aparece na linha List list; Essa linha declara uma variável denominada list do tipo class List. Repare que a classe List inicia-se de modo muito semelhante a uma estrutura. Ela declara duas variáveis do mesmo modo que se faria em uma declaração de estrutura. Essas variáveis são denominadas dados membro. Em continuação, a definição da classe contem a palavra public. Essa palavra indica que as funções seguintes poderão ser invocadas por qualquer código que use essa classe. O termo de sentido oposto é private, e é usado quando funções ou dados devem permanecer ocultos dentro da classe, invisíveis a qualquer código que use a classe. As variáveis e funções definidas dentro de uma classe são, por default, private a menos que você explicitamente as faça public. Após a definição dos dados membro vem a definição das funções membro. São essas as funções que podem ser aplicadas às instâncias da classe. As primeiras duas funções em nosso exemplo - List e ~List - tem um significado único. São denominadas construtor e destrutor, respectivamente. O construtor é chamado automaticamente sempre e quando passa a existir uma instância da classe. Nesse caso, uma instância da classe List passa a existir logo que se inicia o programa porque está declarada como uma variável global, mas nem sempre as instâncias de uma classe são declaradas como variáveis globais. Então, como regra, o construtor é chamado automaticamente quando uma instância da classe passa a existir, e os construtores de pointers são ativados quando new é chamado para o pointer. O construtor tem o mesmo nome da classe: |
| List(): numInList(0)
// constructor { } |
| A inicialização do dado membro numInList é única nesse caso. Um outro modo de se fazer isso é |
| List() // constructor { numInList = 0; } |
| A primeira forma é, no entanto, mais eficiente em tempo
de execução, devido à maneira como o C++ internamente inicializa as classes. A sintaxe,
quando usada como mostrada nesse construtor, inicializa o dado membro numInList atribuindo-lhe
o valor 0 (zero) e deve ser usada sempre que se inicializa dados membro em um
construtor. O destrutor - ~List em nosso exemplo - é chamado automaticamente quando se encerra o escopo dentro do qual a instância da classe foi declarada, que é onde a instância é então excluída. Destrutores são únicos, rigidamente limitados às variáveis da classe, e podem referenciar variáveis da classe em qualquer momento. A variável list é uma instância da classe List. Se list fosse uma estrutura unidimensional seria declarada de modo semelhante ao que foi declarada em nosso exemplo, e funcionaria da mesma maneira. A variável list é tão grande quanto o tamanho de seus dados membro. As funções, em realidade, não ocupam qualquer espaço físico nas instâncias da classe. A sintaxe da linguagem apenas permite que sejam declaradas, e usadas, com instâncias da classe, mas não as implementa fisicamente a cada instância da classe. A instância list é usada ao longo de todo o programa. A cada vez que algo precisa ser feito com list você encontra o nome da instância seguido de um ponto e do nome da função. De novo, essa notação segue a sintaxe usada para estruturas. O ponto significa chame a função membro da classe List para a instância específica list. Isso pode não fazer sentido imediatamente para você. Ainda assim, tudo ok. O aspecto importante a ser extraído desse exemplo é que tudo o que fizemos foi tomar alguns dados - nesse caso, uma matriz e um inteiro - e as funções necessárias para manipular essas variáveis, e colocamos tudo junto dentro dos limites de uma classe. Agora as variáveis não podem ser acessadas diretamente pelo restante do código, pelo código externo à classe. Devido ao fato de serem membros privados da classe, somente podem ser acessados por funções membro da classe, e não por qualquer outra parte do código, não pertencente a classe. O objeto list - dados e funções se fundem em objeto - podem ser acessados exclusivamente via funções membro. |
| Com papel e lápis, siga em sua
mesa, passo a passo, a execução desse código e tente predizer o que vai acontecer em
uma execução real. Depois execute esse mesmo código com uma ferramenta de debug na
modalidade single-etepping e veja o que acontece Dados membro e funções membro podem ser public ou private, dependendo de como tenham sido definidos dentro do programa. A melhor regra, para preservar os benefícios da orientação a objetos, e não usar dados membro public. Um dado membro public pode ser acessado a partir de qualquer ponto do programa, enquanto os dados membro private somente podem ser acessados pelas funções membro da classe. Vamos modificar um pouco a classe Rect para ver o que acontece. |
| class
Rect { int x1, y1, x2, y2; public: Rect(int left=0,int top=0, int right=0,int bottom=0): x1(left), y1(top), x2(right), y2(bottom) { } ~Rect() {} private: int Height() { return (y2-y1); } int Width() { return (x2-x1); } public: int Area() { return Width()*Height(); } int Perimeter() { return 2*Width()+2*Height();} }; |
| Agora as funções Width e Heigth são private. Elas podem ser chamadas como mostradas aqui porque Area e Perimeter são funções membro. Mas se você tentar |
|
Rect r; ... cout << r.Height(); |
| você vai incorrer em um erro de compilação porque Heigth é
uma função private. Atribuição entre duas instâncias de uma mesma classe simplesmente copia os dados membro de uma instância para a outra. Por exemplo: |
| Rect r1,r2; ... r1=r2; |
| é o mesmo que |
| r1.x1 = r2.x1; r1.y1 = r2.y1; r1.x2 = r2.x2; r1.y2 = r2.y2; |
| Finalmente, há dois modos aceitáveis de se especificar funções membro. Os exemplos mostrados anteriormente nesse tutorial representam um dos métodos, denominado funções inline. O código a seguir mostra o segundo método, aplicado na classe Rect: |
class Rect
{
int x1, y1, x2, y2;
public:
// the constructor uses default param. See tutor 2
Rect(int left=0,int top=0,
int right=0,int bottom=0);
~Rect();
int Height();
int Width();
int Area();
int Perimeter();
};
Rect::Rect(int left, int top, int right, int bottom):
x1(left), y1(top), x2(right), y2(bottom)
// default values are understood from the prototype
{
}
Rect::~Rect()
{
}
int Rect::Height()
{
return (x2-x1);
}
int Rect::Width()
{
return (y2-y1);
}
int Rect::Area()
{
return Width()*Height();
}
int Rect::Perimeter()
{
return 2*Width()+2*Height();
}
|
| Essa última forma é
normalmente mais fácil de se ler quando as funções da classe são extensas. A notação Rect:: especifica
a classe a qual a função pertence. O código de definição da classe contém
basicamente os protótipos das funções membro da classe. Há várias outras coisas que você pode fazer quando usa classes, mas o material apresentado aqui contém as lições suficientes para que você crie abstrações de dados simples, e as correspondentes funções para assim definir classes. Agora já podemos iniciar a criação de hierarquia de classes. |
| © 1998 Interface Technologies, Inc by Marshall Brain Tradução de Dagoberto Haele Arnaut |
| | Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site | | |