Você está na página 1de 80

PROGRAMAO ORIENTADA A OBJETOS EM

BERILHES BORGES GARCIA FLVIO MIGUEL VAREJO


COLABORAO: FABRCIA DOS SANTOS NASCIMENTO

NDICE
1.1 ABSTRAO DE DADOS...............................................................................................................4 1.2 HISTRICO......................................................................................................................................6 1.3 A LINGUAGEM C++......................................................................................................................7 2.1 INTRODUO..................................................................................................................................9 2.2.1 PROBLEMA EXEMPLO....................................................................................................................10 2.2 UM EXEMPLO DE ABSTRAO DE DADOS EM C++.........................................................10 2.2.1 ESPECIFICAO E IMPLEMENTAO.................................................................................................11 2.3 UM EXEMPLO DE ESPECIFICAO.....................................................................................12 2.3.1 ESPECIFICAO DA CLASSE BIGINT.................................................................................................12 2.3.2 OCULTAMENTO DE INFORMAO.....................................................................................................14 2.3.3 FUNES MEMBROS.....................................................................................................................15 2.3.4 OVERLOADING.............................................................................................................................15 2.3.5 FUNES COM ARGUMENTOS DEFAULT............................................................................................16 2.3.6 CHAMANDO FUNES MEMBROS....................................................................................................17 2.3.7 CONSTRUTORES ...........................................................................................................................17 2.3.8 CONSTRUTORES E CONVERSO DE TIPOS..........................................................................................18 2.3.9 CONSTRUTORES E INICIALIZAO....................................................................................................19 2.3.10 OVERLOADING DE OPERADORES....................................................................................................20 2.3.11 DESTRUTORES...........................................................................................................................21 3.1 INTRODUO................................................................................................................................22 3.2 IMPLEMENTAO DE UMA CLASSE BIGINT.....................................................................22 3.2.1 O CONSTRUTOR BIGINT(CONST CHAR*)............................................................................................23 3.2.2 O OPERADOR RESOLUO DE ESCOPO.............................................................................................23 3.2.3 CONSTANTES...............................................................................................................................24 3.2.4 FUNES MEMBROS CONSTANTES.................................................................................................25 3.2.5 O OPERADOR NEW.....................................................................................................................26 3.2.6 DECLARAO EM BLOCOS.............................................................................................................26 3.2.7 O CONSTRUTOR BIGINT (UNSIGNED)...............................................................................................26 3.2.8 O CONSTRUTOR CPIA.................................................................................................................27 3.2.9 REFERNCIAS.............................................................................................................................27 3.2.10 O OPERADOR ADIO DA CLASSE BIGINT.....................................................................................29 3.2.11 FUNES FRIEND.......................................................................................................................32 3.2.12 A PALAVRA RESERVADA THIS.....................................................................................................33 3.2.13 O OPERADOR ATRIBUIO DA CLASSE BIGINT...............................................................................33 3.2.14 A FUNO MEMBRO BIGINT::PRINT()...........................................................................................34 3.2.15 O DESTRUTOR DA CLASSE BIGINT................................................................................................34 3.2.16 FUNES INLINE........................................................................................................................35 4.1 INTRODUO................................................................................................................................37 4.1.1 CLASSES DERIVADAS....................................................................................................................38 4.1.2 REDECLARAO - FUNES MEMBROS VIRTUAIS..............................................................................39 4.2 UM EXEMPLO DE APLICAO...............................................................................................40 4.2.1 CLASSE PONTO...........................................................................................................................40 4.2.2 CLASSE LINHA E CLASSE CIRCULO.................................................................................................41 4.2.3 INSTNCIAS DE CLASSE COMO VARIVEIS MEMBROS DE UMA CLASSE...................................................42 4.2.4 A FUNO MEMBRO MOVER().......................................................................................................42 4.2.5 A FUNO MEMBRO DESENHAR() ...............................................................................................43 4.2.6 CLASSE QUADRO.........................................................................................................................45 4.2.7 COMPATIBILIDADE DE TIPOS..........................................................................................................47 4.2.8 CLASSE INCOMPLETAS - FUNES VIRTUAIS PURAS...........................................................................47

4.3 O EXEMPLO GEOMTRICO MELHORADO.........................................................................48 4.3.1 INICIALIZAO DE OBJETOS...........................................................................................................50 4.3.2 FINALIZAO DE OBJETOS.............................................................................................................52 5.1 INTRODUO................................................................................................................................54 5.2 CONSTRUTOR PR-DEFINIDO DE CPIA, OPERADOR PR-DEFINIDO DE ATRIBUIO E CONSTRUTOR PR-DEFINIDO DEFAULT....................................................54 5.3 OBJETOS TEMPORRIOS .......................................................................................................55 5.4 RETORNO DE OBJETOS EM FUNES.................................................................................56 5.5 ESPECIFICADORES DE ACESSO..............................................................................................57 5.6 ESTRUTURAS INTERNAS A CLASSES ...................................................................................59 5.7 HERANA MLTIPLA ...............................................................................................................60 5.7.1 AMBIGIDADES EM HERANA MLTIPLA..........................................................................................62 5.8 FUNES E CLASSES GENRICAS.........................................................................................65 5.9 NAMESPACES................................................................................................................................68 5.10 BOOLEANOS................................................................................................................................69 5.11 STRINGS........................................................................................................................................70 5.12 STREAMS......................................................................................................................................70 5.13 TRATAMENTO DE EXCEES..............................................................................................72 5.13.1 SINALIZANDO UMA EXCEO........................................................................................................73 5.13.1 CAPTURANDO E TRATANDO UMA EXCEO.....................................................................................74 5.14 COLEES E ITERADORES DA BIBLIOTECA PADRO C++........................................77 ESTA SEO OBJETIVA INTRODUZIR AS CLASSES DE COLEES E ITERADORES QUE FAZEM PARTE DA BIBLIOTECA PADRO C++. IMPORTANTE ESCLARECER QUE EXISTE UMA PEQUENA CONFUSO A RESPEITO DESTAS CLASSES, QUE SO FREQUENTEMENTE REFERIDAS COMO PARTE DA STL (STANDARD TEMPLATE LIBRARY). STL FOI O NOME DADO POR ALEX STEPANOV PARA APRESENTAR SUA BIBLIOTECA PARA O COMIT DE PADRONIZAO DE C++, EM 1994. O NOME FOI AMPLAMENTE ACEITO PELA COMUNIDADE DE PROGRAMADORES C++. O COMIT DE PADRONIZAO DE C++ DECIDIU INTEGR-LA BIBLIOTECA PADRO DE C++, PORM FAZENDO UM NMERO SIGNIFICATIVO DE MODIFICAES. O DESENVOLVIMENTO DA STL CONTINUOU NA SILICON GRAPHICS (SGI), DE ONDE SURGIU A SGI STL. ESTA BIBLIOTECA DIVERGE SUTILMENTE DA BIBLIOTECA PADRO EM MUITOS PONTOS. PORTANTO, EMBORA SEJA UMA CONFUSO POPULAR, A BIBLIOTECA PADRO C++ NO INCLUI A STL. AQUI NO DISCUTIREMOS EM DETALHES AS CLASSES DESTAS BIBLIOTECAS. VAMOS APRESENTAR UMA INTRODUO QUE SER VLIDA TANTO PARA UMA QUANTO PARA OUTRA.......................................................................................................................................77

Captulo I - Introduo

Este captulo contm uma introduo histria da linguagem C++, idias que influenciaram o projeto da linguagem C++, bem como apresenta uma anlise comparativa entre as linguagens C e C++.

1.1 Abstrao de Dados


Abstrao de Dados e Programao Orientada a Objetos representam um estilo de programao que oferece oportunidades para a melhoria na qualidade de software. Programao orientada a objetos, com abstrao de dados como um fundamento necessrio, difere enormemente dos outros estilos e metodologias de programao, uma vez que requer uma abordagem diferente para a resoluo de problemas.

Programadores h muito reconhecem o valor de se organizar tens de dados correlatos em construes de programas tais como Records de Pascal e structs de C, tratando-as posteriormente como unidades. Abstrao de dados estende essa organizao de forma a incorporar um conjunto de operaes que possam ser executadas sob uma instncia particular da estrutura. Usualmente, os elementos de dados e a implementao das operaes que podem ser executadas sobre eles so mantidos privativos ou protegidos de forma a prevenir alteraes indesejveis. Ao invs de acessar os elementos de dados diretamente, os programas clientes (cdigo do usurio) devem invocar as operaes exportveis de forma a produzir os resultados desejveis. Quando ns encapsulamos dados e operaes que atuam sob esses dados desta maneira, estas estruturas encapsuladas comportam-se analogamente a tipos prconstrudos ou fundamentais como nmeros inteiros ou de ponto flutuante. Pode-se, ento, us-las como caixas pretas que proporcionam uma transformao entre a entrada e a sada. Ns no necessitamos saber como o compilador trata os tipos fundamentais. Abstrao de dados, em essncia, nos permite criar novos tipos de dados, dai surgiu a idia de cham-los de Tipos Abstratos de Dados. Encapsulamento + Proteo = Tipo Abstrato de Dados

1.2 Histrico
A Programao Orientada a Objetos no nova; seus conceitos remontam h duas dcadas atrs. A origem da programao orientada a objetos surge com as linguagens de programao Simula 67 e Smalltalk. Novo o fluxo recente de interesse nesta importante e promissora metodologia. Foi dito que a programao orientada a objetos difere substancialmente dos estilos de programao com os quais ns estamos mais familiarizados, requerendo uma abordagem diferente para se resolver problemas, mas o que programao orientada a objetos, e o que esta tem de diferente? Quando se est programando orientado a objetos, um programador especifica o que fazer com um objeto antes de se concentrar no aspecto procedimental convencional de como algo deve ser feito. Programao orientada a objetos lida com a manipulao de objetos. Um objeto, por sua vez, pode representar quase tudo - um numero, uma string, um registro de paciente em um hospital, um trem ou uma construo grfica como um retngulo ou alguma outra forma geomtrica. Em essncia, um objeto compreende os elementos de dados necessrios para descrever os objetos, junto com o conjunto de operaes permissveis sobre esses dados. Pode-se ver que um objeto nada mais do que uma instncia particular de um tipo abstrato de dados, o qual foi projetado de acordo com um conjunto particular de regras. Uma parte considervel do potencial da programao orientada a objeto resulta da utilizao do mecanismo de herana - a idia pela qual um programador comea com uma biblioteca de classes (tipos de objetos) j desenvolvida e a estende para uma nova aplicao adicionando novos elementos de dados ou operaes (atributos) de forma a construir novas classes. Em outras palavras, ao invs de desenvolver uma nova aplicao escrevendo o cdigo a partir do zero, um cliente herda dados e operaes de alguma classe base til, e ento adiciona nova funcionalidade descrevendo como a nova classe difere da classe base. Ao adicionar novos elementos de dados ou funes, o programador no necessita modificar a classe base. Isto nos conduz a um dos benefcios da programao orientada a objetos: a reutilizao de cdigo. O mecanismo de herana abre a possibilidade de ocorrncia de late ou dynamic binding (amarrao tardia ou amarrao dinmica), onde um programa cliente pode aplicar uma funo a esse objeto sem saber a classe especfica do objeto. Em tempo de execuo, o sistema run-time determinar a classe especfica do objeto e invocar a implementao correspondente da funo. Mas o que tudo isto significa, afinal de contas? Para comear a compreender estes novos conceitos, considere um programa que manipula estruturas de dados representando figuras geomtricas. Talvez pudssemos comear com uma classe Forma para representar as formas gerais. Atravs da herana, nos poderamos projetar novas classes de Forma a representar formas mais especificas, como Crculos e Quadrados. Imagine agora uma situao na qual ns necessitamos de um programa que ao invs de operar sob estruturas de dados especificas como crculos, possa lidar com Formas genricas cujos tipos particulares ns no conhecemos quando escrevemos o

programa. Por exemplo, ns podemos querer que o programa desenhe um conjunto de Formas, tais com Crculos, Tringulos e Retngulos. Quando o programa estiver executando, o sistema run-time determinar a classe particular de cada objeto do tipo Forma e, utilizando late binding, chamar a funo desenhar() apropriada para este.

1.3 A Linguagem C++


A linguagem C++ foi projetada e implementada por Bjarne Stroustrup, do AT&T Bell Laboratories, para se tornar a linguagem sucessora de C. C++ estende C atravs do uso de vrias idias emprestadas das linguagens de programao Simula 67 e Algol 68. Mesmo assim, C++ mantm total compatibilidade com C, isto , qualquer programa feito em C tambm um programa em C++. A figura 1 ilustra a rvore genealgica das linguagens de programao mais conhecidas.

1950 Fortran 1960 Cobol Algol 60 Simula 67 1970 Smalltalk 1980 Ada Eiffel 1990 C++ CLOS Miranda Pascal C ML PL 1 Algol 68 Prolog Lisp

Figura 1 - rvore Genealgica das Principais LPs

C++ tambm adiciona muitas novas facilidades a C, fazendo com que esta seja conveniente para um amplo leque de aplicaes: de drivers de dispositivos s aplicaes comerciais. Muitos programadores concordam que C uma linguagem compacta e eficiente. Contudo, ela possui deficincias, algumas das quais foram corrigidas na linguagem C++. Por exemplo, C possui um nico mecanismo de passagem de parmetros, conhecido como passagem por valor. Quando se necessita passar um argumento para uma funo por referncia, deve-se utilizar um mecanismo obscuro e indutor de erros, que a passagem de um ponteiro como parmetro. C++ resolve este problema de uma forma elegante e eficiente.

C++ no apenas corrige muitas das deficincias de C, mas tambm introduz muitas caractersticas completamente novas que foram projetadas para suportar abstrao de dados e programao orientada a objetos. Exemplos de tais caractersticas so: Classes, a construo bsica que permite ao programador definir novos tipos de dados; Variveis membros, que descrevem a representao de uma classe, e funes membros, que definem as operaes permissveis sob uma classe; Overloading (Sobrecarga) de operadores, que permite a definio de significados adicionais para muitos operadores, de modo que estes possam ser usados com os tipos de dados criados pelo programador, e overloading de funes, similar ao overloading de operadores, que permite reduzir a necessidade de criao de nomes para funes, tornando o cdigo mais fcil de ler; Herana, que permite que uma classe derivada herde as variveis membros e funes membros de sua classe base; Funes virtuais, que permitem que uma funo membro herdada de uma classe base seja redefinida em uma classe derivada.

Captulo II - Abstrao de Dados

Este captulo descreve as facilidades que C++ fornece para a declarao de novos tipos de dados, bem como as maneiras pelas quais uma estrutura de dados pode ser protegida, inicializada, acessada e, finalmente, destruda, atravs de um conjunto especifico de funes.

2.1 Introduo
As linguagens de programao tratam as variveis de programa e constantes como instncias de um tipo de dados. Um tipo de dado proporciona uma descrio de suas instncias fornecendo ao compilador informaes, tais como: quanto de memria alocar para uma instncia, como interpretar os dados, e quais operaes so permissveis sob aqueles dados. Por exemplo, quando ns escrevemos uma declarao tal como float x; em C ou C++, estamos declarando uma instncia chamada x do tipo de dado float. O tipo de dado float diz ao compilador para reservar, por exemplo, 32 bits de memria, para usar instrues de ponto flutuante para manipular esta memria, que operaes tais como "somar" e "multiplicar so aceitas e que operaes como "mdulo" e "shift no so. Ns no temos que escrever esta descrio do tipo float - o implementador do compilador fez isto para ns. Tipos de dados como float, int e char so conhecidos com tipos de dados primitivos. Algumas linguagens de programao tm caractersticas que nos permitem estender efetivamente a linguagem atravs da adio de novos tipos de dados. Pode-se implementar novos tipos de dados na linguagem C++ atravs da declarao de uma classe. Uma classe um novo modelo de dados definido pelo usurio que proporciona encapsulamento, proteo e reutilizao. Uma classe tipicamente contm: Uma especificao de herana; Uma definio para a representao (adicional) de dados de suas instncias (objetos); Definies para as operaes (adicionais) aplicveis aos objetos; A melhor maneira de aprender sobre abstraes de dados em C++ escrever um programa, e isto que ser feito nas prximas sees. Vamos comear em um territrio familiar, escrevendo um programa simples em C. #include <stdio.h> main () { int a = 193; int b = 456; int c; c = a + b + 47; printf("%d\n",c); }

Exemplo 1

