| A classe contém uma pequena
rotina de verificação de erros, obviamente essa rotina teria que ser ampliada se este
fosse um produto com fins comerciais. Suponha agora que você quer fazer duas modificações nessa classe, para adicionar dois novos recursos. Primeiramente, você quer uma função de inserção classificada, de tal modo que após a inserção a classe mantenha a lista classificada corretamente. Em segundo lugar, você quer manter atualizada a soma total dos valores dos itens que compões a lista. Ao invés de percorrer toda a lista efetuando a totalização a cada vez que a função soma for chamada, você quer que a soma total seja atualizada a cada inserção de novo item. Obviamente você poderia simplesmente modificar o código da classe List mostrado anteriormente. Em C++ você usa herança ao invés de modificar o código existente. Vamos criar uma classe SortedList herdando a classe List e assentando sobre ela as nossas modificações. Comecemos por adicionar a capacidade de inserção classificada. |
| class SortedList:
public List { public: SortedList():List() {} SortedInsert(int n) { int i,j; i=0; do { j = Get(i); if (j < n ) i++; } while (j < n && i < Size()); Insert(n, i); } }; |
| A classe List original
permanece totalmente inalterada.
Nós simplesmente criamos a classe SortedList sobre a classe
List. A classe SortedList herda o
comportamento da classe List, ou seja, a classe SortedList é uma classe derivada da classe
List. A classe List é a classe base para SortedList. A classe List é herdada na primeira linha de código de SortedList: class SortedList: public List Os dois pontos ( : ) após SortedList indicam que queremos usar o mecanismo de herança. O termo public indica que queremos que as funções e variáveis public na classe List permaneçam public na classe SortedList. Em lugar do termo public, poderíamos optar por private ou protected. Em qualquer desses casos todas as variáveis e funções públicas da classe base seriam convertidas para a classe derivada. O uso de public nesses casos é o padrão. O diagrama seguinte ilustra o que acontece. |
![]() |
| A classe SortedList simplesmente estende, amplia as
capacidades da classe List. Qualquer um que use a classe SortedList tem acesso tanto
às funções de List quanto às novas funções de SortedList. O construtor de SortedList também tem um formato novo. Usamos o sinal de dois pontos ( : ) para chamar o construtor da classe base. SortedList():List() {} Essa linha significa que o construtor denominado List da classe base deve ser chamado, e que construtor de SortedList não tem nada a fazer. No restante do código da classe SortedList nós simplesmente adicionamos a nova função SortedInsert à classe. Essa nova função faz uso das funções originais Insert, Get e Size pertencentes à classe List, mas não acessa diretamente nenhum dado membro da classe List, até porque não poderia. Repare que os dados membro da classe List são private e portanto podem ser acessados exclusivamente por funções membro da classe List. São invisíveis à classe derivada. Suponha que você queira ter uma variável ou uma função que pareça private para usuários externos, mas se comporte como public para a classe derivada. Por exemplo, digamos que a classe SortedList precise acessar diretamente a matriz contida em List para melhor performance, mas ainda desejamos impedir que os programas de aplicação, usuários de List ou de SortedList, acessem diretamente a matriz. Podemos fazer isso usando protected:, onde usamos public: ou private: Declarando que a matriz é um membro protected na classe List, nós a tornamos acessível pelas classes derivadas de List, mas não pelas instâncias normais de List ou de SortedList. Agora vamos adicionar a capacidade de totalização à classe SortedList. Para isso vamos precisar de uma nova variável, e vamos ainda precisar modificar o comportamento da função de inserção, para que esta passe a atualizar a soma total. O código necessário para essa implementação é mostrado a seguir. |
| class SortedList:
public List { private: int total; public: SortedList():List(), total(0) {} void Insert( int n, int location ) { total = total + n; List::Insert(n, location); } int GetTotal() { return total; } SortedInsert(int n) { int i,j; i=0; do { j = Get(i); if (j < n ) i++; } while (j < n && i < Size()); Insert(n, i); } }; |
| Nessa nova versão da classe SortedList,
nós acrescentamos um novo dado membro denominado total, uma nova função membro de
nome GetTotal para resgatar o total atual, e ainda uma nova função Insert que se sobrepõe à função Insert original. Modificamos o construtor de SortedList que passa a
inicializar a variável total. Agora, sempre que a classe SortedList for utilizada e a função Insert for invocada, a nova versão da função Insert será ativada, ao invés da versão original que permanece inalterada dentro da classe List. Isso vale inclusive para a função SortedInsert da classe SortedList. Quando a função SortedInsert chama a função Insert, a nova função Insert é ativada. O código da nova função Insert é simples e de compreensão quase automática: |
| void Insert( int n,
int location ) { total = total + n; List::Insert(n, location); } |
| A essa altura, esse código já deve ser facilmente
compreensível por você. List é simplesmente uma lista genérica de números inteiros. Um dado membro denominado pointer aponta para um dos elementos da lista e é atualizado pelas quatro funções Get.... Cada uma dessas funções retorna 0 para indicar que a operação foi bem sucedida, ou 1 para indicar insucesso na operação. Por exemplo, se pointer não aponta para o elemento 0 da lista (o elemento mais a esquerda), então ainda há elementos a esquerda do elemento apontado, e a função GetPrevious vai retornar 0). As duas funções Add.... realizam soma no início e no fim da lista. Na versão atual, essas funções não possuem código para verificação de erros. A função AddToFront contém uma ineficiência intrínseca, porque a cada inserção ela desloca todo o conteúdo da matriz uma posição para baixo. A classe Mint herda a classe List e a utiliza para construir o tipo numérico mint. A classe Mint implementa dois construtores: um construtor default que não recebe qualquer parâmetro, e um segundo construtor que recebe um string e o utiliza para preencher a lista. Implementa ainda as funções para somar e para imprimir dois números mint. O código é mostrado a seguir: |
| class Mint: public
List { public: Mint():List() {} Mint(char *s):List() { char *p; for (p=s; *p; p++) AddToEnd(*p-'0'); } void Add(Mint & a, Mint & b) { int carry, temp; int erra, errb, na, nb; carry=0; Clear(); erra=a.GetLast(na); errb=b.GetLast(nb); while (!erra || !errb) { if (erra) temp=nb+carry; else if (errb) temp=na+carry; else temp=na+nb+carry; AddToFront(temp%10); carry=temp/10; erra=a.GetPrevious(na); errb=b.GetPrevious(nb); } if (carry > 0) AddToFront(carry); } void Print() { int n, err; err=GetFirst(n); while( !err ) { cout << n; err=GetNext(n); } cout << endl; } }; |
| A seguinte função main testa a classe Mint somando dois números e imprimindo o resultado da soma: |
| void main() { Mint a("1234567"); Mint b("1234"); Mint c; c.Add(a,b); c.Print(); } |
| Os construtores e a função Print são
simples e facilmente compreensíveis. A função Add talvez remeta você aos dias de banco escolar porque faz adição à moda antiga. Começa com os dois últimos dígitos de cada um dos números a serem somados; soma esses dois dígitos; salva o resultado e anota o valor da casa da dezena decorrente da soma ("vai um"). Move-se então para o elemento anterior da lista e repete as mesmas operações. Provavelmente os dois números mint não tenham a mesma quantidade de dígitos, portanto o código deve certificar-se de que não está operando além do dígito mais a esquerda de um dos números mint. Isso é feito usando as variáveis erra e errb. Quando os dois números mint forem inteiramente processados, o código verifica a existência de vai um e salva o último dígito, se necessário. Executando o código de teste você verá que a classe Mint funciona como descrito aqui e pode somar dois números de até 100 dígitos cada um. Após usar a classe Mint algumas vezes, você começará a perceber um problema com a função Add - não há como escrever algo parecido com m = m + 1 já que o formato obrigatório da chamada de Add é m.Add(m,one), onde a variável one foi inicializada com conteúdo 1. A causa dessa limitação é que Add deve limpar a área destinada a conter a soma antes de salvar aí qualquer resultado. Isso leva a perda de dados quando a função Add é usada para m.Add(m,one). A solução para esse problema nos leva a criação de uma área temporária para conter o resultado durante a execução da soma. Ao término da função, o resultado final deve então ser copiado para a instância atual. O pointer this é usado para solução desse problema, como mostrado a seguir: |
| void Add(Mint &
a, Mint & b) { int carry, temp; int erra, errb, na, nb; Mint x; carry=0; erra=a.GetLast(na); errb=b.GetLast(nb); while (!erra || !errb) { if (erra) temp=nb+carry; else if (errb) temp=na+carry; else temp=na+nb+carry; x.AddToFront(temp%10); carry=temp/10; erra=a.GetPrevious(na); errb=b.GetPrevious(nb); } if (carry > 0) x.AddToFront(carry); *this = x; } |
| Nessa última versão da função Add, foi criado uma variável temporária denominada x. O resultado da soma é colocado em x, dígito a dígito. A última linha do código da função copia o conteúdo de x para a instância atual. O pointer this aponta para a instância corrente da classe e pode ser aplicado a qualquer instância de classes em C++. Em outras palavras, this é um pointer que aponta para o conjunto dos dados membro (a estrutura de dados) que formam a instância corrente da classe. Nesse caso nós usamos this para economia de código. Uma alternativa seria substituir a última linha da função Add por |
| array = x.array; count = x.count; pointer = x.pointer; |
| O valor de *this é a estrutura apontada por this. É uma forma mais
expressa de se copiar toda a estrutura de dados de uma só vez. Como exemplo final da classe Mint, vamos usá-la para implementar um localizador de número Fibonacci. A seqüência Fibonacci tem a seguinte forma: 1, 1, 2, 3, 5, 8, 13, 21, 34, etc. Cada número na seqüência é a soma dos dois números anteriores. Para implementar a função que queremos vamos precisar de um modo de verificar igualdade em números mint de modo a poder controlar um loop. A função membro seguinte poderia ser acrescentada a classe Mint para verificar igualdade entre dois números mint: |
| int Equal(Mint &
a) { if (a.Size()!=Size()) return 0; else { int i, na, nb; a.GetFirst(na); GetFirst(nb); for (i=0; i < a.Size(); i++) if (na!=nb) return 0; else { a.GetNext(na); GetNext(nb); } return 1; } } |
| Implementada essa nova função, o seguinte código vai encontrar o centésimo número em uma seqüência Fibonacci: |
| void main() { Mint max("100"); Mint counter("1"), one("1"); Mint t1("0"), t2("1"); Mint d; do { d.Add(t1,t2); t1=t2; t2=d; counter.Add(counter,one); } while (!counter.Equal(max)); d.Print(); } |
| O código usa duas variáveis t1 e t2 para
conter os valores anteriores. Esses valores são somados e t1 e t2 são
atualizados para os próximos dois valores. O contador é incrementado e o loop continua
até que o contador atinja o valor pré-determinado. Usando esse código, o centésimo
número foi encontrado: 354.224.848.179.261.915.075 |
| © 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 | | |