Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas

TUTORIAL "C+ COMO UMA LINGUAGEM DE PROGRAMAÇÃO ORIENTADA A OBJETOS, Notas de estudo de Informática

- - - - - - -

Tipologia: Notas de estudo

Antes de 2010

Compartilhado em 25/10/2008

marcus-werneck-correa-5
marcus-werneck-correa-5 🇧🇷

1 documento

1 / 174

Documentos relacionados


Pré-visualização parcial do texto

Baixe TUTORIAL "C+ COMO UMA LINGUAGEM DE PROGRAMAÇÃO ORIENTADA A OBJETOS e outras Notas de estudo em PDF para Informática, somente na Docsity! 1 TUTORIAL:...................................................................................................... 3 1.1 "C++ COMO UMA LINGUAGEM DE PROGRAMAÇÃO ORIENTADA A OBJETOS."................................................................................................................... 3 2 1. CLASSES E OBJETOS ............................................................................... 3 2.1 1.1. ESPECIFICANDO UMA CLASSE .......................................................... 4 2.2 1.2. STRUCT EM C++ .................................................................................... 5 2.2.1 1.2.1. ATRIBUTOS OU DADOS MEMBRO. ................................................. 5 2.2.2 1.2.2. MÉTODOS OU FUNÇÕES MEMBRO. ............................................... 7 2.2.3 1.2.3. FUNÇÕES MEMBRO QUE RETORNAM VALORES. ...................... 10 2.2.4 1.2.4. FUNÇÕES DECLARADAS EXTERNAS A CLASSE , FUNÇÕES MEMBRO CHAMAMANDO FUNÇÕES MEMBRO. .............................................. 11 2.2.5 1.2.5. ALGO PARECIDO EM UMA LINGUAGEM PROCEDURAL .......... 14 2.2.6 1.2.6. CONSTRUTORES ................................................................................. 16 2.2.7 1.2.7. CONSTRUTORES E AGREGAÇÃO ................................................... 19 2.2.8 1.2.8. DESTRUTORES. ................................................................................... 21 2.3 1.3. ENCAPSULAMENTO COM "CLASS" .................................................. 23 2.3.1 1.3.1. ATRIBUTOS PRIVATE, FUNÇÕES MEMBRO PUBLIC ................... 26 2.3.2 1.3.2. UM DADO MEMBRO É PUBLIC ....................................................... 27 2.3.3 1.3.3. COMPILANDO UM PROGRAMA COM VÁRIOS ARQUIVOS. ...... 29 2.4 1.4. TIPO ABSTRATO DE DADOS ................................................................ 31 2.4.1 1.4.1. TAD FRAÇÃO ....................................................................................... 31 2.5 1.5. CONSIDERAÇÕES C++: ........................................................................ 37 2.5.1 1.5.1. CONST ................................................................................................... 37 2.5.2 1.5.2. FUNÇÕES INLINE ............................................................................... 39 2.5.3 1.5.3. ALOCAÇÃO DINÂMICA COM NEW E DELETE. ............................ 41 2.5.3.1 1.5.3.1. PONTEIROS, "POINTERS" .............................................................. 41 2.5.3.2 1.5.3.2. VETORES CRIADOS ESTATICAMENTE ....................................... 42 2.5.3.3 1.5.3.3. COPIA DE OBJETOS COM VETORES ALOCADOS ESTATICAMENTE. ....................................................................................................................................... 43 2.5.3.4 1.5.3.4. VETORES CRIADOS DINAMICAMENTE ..................................... 44 2.5.3.5 1.5.3.5. COPIA DE OBJETOS COM VETORES ALOCADOS DINAMICAMENTE. ................................................................................................... 45 2.5.3.6 1.5.3.6. TAD E ALOCAÇÃO DINÂMICA. .................................................... 47 2.5.3.7 1.5.3.7. ALOCANDO OBJETOS .................................................................... 52 2.5.4 1.5.4. REFERÊNCIA & ................................................................................... 52 2.6 1.6. RECAPITULANDO ................................................................................. 54 2.6.1 1.6.1. ARGUMENTOS DE LINHA DE COMANDO. .................................... 54 3 2. HERANÇA ................................................................................................... 64 3.1 2.1. HIERARQUIAS DE TIPOS ..................................................................... 64 3.1.1 2.1.1. UMA HIERARQUIA SIMPLES. ........................................................... 64 3.1.2 2.1.2. PROTECTED ......................................................................................... 67 3.1.3 2.1.3. REDEFINIÇÃO DE FUNÇÕES MEMBRO HERDADAS .................. 70 3.1.4 2.1.4. UMA HIERARQUIA DE LISTAS LIGADAS ...................................... 73 3.2 2.2. HIERARQUIAS DE IMPLEMENTAÇÃO .............................................. 82 3.2.1 2.2.1. FILA A PARTIR DE UMA LISTA ......................................................... 82 4 3. POLIMORFISMO, FUNÇÕES VIRTUAIS ................................................ 84 4.1 3.1. O QUE SIGNIFICA POLIMORFISMO ................................................... 84 4.1.1 3.1.1. SOBRECARGA DE MÉTODOS ........................................................... 85 4.1.2 3.1.2. REDEFINIÇÃO DE UMA FUNÇÃO MEMBRO PARA UMA CLASSE HERDEIRA .................................................................................................................. 85 4.1.3 3.1.3. "COPY CONSTRUCTOR" .................................................................... 85 4.1.4 3.1.4. SOBRECARGA DE FUNÇÃO EM C++. ............................................. 87 4.1.5 3.1.5. "DEFAULT ARGUMENTS", VALORES SUGESTÃO ........................ 89 4.1.6 3.1.6. SOBRECARGA DE OPERADOR ........................................................ 90 4.2 3.2. CLASSES ABSTRATAS E CONCRETAS .............................................. 94 4.2.1 3.2.1. CLASSE ABSTRATA ITERADOR ....................................................... 94 4.2.2 3.2.2. ACOPLAMENTO DE MENSAGENS .................................................. 98 4.2.2.1 3.2.2.1. CASO ESTÁTICO .............................................................................. 98 4.2.2.2 3.2.2.2. DINÂMICO SEM VIRTUAL ............................................................. 99 4.2.2.3 3.2.2.3. DINÂMICO COM VIRTUAL ............................................................ 100 4.2.3 3.2.3. CONTAS BANCÁRIAS ........................................................................ 102 4.2.4 3.2.4. LISTA HETEROGÊNEA DE CONTAS BANCÁRIAS. ....................... 109 5 4. TÓPICOS AVANÇADOS ............................................................................ 110 5.1 4.1. FRIENDS .................................................................................................. 110 5.1.1 4.1.1. UMA CLASSE PERMITINDO ACESSO A OUTRA ........................... 110 5.1.2 4.1.2. OPERADORES E FRIENDS ................................................................. 112 5.2 4.2. HERANÇA MÚLTIPLA ........................................................................... 121 5.2.1 4.2.1. UM EXEMPLO SIMPLES. ................................................................... 122 5.2.2 4.2.2. VIRTUAL PUBLIC E RESOLUÇÃO DE CONFLITOS. ..................... 122 5.3 4.3. POLIMORFISMO PARAMÉTRICO (TEMPLATE) ............................... 125 5.3.1 4.3.1. TAD VETOR .......................................................................................... 125 5.3.2 4.3.2. TEMPLATE DE FUNÇÃO .................................................................... 129 5.3.3 4.3.3. HERANÇA E TEMPLATES. ................................................................. 131 5.3.4 4.3.4. TEMPLATES E AGREGAÇÃO ............................................................ 141 5.4 4.4. METACLASSES ....................................................................................... 143 5.4.1 4.4.1. UM TIPO SIMPLES COMO STATIC ................................................... 144 5.4.2 4.4.2. UM TIPO DEFINIDO PELO USUÁRIO USADO COMO STATIC .... 146 5.5 4.5. TRATAMENTO DE EXCEÇÕES ............................................................ 149 5.6 4.6. CONCLUSÕES ......................................................................................... 154 5.6.1 4.6.1. ÁRVORE BINÁRIA. ............................................................................. 154 5.6.2 4.6.2. SIMULAÇÃO DIRIGIDA A EVENTOS. ............................................. 159 Um diagrama simplificado da classe motor com os dados membro e as funções membro: Exercícios: 1)Lembre-se de algum programa em que você trabalhou, cite que tipos de classes seriam criadas se esse programa fosse escrito em C++, que atributos e que funções membro estariam associadas a esses objetos? Exemplo: "Eu trabalhei em um programa de contas a pagar e contas a receber. Se esse programa fosse escrito em C++ eu definiria a classe conta_bancaria. Os atributos seriam: saldo, taxa_de_juros, limite_de_saque, etc. Minha opção seria por representá-los como variáveis do tipo float. " "Dentre as funções membros desta classe estariam funções para efetuar saques, depósitos e computar juros." 1.2. STRUCT EM C++ Objetos são instâncias de uma classe. Quando um objeto é criado ele precisa ser inicializado, ou seja para uma única classe : Estudante de graduação podemos ter vários objetos num programa: Estudante de graduação Carlos, Identificação 941218, Curso Computação; Estudante de graduação Luiza , Identificação 943249, Curso Engenharia Civil... A classe representa somente o molde para a criação dos objetos, estes sim contém informação, veja tópico classes e objetos. 1.2.1. ATRIBUTOS OU DADOS MEMBRO. Este exemplo declara uma struct e em seguida cria um objeto deste tipo em main alterando o conteúdo desta variável. Uma struct é parecida com um record de Pascal, a nossa representa um círculo com os atributos raio, posição x , posição y, que são coordenadas cartesianas. Note que este objeto não possui funções membro ainda. #include <iostream.h> struct circulo //struct que representa um circulo. { float raio; float x; //posicoes em coordenadas cartesianas float y; }; void main() { circulo ac; //criacao de variavel , veja comentarios. ac.raio=10.0; //modificacao de conteudo (atributos) da struct ac.x=1.0; //colocando o circulo em uma posicao determinada ac.y=1.0; //colocando o circulo em uma posicao determinada cout << "Raio:"<<ac.raio <<endl; //verificacao dos atributos alterados. cout << "X:"<<ac.x << "\n"; // "\n"==endl cout << "Y:" <<ac.y<< endl; } Resultado do programa: Raio:10 X:1 Y:1 Comentários: struct circulo //struct que representa um circulo. { float raio; float x; //posicoes em coordenadas cartesianas float y; }; Este código é a declaração da classe círculo, entre chaves vem os dados membro e as funções membro que não foram apresentadas ainda. A sintaxe para criação de objetos da classe círculo (circulo ac;) , por enquanto não difere da sintaxe para a criação de variáveis do tipo int. O acesso aos dados membro deve ser feito usando o nome do objeto e o nome do dado membro, separados por um ponto: ac.raio=10.0; . Note que raio sozinho não faz sentido no programa, precisa-se especificar de que objeto se deseja acessar o raio. Aos que programam em C: Os programadores C podem notar algo interessante: "C++ não requer a palavra struct na declaração da variável, ela se comporta como um tipo qualquer: int , float ...". Outros programadores que não haviam usado struct previamente em C não se preocupem, façam apenas os exercícios deste exemplo e estarão aptos a prosseguir. Exercícios: 1) Repita o mesmo exemplo só que agora mova o círculo alterando as componentes x e y. Ponha o círculo em (0.0,0.0) através de atribuições do tipo ac.x=1.0; mova o círculo para (1.0,1.0). Acompanhe todas as modificações da struct através de cout's. 2)Simplifique o programa anterior retirando o atributo raio. Você pode dar o nome de ponto ou ponto_geometico para esta classe. 1.2.2. MÉTODOS OU FUNÇÕES MEMBRO. C++ permite que se acrescente funções de manipulação da struct em sua declaração, juntando tudo numa só entidade que é uma classe. Essas funções membro podem ter sua declaração (cabeçalho) e implementação (código) dentro da struct ou só o cabeçalho (assinatura) na struct e a implementação, código, fora. Este exemplo apresenta a primeira versão, o próximo a segunda versão (implementação fora da classe). Essas funções compõem a interface da classe. A terminologia usada para designá-las é bastante variada: funções membro, métodos, etc. Quando uma função membro é chamada, se diz que o objeto está recebendo uma mensagem (para executar uma ação). Um programa simples para testes sobre funções membro seria o seguinte: #include <iostream.h> struct contador //conta ocorrencias de algo { int num; //numero do contador void incrementa(void){num=num+1;}; //incrementa contador void comeca(void){num=0;}; //comeca a contar }; void main() //teste do contador { contador umcontador; umcontador.comeca(); //nao esqueca dos parenteses, e uma funcao membro e nao atributo! cout << umcontador.num << endl; umcontador.incrementa(); cout << umcontador.num << endl; } Resultado do programa: 0 1)Neste mesmo programa, crie uma função para a struct chamada "inicializa" que deve ter como argumentos um valor para x, um para y e outro para o raio, e deve alterar os atributos inicializando-os com os valores passados. Você pode abstrair o uso dessa função como uma maneira de inicializar o objeto de uma só vez embora a função o faça seqüencialmente. Comente as vantagens de fazê-lo, comparando com as outras opções, tenha sempre em mente a questão de segurança quando avaliar técnicas diferentes de programação. 2)No programa anterior, verifique que nada impede que você acesse diretamente os valores de x , y e raio e os modifique. Como você pode criar um número enorme de funções : altera_x(float a); move_raio(float dr); seria desejável que somente essas funções pudessem modificar x, y e raio. Você verá que isso é possível em encapsulamento 1.3. Por hora, crie essas funções. 3)Teste a função membro move com argumentos negativos, exemplo ac.move(-1.0,-1.5);. O resultado é coerente? 4)Crie uma nova struct que representa um ponto, que informações você precisa armazenar? Que funções seriam úteis ? Faça um programa para testar sua classe. 5)Melhore a classe contador, defina uma função que imprime o contador na tela. Se você estivesse fazendo um programa para rodar numa interface gráfica com o usuário esta função de imprimir na tela seria a mesma? Definir uma função que retorna uma copia do valor atual do contador garante maior portabilidade? Por quê? Para aprender a retornar valores consulte: 1.2.3. 6)"Há uma tendência em definir o maior número de funções membro em uma classe, porque nunca se pode prever exatamente o seu uso em programas futuros". Comente esta frase, tendo em vista o conceito de portabilidade. Você já é capaz de citar outras medidas que tornem suas classes mais portáveis? Leia o exercício anterior. 1.2.3. FUNÇÕES MEMBRO QUE RETORNAM VALORES. Até agora só tínhamos visto funções membro com valor de retorno igual a void. Uma função membro, assim como uma função comum, pode retornar qualquer tipo, inclusive os definidos pelo usuário. Sendo assim, sua chamada no programa se aplica a qualquer lugar onde se espera um tipo igual ou equivalente ao tipo do seu valor de retorno, seja numa lista de argumentos de outra função , numa atribuição ou num operador como o cout << variavel; #include <iostream.h> struct contador //conta ocorrencias de algo { int num; //numero, posicao do contador void incrementa(void){num=num+1;}; //incrementa contador void comeca(void){num=0;}; //comeca a contar, "reset" int retorna_num(void) {return num;}; }; void main() //teste do contador { contador umcontador; umcontador.comeca(); //nao esqueca dos parenteses, e uma funcao membro nao dado! cout << umcontador.retorna_num() << endl; umcontador.incrementa(); cout << umcontador.retorna_num() << endl; } Resultado do programa: 0 1 1.2.4. FUNÇÕES DECLARADAS EXTERNAS A CLASSE , FUNÇÕES MEMBRO CHAMAMANDO FUNÇÕES MEMBRO. Este exemplo apresenta a implementação, definição, das funções fora da declaração da struct. Além disso introduz uma nova função chamada "inicializa" e funções float retorna_raio(void); e void altera_raio(float a). Inicializa coloca o ponto nas coordenadas passadas como seus argumentos. Introduzimos esta função membro aqui para preparar a explicação sobre construtores dada no próximo exemplo: 1.2.6. Comentários: Em uma declaração de uma classe normalmente se coloca a declaração das funções membro depois da declaração dos atributos, porém podemos fazer intercalações ou adotar qualquer ordem que nos convenha. O programador não é obrigado a implementar as funções membro dentro da declaração da classe, basta defini-las e apresentar a implementação em separado segundo a sintaxe (compilável) descrita a seguir: #include <iostream.h> struct teste { int x; void altera_x(int v); //somente definicao implementacao vem depois, fora da classe }; void teste::altera_x(int v) { x=v;} //esta ja e a implementacao codigo void main() { teste a; //instaciacao de um objeto a.altera_x(10); //chamada da funcao membro com valor 10 que sera impresso a seguir cout << a.x; //imprimindo o dado membro } Resultado do programa anterior: 10 Programa exemplo círculo, mais complexo baseado no exemplo de 1.2.2: #include <iostream.h> //para cout struct circulo { float raio; float x; float y; void inicializa(float ax,float by,float cr); void altera_raio(float a); float retorna_raio(void); void move(float dx,float dy); void mostra(void); }; void circulo::inicializa(float ax,float by,float cr) { x=ax; y=by; raio=cr; } void circulo::altera_raio(float a) { raio=a; } float circulo::retorna_raio(void) { altereme.r:=cr; END; PROCEDURE Altera_Raio(var altereme:Circulo;ar:real); {ALTERA O RAIO DO CIRCULO} BEGIN altereme.r:=ar; END; FUNCTION Retorna_Raio(copieme:Circulo):real; BEGIN Retorna_Raio:=copieme.r; END; PROCEDURE Move(var altereme:Circulo;dx,dy:real); {MODE AS COORDENADAS X E Y ACRESCENTANDO DX E DY} BEGIN altereme.x:=altereme.x+dx; altereme.y:=altereme.y+dy; END; PROCEDURE Mostra(copieme:Circulo); {MOSTRA O CIRCULO NA TELA} BEGIN writeln('X:',copieme.x,' Y:',copieme.y,' R:',copieme.r); END; BEGIN {TESTES} Inicializa(ac,0.0,0.0,10.0); Mostra(ac); Move(ac,1.0,1.0); Mostra(ac); ac.x:=100.0; Altera_Raio(ac,12.0); Mostra(ac); read(leitura); END. Resultado do programa: X: 0.0000000000E+00 Y: 0.0000000000E+00 R: 1.0000000000E+01 X: 1.0000000000E+00 Y: 1.0000000000E+00 R: 1.0000000000E+01 X: 1.0000000000E+02 Y: 1.0000000000E+00 R: 1.2000000000E+01 Comentários: C++: As classes em C++ englobam os dados membros e as funções membros. Para executar uma ação sobre o objeto ou relativa a este basta chamar uma função membro para este: ac.mostra(); A função membro não precisa de muitos argumentos, porque é própria da classe e portanto ganha acesso aos dados membro do objeto para ao qual ela foi associada: float circulo::retorna_raio(void) { return raio; //tenho acesso direto a raio. } Pascal: Em Pascal os procedimentos e os dados são criados de forma separada, mesmo que só tenham sentido juntos. A junção entre os dados e procedimentos se dá através de passagem de parâmetros. No caso de uma linguagem procedural como Pascal, o que normalmente é feito se assemelha ao código seguinte: Move(ac,1.0,1.0); . Ac nesse caso é um "record", mas sem funções membro, algo semelhante ao struct de C (não C++). Move, acessa os dados do "record" alterando os campos. O parâmetro é passado por referência e o procedimento é definido a parte do registro, embora só sirva para aceitar argumentos do tipo Circulo e mover suas coordenadas. Segurança: Em ambos programas (Pascal, C++) o programador pode acessar diretamente os dados do tipo definido pelo usuário: ac.x:=100.0; (Pascal) ou ac.x=100.0; (C++). Veremos em 1.3 ENCAPSULAMENTO maneiras de proibir em C++ este tipo de acesso direto ao dado membro, deixando este ser modificado somente pelas funções membro. Isto nos garante maior segurança e liberdade pois podemos permitir ou não o acesso para cada dado membro de acordo com nossa vontade. Eficiência: Alguém pode argumentar que programas que usam bastante chamadas de funções podem se tornar pouco eficientes e que poderia ser melhor acessar diretamente os dados de um tipo definido pelo usuário ao envés de passar por todo o trabalho de cópia de argumentos, inserção da função no pilha, etc. Em verdade não se perde muito em eficiência, e além disso muitas vezes não se deseja permitir sempre o acesso direto aos dados de um tipo definido pelo usuário por razões de segurança. Nesse sentido C++ oferece um recurso que permite ganhos em segurança sem perder muito em eficiência, veja: 1.5.2. Exercícios: 1)Verifique que em main() você pode modificar o atributo x do objeto da classe ponto ou círculo da seguinte forma: a.x=12.2; . Isto pode não ser muito útil, imagine-se criando uma library que implementa a classe ponto e uma série de funções relacionadas, por razões de segurança você gostaria que o usuário se limitasse ao uso da interface (funções membro) do objeto, como fazê-lo será explicado em 1.3 encapsulamento. Por hora, apenas crie funções que ajudem a evitar este tipo de acesso direto. 1.2.6. CONSTRUTORES Construtores são funções membro especiais chamadas pelo sistema no momento da criação de um objeto. Elas não possuem valor de retorno, porque você não pode chamar um construtor para um objeto. Contrutores representam uma oportunidade de inicializar de forma organizada os objetos, imagine se você esquece de inicializar corretamente ou o faz duas vezes, etc. Um construtor tem sempre o mesmo nome da classe e não pode ser chamado pelo usuário desta. Para uma classe string o construtor teria a forma string(char* a); com o argumento char* especificado pelo programador. Ele seria chamado automaticamente no momento da criação, declaração de uma string: string a("Texto"); //alocacao estatica implica na chamada do construtor a.mostra(); //chamada de metodos estatica. Existem variações sobre o tema que veremos mais tarde: Sobrecarga de construtor, "copy constructor", como conseguir construtores virtuais (avançado, não apresentado neste texto) , construtor de corpo vazio. O exemplo a seguir é simples, semelhante aos anteriores, preste atenção na função membro com o mesmo nome que a classe (struct) , este é o construtor: #include <iostream.h> struct ponto { float x; float y; public: ponto(float a,float b); //esse e o contrutor, note a ausencia do valor de retorno void mostra(void); void move(float dx,float dy); }; ponto::ponto(float a,float b) //construtor tem sempre o nome da classe. { x=a; //incializando atributos da classe y=b; //colocando a casa em ordem } void ponto::mostra(void) {cout << "X:" << x << " , Y:" << y << endl;} void ponto::move(float dx,float dy) Uma outra alternativa seria usar alocação dinâmica para os atributos pontos que passariam a ser agora ponteiros para pontos, deixaremos para discutir este tópico mais tarde em 1.5.3, mas saiba que nesse caso o construtor da classe reta não precisaria criar os pontos. Vamos ao exemplo, que novamente é semelhante aos anteriores, para que o leitor preste atenção somente nas mudanças, que são os conceitos novos, sem ter que se esforçar muito para entender o programa: #include <iostream.h> struct ponto { float x; float y; //coordenadas ponto(float a,float b) { x=a; y=b; } //construtor void move(float dx,float dy) { x+=dx; y+=dy; } //funcao membro comum void inicializa(float a,float b) { x=a; y=b; } void mostra(void) {cout << "X:" << x << " , Y:" << y << endl;} }; struct reta { ponto p1; ponto p2; reta(float x1,float y1,float x2,float y2):p1(x1,y1),p2(x2,y2) { //nada mais a fazer, os contrutores de p1 e p2 ja foram chamados } void mostra(void); }; void reta::mostra(void) { p1.mostra(); p2.mostra(); } void main() { reta r1(1.0,1.0,10.0,10.0); //instanciacao da reta r1 r1.mostra(); } Resultado do programa: X:1 , Y:1 X:10 , Y:10 Exercícios: 1) Implemente um programa que use a mesma lógica do exemplo anterior para criar uma classe composta de outras . Você estará usando agregação. Por exemplo um triângulo precisa de três pontos para ser definido. 2)Uma implementação mais eficiente da classe reta seria feita armazenando apenas um coeficiente angular e um linear, mas esta reta deveria então prover um construtor que aceitasse dois pontos como argumentos. Como você não aprendeu sobrecarga de construtores (definir vários construtores), use só um construtor que tem coeficiente angular e linear como argumentos e implemente esta nova classe reta sem usar agregação agora. 3)Defina uma função membro para a classe reta que retorna o ponto de intercessão com outra reta: ponto reta::intercessao(reta a);. Não se esqueça de tratar os casos degenerados, tais como retas paralelas e coincidentes, use por exemplo mensagens de erro. Verifique que uma função membro de uma reta recebe outra reta (mesmo tipo) como argumento. Dentro da função membro os dados membro do argumento a devem ser acessados do seguinte modo: a.p1.x; Mais tarde, em 4.5 tratamento de exceções, veremos maneiras melhores de lidar com esses casos degenerados. 4)Defina uma função membro chamada move para a classe reta, lembre-se de mover os dois pontos juntos (a inclinação não deve mudar). 6)Defina uma função membro void gira(tipox angulo); para a classe reta. Esta função membro recebe um ângulo como argumento, você pode optar por representar o ângulo em radianos (float) ou criar a classe ângulo (graus, minutos, segundos). Resolva também o problema da escolha do ponto em torno do qual a reta deve ser girada. Use funções matemáticas (seno cosseno) da library <math.h>. 1.2.8. DESTRUTORES. Análogos aos construtores, os destrutores também são funções membro chamadas pelo sistema, só que elas são chamadas quando o objeto sai de escopo ou em alocação dinâmica, tem seu ponteiro desalocado, ambas (construtor e destrutor) não possuem valor de retorno. Você não pode chamar o destrutor, o que você faz é fornecer ao compilador o código a ser executado quando o objeto é destruído, apagado. Ao contrário dos construtores, os destrutores não tem argumentos. Os destrutores são muito úteis para "limpar a casa" quando um objeto deixa de ser usado, no escopo de uma função em que foi criado, ou mesmo num bloco de código. Quando usados em conjunto com alocação dinâmica eles fornecem uma maneira muito prática e segura de organizar o uso do "heap". A importância dos destrutores em C++ é aumentada pela ausência de "garbage collection" ou coleta automática de lixo. A sintaxe do destrutor é simples, ele também tem o mesmo nome da classe só que precedido por ~ , ele não possui valor de retorno e seu argumento é void sempre: ~nomedaclasse(void) { /* Codigo do destrutor */ } O exemplo a seguir é simples, porque melhorias e extensões sobre o tema destrutores serão apresentadas ao longo do texto: //destrutor de uma classe #include <iostream.h> struct contador{ int num; contador(int n) {num=n;} //construtor void incrementa(void) {num+=1;} //funcao membro comum, pode ser chamada pelo usuario ~contador(void) {cout << "Contador destruido, valor:" << num <<endl;} //destrutor }; void main() { contador minutos(0); minutos.incrementa(); cout << minutos.num << endl; { //inicio de novo bloco de codigo contador segundos(10); segundos.incrementa(); cout << segundos.num <<endl; //fim de novo bloco de codigo } minutos.incrementa(); } Fica fácil entender essas declarações se você pensar no seguinte: esses qualificadores se aplicam aos métodos e atributos que vem após eles, se houver então um outro qualificador, teremos agora um novo tipo de acesso para os métodos declarados posteriormente. Mas então porque as declarações são equivalentes? É porque o qualificador private é "default" para class, ou seja se você não especificar nada , até que se insira um qualificador, tudo o que for declarado numa classe é private. Já em struct, o que é default é o qualificador public. Agora vamos entender o que é private e o que é public: Vamos supor que você instanciou (criou) um objeto do tipo ponto em seu programa: ponto meu; //instanciacao Segundo o uso de qualquer uma das definições da classe ponto dadas acima você não pode escrever no seu programa: meu.x=5.0; //erro ! ,como fazíamos nos exemplos de 1.2 , a não ser que x fosse declarado depois de public na definição da classe o que não ocorre aqui. Mas você pode escrever x=5.0; na implementação (dentro) de um método porque enquanto não for feito uso de herança, porque uma função membro tem acesso a tudo que é de sua classe, veja o programa seguinte. Você pode escrever: meu.move(5.0,5.0); ,porque sua declaração (move) está na parte public da classe. Aqui o leitor já percebe que podem existir funções membro private também, e estas só são acessíveis dentro do código da classe (outras funções membro). Visibilidade das declarações de uma classe, fora dela e de sua hierarquia. Veja que só a parte public é visível neste caso: Visibilidade das declarações de uma classe, dentro dela mesma: Exercícios: 1) Qual das seguintes declarações permite que se acesse em main somente os métodos move e inicializa, encapsulando todos os outros elementos da classe? Obs.: A ordem das áreas private e public pode estar invertida com relação aos exemplos anteriores. a) struct ponto { //se eu nao colocar private eu perco o encapsulamento em struct. float x; float y; public: //qualificador void inicializa(float a, float b) {x=a; y=b;}; void move(float dx, float dy) ; {x+=dx; y+=dy; }; }; b) class ponto { public: //qualificador void inicializa(float a, float b) {x=a; y=b;}; void move(float dx, float dy) ; {x+=dx; y+=dy; }; private: float x; float y; }; c) class ponto { public: //qualificador void inicializa(float a, float b) {x=a; y=b;}; //funcao membro void move(float dx, float dy) ; {x+=dx; y+=dy; }; float x; //dados membro float y; }; 1.3.1. ATRIBUTOS PRIVATE, FUNÇÕES MEMBRO PUBLIC Aplicando encapsulamento a classe ponto definida anteriormente. #include <iostream.h> class ponto { private: //nao precisaria por private, em class e default float x; //sao ocultos por default float y; //sao ocultos por default public: //daqui em diante tudo e acessivel. void inicializa(float a,float b) { x=a; y=b; } //as funcoes de uma classe podem acessar os atributos private dela mesma. void mostra(void) {cout << "X:" << x << " , Y:" << y << endl;} }; void main() { ponto ap; //instanciacao ap.inicializa(0.0,0.0); //metodos public ap.mostra(); //metodos public } Resultado do programa: X:0 , Y:0 Comentários: Este programa não deixa você tirar o ponto de (0,0) a não ser que seja chamada inicializa novamente. Fica claro que agora, encapsulando x e y precisamos de mais métodos para que a classe não tenha sua funcionalidade limitada. Novamente: escrever ap.x=10; em main é um erro! Pois x está qualificada como private. Leia os exercícios. Exercícios: 1) Implemente os métodos void altera_x(float a) , float retorna_x(void), void move (float dx,float dy ); .Implemente outros métodos que achar importantes exemplo void distancia(ponto a) { return dist(X,Y,a.X,a.Y); }, onde dist representa o conjunto de operações matemáticas necessárias para obter a distância entre (X,Y) (a.X,a.Y). Você provavelmente usará a funcao sqr() da "library" <math.h> que define a raiz quadrada de um float. Veja que no método distancia, podemos acessar os dados membro X e Y do argumento a, isto é permitido porque distancia é uma função membro da mesma classe de a, embora não do mesmo objeto, uma maneira de prover este tipo de acesso para outras classes é dotar a classe ponto de métodos do tipo float retorna_x(void);. Friends 4.1 é um tópico avançado sobre encapsulamento que lida com visibilidade entre classes distintas. Quando você precisar de outras funções matemáticas (seno, coseno), lembre-se da "library" <math.h>, é só pesquisar o manual do compilador ou usar ajuda sensível ao contexto para <math.h> ou até mesmo dar uma olhadinha na interface dela, no diretório das "libraries", alias essa é uma boa maneira de aprender sobre como implementa-las. 2) Retorne ao início deste tópico e releia as definições equivalentes de classes declaradas com struct e com class. Refaça o programa exemplo anterior usando struct, use encapsulamento em outros programas que você implementou. 1.3.2. UM DADO MEMBRO É PUBLIC Este programa é uma variante do anterior, a única diferença é que Y é colocado na parte public da definição da classe e é acessado diretamente. Além disso fornecemos aqui a função membro move, para que você possa tirar o ponto do lugar. #include <iostream.h> class ponto { ponto ap; //instanciacao ap.inicializa(0.0,0.0); //metodos public ap.mostra(); //metodos public } Os arquivos com extensão .h indicam header files. É costume deixar nesses arquivos somente a interface das classes e funções para que o usuário possa olhá-lo, como numa "library". Note que a parte public da classe foi colocada antes da parte private, isto porque normalmente é essa parte da definição da classe que interessa para o leitor. No nosso caso o único arquivo `header' (.h) que temos é o "ponto.h" que define a classe ponto. Caso as dimensões de seu programa permitam, opte por separar cada classe em um `header file', ou cada grupo de entidades (funções, classes) relacionadas. O arquivo 2 "ponto.cpp" é um arquivo de implementação. Ele tem o mesmo nome do arquivo "ponto.h" , embora isto não seja obrigatório. É mais organizado ir formando pares de arquivos "header / implementation". Este arquivo fornece o código para as operações da classe ponto, daí a declaração: #include "ponto.h". As aspas indicam que se trata de um arquivo criado pelo usuário e não uma "library" da linguagem como <iostream.h>, portanto o diretório onde se deve encontrar o arquivo é diferente do diretório onde se encontra <iostream.h>. O arquivo 3 "princ.cpp" é o arquivo principal do programa, não é costume relacionar seu nome com nomes de outros arquivos como no caso do arquivo 2 e o arquivo 1. Observe que o arquivo 3 também declara #include "ponto.h", isto porque ele faz uso das dos tipos definidos em "ponto.h" , porém "princ.cpp" não precisa declarar #include <iostream.h> porque este não usa diretamente as definições de iostream em nenhum momento, caso "princ.cpp" o fizesse, o include <iostream> seria necessário. O leitor encontrará em alguns dos exemplos seguintes as diretivas de compilação. Quando da elaboração de uma library para ser usada por outros programadores, não se esqueça de usá-las: #ifndef MLISTH_H #define MLISTH_H //Codigo #endif #ifndef MLISTH_H #define MLISTH_H //defina aqui seu header file. //perceba que "Nomearq.h" e escrito na diretiva como NOMEARQ_H #endif Essas diretivas servem para evitar que um header file seja incluído mais de uma vez no mesmo projeto. O seu uso se dá da seguinte forma: Diagrama sugestão para organização de programas relativamente simples: Saber compilar programas divididos em vários arquivos é muito importante. Isto requer um certo esforço por parte do programador, porque os métodos podem variar de plataforma para plataforma. Pergunte para um programador experiente de seu grupo. Se o seu compilador é de linha de comando, provavelmente você poderá compilar programas divididos em vários arquivos usando makefiles. Já se seu compilador opera num ambiente gráfico, esta tarefa pode envolver a criação de um projeto. 1.4. TIPO ABSTRATO DE DADOS Tipo abstrato de dados, TAD, se preocupa em proporcionar uma abstração sobre uma estrutura de dados em termos de uma interface bem definida. São importantes os aspectos de encapsulamento, que mantém a integridade do objeto evitando acessos inesperados, e o fato de o código estar armazenado em um só lugar o que cria um programa modificável, legível, coeso. Uma classe implementa um tipo abstrato de dados. São exemplos de tipos abstratos de dados: 1) Uma árvore binária com as operações usuais de inserção, remoção, busca ... 2)Uma representação para números racionais (numerador, denominador) que possua as operações aritméticas básicas e outras de conversão de tipos. 3)Uma representação para ângulos na forma (Graus, Minutos, Segundos). Também com as operações relacionadas, bem como as operações para converter para radianos, entre outras. Tipo abstrato de dados é um conceito muito importante em programação orientada a objeto e por este motivo é logo apresentado neste tutorial. Os exemplos seguintes são simples devido ao fato de não podermos usar todos os recursos C++ por razões didáticas. Devido a esta importância, a medida em que formos introduzindo novos recursos exemplificaremos também com aplicações na implementação tipos abstratos de dados. 1.4.1. TAD FRAÇÃO Tipo abstrato de dados fração. Baseado no conceito de número racional do campo da matemática. Algumas operações não foram implementadas por serem semelhantes às existentes. O exemplo mais completo se encontra em 4.1.2. Por hora não podemos fazer uso de recursos avançados como sobrecarga de operador e templates. Resumo das operações matemáticas envolvidas: Simplificação de fração: (a/b)=( (a/mdc(a,b)) / (b/mdc(a,b)) ) Onde mdc(a,b) retorna o máximo divisor comum de ab. Soma de fração: (a/b)+(c/d)=( (a.d+c.b) / b.d ) simplificada. Multiplicação de fração: (a/b) * (c/d)= ( (a*c) / (b*d) ) simplificada. Igualdade: (a/b)== (c/d) se a*d == b*c. Não igualdade: (a/b) != (c/d) se a*d b*c Maior ou igual que: (a/b)(c/d) se a*d b*c Tópicos abordados: Construtores em geral, destrutores, tipo long, criando métodos de conversão de tipos, métodos chamando métodos do mesmo objeto, operador % que retorna o resto da divisão de dois inteiros. //header file para o TAD fracao. //File easyfra.h long mdc(long n,long d); //maximo divisor comum metodo de Euclides. class fracao { private: long num; //numerador long den; //denominador public: fracao(long t,long m); //construtor comum void simplifica(void); //divisao pelo mdc ~fracao() { /* nao faz nada*/ } //Nao e preciso fazer nada. //operacoes matematicas basicas fracao soma (fracao j); fracao multiplicacao(fracao j); //operacoes de comparacao int igual(fracao t); int diferente(fracao t); int maiorouigual(fracao t); //operacoes de input output void mostra(void); //exibe fracao no video void cria(void); //pergunta ao usuario o valor da fracao //operacoes de conversao de tipos double convertedbl(void); //converte para double long convertelng(void); long fracao::convertelng(void) { long lng; lng=num/den; return lng; } #include <iostream.h> #include "easyfra.h" //nossa definicao da classe #include <stdio.h> main() { fracao a(0,1),b(0,1); cout << " Entre com fracao a: "; a.cria(); a.mostra(); cout << " Entre com fracao b: "; b.cria(); b.mostra(); fracao c(a.soma(b)); //c(a+b) cout << endl << "c de a+b:"; c.mostra(); cout << endl << "a*b"; c=a.multiplicacao(b); c.mostra(); cout << endl << "a+b"; c=a.soma(b); c.mostra(); cout << endl << "a>=b"; cout << a.maiorouigual(b); cout << endl << "a==b"; cout << a.igual(b); cout << endl << "a!=b"; cout << a.diferente(b); cout << endl << "long(a) "; cout << a.convertelng(); cout << endl << "double(a) "; cout << a.convertedbl(); return 0; } Comentários: Observe o seguinte código usado no programa: fracao c(a.soma(b));. O resultado de a.soma(b) é uma fração, mas não criamos um construtor que recebe uma fração como argumento, como isso foi possível no programa? Simples, a linguagem oferece para as classes que você cria a cópia bit a bit. Cuidado com o uso dessa cópia em conjunto com alocação dinâmica, objetos poderão ter cópias iguais de ponteiros, ou seja compartilhar uso de posições na memória. De qualquer forma, em 0 explicaremos como redefinir esta cópia de modo a evitar situações indesejáveis como a cópia de ponteiros e em 1.5.3 explicaremos melhor os perigos de usar este tipo de cópia em conjunto com alocação dinâmica. Resultado do programa: Entre com fracao a: Numerador:4 Denominador:2 (2/1) Entre com fracao b: Numerador:5 Denominador:3 (5/3) c de a+b:(11/3) a*b(10/3) a+b(11/3) a>=b1 a==b0 a!=b1 long(a) 2 double(a) 2 Exercícios: 1) Modifique o tipo fração para aceitar as seguintes funções membro. long retorna_num(void) {return num;} long altera_den(float a) { den=a;} Considerando que os atributos declarados em private não são acessíveis fora da classe, descreva a utilidade dessas funções. Eles são úteis se usadas pelas próprias funções membro de fração? *2) Implemente o tipo abstrato de dados número complexo com as operações matemáticas inerentes. Faça antes um projeto das funções membro que serão implementadas descrevendo também as operações matemáticas necessárias. *3) Outro tipo abstrato de dados extremamente útil é o tipo abstrato de dados string que deve fornecer: copy constructor, operações de comparação ( ordem lexicográfica ), concatenação, substring , entre outras. Implemente este tipo abstrato de dados, tendo em mente a seguinte pergunta: "Você poderia substituir um tipo fracao ou inteiro usado em uma rotina de ordenacao pela sua implementação de string sem fazer grandes modificações?". Essa pergunta final será muito importante quando começarmos a discutir templates 4.3. 4)Continue trabalhando no TAD fração, implemente os métodos restantes (menor que, subtração) de modo análogo às implementações fornecidas. 5)Pesquise sobre matrizes em C++. Crie um tipo abstrato de dados matriz que suporte atribuições e leituras de células contendo elementos do tipo float. Crie outros métodos para este tipo abstrato de dados como multiplicação por uma constante. 1.5. CONSIDERAÇÕES C++: Neste tópico iremos explicar recursos mais particulares de C++, porém não menos importantes para a programação orientada a objetos na linguagem. Alguns recursos como const que está relacionado com encapsulamento poderão ser retirados deste tópico em novas versões do tutorial. 1.5.1. CONST Este exemplo mostra o uso de funções const e sua importância para o encapsulamento. Const pode qualificar um parâmetro de função (assegurando que este não será modificado), uma função membro (assegurando que esta não modifica os dados membro de sua classe), ou uma instância de objeto/tipo (assegurando que este não será modificado.) Os modos de qualificação descritos atuam em conjunto, para assegurar que um objeto const não será modificado, C++ só permite que sejam chamados para este objeto funções membro qualificadas como const. Const é também um qualificador, "specifier". #include <iostream.h> #include <math.h> //double sqrt(double x); de math.h retorna raiz quadrada do numero //double pow(double x, double y); de math.h calcula x a potencia de y const float ZERO=0.0; class ponto { private: float x; //sao ocultos por default nao precisaria private mas e' bom float y; //sao ocultos por default public: //daqui em diante tudo e acessivel em main. ponto(float a,float b) #include <iostream.h> struct ponto { float x; float y; void inicializa(float a,float b) { x=a; y=b; } //Apesar de nao especificado compilador tenta //expandir chamada da funcao como inline porque esta dentro da definicao da classe. void mostra(void); //com certeza nao e' inline, externa a classe e sem qualificador. inline void move(float dx,float dy); //e' inline , prototipo, definicao }; void ponto::mostra(void) {cout << "X:" << x << " , Y:" << y << endl;} inline void ponto::move(float dx,float dy) //implementacao, codigo { x+=dx; y+=dy; } void main() { ponto ap; ap.inicializa(0.0,0.0); ap.mostra(); ap.move(1.0,1.0); ap.mostra(); ap.x=100.0; ap.mostra(); } Comentários: O compilador tenta converter a função inicializa em inline, embora não esteja especificado com a palavra reservada inline que isto é para ser feito. Esta é uma regra, sempre que a função estiver definida na própria classe (struct{}) o compilador tentará convertê-la em inline. Foi dito que o compilador tenta porque isto pode variar de compilador para compilador, se ele não consegue converter em inline , devido a complexidade da função, você é normalmente avisado, tendo que trocar o lugar da definição da função membro. Note que se a função membro é implementada fora da classe, tanto o protótipo da função membro, quanto a implementação devem vir especificados com inline para que a conversão ocorra. Resultado do programa: X:0 , Y:0 X:1 , Y:1 X:100 , Y:1 1.5.3. ALOCAÇÃO DINÂMICA COM NEW E DELETE. Neste tópico serão apresentados os recursos de C++ para alocação dinâmica de variáveis de tipos simples e objetos, ou seja, estudaremos a alocação de estruturas em tempo de execução. 1.5.3.1. PONTEIROS, "POINTERS" Este exemplo mostra como trabalhar com ponteiros para variáveis de tipos pré-definidos (não definidos pelo usuário) usando new e delete. O programa a seguir cria um ponteiro para uma variável inteira, aloca memória para esta variável e imprime seu valor. #include <iostream.h> void main() { int* a; //declara um ponteiro para endereco de variavel inteira a=new int(3); //aloca memoria para o apontado por a, gravando neste o valor 3 cout << (*a) << endl ; //imprime o valor do apontado por a delete a; //desaloca memoria } Resultado do programa: 3 Diagrama das variáveis na memória: Comentários: Se a fosse uma struct ou class e tivesse um dado membro chamado dd, poderíamos obter o valor de dd através de (*a).dd que pode ser todo abreviado em a->dd onde -> é uma seta, ou flecha que você pode ler como "o apontado" novamente. Os parênteses em (*a).dd são necessários devido a precedência do operador . com relação ao *. Esta sintaxe abreviada será bastante usada quando alocarmos dinamicamente objetos. Observação: int* a; a=new int(3); pode ser abreviado por: int* a=new int(3); 1.5.3.2. VETORES CRIADOS ESTATICAMENTE O exemplo a seguir aloca estaticamente, em tempo de compilação, um vetor de inteiros com três posições. Em seguida, gravamos as três posições e as mostramos na tela: #include <iostream.h> void main() { int a[3]; //aloca o vetor de tamanho 3, estaticamente a[0]=1; //atribui a posicao indice 0 do vetor a[1]=2; //atribui 2 a posicao indice 1 do vetor a[2]=3; //atribui 3 a posicao indice 2 do vetor cout << a[0] << " " << a[1] << " " << a[2] << endl; //mostra o vetor } Resultado do programa: 1 2 3 Resumo da sintaxe de vetores: int a[3]; //cria um vetor de inteiros a com tres posicoes, indices uteis de 0 ate 2 float b[9]; //cria um vetor de float b com nove posicoes, indices uteis de 0 ate 8 b[8]=3.14156295; //grava 3.1415... na ultima posicao do vetor b if (b[5]==2.17) { /*acao*/} ; //teste de igualdade Diagrama do vetor: Perceba que a faixa útil do vetor vai de 0 até (n-1) onde n é o valor dado como tamanho do vetor no momento de sua criação, no nosso caso 3. { cout << "Entre com o valor da posicao " << i << ":"; cin >> vet[i]; cout << endl; } //loop de leitura no vetor for (int j=0;j<tamanho;j++) { cout << "Posicao " << j << ":" << vet[j]<<endl; } //loop de impressao do vetor } Resultado do programa: Entre com o tamanho do vetor a criar3 Entre com o valor da posicao 0:1 Entre com o valor da posicao 1:2 Entre com o valor da posicao 2:3 Posicao 0:1 Posicao 1:2 Posicao 2:3 Comentários: int* a; Declara um ponteiro para inteiro. a=new int[10]; Diferente de new int(10); os colchetes indicam que é para ser criado um vetor de tamanho 10 e não uma variável de valor 10 como em 1.5.3.1. Ao contrário de alocação estática, o parâmetro que vai no lugar do valor 10 não precisa ser uma expressão constante. int* a; a=new int[10]; equivale a forma abreviada: int* a=new int[10]; A faixa de índices úteis do vetor novamente vai de 0 até (10-1) ou (n-1). 1.5.3.5. COPIA DE OBJETOS COM VETORES ALOCADOS DINAMICAMENTE. Essa determinação do tamanho do vetor em tempo de execução vai tornar a cópia de objetos feita pelo compilador diferente, ele vai copiar o ponteiro para o vetor, ou seja os objetos passam a compartilhar a estrutura na memória, o que nem sempre pode ser desejável! Existem maneiras de redefinir esta cópia feita pelo compilador o que veremos em 0. #include <iostream.h> class vetor_tres { public: int* vet; //vetor alocado estaticamente numa classe. vetor_tres(int a,int b,int c) { vet=new int[3]; vet[0]=a; vet[1]=b; vet[2]=c; } //construtor do vetor void mostra(void) { cout << vet[0] << " " << vet[1] << " " << vet[2] << endl;} //funcao membro para mostrar o conteudo do vetor }; void main() { vetor_tres v1(1,2,3); //criacao de um objeto vetor. vetor_tres v2(15,16,17); //criacao de um objeto vetor. v1.mostra(); //mostrando o conteudo de v1. v2.mostra(); //mostrando o conteudo de v2. v2=v1; //atribuindo objeto v1 ao objeto v2. v2.mostra(); //mostrando v2 alterado. v1.vet[0]=44; v1.mostra(); //mostrando o conteudo de v1. v2.mostra(); //mostrando o conteudo de v2. } Resultado do programa: 1 2 3 15 16 17 1 2 3 44 2 3 44 2 3 Comentários: Note que quando alteramos a cópia de v1, v1 se altera. Isto ocorre porque o vetor não é copiado casa a casa como em 1.5.3.3, só se copia o ponteiro para a posição inicial do vetor, a partir do qual se calcula os endereços das posições seguintes v[3]==*(v+3). Sobre o texto acima: "a partir do qual se calcula os endereços das posições seguintes", veja o programa exemplo: #include <iostream.h> void main() { int* v; v=new int[3]; cout << *(v+1)<<endl; //imprime o lixo contido na memoria de v[1] cout << v[1] <<endl; //imprime o lixo contido na memoria de v[1] //*(v)==v[0] é uma expressao sempre verdadeira } Resultado do programa: 152 152 Comentários: O que é importante deste exercício é que só se armazena a posição inicial do vetor, as outras posições são calculadas com base no tamanho do tipo alocado e regras de aritmética de ponteiros. Não podemos nos estender muito neste tema, leia sobre aritmética de ponteiros, porém para este tutorial, basta usar os vetores de forma que esta aritmética fique por conta da linguagem. Vetores alocados dinamicamente e ponteiros são a mesma coisa, isto pode ser visto nesse programa exemplo que mistura a sintaxe de vetores e de ponteiros. 1.5.3.6. TAD E ALOCAÇÃO DINÂMICA. Tipo abstrato de dados vetor. if (v[i]>v[candidato]) candidato=i; return v[candidato];} //programa pricipal #include <iostream.h> #include "exvet1.h" main() { int aux; //para ler valor a atribuir vetor meu(5); for (int i=meu.primeiro();i<=meu.ultimo();i++) { cout << "Entre com valor da posicao:" << i << "\n"; cin >> aux; meu.atribui(i,aux); } for (int j=meu.primeiro();j<=meu.ultimo();j++) cout<< meu.conteudo(j)<< " "; cout <<endl << "Maximo:" << meu.maximo(); return 0; } Comentários: O método ~vetor() {delete v;} //use delete []v;depende do compilador é um destrutor, assim como o construtor ele não tem valor de retorno porque é chamado pelo sistema operacional quando o objeto sai de escopo ou quando você desaloca memória de um ponteiro para o objeto. A única ação do destrutor é liberar a memória ocupada pelo atributo vetor de inteiros (int * v) da classe vetor. De um modo geral os destrutores servem para "arrumar a casa" quando objetos são desalocados ou saem de escopo. A sintaxe dos métodos para deleção de vetores delete v; ou delete []v; podem variar de compilador para compilador, o usuário deve checar que sintaxe seu compilador suporta. Note que quando não dispúnhamos do destrutor o programador era obrigado a deletar passo a passo todas as estruturas dinâmicas dos objetos que saiam de escopo, existem técnicas avançadas para obter coleta automática de lixo em C++ baseadas neste ponto. Resultado do programa: Entre com valor da posicao:0 4 Entre com valor da posicao:1 5 Entre com valor da posicao:2 9 Entre com valor da posicao:3 2 Entre com valor da posicao:4 1 4 5 9 2 1 Maximo:9 Dica de programação: Saiba que uma prática bastante útil na fase de testes de um programa é introduzir mensagens informativas em pontos convenientes. Quando trabalhando com objetos tal prática pode ser usada de vários modos, por exemplo pode-se inserir uma mensagem no destrutor de uma classe para verificar quando os objetos são eliminados e se são eliminados corretamente. Exercícios: 1) Note que os métodos atribui e conteúdo apresentam estilos diferentes de lidar com índices fora dos limites do vetor. Um apresenta uma mensagem de erro e termina o programa. Outro simplesmente não executa a ação se os parâmetros não estiverem corretos. Escolha um dos estilos e uniformize o programa. Quando você estudar "exception handling" 4.5 verá maneiras melhores de lidar com esses problemas. 2) Implemente uma função membro chamada ordena para o tipo abstrato de dados vetor definido acima. Use qualquer algoritmo de ordenação. 3)Crie destrutores para as classes que você já implementou, mesmo que elas não tenham atributos que usem alocação dinâmica. Introduza mensagens tipo cout << "Goodbye..."; nos destrutores destas classes . Como essas mensagens podem ser úteis? Defina vários níveis de blocos de código, fazendo assim com que objetos saiam de escopo em tempos diferentes e teste seus destrutores. 4)Melhore o exercício anterior introduzindo um nome para o objeto. Este nome deve ir nas 0 0 7 Fsaídas de tela e é nome dado para o construtor como uma string (char*). Não esqueça de deletar esta string no destrutor da classe depois de imprimir a mensagem. 5)Crie uma função membro de nome preenche, que inicializa todas as posições de um vetor com o valor de um de seus argumentos. 6)Defina um programa chamado grandes que implementa o tipo abstrato de dados números grandes e inteiros, este tipo deve usar um vetor do tipo numérico que você achar conveniente para representar os algarismos. Talvez estudar circuitos lógicos como somadores, etc o ajude a implementar as quatro operações matemáticas básicas para estes tipo em termos de "look ahead carrier" e outras técnicas de performance para implementar as operações. 7)Note que na alocação do vetor: vetor::vetor (int tam) {v=new int[tam]; tamanho=tam;} , não é checado se o valor passado tam é maior que 0, faça este teste. Quando explicarmos "exception handling" você terá métodos melhores de lidar com esses erros. 1.5.3.7. ALOCANDO OBJETOS Pula, em Modula-3 já são alocados. Este exemplo mostra como trabalhar com ponteiros para objetos, new e delete, como alocar memória e chamar corretamente os construtores. Comentários: Perceba que agora não estamos alocando um vetor de inteiros com três posições (new int[3]), mas um inteiro que contém o valor 3. Você pode ler o código (*a) como: "O apontado de a". Se a fosse uma struct ou class e tivesse um dado membro chamado dd, poderíamos obter o valor de dd através de (*a).dd que pode ser todo abreviado em a->dd onde -> é uma seta, ou flecha que você pode ler como "o apontado" novamente. Os parênteses em (*a).dd são necessários devido a precedência do operador . com relação ao *. 1.5.4. REFERÊNCIA & Aqui vou ensinar passagem por referência, lembre-se que objetos são passados por referência, porque eles são referência, ponteiros. Este tópico vai explicar como usar o operador &, também chamado de operador "endereço de...". Este operador fornece o endereço, a posição na memória de uma variável, de um argumento, etc. Sua utilização é muito simples, se a é uma variável inteira, &a retorna um ponteiro para a. O programa a seguir ilustra a sintaxe do operador: #include <iostream.h> void main() { int a; a=10; int* p; p=& a; (*p)=13; cout << a; } Explicando o programa passo a passo: int a; A matriz bidimensional é representada linearmente (vetor) e as funções membro de conversão de índice linear para colunas e/ou linhas e atribuições são fornecidas, mas somente as necessárias para o jogo . Recursos utilizados: Argumentos de linha de comando, funções de conversão da stdlib, representação linear de uma matriz, const. Argumentos de linha de comando: Você pode fazer programas que aceitem argumentos de linha de comandos, resultando em algo equivalente a: dir /w //dos format A: //dos ou ls -la //unix Esses argumentos podem ficar disponíveis na chamada da função main de seu programa como os parâmetros "argument counter" e "argument values", veja trecho de programa abaixo. void main(int argc, char *argv[]) {.../*use argv e argc*/ ...} argc é um inteiro argv é um vetor de char* contendo os argumentos passados. (/w ...) argv [0] é o nome do programa chamado. A faixa útil de argvalues é : argv[1]...argv[argc-1] Portanto se argc==2, temos um único argumento de linha de comando, disponível como string em argv[1] . Faça alguns testes com o mesma main usada no programa abaixo e alguns cout's imprimindo os argumentos para entender melhor o assunto. Const: Já exemplificado em 1.5.1 Representação linear de uma matriz: Pode-se representar uma matriz de qualquer dimensão em um vetor. Veja o exemplo de uma matriz bidimensional de inteiros mostrada no formato indicelinear:valor armazenado Vantagem da representação linear (vetor): para referenciar uma posição gasta-se somente um inteiro contra dois da representação matriz (linha,coluna). Podemos agora considerar posições que apontam para outras posições, basta interpretar o conteúdo do vetor como um índice linear. Desvantagem da representação linear (vetor): é necessário criar funções de conversão de índice na forma (linha,coluna) para (índice linear) e de (índice linear) para (coluna) ou (linha). Veja as funções lin e col e linear do exemplo seguinte. Funções de conversão da Stdlib: A função int atoi(char* a) converte uma string para um inteiro. #include <stdlib.h> class matriz //quebra cabeca de tijolos deslizantes { private: int linhas; //numero de linhas da matriz int colunas; //numero de colunas na matriz int tam; //=linhas*colunas int *lc; //vetor de tamanho linha*colunas representando matriz:0..(tam-1) public: matriz(const int l,const int c); //cria matriz LxC //qualquer uma das funcoes abaixo retorna nil se nao conseguiu int* linear(const int lin,const int col)const ; //ind linear a partir de linha e coluna int* col(const int indlin)const ;//coluna a partir do indice linear int* lin(const int indlin)const ; //linha a partir do indice linear int trocaindlin(const int i,const int j); //argumentos: 2 indices lineares //retorna 1 conseguiu, 0 nao conseguiu int atribuiindlin(const int i,const int v); //atribui v ao indice i //retorna 1 conseguiu, 0 nao conseguiu int* retornaindlin(const int i); //retorna conteudo do indice i //retorna nil se nao conseguiu int getl(void); //retorna numero de linhas int getc(void); //retorna numero de colunas int gett(void); //retorna tamanho = linhas*colunas ~matriz(); }; #include "matriz.h" matriz::matriz(const int l,const int c) //cria matriz { lc=new int[l*c]; //l,c dimensoes ; lc vetor[l*c] linhas=l; colunas=c; tam=linhas*colunas; } int* matriz::linear(const int alin,const int acol) const //ind linear a partir de linha e coluna { int* result=new int; //valor de retorno if ( (0<alin) && (alin<=linhas) && (0<acol) && (acol<=colunas) ) { (*result)=(alin-1)*colunas+acol; return result; } else return NULL; } int* matriz::col(const int indlin)const //coluna a partir do indice linear { int* result=new int; if ( (0<indlin) && (indlin<=tam) ) { (*result)=(indlin % colunas); if ((*result)==0) (*result)=colunas; return result; } else return NULL; } int* matriz::lin(const int indlin)const //linha a partir do indice linear { int* result=new int; if ( (0<indlin) && (indlin<=tam) ) { (*result)=(int((indlin-1) / colunas)+1); return result; } else return NULL; } int matriz::trocaindlin(const int i,const int j) //argumentos: 2 indices lineares //retorna 1 conseguiu, 0 nao conseguiu { int aux; if ( (0<i) && (i<=tam) && (0<j) && (j<=tam) ) { aux=lc[i-1]; //efetua a troca lc[i-1]=lc[j-1]; //embora para usuario a matriz vai de 1 ate l*c //para mim vai de o ate l*c-1 mqc->atribuiindlin(mqc->gett(),0);//atribui zero a posicao da celula vazia vazio=mqc->gett(); //define posicao da celula vazia mov=0; //sem nenhum movimento embaralha(); //embaralha quebracabeca } void quebracab::mostra() const //mostra quebra cabeca { int i,j; //linha e coluna int* ind; //valor atual for(i=1;i<=mqc->getl();i++) //loop das linhas { //linhas for(j=1;j<=mqc->getc();j++) //loop das colunas { //colunas ind=mqc->linear(i,j); //resultado tambem e ponteiro if ((*ind)==vazio) cout << setw(4)<<" "; //vazio=espaco else cout << setw(4) << (*mqc->retornaindlin(*ind)); //nao e o vazio, mostra conteudo } //colunas cout << endl; //mudanca de linha } //linhas cout << endl; } void quebracab::movedir() //move celula a esq de vazio para direita //espaco move para esquerda { if ( (*(mqc->col(vazio))) !=1) /*nao esta na borda esquerda*/ { mqc->trocaindlin(vazio,vazio-1); mov++; vazio=vazio-1; } } void quebracab::moveesq() //espaco move para direita { if ( (*(mqc->col(vazio)))!=mqc->getc() ) /*nao esta na borda direita*/ { mqc->trocaindlin(vazio,vazio+1); mov++; vazio=vazio+1; } } void quebracab::movebaixo() //espaco move para cima { if ((*(mqc->lin(vazio)))!=1) /*nao esta no topo*/ { mqc->trocaindlin(vazio,vazio-(mqc->getc())); //chama funcao private mov++; vazio=vazio-(mqc->getc()); } } void quebracab::movecima() //espaco move para baixo { if ((*mqc->lin(vazio))!=mqc->getl()) /*nao esta em baixo*/ { mqc->trocaindlin(vazio,vazio+(mqc->getc())); mov++; vazio=vazio+(mqc->getc()); } } void quebracab::embaralha() //embaralha quebracabeca { int i,j; //loop principal, loop secundario int r; //r times for(j=0;j<mqc->gett();j++) { r=(rand()% mqc->getc()); for(i=0;i<r;i++) {this->movedir();} //move r vezes r=(rand()% mqc->getl()); for(i=0;i<r;i++) {this->movebaixo();} r=(rand()% mqc->getc()); for(i=0;i<r;i++) {this->moveesq();} r=(rand()% mqc->getl()); for(i=0;i<r;i++) {this->movecima();} } mov=0; //inicializa movimentos } int quebracab::tstsolucao() const //testa se quebracabeca esta solucionado { int i=1,cont=1; while( cont && (i< (mqc->gett()) ) ) { if ((*(mqc->retornaindlin(i)))==(i)) cont=1; else cont=0; i++; } return (cont); //i=qctam esta solucionado 1 2 3... 24 0 } quebracab::~quebracab() {if (mqc!=NULL) delete mqc; cout << " Quebra cabeca destruido!\n";} // destroi quebracab main(int argc,char *argv[]) //argumentos sao declarados aqui { //main int ladocomp; char opcao; //usada em menu como variavel de opcao if (argc>1) ladocomp=atoi(argv[1]); //convertendo argumento de linha de comando para inteiro else ladocomp=tam_padrao; //valor default quebracab aquebracab(ladocomp); //criando quebracab do { aquebracab.mostra(); cout <<"\n"; //menu de opcoes cout <<" Movimentos:" << aquebracab.retorna_mov()<< "\n"; cout <<" 4<- 6-> 8Cima 2Baixo SSair Eembaralha \n"; cout <<" Reconhece sequencias de comandos: 268624 <Enter>\n"; cout <<" Aceita argumento de linha de comando: quebracab 4 (cria quebracabeca 4 x 4)\n"; cout <<" Entre comando:"; cin >> opcao; //le opcao do usuario cout <<"\n"; switch(opcao) //executa opcao do usuario { case 'E': case 'e': aquebracab.embaralha(); break; case '8': aquebracab.movecima(); //header file class ponto { private: float x; //sao ocultos por default float y; //sao ocultos por default public: //daqui em diante tudo e acessivel. ponto(float a,float b); void inicializa(float a,float b); float retorna_x(void); float retorna_y(void); void altera_x(float a); void altera_y(float b); void mostra(void); }; class ponto_reflete:public ponto //classe filha { private: //se voce quer adicionar atributos... public: ponto_reflete(float a, float b); void reflete(void); }; class ponto_move:public ponto { public: ponto_move(float a,float b); void move(float dx,float dy); }; //implementation file #include <iostream.h> #include "pontos.h" ponto::ponto(float a,float b) { inicializa(a,b); } void ponto::inicializa(float a,float b) { x=a; y=b; } float ponto::retorna_x(void) { return x; } float ponto::retorna_y(void) { return y; } void ponto::altera_x(float a) { x=a; } void ponto::altera_y(float b) { y=b; } void ponto::mostra(void) { cout << "(" << x << "," << y << ")" <<endl; } ponto_reflete::ponto_reflete(float a,float b):ponto(a,b) { } void ponto_reflete::reflete(void) { altera_x(-retorna_x()); altera_y(-retorna_y()); } ponto_move::ponto_move(float a,float b):ponto(a,b) { } void ponto_move::move(float dx,float dy) { altera_x(retorna_x()+dx); altera_y(retorna_y()+dy); } #include <iostream.h> #include "pontos.h" void main() { ponto_reflete p1(3.14,2.17); p1.reflete(); cout << "P1"; p1.mostra(); ponto_move p2(1.0,1.0); p2.move(.5,.5); cout << "P2"; p2.mostra(); } Resultado do programa: P1(-3.14,-2.17) P2(1.5,1.5) Herança Private: Ainda não foi inserido no tutorial nenhum exemplo com este tipo de herança, mas o leitor pode experimentar mais tarde especificar uma classe herdeira por heranca private como: class herdeira: private nome_classe_base; , e notará que as funções membro desta classe base só são acessíveis dentro das declarações da classe filha, ou seja a classe filha não atende por essas funções membro, mas pode usá-las em seu código. Herança private é um recurso que você precisa tomar cuidado quando usar. Normalmente, quando usamos herança dizemos que a classe filha garante no mínimo o comportamento da classe pai (em termos de funções membro) , a herança private pode invalidar esta premissa. Muitos programadores usam herança private quando ficaria mais elegante, acadêmico, trabalhar com agregação. Uma classe pilha pode ser construída a partir de uma classe que implementa uma lista ligada por agregação ou por herança private. Na agregação (a escolhida em hierarquias de implementação) a classe pilha possui um dado membro que é uma lista ligada. 2.1.2. PROTECTED Igual ao exemplo um, mas agora tornando os atributos da classe pai acessíveis para as classes filhas através do uso de protected. Protected deixa os atributos da classe pai visíveis, acessíveis "hierarquia abaixo". Diagramas de acesso, visibilidade, de dados membro e funções membro de uma classe pai para uma classe filha ou herdeira: Para uma classe filha por herança pública: Visibilidade da classe herdeira para o restante do programa: //header file class ponto { protected: //*****aqui esta a diferenca ****** float x; p2.move(.5,.5); cout << "P2"; p2.mostra(); } 2.1.3. REDEFINIÇÃO DE FUNÇÕES MEMBRO HERDADAS . Igual ao exemplo anterior, mas agora redefinindo a função membro mostra para a classe filha ponto reflete. Comentários: Na verdade este exemplo deveria pertencer ao tópico de polimorfismo, contudo, nos exemplos seguintes usaremos também redefinições de funções membro, portanto faz-se necessário introduzi-lo agora. Teremos mais explicações sobre o assunto. Uma classe filha pode fornecer uma outra implementação para uma função membro herdada, caracterizando uma redefinição "overriding" de função membro. Importante: a função membro deve ter a mesma assinatura (nome, argumentos e valor de retorno), senão não se trata de uma redefinição e sim sobrecarga "overloading". No nosso exemplo a classe ponto_reflete redefine a função membro mostra da classe pai, enquanto que a classe herdeira ponto_move aceita a definição da função membro mostra dada pela classe ponto que é sua classe pai. //header file class ponto { private: float x; //sao ocultos por default float y; //sao ocultos por default public: //daqui em diante tudo e acessivel. ponto(float a,float b); void inicializa(float a,float b); float retorna_x(void); float retorna_y(void); void altera_x(float a); void altera_y(float b); void mostra(void); }; class ponto_reflete:public ponto { private: //se voce quer adicionar dados membro public: ponto_reflete(float a, float b); void reflete(void); void mostra(void); //redefinicao }; class ponto_move:public ponto { public: ponto_move(float a,float b); void move(float dx,float dy); //esta classe filha nao redefine mostra }; //implementation file #include <iostream.h> #include "pontos.h" ponto::ponto(float a,float b) { inicializa(a,b); } void ponto::inicializa(float a,float b) { x=a; y=b; } float ponto::retorna_x(void) { return x; } float ponto::retorna_y(void) { return y; } void ponto::altera_x(float a) { x=a; } void ponto::altera_y(float b) { y=b; } void ponto::mostra(void) { cout << "(" << x << "," << y << ")" <<endl; } ponto_reflete::ponto_reflete(float a,float b):ponto(a,b) { } void ponto_reflete::reflete(void) { altera_x(-retorna_x()); altera_y(-retorna_y()); } void ponto_reflete::mostra(void) { cout << "X:" << retorna_x() << " Y:"; cout << retorna_y() << endl; } //somente altera o formato de impressao ponto_move::ponto_move(float a,float b):ponto(a,b) { } void ponto_move::move(float dx,float dy) { altera_x(retorna_x()+dx); altera_y(retorna_y()+dy); } #include <iostream.h> #include "pontos.h" void main() { ponto_reflete p1(3.14,2.17); p1.reflete(); cout << "P1"; p1.mostra(); ponto_move p2(1.0,1.0); p2.move(.5,.5); cout << "P2"; p2.mostra(); } Resultado do programa: P1X:-3.14 Y:-2.17 P2(1.5,1.5) {prox=NULL;cout << "Hi";} no::no(int i,no* p) {info=i;prox=p;cout << "Hi";} no* no::get_prox(void){return prox;} void no::set_prox(no* p) {prox=p;} int no::get_info(void) {return info;} void no::set_info(int i) {info=i;} no* no::dobra(void) { if (get_prox()==NULL) return new no(get_info(),NULL); else return new no(get_info(),this->get_prox()->dobra()); //recursividade para duplicacao da lista } no::~no(void) {cout << "bye";} lista::lista(void):primeiro(NULL) {} //bloco de codigo vazio Boolean lista::vazia(void)const { return Boolean(primeiro==NULL); } Boolean lista::contem(int el) const//mais rapido que iterador { no* curr; int Conti; curr=primeiro; Conti=TRUE; while ((curr!=NULL) && Conti ) { if (curr->get_info()!=el) {if (curr->get_prox()==NULL) Conti=FALSE; else curr=curr->get_prox();} else Conti=FALSE; }; //while return Boolean(curr->get_info()==el); }; void lista::insere_primeiro(int elem) { no* insirame; if (primeiro==NULL) //lista vazia primeiro=new no(elem,NULL); else { insirame=new no(elem,primeiro); primeiro=insirame; }; }; int* lista::remove_primeiro() { int* devolvame=new int; //return no* temp; //to delete if (primeiro==NULL) return NULL; //lista vazia else { (*devolvame)=primeiro->get_info(); temp=primeiro; primeiro=primeiro->get_prox(); delete temp; return devolvame; }; }; void lista::mostra() const { no* curr; cout << "="; curr=primeiro; while (curr!=NULL) { cout <<"("<<curr->get_info()<<")"<<"-"; curr=curr->get_prox(); }; } lista::~lista(void) { no* temp; while (primeiro!=NULL) { temp=primeiro; primeiro=primeiro->get_prox(); delete temp; }; } listaordenada::listaordenada(void):lista() {}; Boolean listaordenada::contem(int el)const { no* curr; Boolean conti=TRUE; curr=primeiro; while ((curr!=NULL) && conti) { if (curr->get_info()<el) curr=curr->get_prox(); else conti=FALSE; }; if (curr==NULL) return FALSE; else return Boolean(curr->get_info()==el); } void listaordenada::insere_primeiro(int elem) { no* curr=primeiro; no* prev=NULL; no* insirame; Boolean conti=TRUE; while ((curr!=NULL) && conti) { if (curr->get_info()<elem) {prev=curr; curr=curr->get_prox();} else conti=FALSE; }; insirame=new no(elem,curr); if (prev==NULL) primeiro=insirame; else prev->set_prox(insirame); } int* listaordenada::remove_elemento(int el) { int* devolvame=new int; no* curr=primeiro; no* prev=NULL; no* deleteme; }; delete ultimo; } #include "mlisth.h" main() { listaordenada minha; char option; //use in menu as option variable int el; //elemento a inserir int* receptor; do { cout <<"\n"; //menu options display cout <<"P:Insere no primeiro.\n"; cout <<"R:Remove no primeiro.\n"; cout <<"D:Remove elemento.\n"; cout <<"E:Existe elemento?\n"; cout <<"V:Vazia?\n"; cout <<"M:Mostra lista.\n"; cout <<"Q:Quit teste lista.\n"; cout <<"Entre comando:"; cin >> option; //reads user option switch(option) //executes user option { case 'D': case 'd': cout << "Entre elemento:"; cin >>el; receptor=minha.remove_elemento(el); if (receptor==NULL) cout << "NULL" << endl; else cout << (*receptor) << endl; break; case 'P': case 'p': cout << "Entre elemento:"; cin >> el; minha.insere_primeiro(el); break; case 'R': case 'r': if (!minha.vazia()) cout << (*minha.remove_primeiro()) <<endl; else cout << "NULL, Lista vazia." <<endl; break; case 'M': case 'm': minha.mostra(); break; case 'E': case 'e': cout << "Entre elemento:"; cin >>el; cout << minha.contem(el); break; case 'V': case 'v': cout << minha.vazia(); break; default: ; } //switch-case code block } while ((option!='Q') && (option!='q')); //menu loop code block return 0; } //main code block Comentários: Note que o programa principal só testa a lista ordenada, em outros programas exemplo baseados neste veremos testes para a lista_ultimo. Exercícios: 1)Experimente derivar (criar classes herdeiras) outras classes lista com propriedades de seu interesse tais como obtenção de sublista. 2)Introduza na classe lista a contagem do número de elementos numa lista. 3)Crie uma função membro chamado void remove_todos(void); que simplesmente deixa a lista vazia. *4)Suponha que você é um programador de uma empresa e teve que implementar a hierarquia de listas para seu grupo de desenvolvimento, segundo uma especificação dada pelo seu chefe. Você introduziria as mudanças sugeridas nos exercícios anteriores, mesmo sabendo que elas não estavam na especificação? Que dificuldade um usuário de sua classe lista teria para introduzi-las caso surgisse a necessidade e você não tivesse feito? Discuta as seguintes maneiras de um programador de seu grupo conseguir o efeito desejado de adicionar as sugestões dos exercícios anteriores a hierarquia: usar herança (derivar uma classe com void remove_todos(void) ), alterar o código original, pedir para você o programador do código original mudar a implementação. Lembre-se que pode existir mais de um programador usando a versão original da hierarquia de listas. 2.2. HIERARQUIAS DE IMPLEMENTAÇÃO Nossas hierarquias de implementação em termos de código (herança) não são hierarquias, usamos delegação para obter pilhas a partir listas. Agregamos uma lista em nossas classes e usamos esta lista de acordo com a lógica envolvida. Tudo o que fizemos poderia ser feito usando herança private, o que justificaria o título "hierarquias de implementação", embora tornasse o nosso texto menos acadêmico. 2.2.1. FILA A PARTIR DE UMA LISTA Reuso de código de uma lista ligada 2.1 para a implementação de uma fila através de agregação. Para podermos declarar e usar um objeto lista na nossa classe fila precisamos conhecer sua interface. Sabemos que nosso objeto lista permite inserir em ambas extremidades, inicio e fim da lista, mas só permite remoções em um extremo, o inicio. Como uma fila permite inserções somente num extremo e remoções nos extremo oposto, precisaremos usar nossa lista da seguinte forma: inserção no final da lista e remoções no começo. Seguem abaixo exemplos de chamadas de funções membro da classe lista implementada em 2.1 para trabalhar com números inteiros. Consideremos uma lista al, são válidas as seguintes operações: al.vazia(); //retorna se a lista (fila agora) esta vazia. al.contem(el); //retorna 1 se el pertence a lista e 0 se el nao pertence a lista. al.insere_ultimo(el); //insere no final da lista (entrada da fila). al.insere_primeiro(el); //nao usaremos na implementacao de fila, usariamos se //implementassemos uma pilha. al.remove_primeiro(); //remove no comeco da lista (saida da fila). al.mostra(); //mostra lista (mostra fila em ordem contraria de insercao) Para maiores informações consulte tópico anterior onde definimos esta lista. Por este motivo não vamos incluir sua definição a seguir. //header file para a classe fila #include "mlisth.h" class fila { //agregacao de uma lista de private: listaultimo al; //a lista public: fila(); Boolean vazia(); Boolean contem(int el); Polimorfismo, do grego: muitas formas. Polimorfismo é a capacidade de um operador executar a ação apropriada dependendo do tipo do operando. Aqui operando e operador estão definidos num sentido mais geral: operando pode significar argumentos atuais de um procedimento e operador o procedimento, operando pode significar um objeto e operador um método, operando pode significar um tipo e operador um objeto deste tipo. 3.1.1. SOBRECARGA DE MÉTODOS Modula-3 não oferece este tipo de polimorfismo que pode ser considerado com "ad-hoc". Este tipo de polimorfismo permitiria a existência de vários procedimentos e métodos de mesmo nome, porém com assinaturas levemente diferentes, variando no número e tipo de argumentos. Ficaria a cargo do compilador escolher de acordo com as listas de argumentos os procedimentos ou métodos a serem executados. 3.1.2. REDEFINIÇÃO DE UMA FUNÇÃO MEMBRO PARA UMA CLASSE HERDEIRA .Este exemplo já foi apresentado em 2.1.3. Também trata-se de um polimorfismo, pode ser classificado como polimorfismo de inclusão. 3.1.3. "COPY CONSTRUCTOR" A função membro ponto(ponto& a); é um copy constructor, além disso tem o mesmo nome que ponto(float dx,float dy);. Tal duplicação de nomes pode parecer estranha, porém C++ permite que eles coexistam para uma classe porque não tem a mesma assinatura (nome+argumentos). Isto se chama sobrecarga de função membro, o compilador sabe distinguir entre esses dois construtores. Outras funções membro, não só construtores poderão ser redefinidas, ou sobrecarregadas para vários argumentos diferentes, esse recurso é um polimorfismo do tipo "ad-hoc". O que é interessante para nós é o fato de o argumento do construtor ponto(ponto& a); ser da mesma classe para qual o construtor foi implementado, esse é o chamado "copy constructor". Ele usa um objeto de seu tipo para se inicializar. Outros métodos semelhantes seriam: circulo(circulo& a); mouse(mouse& d);. Implementar copy constructor pode ser muito importante, lembre-se dos problemas com cópias de objetos apresentados em 1.5.3.5. #include <iostream.h> struct ponto { float x; float y; ponto(float a,float b); //construtor tambem pode ser inline ou nao ponto(ponto& a); //copy constructor void mostra(void); void move(float dx,float dy); }; ponto::ponto(float a,float b) { x=a; y=b; } ponto::ponto(ponto& a) { x=a.x; y=a.y; } void ponto::mostra(void) {cout << "X:" << x << " , Y:" << y << endl;} void ponto::move(float dx,float dy) { x+=dx; y+=dy; } void main() { ponto ap(0.0,0.0); ap.mostra(); ap.move(1.0,1.0); ap.mostra(); ponto ap2(ap); ap2.mostra(); } Comentários: Observe o código: ponto::ponto(ponto& a) { x=a.x; y=a.y; } Essa função membro, esse método, pertence a outro objeto que não o argumento a, então para distinguir o atributo x deste objeto, do atributo x de a usamos a.x e simplesmente x para o objeto local (dono da função membro). Exercícios: 1)O copy constructor usa passagem por referência, construa uma função que troca duas variáveis inteiras usando passagem por referência. Analise esse recurso sob a ótica do assunto encapsulamento. Em que casos você pode afirmar ser seguro usar esse recurso? 2) Defina um copy constructor para o tipo abstrato de dados fração apresentado em: 1.4. 3.1.4. SOBRECARGA DE FUNÇÃO EM C++. Este tópico apresenta exemplos importantes para o estudo do restante do tutorial. Sobrecarga "Overloading" de função é um tipo de polimorfismo classificável como ad-hoc. C++ permite que funções de mesmo nome tenham parâmetros distintos. Este exemplo mostra a sobrecarga da função abs que calcula o valor absoluto de um número: //header file funcover.h float abs(float a); int abs(int a); //implementation file #include "funcover.h" float abs(float a) { if (a>0.0) return a; else return -a; } int abs(int a) { if (a>0) return a; else return -a; } #include <iostream.h> #include "funcover.h" void main() { int i1; float f1; cout << abs(int(-10))<<endl; cout << abs(float(-10.1))<<endl; f1=-9.1; i1=8.0; cout << abs(f1) << endl; cout << abs(i1) << endl; "Default arguments" nos dá a oportunidade de fundir esses dois construtores num só resultando no seguinte: fracao(long t=0,long m=1); {num=t; den=m;} onde 1 e 0 são valores sugestão para os argumentos. A instanciação fracao a( ) segundo aquele único construtor cria : (0/1) A instanciação fracao b(1) segundo aquele único construtor cria: (1/1) A instanciação fracao c(1,2) segundo aquele único construtor cria: (1/2) Regras para a criação de "Default arguments": Não são permitidas declarações do tipo fracao(long t=0,long m); uma vez que você inseriu um argumento padrão na lista de argumentos todos a direita deste também deverão ter seus valores sugestão. Então, por esta regra a única alternativa restante para o tipo fração seria fracao(long t,long m=1); Exercícios: 1)Faça a modificação do tipo abstrato de dados fracao retirando os dois construtores mencionados e substituindo por um único. O copy constructor você pode deixar como está. 3.1.6. SOBRECARGA DE OPERADOR O tipo abstrato de dados fração (versão completa) de 1.2.4.4 possui vários operadores sobrecarregados. Algumas sobrecargas deste exemplo envolvem o uso da palavra chave friends que você não aprendeu ainda, portanto atenha-se aos exemplos que não contém essa palavra reservada. Extensão da classe vetor de 1.5.3.6 para incluir um iterador. Este exemplo já é apresentado com templates. Tópicos abordados: Sobrecarga de operador. //header file para classe vetor: vet.h #include <iostream.h> #include <stdlib.h> //exit(1) const int inicio=0; //inicio do vetor class vetor{ private: float* v; //pode ser qualquer tipo que atenda as operacoes < > = int tamanho; public: vetor (int tamanho) ; float& operator[] (int i); float maximo(); //acha o valor maximo do vetor int primeiro(void); int ultimo(void); }; vetor::vetor (int tam) {v=new float[tam]; tamanho=tam;} int vetor::primeiro (void) {return inicio;} int vetor::ultimo (void) {return tamanho-1;} float& vetor::operator[](int i) { if (i<0 || i>=tamanho) {cout << "Fora dos limites! Exit program"; exit(1);} return v[i]; } float vetor:: maximo(void) {int candidato=inicio; for (int i=inicio;i<tamanho;i++) if (v[i]>v[candidato]) candidato=i; return v[candidato];} Explicação das operações, das função membros do iterador vetor: iteradorvetor(vetor & v); :Construtor, já cria o iterador de vetor inicializando-o para apontar para o começo do vetor. virtual int comeca();: Inicializa o iterador para o começo do vetor. virtual int operator!(); : Verifica se a iteração não chegou no fim do vetor: 1 indica que não chegou, 0 indica que chegou no fim do vetor. virtual int operator ++ ();: Faz o iterador mover adiante uma posição. virtual float operator() (); :Retorna o elemento daquela posição do vetor. virtual void operator= (float entra); : Atribui a posição atual do vetor. int pos(); : Retorna a posição (índice) do vetor em que o iterador se encontra, não é virtual porque não faz sentido para um iterador de árvore por exemplo. //it.h , arquivo com definicoes do iterador. class iteradorvetor { private: vetor vetorref; int posicao; public: iteradorvetor(vetor & v); int comeca(); int operator!(); int operator ++ (); float operator() (); void operator= (float entra); int pos(); //retorna posicao, n~ virtual pq n~ faz sentido p/ arvore por ex. }; int iteradorvetor::pos() { return posicao; } int iteradorvetor::operator!() { return posicao<=vetorref.ultimo(); } iteradorvetor::iteradorvetor(vetor & vet):vetorref(vet) { comeca(); } int iteradorvetor::comeca() { posicao=vetorref.primeiro(); return operator!(); } int iteradorvetor::operator ++() { posicao++; return operator!(); } void iteradorvetor::operator=(float entra) { vetorref[posicao]=entra; } float iteradorvetor::operator() () { float copia; copia=vetorref[posicao]; return copia; } int pos(); //retorna posicao, n~ virtual pq n~ faz sentido p/ arvore por ex. //esta e' uma funcao membro acrescentada pela classe filha }; int iteradorvetor::pos() { return posicao; } int iteradorvetor::operator!() { return posicao<=vetorref.ultimo(); } iteradorvetor::iteradorvetor(vetor & vet):vetorref(vet) { comeca(); } int iteradorvetor::comeca() { posicao=vetorref.primeiro(); return operator!(); } int iteradorvetor::operator ++() { posicao++; return operator!(); } void iteradorvetor::operator=(float entra) { vetorref[posicao]=entra; } float iteradorvetor::operator() () { float copia; copia=vetorref[posicao]; return copia; } Os demais arquivos são identicos aos do exemplo sobre vetores de 3.1.6. //header file para classe vetor #include <iostream.h> #include <stdlib.h> //exit(1) const int inicio=0; //inicio do vetor class vetor{ private: float* v; //pode ser qualquer tipo que atenda as operacoes < > = int tamanho; public: vetor (int tamanho) ; float& operator[] (int i); float maximo(); //acha o valor maximo do vetor int primeiro(void); int ultimo(void); }; vetor::vetor (int tam) {v=new float[tam]; tamanho=tam;} int vetor::primeiro (void) {return inicio;} int vetor::ultimo (void) {return tamanho-1;} float& vetor::operator[](int i) { if (i<0 || i>=tamanho) {cout << "Fora dos limites! Exit program"; exit(1);} return v[i]; } float vetor:: maximo(void) {int candidato=inicio; for (int i=inicio;i<tamanho;i++) if (v[i]>v[candidato]) candidato=i; return v[candidato];} #include <iostream.h> #include "exvet6.h" #include "exvetit6.h" main() { int repete=0; int ind; float item; vetor meu(5); iteradorvetor itmeu(meu); for (itmeu.comeca();!itmeu;++itmeu) { cout << "Entre com valor da posicao:" << itmeu.pos() << "\n"; cin >> item; itmeu=item; } for (itmeu.comeca();!itmeu;++itmeu) cout<< itmeu()<< " "; cout << "\nEntre com o indice da posicao a atualizar:\n"; cin >> ind; cout << "Entre com o valor a incluir:"; cin >> item; meu[ind]=item; for (int k=meu.primeiro();k<=meu.ultimo();k++) cout<< meu[k]<< " "; cout <<endl << "Maximo:" << meu.maximo(); return 0; } Resultado do programa: Entre com valor da posicao:0 2 Entre com valor da posicao:1 35 Entre com valor da posicao:2 82 Entre com valor da posicao:3 2 Entre com valor da posicao:4 3 2 35 82 2 3 Entre com o indice da posicao a atualizar: 0 Entre com o valor a incluir:1 1 35 82 2 3
Docsity logo



Copyright © 2024 Ladybird Srl - Via Leonardo da Vinci 16, 10126, Torino, Italy - VAT 10816460017 - All rights reserved