Este programa declara trs variveis inteiras a, b e c, inicializando a e b com os valores 193 e 456, respectivamente. varivel inteira c atribudo o resultado da adio a, b e o literal 47. Finalmente, a funo printf() da biblioteca padro de C chamada para imprimir o valor de c. 2.2.1 Problema Exemplo Agora suponha que se deseja executar um clculo similar, mas desta vez, a e b so nmeros muito grandes, como a dvida interna do Brasil expressa em reais. Tais nmeros so muito grandes para serem armazenados como ints, de modo que se ns tentssemos escrever a = 25123654789456; o compilador C emitiria uma mensagem de erro e o programa no seria compilado com sucesso. Inteiros grandes tm muitas aplicaes prticas como criptografia, lgebra simblica, e teoria dos nmeros, onde pode ser necessrio executar operaes aritmticas com nmeros de centenas ou mesmo milhares de dgitos.

2.2 Um Exemplo de Abstrao de Dados em C++


Utilizando-se C++ possvel construir uma soluo para o problema anterior de uma forma conveniente e elegante. Note como o programa do exemplo 1 similar ao programa do Exemplo 2. Muitas linguagens atualmente em uso, tais como C, COBOL, Fortran ,Pascal, e Modula-2 tornam difcil a abstrao de dados. Isto porque a abstrao de dados requer caractersticas especiais no disponveis nestes linguagens. Afim de obter sentimento a respeito destas idias, ns iremos analisar o programa do Exemplo 2. As trs primeiras instrues no corpo da funo main() declaram trs variveis do tipo BigInt: a, b e c. O Compilador C++ necessita saber como cri-las, quanto de memria necessrio para alocar para elas e como inicializ-las. A primeira e a segunda instrues so similares. Elas inicializam as variveis BigInt a e b com constantes literais representando inteiros grandes escritos como strings de caracteres contendo somente dgitos. Sendo assim, o compilador C++ deve # include "BigInt.h" ser capaz de converter strings de caracteres em BigInts. main() { BigInt a = "25123654789456"; BigInt b = "456023398798362"; BigInt c; c = a + b + 47; c.print(); 10 printf("\n"); }

Exemplo 2

A quarta instruo mais complexa. Ela soma a, b e a constante inteira 47, e armazena o resultado em c. O compilador C++ necessita ser capaz de criar uma varivel BigInt temporria para reter a soma de a e b. E, ento, deve converter a constante inteira 47 em um BigInt e som-la com a varivel temporria. Finalmente, deve atribuir o valor desta varivel temporria a varivel c. A quinta instruo imprime c na sada padro, atravs da funo membro print. A ltima instruo chama a funo printf da biblioteca padro de C para imprimir o caracter de nova linha. Apesar do corpo de main() no possuir nenhuma instruo adicional, o trabalho ainda no est finalizado. O compilador deve gerar cdigo para que as variveis a, b e c, e qualquer BigInt temporrio sejam destrudos antes de deixar uma funo. Isto assegura que a memria ser liberada. De uma forma resumida, o compilador necessita saber como: Criar novas instncias das variveis BigInt; Converter strings de caracteres e inteiros para BigInts; Inicializar o valor de um BigInt com o de outro BigInt; Atribuir o valor de um BigInt a outro; Somar dois BigInts; Imprimir BigInts; Destruir BigInts quando eles no so mais necessrios.

2.2.1 Especificao e Implementao Onde o compilador C++ obtm este conhecimento? Do arquivo BigInt.h, que includo na primeira linha do programa exemplo. Este arquivo contm a especificao de nosso novo tipo de dados BigInt. A especificao contm as informaes que os programas que usam um tipo abstrato de dados, chamado de programas clientes (ou simplesmente, clientes) necessitam, afim de compilarem com xito. Muitos dos detalhes de como um novo tipo funciona, conhecidos como a implementao, so Compilador BigInt.cpp BigInt.o mantidos em um arquivo C++ Em nosso exemplo, este arquivo chamado parte. BigInt.cpp. A Figura 2 mostra como a especificao e a implementao de um tipo abstrato de dados so combinados com o cdigo fonte do cliente para produzir um Linker programa executvel.
BigInt.h

Client.cpp

Compilador C++

Client.o Client.exe 11

Figura 2 - Combinando Especificao e Implementao em um Programa Cliente

O objetivo de se separar o cdigo de um tipo abstrato em uma especificao e uma implementao ocultar os detalhes de implementao do cliente. Ns podemos, ento, mudar a implementao e ter garantias que os programas clientes continuaro a trabalhar corretamente. Um tipo abstrato de dados bem projetado tambm oculta sua complexidade em sua implementao, tornando to fcil quanto possvel para os clientes utiliz-lo.

2.3 Um Exemplo de Especificao


2.3.1 Especificao da Classe BigInt A instruo #include "BigInt.h inclui a especificao da classe BigInt dentro do programa exemplo, de modo que se possa usar BigInts no programa. Aqui est o contedo de BigInt.h (note que em C++, // comea um comentrio que se estende at o fim da linha):

12

#include <stdio.h> // este arquivo contem a especificao da classe BigInt class BigInt { // ... char* digitos; // ponteiro para um array de digitos na memria unsigned ndigitos; // numero de digitos // ... public: BigInt (const char*); // funcao construtora BigInt (unsigned n = 0); // funcao construtora BigInt (const BigInt&); // funcao construtora utilizando copia void operator= (const BigInt&); // atribuio BigInt operator+ (const BigInt&) const; // operador adicao void print (FILE* f = stdout) const; // funo de impressao ~BigInt() { delete digitos; } // destrutor }; Programadores C entenderam muito pouco deste cdigo. Este um exemplo de uma das mais importantes caractersticas de C++, a declarao de uma classe. Esta uma extenso de uma construo que programadores em C j deveriam estar familiarizados: a declarao de uma struct. A declarao de uma estrutura agrupa um certo nmero de variveis, que podem ser de tipos diferentes, em uma unidade. Por exemplo, em C (ou C++) pode-se escrever: struct BigInt { char* digitos; unsigned ndigitos; }; Pode-se ento declarar uma instncia desta estrutura escrevendo-se: struct BigInt a; As variveis membros da struct, digitos e ndigitos, podem ser acessados utilizando-se o operador ponto(.); por exemplo, a.digitos, acessa a varivel membro digitos da struct a. Relembre que em C pode-se declarar um ponteiro para uma instncia de uma estrutura: struct BigInt* p; Neste caso, pode-se acessar as variveis individuais utilizando-se o operador ->, por exemplo, p->digitos.

13

Classes em C++ trabalham de forma similar, e os operadores . e -> podem ser usados da mesma maneira para acessar as variveis membro de uma classe. Neste exemplo, a classe BigInt tem duas variveis membros digitos e ndigitos. A varivel digitos aponta para um array de bytes, alocados na rea de memria dinmica, e contm os dgitos de um inteiro grande (um dgito por byte do array). Os dgitos so ordenados comeando do dgito menos significativo no primeiro byte do array, e so nmeros binrios, no caracteres ASCII. A varivel membro ndigitos contm o numero de dgitos de um inteiro. A figura 3 mostra o diagrama de uma instncia desta estrutura para o nmero 654321.

digitos 1 2 3 4 5 6 6 ndigitos Figura 3 - Uma Instncia da Classe BigInt Representando o Nmero "654321"

2.3.2 Ocultamento de Informao Um programa cliente pode declarar uma instncia da classe BigInt da seguinte forma: BigInt a; Temos agora um problema potencial: o programa cliente pode tentar, por exemplo, usar o fato de que a.ndigitos contm o nmero de dgitos no nmero a. Isto tornaria o programa cliente dependente de implementao da classe BigInt. Necessitase, portanto, de uma forma de preveno contra o acesso no autorizado a variveis membros (representao) de uma classe. C++ permite que o usurio controle o acesso aos atributos de uma classe. A palavra reservada public: dentro de uma declarao de classe indica que os membros que seguem esta palavra reservada podem ser acessados por todas as funes. J os membros que seguem a palavra reservada private:, como digitos e ndigitos em nosso exemplo, s podem ser acessados pelas funes membros declaradas dentro da mesma classe. H um terceiro tipo de controle determinado pela palavra chave protected:, o qual ser analisado posteriormente. Restringir o acesso aos membros de uma classe uma forma de aplicao do princpio de ocultamento de informao, este princpio pode ser estabelecido como: Todas as informaes a respeito de uma classe (mdulo) devem ser privativas desta classe, a menos que uma informao especfica seja declarada pblica. Este princpio visa garantir que mudanas na implementao (representao) de uma classe no afetem os clientes desta classe. Por conseguinte, torna-se mais fcil

14

fazer modificaes, porque o cdigo que manipula as variveis membros protegidas localizado, e isto auxilia no processo de depurao. 2.3.3 Funes Membros Como um programa cliente interage com as variveis membros privadas de uma classe? Enquanto a construo struct de C permite apenas que variveis sejam agrupadas juntas, a declarao class de C++ permite o agrupamento de variveis e funes. Tais funes so chamadas de funes membros, e as variveis privadas das instncias de uma classe somente podem ser acessadas atravs das funes membros1. Sendo assim, um programa cliente s pode ler e modificar os valores das variveis membros privativas de uma classe indiretamente, chamando as funes membros pblicas de uma classe, como mostrado na Figura 4.

BigInt(const char*) (construir) BigInt a = 218 print() char* digitos int ndigitos BigInt(const BigInt&) BigInt c = a + b +47; (inicializar) c.print();

operador+(const BigInt&) (somar) Figura 4 - Acesso a Variveis Privadas atravs de Funes Membro Pblicas

Por exemplo, a classe BigInt tem duas variveis membros privativas, digitos e ndigitos, e sete funes membros pblicas. A declarao destas funes parecer estranha aos programadores C pelas seguintes razes: trs funes tm o mesmo nome, BigInt; os nomes de funes operator=, operator+ e ~BigInt contm caracteres normalmente no permitidos em nomes de funes. 2.3.4 Overloading

Estritamente falando, funes friend tambm podem acessar os membros privativos de uma classe, como ser visto posteriormente.
1

15

Um identificador ou operador sobrecarregado (overloaded) se simultaneamente denota duas ou mais funes distintas. Em geral, sobrecarga (overloading) aceitvel somente quando cada chamada de funo no ambgua, isto , quando cada funo pode ser identificada usando-se somente as informaes de tipos disponveis dos parmetros. Por exemplo, em C++ pode-se declarar duas funes com o mesmo nome abs: int abs(int); float abs(float) Pode-se ento escrever: x = abs (2); y = abs (3.14); A primeira instruo chamara abs(int), e a segunda abs(float) - o compilador C++ sabe qual funo abs deve usar porque 2 um int e 3.14 um float. Overloading reduz o numero de indentificadores de funes que um programador deve memorizar, e tambm elimina nomes artificiais para as funes. Deve-se notar que, ao contrrio de outras linguagens, como por exemplo Ada, C++ considera somente o nmero e o tipo dos argumentos de forma a determinar qual funo ser utilizada. Sendo assim, no se pode sobrecarregar float sqr(float) e double sqr(float), por exemplo. 2.3.5 Funes com Argumentos Default A especificao da classe BigInt contm as seguintes declaraes de funo: BigInt (unsigned n = 0) // funcao construtora void print (FILE* f = stdout); // funo de impressao Estas declaraes demandam explicaes adicionais. Alm da declarao do tipo dos argumentos, estas instrues declaram argumentos default. Se voc chamar uma destas funes sem argumento, C++ utiliza a expresso do lado direito do sinal = como default. Por exemplo, a chamada de funo print() idntica a chamada print(stdout). Ao se especificar defaults convenientes para as funes pode-se abreviar as chamadas de funes e torn-las mais legveis. Tambm se reduz o nmero de funes que se necessita escrever. Os argumentos defaults de uma funo devero ser os argumentos mais a direita em uma determinada funo. Assim, a declarao unsigned Maximo (unsigned a, unsigned b, unsigned c=0, unsigned d=0); legal, mas as declaraes unsigned Maximo (unsigned a, unsigned b=0, unsigned c); unsigned Maximo (unsigned a=0, unsigned b, unsigned c);

16

no so. 2.3.6 Chamando Funes Membros Considere agora a penltima linha do programa do exemplo 2: c.print(); Funes membros so chamadas de uma maneira anloga a forma como variveis membros so acessadas em C, isto , utilizando-se o operador . ou o operador ->. Uma vez que c uma instncia da classe BigInt, a notao c.print() chama a funo membro print da classe BigInt para imprimir o valor atual de c. Se ns declarssemos um ponteiro para um BigInt BigInt* p; p = &c; ento a notao p->print() chamaria a mesma funo. Esta notao assegura que uma funo membro pode operar somente sobre instncias da classe para a qual esta foi definida. Em C++, vrias classes diferentes podem ter funes membros com o mesmo nome. 2.3.7 Construtores Uma das coisas que o compilador C++ necessita saber sobre o tipo abstrato BigInt como criar novas instncias da classe BigInt. Pode-se informar ao compilador C++ como isto deve ser feito definindo-se uma ou mais funes membros especiais chamadas de construtores. Uma funo construtora possui o mesmo nome de sua classe. Quando o programa cliente contm uma declarao como, BigInt a = "123"; O compilador C++ reserva espao para as variveis membros digitos e ndigitos para uma instncia da classe BigInt e ento chama a funo construtora a.BigInt("123"). responsabilidade do implementador da classe BigInt escrever a funo BigInt() de modo que ela inicialize as instncias corretamente. Neste exemplo, BigInt("123") aloca trs bytes para memria dinmica, ajusta a.digitos para apontar para esta memria, modifica os trs bytes para [3,2,1], e atribui o valor trs para a.ndigitos. Isto criar uma instncia da classe BigInt que inicializada com o valor 123. Se uma classe tem uma ou mais funes construtoras (funes construtoras podem ser sobrecarregadas), C++ garante que uma funo ser chamada para inicializar cada instncia da classe. Um usurio de uma classe, tal como BigInt, no tem de se lembrar de chamar uma funo de inicializao separadamente para cada BigInt declarado. Uma classe pode ter um construtor que no requer nenhum argumento, ou pode ter uma funo construtora com defaults para todos os seus argumentos. Ns

17

chamamos esta funo de construtor default. Este o construtor que C++ chama para inicializar a varivel b mostrada abaixo: BigInt b; C++ tambm utiliza o construtor default para inicializar os elementos individuais de um vetor de instncias de classe, por exemplo: BigInt a[10]; C++ chamar o construtor default da classe BigInt 10 vezes, uma vez para a[0], a[1], ..a[9]. Pode-se tambm escrever a declarao: BigInt a = "123"; usando uma lista de argumentos, como a seguir: BigInt a ("123"); // mesmo que BigInt a = "123";

De fato, deve-se utilizar uma lista de argumentos para declarar uma instncia de uma classe que demande mais de um argumento. Por exemplo, se ns tivssemos um classe BigComplex que tivesse o seguinte construtor: BigComplex (BigInt re, BigInt im); ns poderamos declarar uma instncia da classe BigComplex chamada de x e inicializ-la para (12,34) atravs de: BigComplex x(12,34); 2.3.8 Construtores e Converso de Tipos Para compilar o exemplo 2, o compilador C++ necessita saber como converter uma string de caracteres como "25123654789456", ou um inteiro como 47, para um BigInt. Construtores tambm so usados com esse propsito. Quando o compilador C++ encontra uma declarao como: BigInt c = a + b + 47; ele reconhece que um int (47) deve ser convertido para um BigInt antes que a adio se realize. Sendo assim, ele necessita verificar se um construtor BigInt (unsigned) est declarado. Se este existir, uma instncia temporria da classe BigInt ser criada atravs de uma chamada a este construtor com o argumento 47. Se o construtor apropriado no est declarado, o compilador sinalizar um erro. A classe BigInt possui dois construtores BigInt(char*), BigInt (unsigned), de modo que se pode utilizar strings de caracteres ou inteiros, e o compilador C++ automaticamente chamar o construtor apropriado para realizar a converso de tipos.

18

Pode-se tambm chamar o construtor explicitamente para forar a converso de tipo. Por exemplo, C++ processaria as declaraes: unsigned long i = 4000000000; BigInt c = i + 1000000000; adicionando-se i a constante 1000000000, e ento convertendo o resultado para um BigInt de forma a inicializar c. Infelizmente, a soma de i e 1000000000 provavelmente causar um erro de overflow. Pode-se, contudo, evitar este problema convertendo-se explicitamente i ou 1000000000 para um BigInt: BigInt c = BigInt (i) + 1000000000; ou BigInt c = BigInt (1000000000) + i; Pode-se tambm escrever isto utilizando o cast de C e C++: BigInt c = (BigInt) i + 1000000000; ou BigInt c = i + (BigInt) 1000000000; A notao de chamada do construtor mais indicativa do que est ocorrendo do que a notao cast, e tambm mais geral desde que permite que se utilize construtores que requerem mltiplos argumentos. 2.3.9 Construtores e Inicializao Compilar o exemplo 2 tambm requer que o compilador C++ saiba como inicializar um BigInt com o valor de outro BigInt, por exemplo: BigInt c = a + b + 47; A instncia BigInt c deve ser inicializada com o valor da instncia temporria da classe BigInt que contm o valor da expresso a + b + 47. C++ fornece um construtor pr-definido que copia objetos na inicializao. Deve-se, contudo, tomar cuidado ao utilizar este construtor pr-definido quando as instncias da classe alocam dinamicamente algum dos seus elementos. Isto decorre do fato dele fazer cpia bitwise de um objeto sobre o outro, no copiando os elementos alocados dinamicamente. Tal fato pode gerar, portanto, sinonmias e efeitos colaterais. Pode-se controlar como C++ inicializa instncias da classe BigInt definido-se uma funo construtora de copia BigInt (const BigInt&)2. No exemplo, este construtor aloca memria para uma nova instncia da classe e faz uma cpia do contedo da instncia passada como argumento. Quando se programa em C++, importante entender a distino entre inicializao e atribuio. Inicializao ocorre em trs contextos:
O argumento de BigInt& um exemplo de uma referncia em C++, que ser posteriormente descrita.
2

19

declaraes com inicializadores (como descrito acima); argumentos formais de funes; valores de retorno de funes; Atribuies ocorrem em expresses (no em declaraes) que utilizam o operador =. Por exemplo, BigInt c = a + b + 47; uma chamada ao construtor BigInt (BigInt&) inicializa a varivel c. Contudo em: BigInt c; c = a + b + 47; uma chamada ao construtor BigInt (unsigned n=0) inicializa a varivel c para zero, e a segunda instruo atribui o valor da varivel temporria BigInt a c. 2.3.10 Overloading de Operadores C++ tambm deve ser capaz de adicionar dois BigInts e atribuir o valor a um novo BigInt afim de compilar o Exemplo 2. Pode-se definir funes membros chamadas de add e assign para fazer isto, contudo, esta soluo tornaria a escrita de expresses aritmticas deselegantes. C++ permite a definio de significados adicionais para muitos de seus operadores, incluindo + e =, sendo assim estes podem significar "add" e "assign quando aplicados a BigInts. Isto conhecido como overloading de operadores, e similar ao conceito de overloading de funes. Realmente, muitos programadores j esto familiarizados com esta idia porque os operadores de muitas linguagens de programao, incluindo C, j so sobrecarregados. Por exemplo, em C: int a,b,c; float x,y,z; c = a + b; z = x + z; Os operadores + e = realizam coisa diferentes nas duas ltimas instrues: a primeira faz adio e atribuio de inteiros e a segunda faz adio e atribuio de pontos flutuante. Overloading de operadores simplesmente uma extenso disto. C++ reconhece um nome de funo tendo a forma operator@ como um overloading do smbolo @. Pode-se sobrecarregar os operadores + e = declarando-se as funes membros operator+ e operator=, como foi feito na classe BigInt: void operator= (const BigInt&); BigInt operator+(const BigInt&) const; // atribuio // operador adio

Pode-se chamar estas funes utilizando-se a notao usual para chamada de funes membros utilizando-se apenas o operador:

20

BigInt a,b,c; c = operator+(b); c=a+b As duas ltimas linhas so equivalentes. Deve-se ressaltar que a sobrecarga dos operadores no muda os significados pr-definidos, apenas d um significado adicional quando utilizado com instncias da classe. A expresso 2 + 2 continua dando 4. A precedncia dos operadores tambm no alterada. Pode-se sobrecarregar qualquer operador de C++, exceto o operador de seleo (.) e o operador de expresso condicional (?:). A utilizao da sobrecarga de operadores recomendada apenas quando o operador sugere fortemente a funo que este executa. Um exemplo de como sobrecarga NO deve ser usada sobrecarregar o operador + para executar uma subtrao. 2.3.11 Destrutores O compilador C++ necessita saber, ainda, como destruir as instncias da classe BigInt. Pode-se informar ao compilador como fazer isto definido-se um outro tipo especial de funo chamada destrutor. Uma funo destrutora tem o mesmo nome de sua classe prefixado pelo caracter ~. Para a classe BigInt, esta a funo membro ~BigInt(). Deve-se escrever a funo destrutora de uma forma que esta finalize apropriadamente instncias da classe. No exemplo apresentado, isto significa liberar a memria dinmica alocada para a instncia da classe pelo construtor. Se uma classe tem uma funo destrutora, C++ garante que ela ser chamada para finalizar toda instncia da classe quando esta no for mais necessria. Isto significa dizer que o usurio no necessita chamar explicitamente o destrutor da classe, o sistema se encarrega de fazer isto, eliminando assim uma fonte possvel de erros de programao. Diferente dos construtores, os destrutores no tm nenhum argumento, e assim no podem ser sobrecarregados. Por conseguinte, uma classe s possui um nico destrutor.

21

Captulo III - Exemplo de Implementao

3.1 Introduo
O captulo anterior descreve um programa cliente em C++ que utiliza a classe BigInt e a especificao desta classe. O presente captulo ir descrever a implementao da classe BigInt, onde todo o trabalho real feito. Ao se descrever a implementao da classe, vrias caractersticas novas de C++ sero encontradas e explicadas: o operador de resoluo de escopo, tipos constantes, funes membros constantes, referncias, os operadores new e delete, funes friend e funes inline. Como foi dito anteriormente a implementao de uma classe contm os detalhes de como ela trabalha.

3.2 Implementao de uma Classe BigInt


A implementao requer as informaes contidas na especificao, sendo assim a primeira linha no arquivo BigInt.cpp : #include "BigInt.h";

22

Uma vez que a implementao e os programas clientes so compilados com a mesma especificao, o compilador C++ assegura uma interface consistente entre elas. 3.2.1 O construtor BigInt(const char*) A classe BigInt tem trs construtores, um para declarar instncias de um BigInt a partir de string de caracteres, um para criar instncias de um inteiro no negativo (um unsigned) e um para inicializar um BigInt com outro. Aqui est a implementao do primeiro construtor:

BigInt:: BigInt (const char* digitString) { unsigned n = strlen (digitString); if (n!=0) { digitos = new char [ndigitos = n ]; char * p = digitos; const char* q = &digitString[n]; while (n--) *p++ = *--q - '0'; // converte o digito ASCII para binario } else { // string vazia digitos = new char[ndigitos = 1]; digitos[0] = 0; } } Se a string vazia esta tratada como um caso especial e cria-se um BigInt inicializado para zero. 3.2.2 O Operador Resoluo de Escopo A notao BigInt::BigInt identifica BigInt como uma funo membro da classe BigInt, isto se deve ao fato de que vrias classes podem ter funes membros com o mesmo nome. O operador :: conhecido como operador de resoluo de escopo, e pode ser aplicado a funes membros e variveis membros. Logo, BigInt::print() refere-se a funo membro print() que membro da classe BigInt, e BigInt::digitos refere-se a varivel membro digitos que membro da classe BigInt.

23

3.2.3 Constantes Um programador C estar familiarizado com o uso do tipo char* para argumentos que so strings de caracteres, mas o que uma const char*? Em C++, pode-se usar a palavra reservada const para indicar que uma varivel uma constante, e portanto no pode ser modificada por um comando de atribuio (=). Quando utilizado em uma lista de argumentos como acima, isto previne que o argumento possa ser modificado pela funo. Sendo assim, se tentarmos adicionar a seguinte instruo: digitString[0] = 'x'; ao construtor, o compilador C++ emitir uma mensagem de erro. Isto evita erros de programao muito comuns. Recomenda-se a utilizao de const para argumentos ponteiros (referncias) que no devem ser modificados pelas funes. Deve-se tambm empregar const ao invs de se utilizar a diretiva de pr processamento #define quando se deseja definir constantes simblicas: const double PI = 3.1415926; A utilizao de const permite ao compilador verificar o tipo do valor constante, alm de permitir colocar a definio do identificador constante em uma classe, bloco ou funo. Deve-se notar que const restringe a maneira pela qual um objeto pode ser usado. Quando se utiliza um ponteiro, dois objetos esto envolvidos; o prprio ponteiro e o objeto apontado. Quando se deseja que o objeto apontado, mas no o ponteiro, fique inalterado, deve-se prefixar a declarao do ponteiro com a palavra reservada const. Por exemplo: const char* pc = "Nome"; pc[3] = 'a'; pc = "teste" // ponteiro para uma constante // erro // OK

Para declarar um ponteiro constante, ao invs do objeto apontado, o operador *const deve ser utilizado. Por exemplo: char *const cp = "Nome"; cp[3] = 'a'; cp = "Teste"; // ponteiro constante // OK // erro

Para fazer ambos objetos constantes eles devem ser declarados const. Por exemplo: const char *const cp = "Nome"; // ponteiro constante para objeto constante cp [3] = 'a'; // erro

24

cp = "Teste";

// erro

O endereo de uma constante no pode ser atribudo a um ponteiro irrestrito (um ponteiro que no aponta para um objeto constante) porque isto permitiria que o valor do objeto fosse mudado. Por exemplo: int a = 1; const int c = 2; const int* p1 = &c; const int *p2 = &a; int* p3 = &c; *p3 = 7;

// OK // OK // erro, pois // mudaria o valor de c

3.2.4 Funes Membros Constantes Na declarao da classe BigInt, mostrada na seo 2.3, pode-se notar que a palavra reservada const aparece depois da lista de argumentos na declarao das funes membros operator+() e print(). BigInt operator+(const BigInt&) const; // operador adicao void print (FILE* f = stdout) const; // funcao de impressao O que isto significa? Na seo 2.3.6 foi explicado que funes membros so aplicveis a instncias (objetos) de sua classe utilizando os operadores . e ->. Por exemplo: BigInt c = "29979250000"; // velocidade da luz (cm/s) c.print(); BigInt* cp = &c; cp-> print(); A palavra reservada const depois da lista de argumentos de uma funo membro informa ao compilador que a funo membro uma funo membro constante: uma funo membro que no modifica o objeto sobre a qual aplicada, de modo que esta funo pode ser aplicada a objetos constantes: const BigInt c = "29979250000"; // velocidade da luz (cm/s) c.print(); const BigInt* cp = &c; cp-> print(); O compilador C++ diria que estas chamadas a print() so ilegais caso se omitisse a palavra reservada const depois da lista de argumentos na declarao e definio de print(). Alm disso, o compilador C++ tambm verifica se uma funo membro constante no modifica nenhuma das variveis membros das instncias da classe. Sendo assim, por exemplo, se a definio da funo membro BigInt::print() contivesse a instruo: ndigitos--;

25

o compilador indicaria um erro, desde que isto muda o valor de uma das variveis membros da classe BigInt.

3.2.5 O Operador New Utiliza-se o operador new para alocuo de memria dinmica, necessria, por exemplo para manter os dgitos de um BigInt. Em C, para fazer isto, utilizaria-se a funo padro da biblioteca C, chamada malloc(). O operador new tem duas vantagens: Ele retorna um ponteiro para o tipo de dado apropriado. Enquanto que, para se alocar espao para as variveis membros de uma struct BigInt em C, teramos que escrever: struct BigInt* p; p = (struct BigInt*) malloc(sizeof (struct BigInt)); em C++, ns podemos escrever simplesmente: BigInt* p; p = new BigInt; A segunda vantagem que ao se utilizar o operador new para alocar uma instncia de uma classe, o construtor desta classe automaticamente chamado de modo a inicializar o objeto. 3.2.6 Declarao em Blocos Programadores em C podem ter observado que a declarao de p parece estar mal colocada em if (n!=0) { digitos = new char[ndigitos = n]; char* p = digitos; // uma instruo // uma declarao !!!!!!

uma vez que esta aparece depois da primeira instruo do bloco. Em C++, uma declarao pode aparecer em qualquer lugar dentro de um bloco desde que a varivel seja declarada antes de ser utilizada. A declarao efetiva at o fim do bloco em que ela ocorre. Pode-se frequentemente melhorar a legibilidade de um programa colocando-se as declaraes de variveis prximas aos lugares onde so empregadas. 3.2.7 O Construtor BigInt (unsigned) A implementao do construtor BigInt(unsigned), o qual cria um BigInt de um inteiro positivo, a seguinte:

26

BigInt:: BigInt(unsigned n) { char d[3*sizeof(unsigned)+1]; // buffer para digitos decimais char *dp = d; // ponteiro para o proximo digito decimal ndigitos = 0; do { // converte inteiros para digitos decimais *dp++ = n%10; n /= 10; ndigitos++; } while (n > 0); digitos = new char[ndigitos]; // aloca espaco para digitos decimais for (register i=0; n<ndigitos, i++) digitos[i] = d[i]; } Este construtor trabalha convertendo um argumento inteiro para dgitos decimais no array temporrio d. Sabe-se, ento, quanto espao alocar para um BigInt, e possvel, portanto, alocar a memria dinmica utilizando-se o operador new, e copiar os dgitos decimais do array temporrio para esta rea. 3.2.8 O Construtor Cpia A funo do construtor cpia copiar o valor de um BigInt passado como argumento para uma nova instncia de classe BigInt: BigInt::BigInt(const BigInt& n) { unsigned i = n.ndigitos; digitos = new char[ndigitos = i]; char* p = digitos; char * q = n.digitos; while (i--) *p++ = *q++; } importante observar o acesso a n.ndigitos dentro do construtor de cpia. Se n fosse um parmetro de outra classe diferente de BigInt, o acesso a estrutura privada de n seria ilegal. Contudo, como o parmetro da prpria classe BigInt, C++ permite que a sua estrutura interna seja acessada. Por conta disso, considera-se que C++ mais orientado a classes do que a objetos. Tambm cabe destacar que a funo construtora de cpia faz uso de uma referncia, uma importante caracterstica de C++ que ser descrita a seguir. 3.2.9 Referncias O parmetro formal da funo membro BigInt(const BigInt&) um exemplo de uma referncia em C++. Referncias em C++ objetivam superar a ausncia de mecanismos de passagem de parmetros por referncia em C.

27

Afim de entender o que isto significa, suponha que se deseja escrever uma funo chamada inc() que adiciona um ao seu argumento. Se esta funo fosse escrita em C, seria da seguinte forma: void inc (int x) { x++; } e fosse utilizada pelo seguinte segmento de programa

int y = 1; inc(y); printf("%d\n",y); o programa imprimiria um 1, e no 2. Isto porque em C o valor y copiado dentro do parmetro formal x e a instruo x++ incrementa esta cpia, deixando o valor de y inalterado. Este mecanismo de passagem de parmetros conhecido com passagem por valor. A funo inc() deve ser escrita em C da seguinte forma: void inc (int* x) { *x++; } e o segmento de cdigo que utiliza essa funo deve ser o seguinte: int y = 1; inc(&y); printf("%d\n",y); Note que trs modificaes devem ser feitas: o tipo do argumento da funo deve mudae de int para int*; cada ocorrncia do argumento no corpo da funo deve mudar de x para *x; cada chamada da funo deve mudar de inc(y) para inc(&y). Utilizando-se a passagem por referncia de C++, a funo inc() poderia ser escrita da seguinte forma: void inc(int& x) { x++; } e o segmento de cdigo que utiliza esta funo seria: int y = 1;

28

inc(y); printf("%d\n",y); A utilizao do mecanismo de passagem de parmetros por referncia simplifica, em muitos casos, a funo e a utilizao desta funo. Pode-se pensar em uma referncia como sendo um ponteiro que automaticamente derreferenciado quando utilizado. Pode-se declarar variveis que so referncias. Por exemplo: int i; int* p = &i; // um ponteiro para i int& r = i; // uma referencia para i r++; // incrementa o valor de i Voc pode utilizar r em lugar de *p e &r em lugar de p. As nicas diferenas so: Uma referncia deve ser inicializada em sua declarao. Uma referencia no pode ser apontada para um objeto diferente daquele para o qual ela foi iniciada. Sendo assim: int j, i; int* p = &i; int& r = i; *p = 1; j = *p; r = 2; j = r; p = &j; &r = &j; // um ponteiro para i // uma referencia para i // ajusta i para 1 // ajusta j para 1 // ajusta i para 2 // ajusta j para 2 // ajusta p para apontar para j // ilegal

Uma referncia um nome alternativo (apelido) para um objeto que proporciona uma sintaxe mais adequada e segura que a de ponteiros, contudo h situaes que demandam o emprego de ponteiros. 3.2.10 O Operador Adio da Classe BigInt A implementao da funo membro operator+() a seguinte:

29

BigInt BigInt::operator+(const BigInt& n) const { unsigned maxDigitos = (ndigitos>n.ndigitos ? ndigitos : n.ndigitos) + 1; char* sumPtr = new char [ maxDigitos ]; BigInt sum (sumPtr, maxDigitos); // deve-se definir este construtor unsigned i = maxDigitos - 1; unsigned carry = 0; while (i--) { *sumPtr = /* proximo digito de this */ + /* proximo digito de n */ + carry; if (*sumPtr >= 10) { carry = 1; *sumPtr = 10; } else carry = 0; sumPtr ++; } if (carry) // verifica se numero de digitos cresceu *sumPtr = 1; return sum; else { // elimina casa adicional i = maxDigitos - 1; char* p = new char [i]; char * q = sum.digitos; while (i--) *p++ = *q++; BigInt s (p, maxDigitos - 1); return s; } } A adio de dois BigInts feita utilizando o mtodo aprendido nas escolas de 1 grau; adiciona-se os dgitos de cada operando, da direita para esquerda, comeando do dgito mais a direita, e tambm somando-se um possvel transbordo (carry) da coluna anterior. H dois problemas quando se escreve a funo membro operator+(): Primeiro, h necessidade de se declarar uma instncia da classe BigInt chamada sum que conter o resultado da adio, o qual ser deixado em um array apontado por sumPtr. Deve-se utilizar um construtor para criar uma instncia de BigInt a partir de sumPtr, mas nenhum dos construtores disponveis at agora capaz de executar esta operao; sendo assim se faz necessrio escrever um outro construtor para tratar esta nova situao. Este novo construtor recebe como argumentos um array contendo os dgitos e os nmeros de dgitos do array e cria um objeto da classe BigInt a partir desses argumentos. Deve-se ressaltar que no conveniente que os programas clientes tenham acesso a essa funo dependente de implementao, sendo assim ns a declaramos na parte privada da classe BigInt, de modo que ela s possa ser acessada pelas funes membros da classe BigInt. Logo, a declarao:

30

BigInt(char* , unsigned); acrescentada antes da palavra public: na declarao da classe BigInt no arquivo BigInt.h, e a implementao deste construtor adicionada ao arquivo BigInt.cpp: BigInt:: BigInt(char* d, unsigned n) { digitos = d; ndigitos = n; } O segundo problema que a operao de percorrer os dgitos dos operandos na instruo *sumPtr = /* proximo digito de this */ + /* proximo digito de n */ + carry; pode se tornar complicada porque um dos operandos pode conter um nmero menor de dgitos que o outro. Neste caso deve-se complet-lo com zeros mais a esquerda. Este mesmo problema aparecer na implementao das operaes de subtrao, multiplicao e diviso, logo relevante encontrar uma soluo adequada para este problema. Uma boa soluo utilizar um tipo abstrato de dados ! Sendo assim, uma nova classe, chamada SeqDigitos, definida de forma a manter uma trilha de qual dgito em um certo BigInt est sendo processado. A classe SeqDigitos tem um construtor que recebe como um argumento uma referncia para um BigInt e inicializa uma SeqDigitos para esse BigInt. A classe SeqDigitos tambm tem uma funo membro que retorna o prximo dgito do BigInt cada vez que ela chamada, comeando no dgito mais a direita (menos significativo). A declarao da classe SeqDigitos e a implementao de suas funes membros so as seguintes: class SeqDigitos { char* dp; // ponteiro para o digito corrente unsigned nd; // numero de digitos restantes public: SeqDigitos (const BigInt& n ) // construtor { dp = n.digitos; nd = n.ndigitos; } unsigned operator++( ) // retorna o digito atual e avana para o // prximo digito { if (nd == 0) return 0; else { nd--; return *dp++; } } };

31

Pode-se agora declarar uma instncia da classe SeqDigitos para cada um dos operandos e utilizar o operador ++ quando se necessita ler o prximo dgito. Como esses dois problemas resolvidos, a implementao da funo membro operator+() a seguinte: BigInt BigInt::operator+(const BigInt& n ) const { unsigned maxdigitos = (ndigitos>n.ndigitos ? ndigitos : n.ndigitos) + 1; char* sumPtr = new char[maxdigitos]; BigInt sum (sumPtr, maxdigitos); // aloca memria para sum SeqDigitos a (*this); // mais sobre a varivel this posteriormente SeqDigitos b (n); unsigned i = maxdigitos - 1; unsigned carry = 0; while (i--) { *sumPtr = (a++) + (b++) + carry; if (*sumPtr >=10) { carry = 1; *sumPtr = 10; } else carry = 0; sumPtr++; } if (carry) // verifica se numero de digitos cresceu *sumPtr = 1; return sum; else { // elimina casa adicional i = maxDigitos - 1; char* p = new char [i]; char * q = sum.digitos; while (i--) *p++ = *q++; BigInt s (p, maxDigitos - 1); return s; } } Deve-se observar que ao sobrecarregar o operador ++ e -- para uma classe, C++ no faz nenhuma distino entre a forma pr-fixada e ps-fixada desses operadores. 3.2.11 Funes Friend Voc deve estar se perguntando como o construtor SeqDigitos(const BigInt&) capaz de acessar os membros privativos digitos e ndigitos da classe BigInt. De fato, isso no possvel. Sendo assim h a necessidade de se conceder acesso a essas variveis privativas funo construtoras da classe SeqDigitos, mas somente a essa funo e nenhuma outra. C++ proporciona uma maneira de fazer isto - tornando o construtor friend da classe BigInt, isto pode ser feito adicionando-se a seguinte declarao a especificao da classe BigInt:

32

friend SeqDigitos::SeqDigitos(const BigInt&); Pode-se tambm fazer todas as funes membros de uma classe friend de outra declarando um classe inteira como um friend. Por exemplo, pode-se tornar todas as funes membros da classe SeqDigitos friends da classe BigInt declarando-se na especificao da classe BigInt friend class SeqDigitos; 3.2.12 A Palavra Reservada This Retornando a implementao da funo BigInt::operator+(), observa-se a utilizao da varivel ponteiro this na seguinte declarao: SeqDigitos a (*this); C++ automaticamente declara uma varivel chamada de this em toda a funo membro de uma classe e a inicializa para apontar para a instncia da classe a qual a funo membro foi aplicada. Sendo assim, na funo membro operator+() da classe BigInt, this implicitamente declarada como: BigInt* this; e quando operator+() chamada, escrevendo-se uma expresso como b + 47, onde b um BigInt, this automaticamente inicializada para apontar para b. Logo, o efeito da declarao SeqDigitos a(*this) na funo operator+() criar uma instncia de SeqDigitos para o operando da esquerda do operator+(), neste caso b. Analogamente, quando uma funo membro tal como BigInt::print() chamada por uma expresso como c.print(), onde c um BigInt, this inicializado para apontar para c. 3.2.13 O Operador Atribuio da Classe BigInt O objetivo do operador atribuio (=) copiar o valor de um BigInt ( o argumento da direita) para outro (o argumento da esquerda). Embora o operador de atribuio seja similar ao construtor cpia BigInt(const BigInt&), h uma importante diferena: o construtor copia o valor para uma instncia no inicializada de BigInt, enquanto o operador atribuio copia o valor dentro de uma instncia inicializada de BigInt, isto , uma que j contm um valor. void BigInt::operator=(const BigInt& n) { if (this == &n) return; // para manipular x = x corretamente delete digitos; unsigned i = n.digitos; digitos = new char[n.digitos=i];

33

char* p = digitos; char* q = n.digitos; while (i--) *p++ = *q++; } Deve-se ressaltar que o corpo da funo operator=() idntico ao do construtor de cpia BigInt (const BigInt&), exceto as duas primeiras instrues. A primeira instruo verifica se o endereo do operando da esquerda o mesmo do operando da direita. Se for, nada necessita ser feito. Caso contrrio, a memria dinmica para os dgitos do operando da esquerda liberada, chamando-se o operador delete. 3.2.14 A Funo Membro BigInt::print() A implementao da funo membro constante print() simples e direta: void BigInt::print(FILE* f) const { for (int i = ndigitos-1, i>=0, i--) fprintf(f, "%d", digitos[i]); } 3.2.15 O Destrutor da Classe BigInt A nica coisa que a funo destrutora ~BigInt() da classe BigInt deve fazer liberar a memria dinmica alocada pelos construtores: BigInt::~BigInt() { delete digitos; } Isto feito utilizando-se o operador delete de C++, o qual, neste caso, libera a memria dinmica que apontada por digitos. Ao se utilizar o operador delete para desalocar uma instncia (objeto) de uma classe que tem uma funo destrutora definida, o destrutor automaticamente chamado para finalizar a instncia da classe antes que a memria seja liberada. Por exemplo: BigInt* a = new BigInt("9834567843"); //... delete a; automaticamente executar-se- a->~BigInt(), que desalocar a memria apontada por digitos antes de desalocar a memria para as variveis membros das instncias da classe BigInt. A funo destrutora deve ser chamada para cada elemento de um array quando este array destrudo. Isto feito implicitamente para arrays que no so alocados utilizando-se new. No entanto, isso no pode ser feito implicitamente para arrays alocados na memria dinmica porque o compilador no consegue determinar

34

se um ponteiro aponta para um objeto nico ou para o primeiro elemento do array de objetos. Por exemplo: void f() { BigInt* a = new BigInt; BigInt* b = new BigInt[10]; delete a; // OK delete b; // Problemas!!! } Neste caso deve-se especificar que b aponta para um array: void f() { BigInt* a = new BigInt; BigInt* b = new BigInt[10]; delete a; // OK delete[10] b; // OK!!! } Desta forma, sabe-se quantas instncias de classe o array contm, podendo assim chamar o destrutor para cada instncia do array. 3.2.16 Funes Inline A utilizao de funes membros pequenas comum quando se programa orientado a objetos. O emprego de tais funes, pode gerar uma considervel ineficincia devido ao custo de uma chamada de funo. Sendo assim, C++ permite que se declare uma funo como inline. Nesse caso, cada chamada de funo substituda por uma cpia da funo inteira, semelhante a expanso de macro. Isso elimina o custo de uma chamada de funo. H duas maneiras de se definir uma funo como sendo inline: funes inline definidas dentro da especificao da classe - Quando uma funo membro definida dentro da prpria classe, o compilador faz uma chamada em linha dessa funo toda vez que o compilador a chamar. Por exemplo, o destrutor da classe BigInt. funes inline definidas fora da especificao da classe - Para que uma funo membro seja inline sem ser definida dentro da classe, deve-se colocar a palavra reservada inline antes da definio da funo. Deve-se recomendar, contudo, um certo cuidado ao se utilizar funes inline. As funes candidatas a serem colocadas em linha so as que apresentam pouco cdigo e este cdigo tambm bastante simples.

35

36

Captulo IV - Conceitos de Programao Orientada a Objetos em C++

4.1 Introduo
As caractersticas mais interessantes da linguagem C++ so aquelas que suportam o estilo de Programao Orientada a Objetos (POO). Programao orientada a objetos uma tcnica de organizao de tipos abstratos de dados que explora as caractersticas em comum destes tipos de modo a reduzir o esforo de programao atravs da reutilizao de cdigo. Esse estilo de programao se caracteriza por: Encapsulamento Herana Amarrao Dinmica Nos captulos anteriores discutiu-se encapsulamento, que restringe os programas clientes a interagirem com as instncias da classe somente por meio de um conjunto bem definido de operaes: a interface (especificao) da classe. Isso evita que os programas clientes conheam detalhes de como uma classe representa os dados que ela contm e dos algoritmos que ela utiliza, ou seja, da implementao de uma classe. Isso faz com que as mudanas (corretas) que ocorram na implementao de uma classe no tenham nenhum efeito permissivo nos programas clientes desta classe, alm de facilitar o processo de depurao e manuteno porque o cdigo que lida com os dados de uma classe est localizado na prpria classe. Herana simplifica a tarefa de criao de uma nova classe que seja similar a uma classe existente, uma vez que permite ao programador expressar apenas as diferenas existentes entre a nova classe e a classe existente, ao invs de exigir que o programador crie uma classe a partir do comeo. Amarrao Dinmica, ou amarrao tardia, ajuda a fazer um programa cliente mais geral, ocultando as diferenas entre um grupo de classes que esto relacionadas entre si. Amarrao Dinmica permite que cada classe de um grupo de classes relacionadas tenha uma implementao diferente para uma funo particular. Programas clientes podem aplicar a operao a uma instncia de classe sem ter que considerar a classe especfica da instncia. Em tempo de execuo, determina-se a classe especfica da instncia e chama-se a implementao da funo para esta classe. Sendo assim, encapsulamento, herana e amarrao dinmica trabalham juntas: Encapsulamento assegura que os programas cliente interagem com os objetos somente por meio das funes membros.

37

Herana proporciona um meio de se expressar as diferenas entre as classes, mas permitindo o reuso do cdigo e das variveis membros para implementar as caractersticas similares entre elas. Amarrao Dinmica das funes usadas pelos programas clientes oculta as diferenas entre as classes expressas via herana. Foi visto anteriormente que C++ suporta encapsulamento atravs da definio dos atributos pblicos e privados de uma classe. C++ suporta herana por meio de classes derivadas e suporta amarrao dinmica por meio de funes virtuais. O presente captulo introduzir as caractersticas de C++ que suportam o estilo de programao orientada a objetos, em especial herana e amarrao dinmica. 4.1.1 Classes Derivadas O mecanismo de herana (B herda de A) associa classe que est sendo declarada (B): Uma representao inicial para os seus objetos (aquelas dos objetos de A); Um conjunto inicial de mtodos aplicveis aos objetos desta classe (aqueles da classe A). A nova declarao de classe pode conter representao e operaes adicionais, especializando estado e o comportamento de novos objetos a serem criados. Deve-se notar, contudo, que os mtodos iniciais so ainda aplicveis aos objetos desta nova classe. Esta visibilidade tem relao com o conceito de subtipo. Suponha uma classe Pessoa em C++, como declarada abaixo: class Pessoa { char* nome; int idade; public Pessoa ( char*auxnome, int a) {nome=auxnome; idade=a;} void MudarIdade (int a) {idade=a;} virtual void Print ( ); }; Suponha tambm que se necessite representar empregados em um programa. Uma vez que todo o empregado basicamente uma pessoa pode-se tomar vantagem da declarao existente para pessoa, declarando-se uma nova classe Empregado derivada da classe Pessoa: class Empregado: public Pessoa { };

38

Com esta declarao pode-se criar instncias de Empregado (objetos): Empregado e1, e2; Contudo, nada foi modificado at agora, uma vez que empregados so exatamente iguais a pessoas, tanto na estrutura de dados quanto no comportamento. Isso se deve ao fato de que nenhuma declarao adicional foi acrescentada localmente a Empregado. Obviamente faz-se necessrio refinar a classe Pessoa de maneira a representar um empregado. Isto pode ser feito adicionando-se novas estruturas de dados e operaes dentro da classe Empregado, como mostrado abaixo: class Empregado : public Pessoa { float salario; public: Empregado(char *auxnome, int auidade, float auxsal) : Pessoa (auxnome, auxidade) { salario=auxsal;} void MudarSalario (float r) { salario = r; } void Print (); }; Com essa nova declarao da classe Empregado, o mecanismo de herana estabelece que: todo empregado tem a seguinte estrutura de dados: nome, idade e salario. todo empregado pode receber as seguintes operaes: MudarIdade, MudarSalario, Print. Pode-se notar que uma operao para mudar a idade de um empregado no necessita ser redeclarada na classe Empregado; o mtodo MudarIdade na classe Pessoa serve perfeitamente para mudar a idade de um empregado. Sendo assim, pode-se escrever: e1.MudarIdade (56); mudando a idade de e1 para 56 anos. Uma classe derivada herda todos os atributos (variveis membros e funes) da classe base. Pode-se, contudo, diferenciar a classe derivada de sua classe base de trs formas: adicionando variveis membros; adicionando funes membros; redeclarando funes membros herdadas da classe base. Uma classe base pode ter mais de uma classe derivada, uma classe derivada pode, por sua vez, servir como classe base para outras classes derivadas. Uma classe derivada normalmente mais especializada que sua classe base. 4.1.2 Redeclarao - Funes Membros Virtuais

39

Uma outra caracterstica relacionada com o mecanismo de herana a habilidade de redeclarar operaes na subclasse. Atravs da redeclarao ns objetivamos a criao, na subclasse, de uma nova operao com a mesma interface (nome, parmetros e resultado) que uma operao na superclasse. Se uma operao da superclasse redeclarada em uma subclasse, ento a nova operao visvel para os usurios da subclasse. Contudo, a operao da superclasse tambm aplicvel dentro da subclasse desde que o nome da subclasse preceda o nome da operao (em caso contrrio, uma chamada recursiva ocorrer) . A operao redeclarada tem a habilidade de manipular a representao de dados inteira da subclasse, chamando a operao homnima da superclasse para lidar com a representao comum, e incluindo declaraes para lidar com a representao adicional. Por exemplo: void Empregado::Print ( ) { Pessoa :: Print ( ); cout salario \n; } A palavra chave virtual em C++ indica que a funo Print da classe Pessoa pode ter diferentes verses para diferentes classe derivadas. Quando um programa cliente aplica uma funo virtual a um objeto, C++ automaticamente determina a que classe o objeto pertence, em tempo de execuo, e transfere o controle para a implementao correta da funo. Isso ocorre mesmo quando o programa cliente trata o objeto como uma instncia da classe base. Deve-se ressaltar que uma funo virtual deve ser definida na primeira classe onde ela declarada. Uma classe base com mltiplas classes derivadas, alm de permitir a eliminao de cdigo redundante, tambm cria uma oportunidade de se fazer programa clientes mais gerais.

4.2 Um Exemplo de Aplicao


Nesta seo ser desenvolvido um programa grfico simples que desenha algumas figuras geomtricas em duas dimenses. Este programa utilizar os conceitos de encapsulamento, herana e amarrao dinmica. 4.2.1 Classe Ponto Esse exemplo utiliza um tipo abstrato de dados, chamado de classe Ponto, para executar operaes sob pares de coordenadas (x, y): // Hierarquia de classes geometria class Ponto{ int xc, yc; public: Ponto ( ) Ponto (int novox, int novoy) int x ( ) const int x (int novox) // coordenadas x, y {xc = yc = 0;} {xc = novox; yc = novoy} {return xc;} {return xc = novox;}

40

int y ( ) const {return xy;} int y (int novoy) {return yc = novoy;} Ponto operator+ (const Ponto&p) const { return Ponto (xc + p.xc, yc + p.yc); } void operator+= (const Ponto & p) { xc += p.x ( ); yc += p.y ( ); } void printOn() const { cout << "( " << xc << ", " << yc << " )"; } } ostream& operator<< (ostream& strm, const Ponto& p) { return strm << "( " << p.x() << ", " << p.y() << " )"; } A classe Ponto no utiliza nenhuma caracterstica de C++ que no tenha sido discutida anteriormente. Para que se possa utilizar o operador << para objetos da classe Ponto necessrio sobrecarreg-lo. Maiores detalhes de como sobrecarregar o operador << sero discutidos posteriormente. 4.2.2 Classe Linha e Classe Circulo O exemplo geomtrico requer algumas classes para representar vrias formas geomtricas, como linha, retngulo, tringulo e crculo. Todas essas classes tm algumas operaes em comum, como por exemplo, desenhar( ) e mover( ), e todas tm uma varivel membro, org, que contm as coordenadas de origem da forma geomtrica. Pode-se comear escrevendo-se declaraes representativas para as classes Linha e Circulo, como por exemplo:

class Linha{ Ponto org; Ponto p; public:

// origem // ponto de fim

Linha (const Ponto & a, const Ponto & b): org (a.x(),a.y()), p(b.x(),b.y()) {} void mover (const Ponto & d); // move linha de um montante d void desenhar() const ; // desenha uma linha de org ate p }; class Circulo {

41

Ponto org; int raio; public:

// origem // raio do circulo

Circulo(const Ponto & c, int r): org (c.x(),c.y()) { raio = r;} void mover(const Ponto & d); // move um circulo com centro org void desenhar( ) const; // desenha um circulo com centro org e raio r }; 4.2.3 Instncias de Classe como Variveis Membros de uma Classe Pode-se ver, dos exemplos acima, que uma instncia de uma classe pode ser uma varivel membro de outra classe: as variveis membro Linha::org, Linha::p, Circulo::org so instncias da classe Ponto. Quando isto ocorre, o construtor da classe pode necessitar utilizar uma notao especial, para inicializar as variveis membros que aparecem como instncias de uma classe. Entre a lista de argumentos da funo e o corpo da funo, escreve-se o nome das variveis membros que se deseja inicializar, seguido por uma lista de argumentos que se deseja passar para os construtores das variveis membros: Linha (const Ponto& a, const Ponto& b ): org (a.x(),a.y()), p (b.x(),b.y()) {} Circulo (const Ponto& c, int r): org (c.x(),c.y()) {raio = r;} O construtor de cada uma das variveis membros processado antes que as instrues no corpo do construtor da classe seja executado. Pode-se tambm escrever: Circulo (const Ponto & c, int r) { org = c; raio = r; } mas provvel que isto no seja to eficiente. Relembre que, se uma classe declara um ou mais construtores, ento C++ garante que um deles ser chamado para inicializar toda instncia da classe. Relembre tambm que a atribuio org = c assume que org j est inicializado. 4.2.4 A Funo Membro mover() As funes membros Linha::mover() e Circulo::mover() aceitam um Ponto como argumento, representando um deslocamento (x, y), e ento os adiciona as variveis membros: void Linha::mover(const Ponto& d) { org += d; p += d; }

42

void Circulo::mover(const Ponto& d) { org += d; } 4.2.5 A Funo Membro desenhar() As implementaes das funes membros das classes Linha e Circulos esto completas a menos para as funes Linha::desenhar e Circulo::desenhar(). Afim de manter estes exemplos simples, os detalhes de como se desenhar estas formas em um dispositivo grfico particular ser omitido, sendo assim a implementao apenas imprimir o tipo da forma e suas coordenadas: void Linha::desenhar() const { cout << "Linha de " << org << "at" << p << "\n"; } void Circulo::desenhar() const { cout << "Circulo com centro em " << org << " e raio de " << raio << "\n"; } Seria til ter um tipo abstrato de dados chamado de Quadro que seria uma coleo de Linhas, Triangulos, Retangulos e Circulos. Tambm seria til ser capaz de desenhar() e mover() os quadros. Seria extremamente elegante se a classe Quadro fosse geral, e no contivesse nenhuma meno a formas especficas. Desta maneira, poderia se introduzir um nova forma, por exemplo trapzio, sem que haja necessidade de se alterar a classe Quadro. Pode-se fazer isto definindo-se um classe base Forma com classes derivadas Linha, Triangulo, e assim sucessivamente, como mostrado na figura 5.
Forma

Linha

Triangulo

Circulo

Quadro

Figura 5 - Hierarquia de Exemplo

A primeira verso da classe Forma declara funes, tais como desenhar() e mover(), como sendo funes virtuais. Estas funes podem ser aplicadas a qualquer tipo de forma e, so implementadas para que escrevam uma mensagem de erro se chamadas: class Forma {

43

public: virtual void mover (const Ponto&); virtual void desenhar() const; }; void Forma::mover (const Ponto& p) { cout << "Esqueceu de implementar mover()! \n"; exit (1); } void Forma::desenhar () const { cout << "Esqueceu de implementar desenhar()! \n"; exit (1); } Deve-se mudar as declaraes da classe Linha, Circulo e, assim sucessivamente para faz-las classes derivadas (herdeiras) da classe Forma, isto feito adicionando-se o nome da classe base Forma, s declaraes das classes derivadas: class Linha: public Forma { Ponto org; // origem Ponto p; // ponto de fim public: Linha (const Ponto& a, const Ponto& b): org (a.x(), a.y()), p(b.x(), b.y()) {} virtual void mover (const Ponto& d); // mover linha um montante d virtual void desenhar () const; // desenha uma linha de org ate p };

class Circulo: public Forma { Ponto org; // origem int raio; // raio do circulo public: Circulo (const Ponto& c, int r): org(c.x(), c.y()) { raio = r; } virtual void mover (const Ponto& d); // move um circulo de um montante d virtual void desenhar() const; // desenha um circulo com centro org // e raio raio }; Utilizando-se da palavra reservada public (especificador de acessabilidade) antes do nome da classe base torna os membros pblicos da classe base membros pblicos da classe derivada, de modo que os clientes da classe derivada podem ter acesso a eles. A omisso da palavra reservada public torna os membros pblicos da classe base membros privados da classe derivada, e, portanto, inacessveis aos clientes da classe derivada.

44

A palavra reservada virtual tambm adicionada s declaraes das funes desenhar() e mover() nas classes derivadas, mas no h motivo para mudar a implementao destas funes3. 4.2.6 Classe Quadro Pode-se agora escrever a classe Quadro para lidar somente com a classe Forma e no suas classes derivadas Linha, Triangulo, Retangulo e Circulo, etc. Podese representar um Quadro como um array contendo ponteiros para as suas formas componentes. Pode-se, tambm, implementar Quadro::desenhar(), por exemplo, simplesmente chamando-se desenhar() para cada forma no quadro: const unsigned CAPACIDADE_QUADRO = 100; class Quadro: public Forma { Ponto org; Forma* s[CAPACIDADE_QUADRO]; int n; public: Quadro() {n = 0;} Quadro(Ponto& o): org (o.x(), o.y()) void adicionar (Forma&); virtual void mover (const Ponto& d); virtual void desenhar() const; }; // origem // ponteiro de array para formas // numero de formas neste quadro // construtor {n = 0;} // construtor // adicionar forma a quadro // mover quadro // desenhar quadro

void Quadro::adicionar(Forma & t) { if (n == CAPACIDADE_QUADRO) { cerr << "Capacidade do quadro excedida\n"; exit(1); } s[n++] = &t; // adicionar ponteiro para forma a quadro } void Quadro::mover(const Ponto& d) { org += d; for (int i = 0; i < n; i++) s[i]->mover(d); } void Quadro::desenhar() const // desenhar um Quadro { for (int i = 0; i<n; i++) s[i]->desenhar(); }

A palavra reservada virtual requerida somente nas declaraes das funes na classe base. Pode-se omitir a palavra virtual nas classes derivadas - contudo considera-se um bom estilo de programao, porque torna possvel identificar quais funes so virtuais sem ter que olhar as declaraes na classe base.
3

45

Uma vez que desenhar() uma funo virtual, C++ tem o cuidado de determinar a classe especfica de cada componente Forma quando o programa executado e ento chama a implementao apropriada de desenhar() para cada classe. Isto chamado de amarrao dinmica, em contraste com a chamada de funes novirtuais, as quais empregam amarrao esttica. Por exemplo, se em tempo de execuo s[i] aponta para uma instncia da classe Linha, ento a instruo s[i]->desenhar() chama Linha::desenhar(); se s[i] aponta para uma instncia de um Circulo, chama-se Circulo::desenhar(), e assim sucessivamente. Considere o seguinte programa exemplo: main() { Linha l(Ponto(1,2), Ponto(3,4)); Circulo c(Ponto(5,6),1); l.desenhar(); c.desenhar(); Quadro q; l.mover(Ponto(1,0)); q.adicionar(l); c.mover(Ponto(1,0)); q.adicionar(c); q.desenhar(); q.mover(Ponto(10,10)); q.desenhar(); } Quando se executa este programa, o resultado obtido : Linha de (1,2) ate (3,4) Circulo com centro em (5,6) e raio 1 Linha de (2,2) ate (4,4) Circulo com centro em (6,6) e raio 1 Linha de (12,12) ate (14,14) Circulo com centro em (16,16) e raio 1 Amarrao dinmica de uma chamada de funo virtual ocorre somente quando a funo virtual aplicada a um ponteiro ou referncia a um objeto, como em Quadro::desenhar(), e no quando aplicada diretamente a um objeto: void desenharIsto(Forma& referencia) { Linha linha(Ponto(1,2), Ponto(3,4)); Forma* ponteiro = &linha; //... ponteiro->desenhar(); // amarrado dinamicamente referencia.desenhar(); // amarrado dinamicamente linha.desenhar(); // amarrado estaticamente } Em tempo de execuo, as variveis ponteiro e referncia podem apontar para uma instncia de qualquer classe derivada de Forma. Sendo assim, C++ utiliza // cria uma linha // cria um circulo // desenha uma linha // desenha um circulo //cria um quadro vazio // move a linha // adiciona a linha ao quadro // move o circulo // adiciona o circulo ao quadro // desenha o quadro // translada o quadro para (10,10) // desenha o quadro novamente

46

amarrao dinmica para determinar qual implementao de desenhar deve chamar. No exemplo, a varivel linha uma instncia da classe Linha, portanto, C++ estaticamente amarra a chamada de linha.desenhar() a Linha::desenhar() e evita a sobrecarga da amarrao dinmica. 4.2.7 Compatibilidade de Tipos Porque o compilador C++ no acusa erro de compatibilidade de tipos nas seguintes chamadas de funes: q.adicionar(l) e q.adicionar(c), uma vez que a declarao para Quadro.adicionar() especifica que esta funo requer um argumento do tipo Forma&, e o argumento passado para estas duas chamadas de funes so do tipo Linha e Circulo, respectivamente. C++ permite isto porque Linha e Circulo so classes derivadas da classe Forma. Em geral, C++ permite que uma referncia ou ponteiro para uma instncia de uma classe derivada seja utilizada em lugar de uma referncia ou ponteiro para a sua classe base sem que seja necessrio uma converso de tipos explcita (cast). Pode-se, portanto, entender uma classe derivada como sendo um subtipo de sua classe base. Subtipos so teis e no comprometem a segurana porque classes derivadas herdam todas as variveis membros e funes de sua classe base; sendo assim, uma referncia a qualquer varivel membro ou chamada de funo membro de uma instncia da classe base est bem definida para uma instncia da classe derivada. Pode-se pensar em Linha e Circulo como sendo espcies diferentes (subtipos) de Formas. O oposto, contudo, no verdade: C++ no permite que uma referncia ou um ponteiro para uma instncia de uma classe base seja utilizada em lugar de referncia ou ponteiro para uma instncia de uma classe derivada. Isto reflete o fato de que uma Linha ou Circulo uma espcie de Forma, mas uma Forma no um tipo de Linha ou Circulo. 4.2.8 Classe Incompletas - Funes Virtuais Puras Classes tambm podem ser incompletas. Isto ocorre quando uma ou mais operaes so declaradas em uma classe, mas no so implementadas. A implementao destas operaes deixada para as classes dependentes. Isto conveniente para disciplinar a construo de classes: para cada operao incompleta em uma superclasse, todas as subclasses herdeiras desta superclasse devem implementar ou virtualizar esta operao. Classes incompletas no modelam objetos, mesmo se uma ou mais de suas operaes est completamente declarada. Afim de exemplificar a convenincia das operaes virtuais puras, considere novamente a classe Forma descrita anteriormente. Pode-se transformar esta classe em uma classe incompleta da seguinte forma: class Forma { public: virtual void mover(const Ponto&) =0; virtual void desenhar() const =0; };

47

O inicializador =0 nas declaraes de mover() e desenhar() indicam ao compilador C++ que estas so funes virtuais puras: funes virtuais que a classe base no implementa, e que, portanto, devem ser definidas pelas classes derivadas. H duas consequncias de se declarar um funo virtual pura na classe base: O compilador verifica cada classe derivada para assegurar que elas implementam a funo virtual pura da classe base ou adiam sua implementao (declarando-a novamente como funo virtual pura a ser implementada pelas suas classes derivadas). No se pode criar instncias de uma classe incompleta. Sendo assim, a seguinte declarao ilegal: Forma f; As vantagens da utilizao de declaraes incompletas podem ser resumidas da seguinte forma:

Melhoria da organizao hierrquica, atravs do encapsulamento de propriedades


na raiz da estrutura;

Uma maior disciplina na programao, j que fora o comportamento necessrio


nas classes descendentes; genrico para os objetos.

Incentiva o uso de polimorfismo, permitindo um comportamento mais abstrato e


Deve-se ressaltar que nem todas as classes bases so classes abstratas. Por exemplo, pode-se implementar uma classe desenhando arcos com uma classe derivada da classe Circulo: class Arco : public Circulo { float inicio, fim; // angulo de inicio e de fim do arco public: Arco(const Ponto& c, int r, float a1, float a2) : Circulo(c,r) { inicio = a1; fim = a2; } virtual void desenhar(); }; Instncias da classe Circulo so ainda teis, apesar de se utilizar esta classe como classe base da classe Arco. O desenvolvimento da classe Arco tambm ilustra como uma hierarquia de classes pode crescer atravs do processo de especializao das classes existentes.

4.3 O Exemplo Geomtrico Melhorado


O prximo passo no desenvolvimento de nosso exemplo de programa grfico consiste em tirar proveito do fato de que todas as classes derivadas de Forma possuem uma varivel membro comum, org. Sendo assim, pode-se mover esta varivel membro das classes derivadas para a classe base Forma. A funo membro
48

Forma::mover() pode agora ajustar org, logo ela no mais uma funo virtual pura. A classe base Forma agora a seguinte: class Forma { Ponto org; public: Forma(const Ponto& p): org(p.x(), p.y()) {} Ponto origem() const { return org; } virtual void mover(const Ponto& d) { org += d;} virtual void desenhar() const =0; }; // origem

Uma vez que a classe Forma tem agora uma varivel membro, pode-se prover um construtor para inicializ-la e a funo membro origem() para ler o seu valor. Utilizando-se esta definio da classe Forma pode-se simplificar as classes derivadas Linha, Circulo e Quadro, uma vez que elas herdam as funes membros origem(), mover(), desenhar() e a varivel org da classe Forma:

class Linha: public Forma { Ponto p; // ponto de fim public: Linha (const Ponto& a, const Ponto& b): Forma(a), p(b.x(), b.y()) {} virtual void mover(const Ponto&); // mover linha de um montante d virtual void desenhar() const; // desenha uma linha de org ate p }; void Linha::mover(const Ponto& d) { Forma::mover(d); p+= d; } void Linha::desenhar() const { cout << Linha de << origem() << ate << p << \n; } classe Circulo: public Forma { int raio; // raio do circulo public: Circulo(const Ponto& c, int r): Forma (c) { raio = r } virtual void desenhar() const; // desenha um circulo com // centro org e raio raio }; void Circulo::desenhar() const {

49

cout << Circulo com centro em << origem() << e raio de << raio << \n; } const unsigned CAPACIDADE_QUADRO = 100; class Quadro: public Forma { Forma* s[CAPACIDADE_QUADRO]; // array de ponteiros para formas int n; // numero de formas neste quadro public: Quadro(): Forma(Ponto(0,0)) { n = 0; } // construtor Quadro(Ponto& org): Forma (org) { n = 0;} // construtor void adicionar(Forma&); // adiciona Forma a Quadro virtual void mover(const Ponto&); // mover quadro de um montante d virtual void desenhar() const; // desenhar quadro; };

void Quadro::adicionar(Forma& t) { if (n == CAPACIDADE_QUADRO) { cerr << Capacidade do quadro excedida \n ; exit(1); } s[n++] = &t; // adiciona ponteiro para Forma a Quadro } void Quadro::mover(const Ponto& d) { Forma::mover(d); for (int i = 0; i < n; i++) s[i]->mover(d); } void Quadro::desenhar() const { for (int i=0; i<n; i++) s[i]->desenhar(); } Deve-se observar que enquanto a classe Circulo pode simplesmente herdar Forma::mover(), a classe Linha no pode uma vez que esta deve tambm ajustar o outro ponto extremo p quando da operao mover. 4.3.1 Inicializao de Objetos Os construtores da classe Linha, Circulo e Quadro ilustram como uma classe derivada pode passar argumentos para um construtor de sua classe base. Para tal, deve-se utilizar a seguinte sintaxe:

50

Nome_Classe_Derivada (parmetros da classe base, parmetros da classe derivada4): Nome_Classe_Base(parmetros da classe base), Construtores_Membros (parmetros para construtores dos membros) { //... definio da funo //... }

Por exemplo: Linha (const Ponto& a, const Ponto&b) : org(a.x(), a.y()), p(b.x(), b.y()) {} Circulo (const Ponto& c,int r) : Forma (c) { raio = r } Quadro (): Forma (Ponto(0,0)) {n = 0 ;} Quadro (Ponto& org): Forma (org) {n = 0 ;} As regras para se determinar a ordem pela qual C++ inicializa as instncias da classe base e variveis membros em um objeto so um pouco complexas. A ordem de inicializao no caso onde as classes tm, no mximo, uma nica classe base a seguinte: 1. a classe base, se existir alguma; 2. as variveis membros, na ordem em que so declaradas na declarao de classe, no na ordem em que elas aparecem na lista de parmetros do construtor. O que complica o fato de que C++ aplica estas regras recursivamente; por exemplo, se a classe base tem uma classe base, C++ inicializa a classe base da classe base e qualquer membro que esta possa conter antes de inicializar a classe base. O exemplo seguinte mostra a ordem pela qual os construtores so chamados durante a inicializao de um objeto complexo: #include <iostream.h> class X { int i; public: X(const char* s) X() };

{ cout << s << ;} { cout << X::X() ;}

class A { X a1; X a2; public: A(const char* s) : a2(A::a2) }; class B: public A{ X b1; X b2; public:
4

{cout << s << ;}

A ordem dos parmetros na realidade no importa.

51

B(const char* s): b2(B::b2), b1(B::b1), A(B::A) {cout << s << ;} }; int initCi() { cout << C::i; return 0; } int& initCr() { static int n = 1; cout << C::r; return n; } class C: public B { cont int i; int& r; X c1; X c2; public: C(const char* s): B(C::B), c1(C::c1), r(initCr()), i (initCi()), c2(C::c2) { cout << s << \n; } }; main() { C c(c); } A sada deste programa : X::X() A::a2 B::A B::b1 B::b2 C::B C::i C::r C::c1 C::c2 c Tente demonstrar como as regras produzem estes resultados. Este exemplo tambm mostra como inicializar variveis membros constantes e referncias (C::i e C::r neste caso) - eles devem ser inicializados pela lista de inicializao do construtor. 4.3.2 Finalizao de Objetos A regra que C++ utiliza para determinar a ordem pela qual destrutores so chamados simples: destrutores so chamados na ordem inversa dos construtores. Isto explica porque C++ ignora a ordem dos membros na lista de inicializao e utiliza a ordem dos membros na declarao de classe: uma classe pode ter vrios construtores, e cada um destes construtores poderia ordenar os membros em sua lista de inicializao de uma maneira diferente. No momento de destruir um objeto, o destrutor no saber qual construtor foi utilizado para criar o objeto. Utilizando-se a

52

ordem dos membros na declarao de classe evita-se este problema, permitindo-se que os destrutores das variveis membros sejam chamados na ordem inversa, sem ter que considerar qual construtor criou o objeto.

53

Captulo V - Conceitos Complementares de Programao em C++

5.1 Introduo
Agora que voc j conhece os principais conceitos de programao na linguagem C++, vamos apresentar alguns conceitos adicionais desta linguagem de programao. Embora estes conceitos no sejam parte do ncleo bsico da linguagem, os tpicos abordados neste captulo final definem caractersticas complementares de C++ que facilitam o entendimento de programas e permitem a construo de programas mais flexveis.

5.2 Construtor Pr-Definido de Cpia, Operador Pr-definido de Atribuio e Construtor Pr-Definido Default
Ao se construir uma nova classe, importante identificar se nenhuma das variveis membro tm, como seus valores, objetos dinmicos (isto , objetos criados atravs do uso de alocao dinmica). Se for este o caso, no necessrio definir um construtor de cpia e um operador de atribuio especficos para esta classe, visto que C++ pr-define um construtor de cpia e um operador de atribuio que vlido para todas as classes. O construtor pr-definido de cpia realiza a cpia membro a membro das variveis do objeto que ser copiado para as variveis do objeto que ser a cpia. Da mesma maneira, o operador pr-definido de atribuio realiza a atribuio membro a membro das variveis do objeto que ser atribudo para as variveis do objeto que recebe a atribuio. Se a classe no envolve objetos dinmicos, pode-se garantir que a cpia e a atribuio de objetos ser feita de maneira apropriada. O mesmo no pode ser dito quando a classe envolve objetos dinmicos. Neste caso, a utilizao do construtor e do operador pr-definido pode gerar sinonmias e efeitos colaterais. Recomenda-se neste caso, portanto, que sejam definidos um construtor de cpia e um operador de atribuio especficos para a classe em questo. Por exemplo, a classe Ponto, apresentada no captulo IV, no requer que sejam definidos o construtor de cpia e o operador de atribuio, visto que suas variveis membros xc e yc so do tipo inteiro. Por outro lado, a classe BigInt, requer que eles sejam definidos porque a varivel membro digitos tem como valor um ponteiro para um vetor de dgitos alocado dinmicamente. Ao se construir uma classe possvel deixar de especificar construtores para ela. Isto ocorre porque, quando no existem construtores para uma classe, o

54

compilador criar um construtor default para ela. Deste modo, o cdigo seguinte funciona; //: C06:AutoDefaultConstructor.cpp // construtor default gerado automaticamente class V { int i; // private }; // sem construtor int main() { V v, v2[10]; } ///:~ Contudo, se ao menos um construtor da classe for definido, o compilador no criar o construtor default e as instncias de V acima gerariam erros em tempo de compilao. importante tomar cuidado com o uso do construtor default pr-definido porque ele no assegura nenhum tipo de ao especial (por exemplo, inicializar as variveis membro numricas com zero). Portanto, se voc quer inicializar variveis membro com zero, voc deve fazer isso por sua prpria conta escrevendo o construtor default explicitamente. Como vimos nesta seo, o compilador C++ pode fornecer construtores prdefinidos e operadores de atribuio pr-definidos para as suas classes. Contudo, muitas vezes, o comportamento destes construtores e operadores no so os que voc deseja. Deste modo, esta caracterstica de C++ deve ser encarada como uma rede de segurana, mas no deve ser amplamente utilizada. Em geral, trata-se de boa poltica definir os contrutores explicitamente e no deixar que o compilador faa isto por voc.

5.3 Objetos Temporrios


Em algumas situaes, necessrio ou conveniente que o compilador de C++ crie objetos temporrios. A criao destes objetos dependente da implementao do compilador. Sempre que um objeto temporrio criado, o construtor desta classe deve ser chamado. Da mesma maneira, o destrutor da classe deve ser chamado quando o objeto temporrio eliminado. Por exemplo, class X { // public: X(int); X(const X&); ~X();
};

X f(X); void g() { X b = f(X(2)); } Um objeto temporrio pode ser criado para abrigar o resultado da chamada ao construtor X(2). Posteriormente, este objeto ser passado como argumento para f.

55

Uma vez que o objeto tenha sido passado como argumento, o destrutor j pode ser chamado.

5.4 Retorno de Objetos em Funes


Objetos podem ser retornados por funes. Sempre que isto ocorre, o construtor de cpia (definido pelo usurio ou o pr-definido) chamado para criar o objeto que ser retornado. Pode-se usar no comando return objetos cujo tipo podem ser convertidos (de forma pr-definida ou atravs do uso de construtores) para a classe do objeto que deve ser retornado. Por exemplo, na funo f abaixo, o valor 0 ser implicitamente convertido num BigInt, antes de ser retornado, atravs do uso de um construtor da classe BigInt. BigInt f() { return 0; } Objetos tambm podem ser retornados atravs do uso de ponteiros e referncias para o objeto. Nestes casos, importante tomar cuidado para no retornar ponteiros e referncias para variveis locais, como acontece nos exemplos a seguir: Ponto* f () { Ponto a; // return &a; } Ponto& f () { Ponto a; // return a; }

// erro

// erro

O retorno de referncias objetiva permitir o uso de funes no lado esquerdo de atribuies, no acesso seletivo de variveis compostas ou em atribuies mltiplas. Como o operador de atribuio da classe BigInt retorna void, no permitido fazer atribuies mltiplas de BigInts. Logo, o cdigo a seguir incorreto: BigInt c = 243; BigInt a, b; a = b = c;

// erro

Para que a atribuio acima seja permitida, necessrio alterar a declarao e definio do operador atribuio de modo a retonar uma referncia a BigInt, como mostrado abaixo:

BigInt& BigInt::operator=(const BigInt& n) { if (this == &n) return *this; // para manipular x = x corretamente delete digitos;

56

unsigned i = n.digitos; digitos = new char[n.digitos=i]; char* p = digitos; char* q = n.digitos; while (i--) *p++ = *q++; return *this; }

5.5 Especificadores de Acesso


Como j tivemos a oportunidade de ver, C++ permite que se controle o acesso a variveis e funes membros de uma classe atravs dos especificadores public, private e protected. Quando um membro private, seu nome pode ser apenas usado pelas funes membro e friends da classe na qual ele declarado. Quando ele public, seu nome pode ser usado por qualquer funo. Quando ele protected, seu nome s pode ser usado or funes membro e friends da classe na qual ele declarado, e por funes membro e friends de classes derivadas desta classe. C++ tambm permite que se especifique se uma classe herda os membros da classe base de maneira pblica ou privada. Quando se usa o especificador de acesso public, os membros pblicos da classe base so membros pblicos da classe derivada e os membros protegidos da classe base so membros protegidos da classe derivada. Quando se usa o especificador de acesso private, os membros pblicos e protegidos da classe base so membros privados da classe derivada. Se o especificador de acesso omitido, assume-se por default que se est usando o especificador de acesso private. Os seguintes exemplos ilustram como estes especificadores so utilizados. class B { int a; protected: int b; public: int c; void f(); }; void B::f() { a = 10; b = 10; c = 10; } B x; int k = x.a; k = x.b; k = x.c; x.f(); // ok // ok // ok

// erro // erro // ok // ok

57

class C: private B { public: void g(); }; void C::g() { a = 10; b = 10; c = 10; } C y; k = y.a; k = y.b; k = y.c; y.f(); y.g(); class D: public B { public: void h(); }; void D::h() { a = 10; b = 10; c = 10; } D w; k = w.a; k = w.b; k = w.c; w.f(); w.h(); class E: public C { public: void i(); }; // erro // ok // ok // erro // ok // ok

// erro // erro // erro // erro // ok

// erro // erro // ok // ok // ok

void E::i() { a = 10; b = 10; c = 10; } E z; k = z.a;

// erro // erro // erro

// erro
58

k = z.b; k = z.c; z.f(); z.g(); z.i(); class F: public D { public: void j(); }; void F::j() { a = 10; b = 10; c = 10; } F v; k = v.a; k = v.b; k = v.c; v.f(); v.h(); v.j();

// erro // erro // erro // ok // ok

// erro // ok // ok

// erro // erro // ok // ok // ok // ok

5.6 Estruturas Internas a Classes


C++ possibilita que se criem estruturas e classes declaradas e visveis apenas internamente a uma classe. A razo de se permitir este tipo de declarao que pode ser necessrio criar estruturas auxiliares para implementar outras classes. Normalmente, no faz sentido que estas estruturas sejam visveis para outros clientes, uma vez que ela especficamente relacionada com a classe para o qual foi criada. Por exemplo, na implementao de uma classe lista encadeada, necessrio criar uma estrutura n da lista. Esta estrutura no deve ser pblica, visto que ela est intimamente relacionado com o uso da classe lista.

class lista { struct no { no* prox; int info; } no* prim; public: lista () { prim = 0 }; void insere_inicio (int); void insere_fim (int);

59

int remove (); int vazia (); }; No exemplo acima a struct no s visvel internamente s funes membro da classe lista. Nenhuma outra funo ou classe pode utilizar a estrutura n. A combinao deste mecanismo com a herana de classe atravs do especificador de acesso private abre novas possibilidades para a implementao de classes e permite diferenciar herana de propriedades do conceito de subtipos. Se uma classe um subtipo de outra, todas as propriedades pblicas da classe base devem ser pblicas da classe derivada. Se apenas desejamos reutilizar cdigo de uma classe sem que a nova classe seja considerada um subtipo, o uso do especificador de acesso private permite que definamos quais das propriedades de uma classe devem ser pblicas. Por exemplo, a classe pilha no um subtipo classe lista, pois nem todas operaes de lista so operaes de pilha. Contudo, podemos reusar operaes de lista para implementar uma pilha. class pilha: private lista { public: pilha() {} void empilha (int valor) { lista::insere_inicio(int valor); } int pop() { return lista::remove_inicio(); } lista::vazia; };

5.7 Herana Mltipla


Herana Mltipla permite que uma classe derivada seja herdeira de duas ou mais classes bases imediatas, ou seja, filha de mais de uma classe. A sintaxe da declarao de uma classe estendida de forma a permitir uma lista de classes bases e seus respectivos modificadores de acessabilidade. Por exemplo, considere a hierarquia da Figura 6:

Apartamento

Garagem

AptoComGaragem Figura 6 - Exemplo de Herana Mltipla

60

Uma possvel implementao desta hierarquia em C++ a seguinte: class Apartamento { protected: float area; unsigned qtde_quartos; public: Apartamento(float, unsigned); void mostrar(); friend void procura(unsigned, unsigned); }; class Garagem { protected: unsigned qtde_carros; public: Garagem(unsigned); void mostrar(); friend void procura(unsigned, unsigned); }; class AptoComGaragem: Apartamento, Garagem { public: AptoComGaragem(float, unsigned, unsigned); void mostrar(); }; // Herana Mltipla

/************** Funcoes da Classe Apartamento **************/ Apartamento::Apartamento(float metragem, unsigned dormitorios) { area = metragem; qtde_quartos = dormitorios; }

void Apartamento::mostrar() { cout << \n\n\t Numero de Dormitorios : << qtde_quartos << \n\t Area do Apartamento : << setprecision(2) << area << metros quadrados ; } /************** Funcoes da Classe Garagem **************/ Garagem::Garagem(unsigned automoveis) { qtde_carros = automoveis; } void Garagem::mostrar() { cout << Capacidade para << qtde_carros;

61

if (qtde_carros > 1) cout << Automoveis else cout << Automovel ; } /************** Funo da Classe AptoComGaragem **************/ AptoComGaragem::AptoComGaragem(float m2, unsigned td, unsigned ta) : Apartamento (m2,td), Garagem (ta) {} void AptoComGaragem::mostrar() { Apartamento::mostrar(); cout << \n\t Garagem com ; Garagem::mostrar(); } Neste exemplo, a classe AptoComGaragem herda os membros das classes Apartamento e Garagem. Ao se criar uma instncia da classe AptoComGaragem atravs da declarao AptoComGaragem:: AptoComGaragem apt1202(130,4,2); o construtor da classe derivada chamado. Antes de executar seu cdigo, esta funo chama os construtores das classes bases Apartamento e Garagem, nesta ordem, em sua definio: AptoComGaragem:: AptoComGaragem(float m2, unsigned td, unsigned ta) : Apartamento(m2, td), Garagem (ta) {} 5.7.1 Ambigidades em Herana Mltipla Herana Mltipla conceitualmente direta, mas ela introduz algumas complexidades prticas nas linguagens de programao. Existem dois problemas relacionados a ambigidade em Herana Mltipla: conflito entre nomes de classes bases diferentes e herana repetida. Conflitos ocorrem quando duas ou mais classes bases possuem membros homnimos. Em C++, tais conflitos devem ser resolvidos com uma qualificao explcita. Por exemplo: class Surf { float notas[10]; public: virtual void ordena(); } class Vela { float tempos[10]; public: virtual void ordena(); }

62

class WindSurf : public Surf, public Vela { char competicao; }; Caso um objeto da classe WindSurf fosse criado e tentasse utilizar a funo ordena() de uma classe base, haveria uma ambigidade detectada pelo compilador. int main() { //... WindSurf corrida; corrida.ordena(); //... }

// erro de ambiguidade

Para evitar esse erro, deve-se especificar qual classe base est sendo referenciada pelo objeto da classe derivada. No exemplo, o tipo de competio que identificar a classe base adequada. Sendo assim, pode-se eliminar a ambigidade redefinindo-se a operao de ordenao na classe derivada WindSurf e utilizando-se o operador de resoluo de escopo :: para acessar as operaes nas classes bases. class WindSurf : public Surf, public Vela { char competicao; public: virtual void ordena(); } void WindSurf::ordena() { if (competicao == E ) Surf::ordena(); else Vela::ordena(); } Herana repetida ocorre quando uma classe herdeira de duas ou mais classes que, por sua vez, so herdeiras de uma classe base comum. Neste caso os atributos da classe base comum sero herdados duplamente pela classe em questo. Este fato pode ser observado na figura a seguir:

63

base d1 base d2 base

mi d1 base d2 base

Alm de duplicar a memria utilizada para armazenar objetos da classe mi, a herana repetida pode provocar ambigidade. Veja no exemplo seguinte: //: C06:MultipleInheritance1.cpp #include "../purge.h" #include <iostream> #include <vector> using namespace std; class MBase { public: virtual char* vf() const = 0; virtual ~MBase() {} }; class D1 : public MBase { public: char* vf() const { return "D1"; } }; class D2 : public MBase { public: char* vf() const { return "D2"; } }; class MI : public D1, public D2 {}; // erro devido a ambiguidade em vf int main() { vector<MBase*> b; b.push_back(new D1); b.push_back(new D2); b.push_back(new mi); // nao sabe qual Mbase herdado usar for(int i = 0; i < b.size(); i++) cout << b[i]->vf() << endl; purge(b);
64

} ///:~ Para contornar o primeiro erro, o programador deve disambigar a funo vf() atravs de sua redefinio na classe mi. C++ fornece um mecanismo especial, atravs da palavra virtual para resolver o segundo problema. Se uma classe herdada precedida por um virtual somente um subobjeto daquela classe compor o objeto da classe herdeira, independentemente se a classe herdada mltiplas vezes ou no. //: C06:MultipleInheritance2.cpp // Virtual base classes #include "../purge.h" #include <iostream> #include <vector> using namespace std; class MBase { public: virtual char* vf() const = 0; virtual ~MBase() {} }; class D1 : virtual public MBase { // so herda MBase uma unica vez public: char* vf() const { return "D1"; } }; class D2 : virtual public MBase { // so herda MBase uma unica vez public: char* vf() const { return "D2"; } }; // DEVE disambiguar vf() explicitamente class MI : public D1, public D2 { public: char* vf() const { return D1::vf();} }; int main() { vector<MBase*> b; b.push_back(new D1); b.push_back(new D2); b.push_back(new MI); // OK for(int i = 0; i < b.size(); i++) cout << b[i]->vf() << endl; purge(b); } ///:~

5.8 Funes e Classes Genricas


Existem situaes em programao onde importante parametrizar uma determinada funo ou classe pelo tipo do objeto que ela manipula. Por exemplo, um mesmo algoritmo de ordenao aplicado independentemente do fato do tipo do elemento a ordenar ser inteiro, real, string ou mesmo um tipo composto. Da mesma maneira, a estrutura e operaes de uma classe lista independem do tipo dos elementos que compem a lista.
65

Em muitas linguagens de programao, o programador forado a copiar (ou reescrever) o cdigo destas funes e classes para cada um dos tipos desejados. Este procedimento costuma ser redundante, visto que o cdigo eminentemente o mesmo, e provocar erros, visto que durante a atualizao do cdigo copiado necessrio fazer pequenas modificaes para ajustar a estrutura de dados e as operaes ao novo tipo. C++ fornece um mecanismo chamado template para tratar esses casos. Este mecanismo permite parametrizar uma funo ou classe pelo tipo do elemento que ela manipula. Por exemplo, a funo a seguir recebe dois parmetros de um tipo no especificado e retorna o que tem maior valor. #include <iostream.h> template <class T> T max (T p, T s) { return p > s ? p : s; } Para utilizar esta funo, basta cham-la passando como parmetros elementos de um mesmo tipo, visto que p e s so do tipo genrico class T. importante notar que, para a funo funcionar apropriadamente, tambm necessrio que o tipo do elemento que foi passado como parmetro possua a comparao < como uma de suas operaes, visto que a funo vai necessitar comparar elementos deste tipo. O exemplo a seguir mostra como esta funo pode ser utilizada. main() { int c, a = 1, b = 2; char f, d = 'a', e = 'b'; c = max (a, b); f = max (d, e); cout << " results: " << c << " - " << f << "\n"; } O mesmo mecanismo pode ser usado para criar uma classe genrica. O exemplo a seguir mostra a implementao de uma classe genrica que define um tipo pilha. template <class T, int tam> class pilha { T vet[tam]; int max; int top; public: pilha(void); int vazia (void) { return top == -1; } T topo (void) { return vet[top]; } void empilha (T); void desempilha (void); }; template <class T, int tam> void pilha<T, tam>::pilha(void) {

66

max = tam - 1; top = -1; } template <class T, int tam> void pilha<T, tam>::empilha (T e){ if (top == max) { cout << "pilha ja esta cheia\n"; } else { vet[++top] = e; } } template <class T, int tam> void pilha<T, tam>::desempilha (void){ if (top == -1) { cout << "pilha ja esta vazia\n"; } else { top--; } } Esta implementao do tipo pilha utiliza um vetor para armazenar os valores da pilha. Como pode ser observado, a classe no especifica qual o tipo do elemento da pilha. Isso s definido quando da criao de um objeto desta classe. Este exemplo mostra ainda que a classe tambm parametrizada por um inteiro que determina o tamanho mximo do vetor usado na pilha. Este valor s definido no momento em que algum objeto da classe pilha criado. O exemplo a seguir mostra o uso dessa classe na criao de uma pilha de int com tamanho mximo igual a trs e uma pilha de char com tamanho mximo igual a 2: #include "iostream.h" #include "cgen.h" void main () { pilha <int, 3> x; pilha <char, 2> y; x.empilha (1); x.empilha (2); x.empilha (3); x.empilha (4); x.desempilha( ); cout << x.topo( ) << "\n" ; y.desempilha( ); if (y.vazia( )) y.empilha('a'); y.empilha('b'); cout << y.topo( ) << "\n"; } Note que, embora a pilha acima tenha sido usado com os tipos primitivos int e char, nada impede que ela seja tambm usada com um objeto de uma classe definida pelo programador. Outra observao interessante que, ao contrrio do que temos sempre visto, costuma-se colocar a especificao e a implementao de classes genricas no arquivo cabealho (.h). Isso ocorre devido a forma de implementao

67

dos templates que reutiliza cdigo fonte e no cdigo objeto. De fato, em alguns compiladores a separao de especificao e implementao de classes genricas provoca erro de ligao.

5.9 Namespaces
Um dos problemas com a programao em C, que quando programas atingem um certo tamanho fica cada vez mais difcil pensar em novos nomes para as entidades de computao globais. Pior ainda quando o programa dividido em pedaos, cada qual construdo e mantido por um programador diferente. Como C s possui uma nica regio onde todos os identificadores globais so declarados, isto frequentemente provoca colises de nomes. C++ padro inclui o mecanismo de namespaces para subdividir a regio de identificadores globais em pedaos mais gerenciveis. Cada conjunto de definies de C++ numa biblioteca ou programa embutida numa namespace, e se alguma outra definio tem um identificador idntico, mas numa outra namespace, ento no existe coliso. A criao de uma namespace muito similar a criao de uma classe: //: C10:Header1.h #ifndef HEADER1_H #define HEADER1_H namespace MyLib { extern int x; void f(); // ... } #endif // HEADER1_H ///:~ Contudo, ao contrrio de uma definio de classe, uma nova definio de uma namespace no implica numa redefinio da classe e sim numa continuao da definio anterior: //: C10:Header2.h #ifndef HEADER2_H #define HEADER2_H #include "Header1.h" // Adiciona mais entidades a MyLib namespace MyLib { // Nao eh redefinicao! extern int y; void g(); // ... } #endif // HEADER2_H ///:~ //: C10:Continuation.cpp #include "Header2.h" int main() {} ///:~

68

Uma namespace pode ser associada a um outro nome, de modo que o programador no tenha que usar nomes esquisitos ou grandes idealizados pelo criador da namespace. //: C10:BobsSuperDuperLibrary.cpp namespace BobsSuperDuperLibrary { class Widget { /* ... */ }; class Poppit { /* ... */ }; // ... } // Cria outro nome para a namespace pois e muito para teclar!! namespace Bob = BobsSuperDuperLibrary; int main() {} ///:~ O mecanismo de namespaces conveniente e til. Contudo, necessrio dar cincia da sua existncia aos programas antes que eles as utilizem. Incluir apenas um arquivo cabealho provavelmente ocasionar mensagens de erros estranhas do compilador indicando que ele no capaz de encontrar as declaraes de tens que voc acabou de incluir com o arquivo cabealho. C++ fornece a palavra reservada using para indicar que se quer usar as declaraes e definies de uma certa namespace. using namespace std; Toda a biblioteca padro C++ est embutida na namespace std. Isto significa que sempre que a frase acima for includa num programa, voc pode usar qualquer entidade pertencente a biblioteca padro includa. Existe uma correspondncia entre a forma padronizada de incluso de arquivos (sem o uso de .h), namespaces e a forma antiga (com o uso de .h): #include <iostream.h> o mesmo que #include <iostream> using namespace std;

5.10 Booleanos
Enquanto C no possui um tipo booleano, C++ padro resgata este tipo de dado, chamado de bool. Antes que este tipo se tornasse parte de C++, muitos programadores tendiam a usar diferentes tcnicas para produzir o efeito de booleanos. Isto produzia problemas de portabilidade e introduzia erros sutis. O tipo bool pode assumir dois valores: true e false. O valor true converte para um e o valor false converte para zero. Adicionalmente, alguns mecanismos de C++ foram adaptados ao tipo booleano. Agora, enquanto os operadores lgicos e relacionais retornam booleanos, as expresses de comandos condicionais convertem implicitamente seus argumentos para valores booleanos.

69

5.11 Strings
O uso de vetores de char para tratar strings pode requerer grande esforo de programao para lidar com situaes onde necessrio lidar com strings que possam aumentar de tamanho na medida da necessidade. A biblioteca padro de C++ inclui uma classe string que foi projetada para cuidar (e esconder) todas as operaes de gerenciamento de memria, cpia e concatenao de vetores de caracteres requeridas de um programador C. Essas manipulaes so fonte constante de erros e desperdcio de tempo. //: C02:HelloStrings.cpp // O basico da classe string de C++ padrao #include <string> #include <iostream> using namespace std; int main() { string s1, s2; string s3 = "Hello, World."; string s4("I am"); s2 = "Today"; s1 = s3 + " " + s4; if (s1 > s2) s1 += " 8 "; cout << s1 + s2 + "!" << endl; } ///:~

// strings vazias // Inicializada // Inicializada // Atribuicao // Concatenacao // Comparacao // Inclusao

O exemplo anterior mostra como strings podem ser criadas, inicializadas, atribudas, concatenadas e comparadas de uma forma direta, sem que o programador necessite saber como estas operaes so implementadas.

5.12 Streams
No seria interessante voc poder tratar todos os tipos de dispositivos de armazenamento de dados (entrada e sada padro, arquivos e blocos de memria) de uma maneira equivalente de modo a s manipul-los com uma nica interface de operaes? Esta a idia por detrs de iostreams. Elas so muito mais fceis, seguras e frequentemente eficientes do que as funes avulsas da biblioteca padro stdio de C. A biblioteca padro iostream geralmente a primeira que novos programadores C++ aprendem a usar. Isto ocorre porque este pacote define automaticamente objetos chamados cin e cout, que aceitam dados que devem ser lidos ou escritos na entrada e sada padro. Para enviar dados para a sada padro usa-se o operador sobrecarregado <<. Para receber dados da entrada padro usa-se o operador sobrecarregado >>. int i; cin >> i; float f; cin >> f; char c; cin >> c; char buf[100]; cin >> buf;

70

cout << "i = "; cout << i; cout << "\n"; cout << "f = "; cout << f; cout << "\n"; que o mesmo que

cout << "c = "; cout << c; cout << "\n"; cout << "buf = "; cout << buf; cout << "\n";

cin >> i >> f >> c >> buf; cout << "i = " << i << "\nf = "<< f << "\nc = "<< c << "\nbuf = " << buf << "\n"; importante ter em mente que o uso de cin apresenta as mesmas caractersticas da entrada com scanf em C, isto , o uso do delimitador espao para separar entradas e a possibilidade de ler strings alm do tamanho reservado para elas. Para ler strings que contenham brancos com segurana C++ fornece o mtodo getline, que permite ler uma linha inteira cin.getline(buf, sz); // terceiro parmetro o delimitador - default = \n C++ oferece, na biblioteca padro iostream, uma hierarquia de classes para manipulao do sistema de entrada e sada atravs de streams. A figura seguinte apresenta parte desta hierarquia. Uma stream em C++ um objeto que formata e armazena bytes e que corresponde a uma classe desta hierarquia. A classe raiz desta hierarquia ios, a qual contm dados e operaes para todas as classes derivadas. As classes istream e ostream fornecem respectivamente operaes bsicas de entrada e sada e so usadas como classes bases para as demais classes da hierarquia. As classes ifstream e ofstream so usadas para manipular arquivos. As classes istringstream e ostringstream para manipular strings. A classe fstream permite criar e manter arquivos que requeiram acesso de leitura e escrita. ios

istrea m istringstream ifstrea m

ostream

iostrea m

ofstream

ostringstream

fstream Para abrir arquivos para leitura e escrita em C++, necessrio incluir a biblioteca padro <fstream>. Para abrir um arquivo para leitura, necessrio criar um objeto da classe ifstream. Para abrir um arquivo para escrita, necessrio criar um objeto da

71

classe ofstream. Uma vez que voc tenha aberto o arquivo, pode-se ler ou escrever nele como se estivesse manipulando qualquer objeto da classe iostream. //: C02:Scopy.cpp // Copia um arquivo sobre outro, uma linha de cada vez #include <string> #include <fstream> using namespace std; int main() { ifstream in("Scopy.cpp"); // Abrir para leitura ofstream out("Scopy2.cpp"); // Abrir para escrita string s; while(getline(in, s)) // Discarta o caracter de nova linha out << s << "\n"; // ... adiciona de novo o caracter de nova linha } ///:~ Outro exemplo interessante copia um arquivo completo em uma string: //: C02:FillString.cpp #include <string> #include <iostream> #include <fstream> using namespace std; int main() { ifstream in("FillString.cpp"); string s, line; while(getline(in, line)) s += line + "\n"; cout << s; } ///:~ Por causa da natureza dinmica das strings, no necessrio se preocupar com a alocao de memria para a string.

5.13 Tratamento de Excees


Uma exceo um evento que ocorre durante a execuo de um programa que interrompe o fluxo normal das instrues. Muitos tipos de erros podem causar excees, tais como falha de hardware e tentativa de acesso a posies fora dos limites de um vetor. Mas excees no esto relacionadas exclusivamente com erros. Condies excepcionais, tal como a chegada ao fim de um arquivo tambm so excees. O tratamento de excees bastante direto em situaes onde uma determinada condio verificada e j se sabe exatamente o que se deve fazer porque toda a informao necessria est disponvel naquele contexto. Voc simplesmente as trata no ponto onde elas ocorrem. Esta postura tende a carregar o cdigo, comprometendo a legibilidade do programa.

72

Em situaes onde no existe informao disponvel no contexto onde a condio foi verificada, necessrio passar a informao local sobre a exceo para um contexto mais externo onde esta informao exista. Uma maneira de fazer isto retornando cdigos que representam as excees. Contudo, uma das razes pelas quais prtica comum ignorar condies de exceo em programao relacionado com o fato de ser extremamente tedioso e dispersante escrever cdigo que faa checagem extensiva de erros. A proliferao de cdigo para tratamento de erro, alm de ser extremamente cansativa, ainda dificulta em muito a legibilidade do programa. Uma das principais caractersticas de C++ o mecanismo de tratamento de excees, que objetiva tornar esta tarefa menos tediosa e deixar o cdigo do programa mais simples e claro. Com o mecanismo de tratamento de excees de C++: 1. O cdigo de tratamento de excees no fica misturado com o cdigo que cumpre as funcionalidades do programa. Voc escreve o cdigo que voc quer que execute e mais tarde, numa seo separada, voc escreve o cdigo para lidar com os problemas. Se voc faz mltiplas chamadas a uma funo voc trata os erros daquela funo um nica vez, em um nico local. 2. Excees no podem ser ignoradas. Se uma funo necessita enviar uma mensagem de erro para quem chamou aquela funo, ele envia um objeto representando aquele erro para fora da funo. Se quem chamou a funo no trata o erro, o objeto enviado para o prximo nvel de escopo at que alguma funo trate o erro. 5.13.1 Sinalizando uma Exceo Em C++, se voc encontra uma situao excepcional no seu cdigo, voc pode enviar informao sobre o contexto corrente para um contexto mais externo atravs da criao de um objeto contendo a informao e do seu envio para fora de seu contexto corrente. Isto se chama sinalizar uma exceo. O exemplo a seguir mostra um exemplo de como isso feito: throw meuErro ("algo de errado aconteceu"); O objeto meuErro criado especialmente para a sinalizao de excees. possvel utilizar qualquer tipo (inclusive os pr-definidos) para sinalizar excees. O comando indicado pela palavra reservada throw primeiramente constri um objeto que normalmente no existiria na execuo normal do programa. Este objeto posteriormente retornado pela funo onde o comando foi escrito. Ao contrrio do retorno de funes, onde o fluxo de execuo continua a partir do ponto onde a funo foi chamada, uma sinalizao de exceo pode desviar o fluxo para um ponto que pode estar bem distante de onde a funo foi chamada. Alm disso, apenas objetos que foram criados at o ponto onde a exceo sinalizada sero destrudos (ao contrrio de retorno de funes, que assume que todos os objetos no escopo devem ser destrudos). Obviamente, o objeto criado pela exceo tambm ser destrudo no ponto apropriado. Por fim, uma funo pode sinalizar vrias excees e criar objetos de tipos diferentes para cada uma delas tanto quanto for necessrio. Em regra geral, voc vai sinalizar um objeto de tipo diferente para cada tipo diferente de exceo.

73

Se voc est dentro de uma funo e uma exceo sinalizada (ou uma funo que foi chamada sinaliza uma exceo), esta funo ser encerrada durante o processo de sinalizao. Se voc no quer que isso ocorra, voc pode definir um bloco especial dentro da funo onde voc tenta realizar uma funcionalidade (e gerar excees potenciais). Para criar este bloco, deve-se usar a palavra reservada try: try { // Code that may generate exceptions } Se tivssemos de checar os erros sem usar tratadores de exceo, cada chamada de funo deveria ser envolvida com cdigo de teste, mesmo se a mesma funo fosse invocada vrias vezes. Com o try, todas as chamadas de funo so colocadas dentro de um bloco sem que seja necessria alguma checagem de erro. Isto quer dizer que seu cdigo mais fcil de escrever e ler porque o objetivo do seu cdigo no se confunde com a checagem de erros. 5.13.1 Capturando e Tratando uma Exceo Obviamente, a exceo sinalizada deve ser capturada e tratada em algum lugar. Este lugar chamado de tratador de exceo. Existe um tratador para cada tipo de exceo que se pode capturar. Tratadores de exceo seguem imediatamente o bloco try e so denotados pela palavra reservada catch: try { // codigo que pode sinalizar excecoes } catch (tipo1 id1) { // trata excecoes de tipo1 // throw; // repropaga a excecao } catch (tipo2 id2) { // trata excecoes de tipo2 } catch ( ) // trata excecoes de qualquer tipo - deve ser o ultimo } Cada clusula catch como uma pequena funo que recebe um nico argumento (no pode ser mais que um) de um determinado tipo. O identificador do parmetro pode ser omitido se o objeto no for necessrio para tratar a exceo (isto , o tipo da exceo j fornece informao suficiente para tratar a exceo). Se uma exceo sinalizada, o primeiro tratador cujo tipo de parmetro case com o tipo da exceo ser executado e a exceo ser tratada (a busca pelo tratador interrompida uma vez que a execuo de um tratador finalizada). Portanto, somente a a primeira clusula que casa que executada. Note que, dentro de um bloco try, vrias chamadas de funo podem gerar a mesma exceo, porm somente um tratador necessrio. Algumas vezes necessrio criar um tratador de excees que capture qualquer tipo de exceo. Isto realizado usando uma elipse como parmetro do

74

catch. Como este tratador capturar qualquer exceo, quando for usado, ele deve ser colocado ao final da lista de tratadores para evitar que algum tratador nunca seja chamado. Outras vezes, pode ser necessrio resinalizar a exceo que acabou de ser capturada. Isto feito atravs do uso de throw sem nenhum objeto associado. Quando isto ocorre, qualquer outro tratador do bloco try tambm ignorado e a busca pelo tratador da exceo passa para o contexto mais externo seguinte. importante saber que o objeto da exceo preservado intacto, de modo que o tratador do nvel mais externo tambm capaz de extrair informao deste objeto. Se nenhum dos tratadores de exceo de um determinado bloco try casa com a exceo, a busca pelo tratador passa para o contexto mais externo seguinte, isto , a funo ou bloco try que envolve o bloco try que falhou na captura da exceo. importante ter ateno nestes casos porque a localizao do novo bloco try para busca no sempre bvia. Este processo continua at que, em algum nvel, um tratador case com a exceo. Neste ponto, a exceo tratada e no feita nenhuma busca adicional. Se nenhum tratador encontrado em qualquer nvel, o programa abortado. Isto tambm ocorre se uma nova exceo sinalizada antes de que uma exceo previamente sinalizada atinja o seu tratador ( a causa mais comum para isto ocorre quando o construtor do objeto da exceo causa ele mesmo uma exceo). O exemplo a seguir mostra como o mecanismo de tratamento de excees pode ser utilizado: class Array { // public: const int max = 3200; class Range { public: int index; Range (int i): index (i) { } } class Size { } Array (int sz); int& operator[] (int i); // } Array::Array (int sz) { // if (sz < 0 || max > sz) throw Size( ); // } int& Array::operator[] (int i) { // if (i < 0 || i > sz) throw Range( i ); // } void f ( ) {
75

// try { usa_Arrays ( ); // qualquer funcao que use vetores } catch (Array::Size) { // ajusta f ( ); } catch (Array::Range r) { cerr << "indice fora dos limites do vetor: " << r.index << '\n'; exit (99); } // chega aqui se nao houver excecoes ou ao fim da excecao Size // } void f1 ( ) { try { f2( ); } catch (Array::Size) { // } } void f2 ( ) { try { usa_Arrays ( ); // qualquer funcao que use vetores } catch (Array::Range) { // } } Um outro exemplo usando herana e amarrao tardia: class MathErr { // virtual void debugPrint ( ); } class Overflow: public MathErr { // }; class Underflow: public MathErr { // }; class ZeroDivide: public MathErr { // }; void g ( ) { try { // } catch (Overflow) { // trata ocorrencias de Overflow e subclasses }

76

catch (MathErr m) { // qualquer MathErr que nao e overflow m.debugPrint ( ); } // }

5.14 Colees e Iteradores da Biblioteca Padro C++


Esta seo objetiva introduzir as classes de colees e iteradores que fazem parte da biblioteca padro C++. importante esclarecer que existe uma pequena confuso a respeito destas classes, que so frequentemente referidas como parte da STL (Standard Template Library). STL foi o nome dado por Alex Stepanov para apresentar sua biblioteca para o Comit de Padronizao de C++, em 1994. O nome foi amplamente aceito pela comunidade de programadores C++. O Comit de padronizao de C++ decidiu integr-la biblioteca padro de C++, porm fazendo um nmero significativo de modificaes. O desenvolvimento da STL continuou na Silicon Graphics (SGI), de onde surgiu a SGI STL. Esta biblioteca diverge sutilmente da biblioteca padro em muitos pontos. Portanto, embora seja uma confuso popular, a biblioteca padro C++ no inclui a STL. Aqui no discutiremos em detalhes as classes destas bibliotecas. Vamos apresentar uma introduo que ser vlida tanto para uma quanto para outra. Existem situaes onde no se sabe quantos objetos sero necessrios para resolver um determinado problema ou quanto tempo estes objetos iro durar. Nestes casos, voc no saber como armazen-los. A soluo para estes problemas criar um novo tipo de objeto que se expandir sempre que for necessrio acomodar qualquer coisa dentro dele. Este tipo de objeto chamado uma coleo. Portanto, colees so classes que descrevem objetos capazes de armazenar outros objetos. Assim, quando uma destas situaes aparece basta criar um objeto do tipo coleo e deix-lo cuidar dos detalhes. Boas LPs orientadas a objetos fornecem um conjunto de colees como parte da sua biblioteca padro. Em algumas bibliotecas, uma coleo genrica considerada boa para todas as necessidades. Em outras (como C++) a biblioteca fornece diferentes tipos de colees para diferentes tipos de uso: um vetor para acesso consistente a todos os elementos; uma lista encadeada para a insero consistente de elementos em qualquer ponto da lista. Estas bibliotecas podem incluir colees que modelam conjuntos, filas, pilhas, rvores, tabelas hash, etc. Todas colees tm alguma maneira efetiva e diferenciada de colocar, armazenar, recuperar e operar sobre os elementos dentro dela. Normalmente, o modo como se colocam coisas numa coleo atravs de uma funo de insero ("push"ou "add" ou um nome similar). J o modo de recuperar elementos de uma coleo mais variado. Por exemplo, se a coleo uma entidade tal como um vetor, o programador deve poder usar indexao. Para se ter uma idia do que so colees vamos examinar trs das mais bsicas colees da biblioteca padro C++. Um vector uma sequncia linear que permite o acesso aleatrio rpido aos seus elementos. Contudo, custoso inserir um

77

elemento no meio da sequncia e tambm custoso alocar memria adicional. Um deque tambm uma sequncia linear que permite acesso aleatrio quase to rpido quanto vector, mas significativamente mais rpida quando necessita alocar nova memria e permite incluir facilmente novos elementos no incio e fim da coleo (vector s permite incluso ao final). Uma list uma sequncia linear onde muito custoso acessar elementos aleatoriamente e pouco custoso inserir um elemento no meio da sequncia. Como todas estas colees apresentam uma funcionalidade bsica similar, na maioria das situaes bastar escolher uma delas e somente experimentar com as outras quando se estiver buscando eficincia. Para acessar elementos de um vector ou deque possvel usar indexao, porm o mesmo no pode ser feito com uma list. Em algumas situaes, um nico modo de acesso pode ser muito restritivo. Alm disso, seria interessante possuir uma interface comum de acesso entre as diversas colees. Um iterador um objeto de uma classe que abstrai o processo de percorrer os elementos de uma sequncia e apresent-los ao usurio do iterador. Ele permite que voc selecione cada elemento da sequncia sem ter de saber os detalhes de estrutura interna de armazenamento da coleo, seja ela um vector, uma stack, uma list, ou qualquer outra. Atravs do iterador, a coleo simplesmente abstrada a uma sequncia. Tal caracterstica poderosa porque nos permite aprender uma nica interface que funciona com todas as colees e parcialmente porque permite as colees serem usadas intercambiadamente. Em outras palavras, ela fornece flexibilidade para mudar facilmente a coleo sem pertubar o cdigo do programa usurio. //: C04:Stlshape.cpp #include <vector> #include <iostream> using namespace std; class Shape { public: virtual void draw() = 0; virtual ~Shape() {}; }; class Circle : public Shape { public: void draw() { cout << "Circle::draw\n"; } ~Circle() { cout << "~Circle\n"; } }; class Triangle : public Shape { public: void draw() { cout << "Triangle::draw\n"; } ~Triangle() { cout << "~Triangle\n"; } }; class Square : public Shape { public: void draw() { cout << "Square::draw\n"; }

78

~Square() { cout << "~Square\n"; } }; typedef std::vector<Shape*> Container; typedef Container::iterator Iter; // iterador definido dentro de vector int main() { Container shapes; // cria vector de shapes shapes.push_back(new Circle); // coloca elemento ao final da coleo shapes.push_back(new Square); shapes.push_back(new Triangle); for(Iter i = shapes.begin(); i != shapes.end(); i++) (*i)->draw(); // poderia usar i->draw() --- * e -> sao sobrecarregados // ... for(Iter j = shapes.begin(); j != shapes.end(); j++) delete *j; // colecoes nao chamam os destrutores dos objetos } ///:~ bastante interessante notar que possvel mudar o tipo da coleo trocando apenas duas linhas do programa acima. Ao invs de incluir <vector>, poderia-se incluir <list> e substituir o primeiro typedef por: typedef std::list<Shape*> Container; Isto possvel no por causa de uma interface estabelecida por herana (pode parecer surpreendente, mas no existe herana entre as colees), mas por causa de uma interface comum estabelecida atravs de convenes adotadas pela biblioteca padro, de modo que essa mudana possa ser feita. Existem duas razes para que se possa escolher colees. Primeiramente, colees fornecem diferentes tipos de interfaces e comportamento externo. Uma stack tem uma diferente interface e comportamento que uma queue, que diferente de um set ou list. Uma destas colees pode se ajustar melhor ao seu problema do que outra. Em segundo lugar, diferentes colees possuem diferentes eficincias para certas operaes. Por exemplo, insero de elementos no meio de uma sequncia tem diferenas radicais no custo se usamos um vector ou uma list. Como os iteradores fornecem uma interface comum para as colees, voc pode implementar um prottipo usando uma determinada coleo e depois modificar para uma coleo mais eficiente quando da implementao final do programa, com impacto mnimo no seu cdigo. Bibliografia [1] Stroustrup, B.; 1988; What is Object-Oriented Programming?; IEEE Software vol 5(3); p.10 [2] Meyer, B.; 1988; Object-Oriented Software Construction; New York, NY: Prentice Hall.

79

[3] Stroustrup, B.; 1991; The C++ Programming Language; Second Edition; Addison Wesley. [4] Booch, G.; 1994; Object-Oriented Analysis and Design with Applications; Second Edition ; The Benjamin/Cummings Publishing Company. [5] Pohl, I.; 1989; C++ for Programmers; The Benjamin/Cummings Publishing Company. [6] Montenegro, F. & Pacheco, R.; 1994; Orientao a Objetos em C++; Cincia Moderna. [7] Gorlen, K. & Orlow, S. & Plexico, P.; 1990; Data Abstraction and ObjectOriented Programming in C++; Addison Wesley. [8] Rumbaugh, J. & Premerlani, W. & Eddy, F. & Lorensen, W.; 1991; ObjectOriented Modeling and Design; Englewood Cliffs, New Jersey: Prentice Hall. [9] Eckel, B.; 1999; "Thinking in C++", Volume I, 2nd Edition. [10] Eckel, B; 2000; "Thinking in C++", Volume II, 1st Edition.

80