técnicas de programação · 2016-02-04 · de invocar a si mesmo repetidamente até que certa...
TRANSCRIPT
Técnicas de programação
Sílvia Mara da Costa Campos Victer
Técnicas de programação
Tópicos:
• Conceito de algoritmos
• Princípios da programação estruturada
• Conceitos básicos de uma linguagem de programação
• Recursividade
• Vetores e matrizes
• Algoritmos de ordenação: – Ordenação por inserção, bubblesort, ordenação linear
• Pesquisa sequencial e binária
Técnicas de programação Tópicos (cont): • Tipos de dados compostos • Ponteiros • Implementação das estruturas de dados básicas
(listas, filas e pilhas) • Registros e arquivos
(http://pt.scribd.com/doc/28536245/Registros-e-Arquivos-Aula-12)
• Algoritmos recursivos: MergeSort, Quicksort • Programação orientada a objetos. • Linguagens: C, C++ e JAVA
Conceito de algoritmo
Definições: • Processo sistemático para a resolução de um problema. Significa
descrever, de forma lógica, os passos a serem executados no cumprimento de determinada tarefa.
• Procedimento computacional bem definido que toma algum valor ou
conjunto de valores como entrada e produz algum valor ou conjunto de valores como saída. Durante o processo de computação, o algoritmo manipula dados, gerados a partir de sua entrada.
• Ferramenta para resolver um problema computacional bem especificado. • Descrevem soluções de problemas do nosso mundo (ilimitado), afim de
serem implementadas utilizando os recursos do mundo computacional (limitação de hardware/software).
Conceito de algoritmo
Exemplos de problemas a serem resolvidos com algoritmos: – Casos muito simples, como por exemplo, um algoritmo para "fazer um
bolo". – Cálculo de média aritmética / ponderada. – Ordenar uma sequência de números. – Internet – algoritmos inteligentes para gerenciar e manipular grandes
quantidades de informações.. Ex.1: localização de boas rotas que os dados viajarão, Ex.2: uso de mecanismos de pesquisa para encontrar com rapidez páginas que residem informações específicas.
– Comércio eletrônico. Criptografia de chave pública e assinaturas digitais (números de cartão de crédito, senhas...) se baseiam em algoritmos numéricos.
– Mapa rodoviário. Como encontrar a rota mais curta.
Conceito de algoritmo Um algoritmo correto deve possuir 3 qualidades: 1- Cada passo no algoritmo deve ser uma instrução que possa ser realizada. 2- A ordem dos passos deve ser precisamente determinada. 3- O algoritmo deve ter fim. Exemplo 1: Não é um algoritmo: Procedimento para contar: • Passo 1: Faça N igual a zero • Passo 2: Some 1 a N. • Passo 3: Volte ao passo 2 Não satisfaz a condição 3 de um algoritmo correto!!! Exemplo 2: É um algoritmo correto: Procedimento para contar até 100: • Passo 1: Faça N igual a zero • Passo 2: Some 1 a N. • Passo 3: Se N é menor que 100, volte ao Passo 2, senão pare. Satisfaz as três condições!!!
Conceito de algoritmo • Um algoritmo é uma sequência finita e instruções bem
definidas e não ambíguas, cada uma das quais pode ser executada mecanicamente num período de tempo finito e com uma quantidade de esforço finita.
• O conceito de algoritmo é frequentemente ilustrado pelo exemplo de uma receita culinária, embora muitos algoritmos sejam mais complexos. Eles podem repetir passos (fazer iterações) ou necessitar de decisões (tais como comparações ou lógica) até que a tarefa seja completada.
• Um algoritmo corretamente executado não irá resolver um problema se estiver implementado incorretamente ou se não for apropriado ao problema.
Conceito de algoritmo
• Um algoritmo não representa, necessariamente, um programa de computador[3], e sim os passos necessários para realizar uma tarefa. Sua implementação pode ser feita por um computador, por outro tipo de autômato ou mesmo por um ser humano. Diferentes algoritmos podem realizar a mesma tarefa usando um conjunto diferenciado de instruções em mais ou menos tempo, espaço ou esforço do que outros. Tal diferença pode ser reflexo da complexidade computacional aplicada, que depende de estruturas de dados adequadas ao algoritmo.
• Por exemplo, um algoritmo para se vestir pode especificar que você vista primeiro as meias e os sapatos antes de vestir a calça enquanto outro algoritmo especifica que você deve primeiro vestir a calça e depois as meias e os sapatos. Fica claro que o primeiro algoritmo é mais difícil de executar que o segundo apesar de ambos levarem ao mesmo resultado.
Conceito de algoritmo - Um programa de computador é essencialmente um algoritmo que diz
ao computador os passos específicos e em que ordem eles devem ser executados, como por exemplo, os passos a serem tomados para calcular as notas que serão impressas nos boletins dos alunos de uma escola.
- Quando os procedimentos de um algoritmo envolvem o processamento
de dados, a informação é lida de uma fonte de entrada, processada e retornada sob novo valor após processamento, o que geralmente é realizado com o auxílio de uma ou mais estrutura de dados.
- Para qualquer processo computacional, o algoritmo precisa estar rigorosamente definido, especificando a maneira que ele se comportará em todas as circunstâncias. A corretividade do algoritmo pode ser provada matematicamente, bem como a quantidade assintótica de tempo e espaço (complexidade) necessários para a sua execução. Estes aspectos dos algoritmos são alvo da análise de algoritmos.
Conceito de algoritmo • A maneira mais simples de se pensar um algoritmo é por uma lista de
procedimentos bem definida, na qual as instruções são executadas passo a passo a partir do começo da lista, uma idéia que pode ser facilmente visualizada através de um fluxograma.
• Tal formalização adota as premissas da programação imperativa, que é uma forma mecânica para visualizar e desenvolver um algoritmo. Concepções alternativas para algoritmos variam em programação funcional e programação lógica.
• Um exemplo de algoritmo imperativo. O estado em vermelho indica a entrada do algoritmo enquanto os estados em verde indicam as possíveis saídas.
Conceito de algoritmo Implementação: • A maioria dos algoritmos é desenvolvida para ser
implementada em um programa de computador. Apesar disso eles também podem ser implementados por outros modos tais como uma rede neural biológica (tal como no cérebro quando efetuamos operações aritméticas) em circuitos elétricos ou até mesmo em dispositivos mecânicos.
• Para programas de computador existe uma grande
variedade de linguagens de programação, cada uma com características específicas que podem facilitar a implementação de determinados algoritmos ou atender a propósitos mais gerais.
Conceito de algoritmo Análise de algoritmos - Ramo da ciência da computação que estuda as técnicas de projeto de
algoritmos e os algoritmos de forma abstrata, sem estarem implementados em uma linguagem de programação em particular ou implementadas de algum outro modo. Ela preocupa-se com os recursos necessários para a execução do algoritmo tais como o tempo de execução e o espaço de armazenamento de dados. Deve-se perceber que para um dado algoritmo pode-se ter diferentes quantidades de recursos alocados de acordo com os parâmetros passados na entrada. Por exemplo, se definirmos que o fatorial de um número natural é igual ao fatorial de seu antecessor multiplicado pelo próprio número, fica claro que a execução de fatorial(10) consome mais tempo que a execução de fatorial(5).
- Um meio de exibir um algoritmo a fim de analisá-lo é através da
implementação por pseudocódigo em português estruturado. O exemplo a seguir é um algoritmo em português estruturado que retorna (valor de saída) a soma de dois valores (também conhecidos como parâmetros ou argumentos, valores de entrada) que são introduzidos na chamada da função:
Conceito de algoritmo
Exemplo:
Conceito de algoritmo
Classificação por implementação: Pode-se classificar algoritmos pela maneira pelo qual foram implementados:
• Recursivo ou iterativo - um algoritmo recursivo possui a característica
de invocar a si mesmo repetidamente até que certa condição seja satisfeita e ele é terminado, que é um método comum em programação funcional. Algoritmos iterativos usam estruturas de repetição tais como laços, ou ainda estruturas de dados adicionais tais como pilhas, para resolver problemas. Cada algoritmo recursivo possui um algoritmo iterativo equivalente e vice-versa, mas que pode ter mais ou menos complexidade em sua construção.
• Lógico - um algoritmo pode ser visto como uma dedução lógica controlada. O componente lógico expressa os axiomas usados na computação e o componente de controle determina a maneira como a dedução é aplicada aos axiomas. Tal conceito é base para a programação lógica.
Conceito de algoritmo
Classificação por implementação: • Serial ou paralelo - algoritmos são geralmente assumidos por serem
executados instrução a instrução individualmente, como uma lista de execução, o que constitui um algoritmo serial. Tal conceito é base para a programação imperativa. Por outro lado existem algoritmos executados paralelamente, que levam em conta as arquiteturas de computadores com mais de um processador para executar mais de uma instrução ao mesmo tempo. Tais algoritmos dividem os problemas em subproblemas e o delegam a quantos processadores estiverem disponíveis, agrupando no final o resultado dos subproblemas em um resultado final ao algoritmo.
Tal conceito é base para a programação paralela. De forma geral, algoritmos iterativos são paralelizáveis; por outro lado existem algoritmos que não são paralelizáveis, chamados então problemas inerentemente seriais.
Conceito de algoritmo
Classificação por implementação: • Determinístico ou não-determinístico - algoritmos
determinísticos resolvem o problema com uma decisão exata a cada passo enquanto algoritmos não-determinísticos resolvem o problema ao deduzir os melhores passos através de estimativas sob forma de heurísticas.
• Exato ou aproximado - enquanto alguns algoritmos
encontram uma resposta exata, algoritmos de aproximação procuram uma resposta próxima a verdadeira solução, seja através de estratégia determinística ou aleatória. Possuem aplicações práticas sobretudo para problemas muito complexos, do qual uma resposta correta é inviável devido à sua complexidade computacional.
Conceito de algoritmo Classificação por paradigma: (Pode-se classificar algoritmos pela metodologia ou paradigma de seu
desenvolvimento)
• Divisão e conquista - algoritmos de divisão e conquista reduzem repetidamente o problema em sub-problemas, geralmente de forma recursiva, até que o sub-problema é pequeno o suficiente para ser resolvido. Um exemplo prático é o algoritmo de ordenação merge sort. Uma variante dessa metodologia é o decremento e conquista, que resolve um sub-problema e utiliza a solução para resolver um problema maior. Um exemplo prático é o algoritmo para pesquisa binária.
• Programação dinâmica - pode-se utilizar a programação dinâmica para evitar o re-cálculo de solução já resolvidas anteriormente.
Conceito de algoritmo Classificação por paradigma: (Pode-se classificar algoritmos pela metodologia ou paradigma de seu
desenvolvimento)
• Algoritmo ganancioso - um algoritmo ganancioso é similar à programação dinâmica, mas difere na medida em que as soluções dos sub-problemas não precisam ser conhecidas a cada passo, uma escolha gananciosa pode ser feita a cada momento com o que até então parece ser mais adequado.
• Programação linear -são problemas de optimização nos quais a função objetivo e as restrições são todas lineares.
• Redução - a redução resolve o problema ao transformá-lo em outro problema. É chamado também transformação e conquista.
Conceito de algoritmo Classificação por paradigma: (Pode-se classificar algoritmos pela metodologia ou paradigma de seu
desenvolvimento)
• Busca e enumeração - vários problemas podem ser modelados através de grafos. Um algoritmo de exploração de grafo pode ser usado para caminhar pela estrutura e retornam informações úteis para a resolução do problema. Esta categoria inclui algoritmos de busca e backtracking.
• Paradigma heurístico e probabilístico - algoritmos probabilísticos realizam escolhas aleatoriamente. Algoritmos genéticos tentam encontrar a solução através de ciclos de mutações evolucionárias entre gerações de passos, tendendo para a solução exata do problema. Algoritmos heurísticos encontram uma solução aproximada para o problema.
Conceito de algoritmo
Classificação por campo de estudo:
• Cada campo da ciência possui seus próprios problemas e respectivos algoritmos adequados para resolvê-los.
• Exemplos clássicos são algoritmos de busca, de ordenação, de análise numérica, de teoria de grafos, de manipulação de cadeias de texto, de geometria computacional, de análise combinatória, de aprendizagem de máquina, de criptografia, de compressão de dados e de interpretação de texto.
Conceito de algoritmo
Classificação por complexidade: • Alguns algoritmos são executados em tempo
linear, de acordo com a entrada, enquanto outros são executados em tempo exponencial ou até mesmo nunca terminam de serem executados.
• Alguns problemas possuem múltiplos algoritmos
enquanto outros não possuem algoritmos para resolução.
Princípios da programação estruturada
• A programação estruturada (Top-Down) estabelece uma disciplina de desenvolvimento de algoritmos que facilita a compreensão de programas através do número restrito de mecanismos de controle da execução de programas;
• Qualquer algoritmo, independentemente da área de aplicação, de sua complexidade e da linguagem de programação na qual será codificado, pode ser descrito através destes mecanismos básicos.
Elementos chaves da programação estruturada: 1) Estruturas básicas de controle: sequência, condição e repetição. 2) Subprogramação (ou modularização). 3) Tipos Abstratos de Dados. (http://www.devmedia.com.br/introducao-a-programacao-estruturada/24951)
Princípios da programação estruturada
Elementos chaves da programação estruturada: 1) Estruturas básicas de controle: 1.1 - sequência, 1.2 - seleção(condição) 1.3 - repetição (iteração). - Formas de raciocínio intuitivamente óbvias. A legibilidade
e compreensão de cada bloco de código na solução é enormemente incrementada, proibindo o uso irrestrito de comandos de desvio incondicional (GOTO).
- Cada uma destas construções tem um ponto de início (o topo do bloco) e um ponto de término (o fim do bloco) de execução.
Princípios da programação estruturada
1.1 - Sequência, - Implementa os passos de processamento necessários
para descrever qualquer programa. Por exemplo, um segmento de programa da forma “faça primeiro a tarefa A e depois a tarefa B”
Princípios da programação estruturada
1.1 - Seleção - Especifica a possibilidade de selecionar o fluxo de execução do
processamento baseado em ocorrências lógicas. Ex: um segmento de seleção permite representar fluxos da forma “se a condição lógica x for verdadeira, faça a tarefa A; senão (se a condição x for falsa), faça a tarefa B.
Princípios da programação estruturada
1.1 - Iteração - Permite a execução repetitiva de segmentos do programa. Na
forma básica de repetição, uma condição lógica é verificada. Caso seja verdadeira, o bloco de tarefas associado ao comando é executado. A condição é então reavaliada; enquanto for verdadeira, a tarefa é repetidamente executada.
Princípios da programação estruturada
• Estrutura de um algoritmo
Princípios da programação estruturada
2. Subprogramação (ou modularização). • À medida que os programas vão se tornando maiores e mais
complexos, é possível simplificar e melhorar a clareza dividindo o programa em partes menores, chamadas subprogramas.
• Um subprograma, é um nome dado a um trecho de um programa
mais complexo e que, em geral, encerra em si próprio um pedaço da solução de um problema maior (o programa a que ele está subordinado). São sinônimos usados na engenharia de software para o conceito de subprograma: procedimento, função, módulo (estrutura modular), métodos (orientação a objetos) e subrotina.
Princípios da programação estruturada 2. Subprogramação (ou modularização). • Na programação estruturada o "método dos refinamentos sucessivos" é
uma "sistemática" de abordagem útil no projeto detalhado e na implementação de softwares.
• Partindo-se de um dado problema, para qual se deseja encontrar um programa de solução, deve-se procurar subdividi-lo em problemas menores e consequentemente de solução mais simples (dividir para conquistar). Alguns destes problemas menores (subproblemas) terão solução imediata (na forma de um subprograma) e outros não.
• Os subproblemas para os quais não for possível encontrar uma solução direta devem ser novamente subdivididos. Assim, o processo é repetido até que se consiga encontrar um subprograma para solucionar cada um dos subproblemas definidos.
• Então, o programa de solução do problema original será composto pela justaposição dos subprogramas usados para solucionar cada um dos subproblemas em que o problema original foi decomposto.
Princípios da programação estruturada 2. Subprogramação (ou modularização). Vantagens conquistadas com a subdivisão de programas complexos: a) cada parte menor tem um código mais simples; b) facilita o entendimento uma vez que os subprogramas podem ser
analisados como partes independentes (legibilidade); c) códigos menores são mais facilmente modificáveis para satisfazer
novos requisitos do usuário e para correção de erros (manutenibilidade);
d) Simplificação da documentação de sistemas; e) Desenvolvimento de software por equipes de programadores; f) reutilização de subprogramas através de bibliotecas de subprogramas,
na linguagem C, sob a forma dos arquivos de cabeçalhos (.h).
Princípios da programação estruturada 2. Subprogramação (ou modularização). - Principal questão: "reutilização de software" - Objetivo: a economia de tempo e trabalho (benefícios técnicos que
trazem vantagens financeiras). Um conjunto de subprogramas destinado a solucionar uma série de tarefas bastante corriqueiras é desenvolvido e vai sendo aumentado com o passar do tempo, com o acréscimo de novos subprogramas.
- A este conjunto dá-se o nome de biblioteca. - No desenvolvimento de novos sistemas, procura-se ao máximo basear
sua concepção em subprogramas já existentes na biblioteca, de modo que a quantidade de software realmente novo que deve ser desenvolvido é minimizada.
Princípios da programação estruturada
3) Tipos Abstratos de Dados (TAD) • pode ser visto como um modelo matemático, acompanhado das operações
definidas sobre o modelo. • Ex: O conjunto dos inteiros acompanhado das operações de adição,
subtração e multiplicação.
• podem ser considerados generalizações de tipos primitivos de dados, da mesma forma que procedimentos são generalizações de operações primitivas tais como adição, subtração e multiplicação. Assim como um procedimento é usado para encapsular partes de um programa, o tipo abstrato de dados pode ser usado para encapsular tipos de dados.
• Neste caso a definição do tipo de dados e todas as operações definidas sobre ele podem ser localizadas em uma única seção do programa (na Linguagem C, por exemplo, em um arquivo de cabeçalhos).
Princípios da programação estruturada Resumo das principais características: 1- decomposição gradativa dos programas ao nível fundamental (método dos
refinamentos sucessivos, desenvolvimento top-down). 2- programação orientada a procedimentos: subprogramas = blocos
estruturados de códigos (procedimentos, funções ou módulos); a comunicação entre os blocos se faz utilizando variáveis globais e pela
passagem de dados através de parâmetros; os dados são processados nos blocos e migram de um bloco para outro,
através de variáveis globais, parâmetros passados por referência e expressão retornada pela função (através do comando return na linguagem C);
a execução de um programa é caracterizada pelo acionamento de um bloco de código. Obs.: a utilização de variáveis globais não constitui uma boa prática de programação (escopo muito grande).
3- tipo abstrato de dados = modelo matemático + operações.
Princípios da programação estruturada
Conceitos básicos de uma linguagem de programação
Tópicos: • Definição • Diagrama • Tipos de Linguagens • Variáveis e Constantes
• Ref: http://www.ancibe.com.br/Apostila%20de%20Algoritmos/apostila%20de%20linguagem%20C%20otima.pdf
• http://www.digitaldev.com.br/linguagens/
Conceitos básicos de uma linguagem de programação
Definições: • Um computador é uma máquina capaz de executar operações, mas
para que "ele" saiba o que como executar é preciso programá-lo. Um programa de computador nada mais é que um conjunto de
instruções, escrito através de uma linguagem própria, que orienta o computador a realizar uma determinada tarefa.
• A única linguagem que o computador "entende" é a chamada Linguagem de Máquina, que é formada por um conjunto de códigos numéricos, próprios para comandar a máquina. Porém, estes códigos são extremamente complicados para o entendimento humano, por isso surgiram as linguagens simbólicas.
• Uma linguagem simbólica é, na realidade, um conjunto de palavras e regras facilmente compreendidas pelo homem, mas para que o computador as entenda existe a necessidade de uma tradução para a linguagem de máquina. Este processo de tradução é denominado compilação".
Conceitos básicos de uma linguagem de programação
Definições: • Uma linguagem de programação é um método
padronizado para expressar instruções para um computador, ou seja, é um conjunto de regras sintáticas e semânticas usadas para definir um programa de computador.
• Uma linguagem permite que um programador especifique precisamente sobre quais dados um computador vai atuar, como estes dados serão armazenados ou transmitidos e quais ações devem ser tomadas sob várias circunstâncias.
Conceitos básicos de uma linguagem de programação
Diagrama esquemático do processo de criação de um programa:
(a) O programa fonte é um arquivo "texto" digitado pelo programador. Este arquivo contém
as instruções necessárias para que o computador realize a tarefa desejada. Utiliza-se a linguagem simbólica, genericamente chamada de linguagem de programação.
(b) O programa fonte é lido pelo compilador , que traduz as instruções da linguagem simbólica para a linguagem de máquina. Neste processo são apontados os eventuais erros existentes no programa fonte, e enquanto houver erros o compilador não gerará o programa executável (em linguagem de máquina), neste caso o programador deverá retornar ao editor e corrigir os erros apontados.
(c) Após o processo de compilação, se não forem detectados erros, será criado o programa executável (em linguagem de máquina). Este programa receberá o sufixo .EXE e poderá ser executado através do prompt do MS-DOS.
Conceitos básicos de uma linguagem de programação
Tipos de linguagens: Linguagens Compiladas
• São as linguagens que passam por um processo de tradução (compilação), sendo transformados para um segundo código (código de maquina) compreensível ao processador, o programa responsável por essa tradução é chamado de compilador.
Linguagens interpretadas
• São linguagens onde o código fonte da mesma é executado por um outro programa de computador chamado interpretador, que em seguida é executado pelo sistema operacional ou processador. Mesmo que um código em uma linguagem passe pelo processo de compilação, a linguagem pode ser considerada interpretada, se o programa resultante não for executado diretamente pelo sistema operacional ou processador.
Conceitos básicos de uma linguagem de programação
Tipos de linguagens: Linguagens de alto nível • São linguagens com um nível de abstração relativamente elevado,
longe do código de máquina e mais próximo à linguagem humana. Desse modo, as linguagens de alto nível não estão diretamente relacionadas à arquitetura do computador. O programador de uma linguagem de alto nível não precisa conhecer características do processador, como instruções e registradores. Essas características são abstraídas na linguagem de alto nível.
Linguagens de baixo nível • Tratam-se de linguagens de programação que compreendem as
características da arquitetura do computador. Assim, utiliza-se somente instruções do processador, para isso é necessário conhecer os registradores da máquina. Nesse sentido, as linguagens de baixo nível estão diretamente relacionadas com a arquitetura do computador. Um exemplo é a linguagem Assembly, que trabalha diretamente com os registradores do processador, manipulando dados.
Conceitos básicos de uma linguagem de programação
Variáveis :
• Variáveis são posições de memória que o computador reserva para armazenar os dados manipulados pelo programa. Estas posições devem ser reservadas (declaradas) no início do programa, a partir daí ficam disponíveis para receberem qualquer conteúdo. Este conteúdo pode ser alterado quando necessário (daí o nome variável, pois o conteúdo pode variar).
• Na declaração de uma variável, devemos informar o nome da mesma (identificador) e qual é o tipo de dado que ela receberá.
Conceitos básicos de uma linguagem de programação
Variáveis : Regras de construção dos nomes ou identificadores - Devem começar por uma letra (a - z , A - Z) ou um
underscore ( _ ).
- O resto do identificador deve conter apenas letras, underscores ou dígitos (0 - 9). Não pode conter outros caracteres. Em C, os identificadores podem ter até 32 caracteres.
- Letras maiúsculas são diferentes de letras minúsculas: Por exemplo: MAX, max, Max são nomes diferentes para o compilador.
Conceitos básicos de uma linguagem de programação
Variáveis : • Ao atribuirmos um nome à uma variável, devemos
escolher um nome sugestivo, que indique o conteúdo que ela receberá. Por exemplo:
- Valor1 " Variável que armazenará o primeiro valor - Valor2 " Variável que armazenará o segundo valor - Resultado " Variável que armazenará o resultado de um
cálculo qualquer
Após atribuir o nome devemos indicar o tipo de informação que a variável vai receber...
Conceitos básicos de uma linguagem de programação
Variáveis numéricas : • Definidas para receberem dados numéricos, ou seja,
números que poderão ser utilizados em cálculos diversos.
• A linguagem C classifica os dados numéricos em: inteiro, ponto flutuante e dupla precisão.
Uma variável numérica inteira pode receber um valor inteiro entre -32768 a 32767.
Uma variável numérica de ponto flutuante pode receber um número real de até 7 dígitos.
Uma variável numérica de dupla precisão pode receber um número real de até 15 dígitos.
Conceitos básicos de uma linguagem de programação
Variáveis alfanuméricas : • Definidas para receberem dados do tipo texto ou
caractere. • Podem armazenar letras, caracteres especiais, números,
ou a combinação de todos eles, porém, nunca poderão ser utilizadas em cálculos, pois a característica deste tipo é meramente "texto".
• Quando definirmos uma variável alfanumérica em
linguagem C, devemos indicar também o tamanho da mesma, ou seja, quantos caracteres ela poderá comportar.
Conceitos básicos de uma linguagem de programação
Constantes
• Assim como as variáveis, são posições de memória que armazenam conteúdos numéricos ou alfanuméricos.
• As formas e regras para a construção de constantes são as mesmas das variáveis, a única diferença está no conceito de funcionamento: uma constante recebe um conteúdo inicial que não sofre alteração durante a execução do programa (é um conteúdo constante!).
Recursividade • http://wiki.icmc.usp.br/images/2/26/Aula_recur
sividade.pdf
• Livro: estruturas de dados e seus algoritmos (Lilian)
Recursividade Recursão:
• Técnica que define um problema em termos de uma ou mais versões menores deste mesmo problema.
• Esta ferramenta pode ser utilizada sempre que for possível expressar a solução de um problema em função do próprio problema.
• Tipo de procedimento que contém, em sua descrição, uma ou mais chamadas a si mesmo – procedimento recursivo. A chamada a si mesmo é dita chamada recursiva.
Recursividade Recursão: • Para se codificar programas de modo recursivo usa-se um
procedimento ou sub-rotina, que permite dar um nome a um comando, o qual pode chamar a si próprio.
• Todo procedimento, recursivo ou não, deve possuir pelo menos uma chamada proveniente de um local exterior a ele (chamada externa). Procedimento não-recursivo: todas as chamadas são externas!
• Esta chamada pode ser diretamente recursiva, quando o procedimento P contiver uma referência explícita a si próprio, ou indiretamente recursiva, quando o procedimento P contiver uma referência a outro procedimento Q, que por sua vez contém uma referência direta ou indireta a P.
Recursividade Exemplo: Soma N primeiros números inteiros Supondo N = 5; S(5) = 1+2+3+4+5 = 15 -> S(5) = S(4) + 5 -> 10 + 5 = 15 S(4) = 1+2+3+4 =10 -> S(4) = S(3) + 4 -> 6 + 4 = 10 S(3) = 1+2+3 = 6
-> S(3) = S(2) + 3 -> 3 + 3 = 6
S(2) = 1+2 = 3 -> S(2) = S(1) + 2 -> 1 + 2 = 3 S(1) = 1 =1 -> S(1) = 1 --------------->solução trivial
Recursividade
• Em se tratando de procedimentos recursivos pode-se ocorrer um problema de terminação do programa, como um “looping interminável ou infinito”.
• Portanto, para determinar a terminação das repetições, deve-se:
1) Definir uma função que implica em uma condição de terminação (solução trivial), e
2) Provar que a função decresce a cada passo de repetição,permitindo que, eventualmente, esta solução trivial seja atingida.
Recursividade
Recursividade
Recursividade Seqüência de Fibonacci
• Os números de Fibonacci podem ser calculados por um esquema iterativo, que evita o cálculo repetido dos mesmos valores, através do uso de variáveis auxiliares.
Vantagens X Desvantagens • Vantagens: - Um programa recursivo é mais elegante e menor que a
sua versão iterativa, além de exibir com maior clareza o processo utilizado, desde que o problema ou os dados sejam naturalmente definidos através de recorrência.
- Mais concisos • Desvantagem: - Um programa recursivo exige mais espaço de memória
e é, na grande maioria dos casos, mais lento do que a versão iterativa.
Recursividade
Recursividade
Vetores e matrizes
• Tópicos:
- Definição
- Operações
- Implementações em Java - (Ref:ttp://www.ic.unicamp.br/~vanini/mc202/apresentacoes/Vetor
es%20e%20Matrizes.pdf)
- Implementações em C: - (http://www.inf.pucrs.br/~pinho/LaproI/Vetores/Vetores.htm#Decl
ara)
Vetores e matrizes
Vetor (Array): • Em computação um Vetor (Array) ou Arranjo é o nome de
uma matriz unidimensional considerada a mais simples das estruturas de dados. Geralmente é constituída por dados do mesmo tipo (homogêneos) e tamanho que são agrupados continuamente na memória e acessados por sua posição (índice - geralmente um número inteiro) dentro do vetor.
• Abaixo temos o exemplo de um vetor. Os valores internos seriam os dados alocados no vetor , enquanto seu tamanho é dado pelo número de casas disponíveis (no caso 8) e o índice representa a posição do dado no vetor ( por exemplo podemos definir que 0 tem o índice 1, 2 tem índice 2, 8 tem índice 3 e assim sucessivamente).
Vetor Declaração e inicialização de um vetor
• A declaração de um vetor em português estruturado pode ser da seguinte forma: Nome_Vetor:vetor[tamanho]tipo
• Exemplo de declarações:
VECTOR:vetor [10] numérico
MEDIA: vetor [3] numérico
Vetor
Operações com Vetores
• Podemos trabalhar com vetores numéricos, executando operações sobre eles da mesma forma como executaríamos com variáveis numéricas comuns.
• Devemos assumir que ao declararmos um determinado vetor[índice], com um índice específico, estamos fazendo referência a um número.
Matrizes • Arranjos ordenados que, ao contrário dos vetores, podem
ter n dimensões, sendo que estas dimensões lhes dão o nome n-dimensional .
• Uma matriz de duas dimensões será chamada bi-dimensional, uma de três dimensões tri-dimensional e assim consecutivamente.
• Funciona praticamente da mesma forma que um vetor exceto que utilizaremos o número n de índices para acessar um dado que queremos. Para efeitos de estudo por enquanto nos limitaremos somente às matrizes bidimensionais (duas dimensões linha X colunas). Assim se possuímos uma matriz bidimensional de duas linhas e duas colunas:
• Consideramos que para acessarmos o valor 3, localizamos o índice por sua linha (1) e coluna (1) , deste modo seu índice é (1,1). O valor quatro por exemplo será (1, 2).
Matrizes
Declaração e inicialização
• A declaração de uma matriz em português estruturado pode ser da seguinte forma:
tipo Nome_Matriz [tamanho_linha][tamanho_coluna]
• Exemplo de declarações:
inteiro matrix [10][10]
real media [3][3]
Matrizes • Exemplo de um algoritmo em que o usuário digita 100 elementos e
estes aparecem na tela:
Matrizes
Operações com matrizes
• Soma e subtração entre matrizes
Matrizes
Operações com matrizes
• Multiplicação por um escalar
Matrizes
Operações com matrizes
• Multiplicação entre matrizes
Matrizes
Operações com matrizes
• Calcular a diagonal
e a transposta
Algoritmo de Ordenação
1. Ordenação por inserção (Insertion-Sort)
2. Bubblesort
3. Ordenação linear
Ordenação por inserção Algoritmo Insertion-Sort:
• Abordagem incremental
- Entrada: uma sequência de n números <a1, a2,..., an>
- Saída: uma permutação (reordenação) <a1‘, a2’, .., an’> da sequência de entrada, tal que:
- a1‘ ≤ a2’ ≤ ... an’
- Eficiente para ordenar um pequeno número de elementos.
- Os números da entrada são ordenados no local: os números são reorganizados dentro do arranjo A, com no máximo um número constante deles armazenado fora do arranjo em qualquer instante.
Chaves: números que queremos ordenar
Ordenação por inserção
Ordenação por inserção
• Índice ‘j’: carta atual • Elementos A[1.. j-1]: ‘mão’ atualmente ordenada • Elementos A[j+1.. n]: pilhas de cartas ainda na mesa.
• Tempo de execução: T(n) = c1n + c2 (n-1) + c4(n-1) + c5.∑(tj) + c6. ∑(tj-1) + c7. ∑(tj-1)
+ c8(n-1) • Melhor caso: Arranjo ordenado: T(n) = (c1 + c2 + c4 + c5 + c8)n – (c2 + c4 + c5 + c8) =
an + b função linear de n! T(n) = ϴ(n) tj=1 para j=1,2,..n a,b: constantes que dependem dos custos de instrução ci.
Ordenação por inserção
Pior caso: Ordem decrescente tj = j, para j=2,3,...n ∑ (j=2 a n) j = (n(n+1)/2) – 1 ∑ (j=2 a n) (j-1) = n(n-1)/2) • Tempo de execução expresso como: an2 + bn + c função quadrática de n!! • T(n) = ϴ(n2) !!
Os elementos A[1..j-1] são os elementos que estavam
originalmente nas posições de 1 a j-1, mas agora em sequência ordenada!!
Ordenação por inserção
• Exemplo: Arranjo de entrada A = < 5,2,4,6,1,3>
• Abordagem incremental: tendo ordenado o subarranjo A[1..j-1], inserimos o elemento isolado A[j] em seu lugar
apropriado, formando o subarranjo ordenado A[1..j]
Ordenação por inserção • Para ajudar a entender por que um algoritmo é correto:
Loops invariantes: • Propriedades: • Inicialização: ele é verdadeiro antes da primeira iteração do
loop
• Manutenção: se for verdadeiro antes da primeira iteração do loop, ele permanecerá verdadeiro antes da próxima iteração.
• Término: e quando o loop termina, o invariante nos fornece uma propriedade útil que ajuda a mostrar que o algoritmo é correto. – uso de loops invariantes para mostrar a correção!
Ordenação por inserção
1- Inicialização: – loop for: após a atribuição de 2 à variável j, MAS antes do
primeiro teste para verificar se j ≤comprimento[A]
2-Manutenção: – corpo do loop for: desloca-se A[j-1], A[j-2],A[j-3]...uma posição à
direita, até ser encontrada uma posição adequada para A[j] (l. 4 a 7), quando o valor de A[j] é inserido (l. 8) propriedade válida para o loop exterior!
3-Término: – O loop for externo termina quando j excede n, quando j=n+1.
Substituindo j por n+1 no enunciado do loop invariante: – o subarranjo A[1..n] consiste nos elementos originalmente
contidos em A[1..n] em sequência ordenada. – A[1..n]: arranjo inteiro ordenado logo, ALGORITMO CORRETO!
Ordenação por inserção
Indução matemática:
para provar que uma propriedade é válida:
• caso básico: equivalente a inicialização (1)
• etapa indutiva: equivalente a manutenção (2)
USADA INDEFINIDAMENTE DIFERENTE DO TÉRMINO (3)
Bubblesort
- Algoritmo de ordenação popular: permuta repetidamente elementos adjacentes que estão fora de ordem.
• Pior caso: θ(n2)
1. for i 1 to comprimento[A]
2. do for j comprimento[A] downto i+1
3. do if A[j] < A[j-1]
4. then trocar A[j]↔ A[j-1]
Ordenação (em tempo) linear • Ordenação por comparação: a sequência ordenada que eles
determinam se baseia apenas em comparações entre os elementos de entrada. (ex. ordenação por inserção)
• Qualquer ordenação por comparação deve efetuar Ω(n lg n) comparações no pior caso para ordenar n elementos.
• Ordenação por intercalação e heapsort: assintoticamente ótimas! Os O(n lg n) limites superiores sobre o tempo de execução correspondem ao limite inferior Ω(n lg n) do pior caso.
Ordenação em tempo linear: • 1) Ordenação por contagem • 2) Ordenação da raiz (radix sort) (máquinas de ordenação de
cartões) • 3) Ordenação por balde (bucket sort)
Ordenação linear Modelo de Árvore de decisão: • Árvore binária cheia que representa as comparações
executadas por um algoritmo de ordenação quando ele opera sobre uma entrada de tamanho dado.
• Árvore de decisão para ordenação por inserção:
• Sequência de entrada: <a1=6, a2=8, a3=5> • Existem 3! = 6 permutações possíveis dos elementos de
entrada (a árvore de decisão deve ter no mínimo 6 folhas!)
Ordenação linear
• Ordenação por contagem
ϴ(n+k) = ϴ(n)
Ordenação linear
Ordenação por contagem: Se todos os n elementos forem distintos: quando entrar pela 1ª. vez na linha 9, para cada A[j],o
valor C[A[j]] será a posição final correta de A[j] no arranjo de saída, pois existem C[A[j]] elementos ≤ A[j].
Como os elementos podem não ser distintos: decrementamos C[A[j]] sempre que inserimos um valor
A[j] no arranjo B. O próximo elemento de entrada com valor A[j], se existir algum, vai para a posição imediatamente anterior a A[j] no arranjo de saída.
Ordenação linear Ordenação por contagem: - Supera o limite inferior de Ω(n lg n): porque NÃO é uma ordenação por comparação: nenhuma
comparação entre elementos de entrada ocorre em qualquer lugar do código!!
Utiliza os valores reais dos elementos para efetuar a indexação em um arranjo.
Propriedade: • É estável: números com o mesmo valor aparecem no arranjo
de saída na mesma ordem em que se encontram no arranjo de entrada.
• Estabilidade – importante 1) quando os dados satélites são transportados juntamente com o elemento que está sendo ordenado. 2) crucial para a correção da radix sort.
Ordenação linear Ordenação por balde (BUCKET SORT) • Funciona em tempo linear quando a entrada é gerada por
uma distribuição uniforme. • É rápida porque pressupõe algo sobre a entrada: a entrada é
gerada por um processo aleatório que distribui elementos uniformemente sobre o intervalo [0,1).
• Divide o intervalo [0,1) em n subintervalos de igual tamanho (ou baldes) e distribui os n números de entrada entre os baldes.
• Como as entradas são uniformemente distribuídas sobre [0,1), muitos números não devem cair em cada balde.
• Para produzir a saída: ordena-se os números em cada balde, e depois percorre-se os baldes em ordem, listando os elementos contidos em cada um.
Ordenação linear Ordenação por balde (BUCKET SORT)
Pesquisa Sequencial
• Definição
• Análise da Complexidade
• Fluxograma do algoritmo
• Código C
• Código JAVA (ref. http:http://pt.wikipedia.org/wiki/Busca_linear)
Pesquisa Sequencial
Definição: • Na área de informática, ou Ciência da Computação,
costuma-se usar o termo busca linear (ou busca sequêncial) para expressar um tipo de pesquisa em vetores ou listas de modo sequencial, i. e., elemento por elemento, de modo que a função do tempo em relação ao número de elementos é linear, ou seja, cresce proporcionalmente.
• Num vetor ordenado, essa não é a pesquisa mais eficiente, a pesquisa (ou busca) binária, por exemplo, é um tipo de pesquisa com o gráfico de tempo logarítmo.
Pesquisa Sequencial
Análise da complexidade:
• No melhor caso, o elemento a ser buscado é encontrado logo na primeira tentativa da busca.
• No pior caso, o elemento a ser buscado encontra-se na última posição e são feitas N comparações, sendo N o número total de elementos. No caso médio, o elemento é encontrado após N/2 comparações.
• O algoritmo de busca linear é um algoritmo O(n).
Pesquisa Sequencial
Fluxograma do algoritmo:
Pesquisa Sequencial
Código C:
Pesquisa Sequencial
Código JAVA:
Pesquisa Binária
• Definição
• Análise da Complexidade
• Pseudocódigo recursivo
• Código C
• Código JAVA
• Código C++ (ref. http://pt.wikipedia.org/wiki/Pesquisa_bin%C3%A1ria)
Pesquisa Binária
• A pesquisa ou busca binária (em inglês binary search algorithm ou binary chop) é um algoritmo de busca em vetores que segue o paradigma de divisão e conquista.
• Ela parte do pressuposto de que o vetor está ordenado e realiza sucessivas divisões do espaço de busca comparando o elemento buscado (chave) com o elemento no meio do vetor.
• Se o elemento do meio do vetor for a chave, a busca termina com sucesso. Caso contrário, se o elemento do meio vier antes do elemento buscado, então a busca continua na metade posterior do vetor.
• E finalmente, se o elemento do meio vier depois da chave, a busca continua na metade anterior do vetor.
Pesquisa Binária
Análise da complexidade:
• A complexidade desse algoritmo é da ordem de Θ(log2 n), em que n é o tamanho do vetor de busca. Apresenta-se mais eficiente que a Busca linear cuja ordem é O(n).
Pesquisa Binária
• Um pseudocódigo recursivo para esse algoritmo, dados V o vetor com elementos comparáveis, n seu tamanho e e o elemento que se deseja encontrar:
Pesquisa Binária Código C (em C para se passar um vetor como parâmetro para uma função, tem
que se passar o ponteiro do vetor)
Pesquisa Binária Código JAVA
Pesquisa Binária Código C++
Ponteiros
- Em programação, um ponteiro ou apontador é um tipo de dado de uma linguagem de programação cujo valor se refere diretamente a um outro valor alocado em outra área da memória, através de seu endereço.
- Um ponteiro é uma simples implementação do tipo referência da Ciência da computação.
Ponteiros Arquitetura
• Ponteiros: abstração da capacidade de endereçamento fornecidas pelas arquiteturas modernas.
• Um endereço de memória, ou índice numérico, é definido para cada unidade de memória no sistema, no qual a unidade é tipicamente um byte ou uma word - em termos práticos transforma toda a memória em um grande vetor.
• A partir de um endereço, é possível obter do sistema o valor armazenado na unidade de memória de tal endereço.
• O ponteiro é um tipo de dado que armazena um endereço.
Ponteiros Arquitetura • Na maioria das arquiteturas, um ponteiro é grande o
suficiente para indexar todas as unidades de memória presentes no sistema. Isso torna possível a um programa tentar acessar um endereço que corresponde a uma área inválida ou desautorizada da memória, o que é chamado de falha de segmentação. Por outro lado, alguns sistemas possuem mais unidades de memória que endereços. Nesse caso, é utilizado um esquema mais complexo para acessar diferentes regiões da memória, como o de segmentação ou paginação.
Ponteiros Arquitetura
• Para fornecer uma interface consistente, algumas arquiteturas fornecem E/S mapeada em memória, o que permite que enquanto alguns endereços são referenciados como áreas de memória, outros são referenciados como registradores de dispositivos do computador, como equipamentos periféricos.
Ponteiros Uso de ponteiros
• Ponteiros são diretamente suportados sem restrições em C, C++ e Pascal, entre outras linguagens.
• para construir referências, elemento fundamental da maioria das estruturas de dados, especialmente aquelas não alocadas em um bloco contínuo de memória, como listas encadeadas, árvores ou grafos.
Ponteiros Uso de ponteiros
• Ao lidar com arranjos, uma operação crítica é o cálculo do endereço para o elemento desejado no arranjo, o que é feito através da manipulação de ponteiros. De fato, em algumas linguagens (como C), os conceitos de "arranjo" e "ponteiro" são intercambiáveis. Em outras estruturas de dados, como listas encadeadas, ponteiros são usados como referências para intercalar cada elemento da estrutura com seus vizinhos (seja anterior ou próximo).
Ponteiros Uso de ponteiros
• Simular a passagem de parâmetros por referência em linguagens que não oferecem essa construção (como o C). Isso é útil se desejamos que uma modificação em um valor feito pela função chamada seja visível pela função que a chamou, ou também para que uma função possa retornar múltiplos valores.
Ponteiros
Uso de ponteiros • Linguagens como C e C++ permitem que ponteiros
possam ser utilizados para apontar para funções, de forma que possam ser invocados como uma função qualquer. Essa abordagem é essencial para a implementação de modelos de re-chamada (callback), muito utilizados atualmente em bibliotecas de rotinas para manipulação de interfaces gráficas. Tais ponteiros devem ser tipados de acordo com o tipo de retorno da função o qual apontam.
Ponteiros
Exemplo • declaração de uma lista encadeada em C - o que não seria
possível sem o uso de ponteiros
Ponteiros Exemplo • Vetores em C são somente ponteiros para áreas consecutivas da memória.
#include <stdio.h> int main() { int arranjo[5] = { 2, 4, 3, 1, 5 }; printf("%p\n", arranjo); /* imprime o endereço do arranjo */ printf("%d\n", arranjo[0]); /* imprime o primeiro elemento do arranjo, 2 */ printf("%d\n", *arranjo); /* imprime o primeiro inteiro do endereço apontado
pelo arranjo, que é o primeiro elemento, 2 */ printf("%d\n", arranjo[3]); /* imprime o quarto elemento do arranjo, 1*/ printf("%p\n", arranjo+3); /* imprime o terceiro endereço após o início do
arranjo */ printf("%d\n", *(arranjo+3)); /* imprime o valor no terceiro endereço após o
início do arranjo, 1 */ return 0; }
• Aritmética de ponteiros – usada em índices de ponteiros
Ponteiros Exemplo - passar variáveis por referência, permitindo que seus valores modificados
tenham efeito no escopo anterior do programa.
#include <stdio.h>
void alter(int *n) {
*n = 120;
}
int main() {
int x = 24;
int *endereco= &x; /* o operador '&' (leia-se "referênca") retorna o endereço de uma variável */
printf("%d\n", x); /* mostra x */
printf("%p\n", endereco); /* mostra o endereço de x */
alter(&x); /* passa o endereço de x como referência, para alteração */
printf("%d\n", x); /* mostra o novo valor de x */
printf("%p %p\n", endereco, &x); /* endereço de x não alterado*/
return 0;
}
Ponteiros - Usados para apontar para funções: passagem de funções como
parâmetros de outras funções
Registros e Arquivos
• http://pt.scribd.com/doc/28536245/Registros-e-Arquivos-Aula-12
• http://www2.icmc.usp.br/~andretta/ensino/aulas/sme0230-1-10/aula13.pdf
Registros
Características:
Registros
Estrutura:
- Vetor e matriz: estruturas compostas homogêneas
- Registro:
estruturas compostas heterogêneas – não possuem apenas um tipo único de dados
serve para agrupar um conjunto de dados não similares, formando um novo tipo de dados.
Agrupam várias variáveis numa só.
Registros
Estrutura:
• Um registro (struct) é uma coleção de dados (que podem ser de tipos diferentes) sobre um objeto particular.
Registros
Exemplo
• Registro de pagamento.
Registros
• Cada campo deve ter um nome e deve ser referenciado por este nome.
• Não confundir com matriz e vetor onde todos os elementos são do mesmo tipo e são referenciados por um índice!!
Registros
• Definição de uma variável tipo registro em C
Registros • Definição de uma variável tipo registro em C No início do programa (em geral, fora de todas as funções): typedef struct { char matricula[10]; char nome[30]; char sexo; float renda; } REG_aluno; //novo tipo de dado • Na declaração de variáveis coloca-se: REG_aluno ALUNO;
Registros • Como fazer Referência a um Elemento de uma Variável do
Tipo Struct:
<nome-da-variável-tipo-struct> . <nome do campo> REG_aluno ALUNO; No Caso do Exemplo • uma atribuição de valores poderia ser: strcpy(ALUNO . matricula, “874356”); strcpy(ALUNO . nome, “João Miguel”); ALUNO . sexo = ‘F’; ALUNO . Renda = 2.000,00 ;
Registros
• Como fazer Referência a um Elemento de uma Variável do Tipo Struct:
No Caso do Exemplo
• uma leitura de valores poderia ser:
printf("\n\nEntre com o numero da matricula:");
gets(ALUNO.matricula);
printf("\n\nEntre com o nome:");
gets(ALUNO.nome);
printf("\n\nEntre com o sexo(M/F):");
ALUNO.sexo = getche();
printf("\n\nEntre com a renda familiar:");
scanf(“%f”, &ALUNO.renda);
Registros
• Manipulação da variável Tipo Struct:
- As variáveis do tipo struct podem ser manipuladas do mesmo modo que outros dados na memória
Exemplo:
SALBR:=40 * REGEMPR . SALARIO;
• Exibição da variável Tipo Struct:
- As variáveis do tipo struct podem ser manipuladas do mesmo modo que outros dados na memória
Exemplo:
printf (“nome do aluno: %s”, ALUNO . NOME);
Registros
• É possível criar um vetor de registros
• Exemplo: ao invés de um única ficha do empregado, cadastrar várias fichas
Registros
• Vetor de registros
Registros
• Vetor de registros
Registros
• Vetor de registros – COMO DEFINIR?
typedef struct {
tipo1 campo1;
tipo2 campo2;
...
tipon campo n;
} <nome-do-registro>;
<nome-do-registro> <nome-da-variavel [dimensao]>
Registros
Vetor de registro
typedef struct {
char matricula[10];
char nome[30];
char sexo;
float renda;
} REGALUNO;
REGALUNO aluno[32];
Registros
Vetor de registros
Registros
Vetor de registros:
• Exemplo:
Registros
• Matriz e estrutura dentro de uma estrutura
Registros
• Matrizes e estruturas
Registros
• Estruturas dentro de estruturas
Arquivos
Algoritmos recursivos
Abordagem de dividir e conquistar: Algoritmo recursivo: • chamam a si mesmos recursivamente uma ou mais vezes
para lidar com subproblemas intimamente relacionados - Dividir o problema em um determinado número de
subproblemas (semelhantes ao problema original, mas menores em tamanho).
- Conquistar os subproblemas, resolvendo-os recursivamente.
- Combinar as soluções dos subproblemas para formar a solução do problema original.
MergeSort Algoritmo de ordenação por intercalação Dividir - a sequência de n elementos em 2 subsequências de n/2
elementos cada uma.
Conquistar - classifica as duas subsequências recursivamente, usando a
ordenação por intercalação.
Combinar (ou intercalar) - intercala as 2 subsequências ordenadas, para produzir a resposta
ordenada.
• MERGE (A,p,q,r), p ≤q < r • intercala A[p..q] e A[q+1..r] A[p..r]
Mergesort • Combinação (Intercalação)
MERGE (A,p,q,r) 1. n1 q-p+1 ϴ(1) 2. n2 r–q ϴ(1) 3. > Criar arranjos L[1..n1+1] e R[1..n2+1] 4. for i 1 to n1 5. do L[i] A[p+i-1] ϴ(n1) 6. for j 1 to n2 7. do R[j] A[q+j] ϴ(n2) 8. L[n1+1] ∞ sentinelas 9. R[n2+1] ∞ cont.
• Combinação (Intercalação)
10. I 1 11. j 1 12. for k p to r 13. do if L[i] ≤ R[j] 14. then A[k] L[i] 15. i i + 1 16. else A[k] R[j] 17. j j + 1 Obs: l.12 a 17: n iterações!! Cada uma com um tempo constante. ϴ(n), para n=r-p+1 (número de elementos intercalados)
Mergesort Algoritmo de ordenação por intercalação
• Procedimento MERGE
MERGE (A,9,12,16)
Mergesort Algoritmo de ordenação por intercalação
• Procedimento MERGE
Mergesort Algoritmo de ordenação por intercalação
Mergesort Algoritmo de ordenação por intercalação
• Ordena os elementos do subarranjo A[p..r]:
MERGE-SORT(A, p, r)
1. if (p<r)
2. then q └(p+r)/2┘ Divisão: ϴ(1)
3. MERGE-SORT (A,p,q) T(┌n/2┐) 2T(n/2)
4. MERGE-SORT (A,q+1,r) T(└n/2┘)
5. MERGE (A,p,q,r) Intercalação: ϴ(n)
• l. 3. e 4. Conquista
• l. 5. Intercalação (combinação) de 2 vetores ordenados
Mergesort Algoritmo de ordenação por intercalação
Ex: arranjo A=<5,2,4,7,1,3,2,6> (obs: n potência de 2)
MERGE-SORT(A,1,comprimento[A])
Mergesort Algoritmo de ordenação por intercalação
Equação de recorrência
T(n) = θ(1), se n ≤ c
aT(n/b) + D(n) + C(n), cc.
θ(1): tamanho do problema muito pequeno
Conquista: aT(n/b)
- Supor problema subdividido em a subproblemas: cada um com 1/b do tamanho do programa original. 2T(n/2)
-Divisão: D(n)
- Dividir o problema em subproblemas. θ(1)
Combinação:C(n)
- Combinar as soluções dadas aos subproblemas. θ(n)
Análise da ordenação por intercalação
Dividir: • calcula o ponto médio do subarranjo, que demora um
tempo constante. • D(n) = θ(1)
Conquistar: • Resolve recursivamente 2 subproblemas, cada um com
tamanho n/2 e cont.ribui com 2T(n/2) para o tempo de execução.
Combinar: • MERGE • C(n) = θ(n)
Equação de Recorrência
Árvore de Recursão
Árvore de Recursão
Quicksort
• Ordenação rápida
• Tempo de execução: pior caso ϴ(n2) • Vantagem: Tempo de execução na média: ϴ(n lg n) Ordenação local
• Abordagem Dividir e Conquistar
Quicksort Abordagem Dividir e Conquistar: DIVIDIR: A [p..r] A [p..q-1] A [q+1..r] (o arranjo A é particionado (reorganizado)) Elementos de A [p..q-1] ≤ A [q] ≤ A [q+1..r] CONQUISTAR: • A [p..q-1] e A [q+1..r] são ordenados por chamadas recursivas a
QuickSort COMBINAR: • Como os arranjos são ordenados localmente (proc. PARTITION),
não há trabalho para combiná-los: o arranjo A[p..r] inteiro já está ordenado.
Quicksort
Quicksort
Quicksort
Particionamento no pior caso: • Quando PARTITION produz um subproblema com n-1
elementos e um com 0 elemento (surgindo em cada chamada recursiva)
T(n) = T(n-1) + T(0) + ϴ(n), onde: - T(0): chamada recursiva do arranjo de tamanho 0: ϴ(1) - ϴ(n): dividir (particionamento) - T(n) = T(n-1) + ϴ(n) • Solução: T(n) = ϴ(n2) (método da substituição) • Não-balanceado: ϴ(n2) ocorre quando o arranjo de entrada
já está completamente ordenado (ordenação por inserção: O(n))
QuickSort
Particionamento no melhor caso: • Quando PARTITION produz 2 subproblemas de
tamanho não maior que n/2: └n/2┘ e ┌n/2┐-1 T(n) ≤ 2T(n/2) + ϴ(n) T(n) = O (n lg n) (Solução pelo método mestre) • O balanceamento equilibrado dos 2 lados da
partição em cada nível da recursão produz um algoritmo assintoticamente mais rápido!!
Quicksort Particionamento balanceado: Tempo de execução: • Caso médio: próximo do melhor caso. O equilíbrio do
particionamento se reflete na recorrência que descreve o tempo de execução.
Ex: T(n) ≤ T(9n/10) + T(n/10) + cn
• T(n) = O (n lg n) (assintoticamente igual caso a divisão fosse ao meio)
• Qualquer divisão de proporcionalidade constante produz uma árvore de recursão de profundidade ϴ(lg n), onde o custo em cada nível é O(n).
QuickSort
QuickSort
QuickSort Exemplo
(a) 2 níveis de uma árvore de recursão para QuickSort. O particionamento
na raiz custa n e produz uma divisão ruim: subarranjos de tamanhos 0 e n-1 (custa n-1 e produz uma divisão boa: subarranjos de tamanhos (n-1)/2-1 e (n-1)/2 ).
- Custo de particionamento combinado: ϴ(n) + ϴ(n-1) = ϴ(n) . (b) Um único nível de uma árvore de recursão que está muito bem
equilibrada!. O tempo de execução quando os níveis se alternam entre divisões boas
e ruins é semelhante ao tempo de execução para divisões boas sozinhas: O (n lg n) (mas com uma constante ligeiramente maior oculta pela
notação O).
Quicksort
QuickSort
Programação orientada a objetos
• Tópicos
Ref: http://www.dca.fee.unicamp.br/cursos/PooJava/Aulas/poojava.pdf
Programação orientada a objetos
Conceitos:
1- Encapsulamento
2- Classe como tipos de dados
3- Objetos
4- Herança
5- Polimorfismo
Programação orientada a objetos Encapsulamento:
• Diferença básica entre a programação estruturada convencional
e a programação orientada a objetos.
• é o princípio de projeto pelo qual cada componente de um programa deve agregar toda a informação relevante para sua manipulação como uma unidade (uma cápsula). Aliado ao conceito de ocultamento de informação, é um poderoso mecanismo da programação orientada a objetos.
• Ocultamento da informação: é o princípio pelo qual cada componente deve manter oculta sob sua guarda uma decisão de projeto única. Para a utilização desse componente, apenas o mínimo necessário para sua operação deve ser revelado (tornado público).
Programação orientada a objetos Encapsulamento:
• Permitem que se oculte, dentro do objeto, tanto os campos
de dados como os métodos que agem naqueles dados controle do acesso aos dados, forçando os programas a extrair ou modificar os dados apenas através da interface do objeto.
• NO PROJETO ORIENTADO A OBJETOS, os dados são sempre PRIVADOS ao objeto. OUTRAS partes do programa jamais devem ter acesso direto àqueles dados.
• Num objeto, os membros de dados encapsulados são globais em relação aos métodos dos objetos, mas são locais em relação ao objeto. Eles não são variáveis globais.
Programação orientada a objetos
Encapsulamento: • Na orientação a objetos, o uso da encapsulação e ocultamento da
informação recomenda que a representação do estado de um objeto deve ser mantida oculta. Cada objeto deve ser manipulado exclusivamente através dos métodos públicos do objeto, dos quais apenas a assinatura deve ser revelada.
• O conjunto de assinaturas dos métodos públicos da classe constitui sua interface operacional. Dessa forma, detalhes internos sobre a operação do objeto não são conhecidos, permitindo que o usuário do objeto trabalhe em um nível mais alto de abstração, sem preocupação com os detalhes internos da classe.
• Essa facilidade permite simplificar a construção de programas com funcionalidades complexas, tais como interfaces gráficas ou aplicações distribuídas.
Programação orientada a objetos Classe como tipos de dados:
• OBJETO: INSTÂNCIA de um tipo de dados.
• Por exemplo, ao declarar uma variável do tipo int, estamos criando uma
instância do tipo de dados int. • Uma classe é como um tipo de dado. Quando se cria um novo objeto
num programa, cria-se uma instância da classe, que é uma espécie de gabarito para o objeto. No programa, você cria uma instância da classe. ESTA INSTÂNCIA É CHAMADA OBJETO!!
• CLASSES: tipos de dados definidos pelo usuário. É possível ter várias
instâncias (objeto) de uma mesma classe (ex: mais de uma janela no aplicativo Windows, cada uma com seu próprio conteúdo).
• Cada instância normalmente tem acesso total aos métodos da classe e obtém sua própria cópia de membros de dados.
Programação orientada a objetos Classe como tipos de dados:
• A definição de classes e seus inter-relacionamentos: principal resultado da
etapa de projeto de software. Em geral, expresso em termos de alguma linguagem de modelagem, tal como UML (Unified Modeling Language).
• Classe: gabarito para a definição de objetos. Através da definição de uma
classe descreve-se que propriedades— ou atributos —o objeto terá. • A definição de uma nova classe descreve:
– A especificação de atributos – o comportamento de objetos da classe – funcionalidades aplicadas a objetos
da classe. Funcionalidades descritas através de métodos. Um método: equivalente a um procedimento ou função, com a restrição que ele manipula apenas suas variáveis locais e os atributos que foram definidos para a classe.
Uma vez que estejam definidas quais serão as classes que irão compor uma
aplicação, assim como qual deve ser sua estrutura interna e comportamento, é possível criar essas classes em Java.
Programação orientada a objetos Classe: • Na UML, a representação para uma classe no diagrama de classes
é tipicamente expressa na forma gráfica. Uma classe em UML:
• Especificação de uma classe: nome, conjunto de atributos, conjunto de métodos.
Programação orientada a objetos Classe (Especificação):
a) nome da classe: um identificador para a classe, que permite referenciá-
la posteriormente — por exemplo, no momento da criação de um objeto.
b) O conjunto de atributos descreve as propriedades da classe. Cada atributo é identificado por um nome e tem um tipo associado. Em uma linguagem de programação orientada a objetos pura, o tipo é o nome de uma classe. Na prática, a maior parte das linguagens de programação orientada a objetos oferecem um grupo de tipos primitivos, como inteiro, real e caráter, que podem ser usados na descrição de atributos. O atributo pode ainda ter um valor_default opcional, que especifica um valor inicial para o atributo.
a) Os métodos definem as funcionalidades da classe, ou seja, o que será
possível fazer com objetos dessa classe. Cada método é especificado por uma assinatura, composta por um identificador para o método (o nome do método), o tipo para o valor de retorno e sua lista de argumentos, sendo cada argumento identificado por seu tipo e nome.
Programação orientada a objetos
Classe:
• Mecanismo de sobrecarga (Overloading)
- dois métodos de uma classe podem ter o mesmo nome, desde que suas assinaturas sejam diferentes.
- Tal situação não gera conflito pois o compilador é capaz de detectar qual método deve ser escolhido a partir da análise dos tipos dos argumentos do método.
- Nesse caso, diz-se que ocorre a ligação prematura (early binding) para o método correto.
Programação orientada a objetos
Classe:
• Mecanismo de sobrecarga (Overloading)
- dois métodos de uma classe podem ter o mesmo nome, desde que suas assinaturas sejam diferentes.
- Tal situação não gera conflito pois o compilador é capaz de detectar qual método deve ser escolhido a partir da análise dos tipos dos argumentos do método.
- Nesse caso, diz-se que ocorre a ligação prematura (early binding) para o método correto.
Programação orientada a objetos Objetos • Instâncias de Classes • É através deles que (praticamente) todo o processamento ocorre em
sistemas implementados com linguagens de programação orientadas a objetos.
• Um objeto é um elemento que representa, no domínio da solução, alguma entidade (abstrata ou concreta) do domínio de interesse do problema sob análise. Objetos similares são agrupados em classes.
• Sob o ponto de vista da programação orientada a objetos, um objeto não é muito diferente de uma variável normal. Por exemplo, quando define-se uma variável do tipo int em uma linguagem de programação como C ou Java, essa variável tem:
um espaço em memória para registrar o seu estado (valor); um conjunto de operações que podem ser aplicadas a ela, através dos
operadores definidos na linguagem que podem ser aplicados a valores inteiros.
Programação orientada a objetos Objetos • Instâncias de Classes • É através deles que (praticamente) todo o processamento ocorre em
sistemas implementados com linguagens de programação orientadas a objetos.
• Um objeto é um elemento que representa, no domínio da solução, alguma entidade (abstrata ou concreta) do domínio de interesse do problema sob análise. Objetos similares são agrupados em classes.
• Sob o ponto de vista da programação orientada a objetos, um objeto não é muito diferente de uma variável normal. Por exemplo, quando define-se uma variável do tipo int em uma linguagem de programação como C ou Java, essa variável tem:
um espaço em memória para registrar o seu estado (valor); um conjunto de operações que podem ser aplicadas a ela, através dos
operadores definidos na linguagem que podem ser aplicados a valores inteiros.
Programação orientada a objetos Objetos • Da mesma forma, quando se cria um objeto, esse objeto adquire um
espaço em memória para armazenar seu estado (os valores de seu conjunto de atributos, definidos pela classe) e um conjunto de operações que podem ser aplicadas ao objeto (o conjunto de métodos definidos pela classe).
• Um programa orientado a objetos é composto por um conjunto de
objetos que interagem através de “trocas de mensagens”. Na prática, essa troca de mensagem traduz-se na aplicação de métodos a objetos.
• As técnicas de programação orientada a objetos recomendam que a
estrutura de um objeto e a implementação de seus métodos devem ser tão privativos como possível. Normalmente, os atributos de um objeto não devem ser visíveis externamente. Da mesma forma, de um método deve ser suficiente conhecer apenas sua especificação, sem necessidade de saber detalhes de como a funcionalidade que ele executa é implementada.
Programação orientada a objetos
Herança:
• Permite criar uma classe que é semelhante a uma classe previamente definida, mas que ainda possui algumas propriedades próprias. Esta nova classe herda todos os dados os métodos da classe-base testada.
• (o nível de herança pode ser controlado com as palavras-chave public, private e protected).
Programação orientada a objetos
Herança: • O modificador de visibilidade pode estar presente tanto para
atributos como para métodos. Em princípio, três categorias de visibilidade podem ser definidas:
- público, denotado em UML pelo símbolo +: nesse caso, o atributo ou método de um objeto dessa classe pode ser acessado por qualquer outro objeto (visibilidade externa total);
- privativo, denotado em UML pelo símbolo -: nesse caso, o atributo ou método de um objeto dessa classe não pode ser acessado por nenhum outro objeto (nenhuma visibilidade externa);
- protegido, denotado em UML pelo símbolo #: nesse caso, o atributo ou método de um objeto dessa classe poderá ser acessado apenas por objetos de classes que sejam derivadas dessa através do mecanismo de herança.
Programação orientada a objetos
Herança: • O conceito de encapsular estrutura e comportamento
em um tipo não é exclusivo da orientação a objetos; • particularmente, a abstratos de dados segue esse mesmo
conceito. O que torna a orientação a objetos única é o conceito de herança.
• HERANÇA: Mecanismo que permite que características
comuns a diversas classes sejam fatoradas em uma classe base, ou superclasse. A partir de uma classe base, outras classes podem ser especificadas. Cada classe derivada ou subclasse apresenta as características (estrutura e métodos) da classe base e acrescenta a elas o que for definido de particularidade para ela.
Programação orientada a objetos
Herança: • Formas de relacionamento: 1) Extensão: a subclasse estende a superclasse,
acrescentando novos membros (atributos e/ou métodos). A superclasse permanece inalterada, motivo pelo qual este tipo de relacionamento é normalmente referenciado como herança estrita.
2) Especificação: a superclasse especifica o que uma
subclasse deve oferecer, mas não implementa nenhuma funcionalidade. Diz-se que apenas a interface (conjunto de especificação dos métodos públicos) da superclasse é herdada pela subclasse.
Programação orientada a objetos
Herança: • Formas de relacionamento:
3) Combinação de extensão e especificação: a subclasse herda a interface e uma implementação padrão de (pelo menos alguns de) métodos da superclasse. A subclasse pode então redefinir métodos para especializar o comportamento em relação ao que é oferecido pela superclasse, ou ter que oferecer alguma implementação para métodos que a superclasse tenha declarado mas não implementado. Normalmente, este tipo de relacionamento é denominado herança polimórfica.
Programação orientada a objetos
Polimorfismo: • Princípio pelo qual duas ou mais classes derivadas de uma
mesma superclasse podem invocar métodos que têm a mesma identificação (assinatura) mas comportamentos distintos, especializados para cada classe derivada, usando para tanto uma referência a um objeto do tipo da superclasse.
• Esse mecanismo é fundamental na programação orientada
a objetos, permitindo definir funcionalidades que operem genericamente com objetos, abstraindo-se de seus detalhes particulares quando esses não forem necessários.
Programação orientada a objetos Polimorfismo: Exemplo: Pseudocódigo para uma classe-base Car: class Car { data direcao; data posição; data velocidade; method Steer(); method PressGasPedal(); method PressBrake(); } - Os campos de dados e métodos estão todos encapsulados no interior da
classe. Campos de dados privados em relação à classe: não podem ser acessados diretamente de fora da classe. Apenas os 3 métodos da classe podem acessar os campos de dados.
Programação orientada a objetos Polimorfismo: Derivando uma Nova Classe através de herança class PassingCar inherits from Car { method Pass(); } Essa classe herda todos os campos de dados e métodos da classe Car. Utilizando polimorfismo para criar uma classe FastCar: class FastCar inherits from PassingCar { Method Pass(); } A classe acima, em vez de apenas herdar o método Pass(), ela define sua própria
versão. Implementa a mesma funcionalidade de um modo um pouco diferente.
Programação orientada a objetos Polimorfismo:
• Para que o polimorfismo possa ser utilizado, é necessário que os métodos que estejam sendo definidos nas classes derivadas tenham exatamente a mesma assinatura do método definido na superclasse;nesse caso, está sendo utilizado o mecanismo de redefinição de métodos (overriding). Esse mecanismo de redefinição é muito diferente do mecanismo de sobrecarga de métodos, onde as listas de argumentos são diferentes.
Programação orientada a objetos Polimorfismo:
• o compilador não tem como decidir qual o método que será utilizado se o método foi redefinido em outras classes: pelo princípio da substituição um objeto de uma classe derivada pode estar sendo referenciado como sendo um objeto da superclasse. Se esse for o caso, o método que deve ser selecionado é o da classe derivada e não o da superclasse.
• Dessa forma, a decisão sobre qual dos métodos método que deve ser selecionado, de acordo com o tipo do objeto, pode ser tomada apenas em tempo de execução, através do mecanismo de ligação tardia. O mecanismo de ligação tardia também é conhecido pelos termos em inglês late binding, dynamic binding ou ainda run-time binding.
• Ver em:
• http://www.dca.fee.unicamp.br/cursos/PooJava/Aulas/poojava.pdf
Exemplos dos conceitos acima para fechar
Tópico programação JAVA
Linguagem C • Características do C • Estrutura de um programa em C • Variáveis e constantes • Diretivas de compilação • Funções de entrada e saída de dados • Operadores lógicos e relacionais • Conversão de tipo (casting) • Controle de fluxo • Matrizes e Strings • Funções • Recursividade • Tipos definidos pelo usuário • Ponteiros • Funções de manipulações de arquivos
Linguagem C Características: • é uma linguagem de alto nível com uma sintaxe bastante estruturada e
flexível. • Programas em C são compilados, gerando programas executáveis. • compartilha recursos de alto nível e de baixo nível: permite acesso e
programação direta do microprocessador rotinas cuja dependência do tempo é crítica, podem ser facilmente implementadas usando instruções em Assembly. Por esta razão, o C é a linguagem preferida dos programadores de aplicativos.
• O C é uma linguagem estruturalmente simples e de grande portabilidade.
• Os fabricantes de compiladores fornecem uma ampla variedade de rotinas pré-compiladas em bibliotecas.
• Implementação de ponteiro para memórias: matriz, estruturas e funções.
Linguagem C Estrutura de um programa em C
• Comandos de preprocessador • definições de tipo • protótipos de função – declara tipos de função e variáveis passadas a função. • Variáveis • Funções
É obrigatório o uso da função main( ). void main() {
printf( ``aula de C \n'' );
} tipo_retorno nome_da_função (lista de parâmetros) { variáveis locais; comandos em C; }
Linguagem C Variáveis: • Posições de memória que o computador reserva para
armazenar os dados manipulados pelo programa. • Estas posições devem ser reservadas (declaradas) no início do
programa, a partir daí ficam disponíveis para receberem qualquer conteúdo. Este conteúdo pode ser alterado quando necessário.
• Locais ou globais • Declaração: nome (identificador) e o tipo de dados que ela
receberá. tipo_variável lista_de_variáveis; int i, ,j, k; float x, y, z; char ch;
Linguagem C Variáveis: Regras de construção dos nomes: • Devem começar por uma letra (a - z , A - Z) ou um
underscore ( _ ). • O resto do identificador deve conter apenas letras,
underscores ou dígitos (0 - 9). Não pode conter outros caracteres. Em C, os identificadores podem ter até 32 caracteres.
• Letras maiúsculas são diferentes de letras minúsculas: Por exemplo: MAX, max, Max são nomes diferentes para o compilador.
Linguagem C Variáveis numéricas: Definidas para receberem dados numéricos (cálculos) inteiro, ponto flutuante e dupla precisão.
• Uma variável numérica inteira pode receber um valor inteiro
entre -32768 a 32767.
• Uma variável numérica de ponto flutuante pode receber um número real de até 7 dígitos.
• Uma variável numérica de dupla precisão pode receber um número real de até 15 dígitos.
Linguagem C Variáveis alfanuméricas: São definidas para receberem dados do tipo texto ou caractere.
• Podem armazenar letras, caracteres especiais, números, ou a combinação de todos eles, porém, nunca poderão ser utilizadas em cálculos, pois a característica deste tipo é meramente "texto".
• Deve definir o tamanho – quantos caracteres poderá comportar..
Linguagem C
Variáveis
tipo de dado C Tamanho
(bytes) Limite inferior Limite Superior
Char 1 - -
Unsigned char 1 0 255
short int 2 -32768 +32767
unsigned short int 2 0 65536
(long)int 4 -291 +291 – 1
float 4 -3.2 x 10 98 +3.2 x 10 +/-98
double 8 -1.7 x 10 908 +1.7 x 10 +/908
Linguagem C Constantes:
• Assim como as variáveis, são posições de memória que armazenam conteúdos numéricos ou alfanuméricos.
• As formas e regras para a construção de constantes são as mesmas das variáveis.
• Diferença para variáveis: uma constante recebe um conteúdo inicial que não sofre alteração durante a execução do programa (é um conteúdo constante!).
int const a = 1;
const int a = 2;
Linguagem C Constantes:
• Frequentemente se vê declaração de const em parâmetros de
função. Isto informa apenas que a função não irá alterar o valor do parâmetro.
Exemplo:
void strcpy (char *buffer, char const *string)
- O segundo argumento string é uma string C que não será alterada
pela função da biblioteca padrão de cópia de string (strcpy()).
• Obs: O preprocessador #define é um método mais flexível para definir constantes num programa.
Linguagem C Diretivas de compilação: • comandos que são processados durante a compilação do
programa. • informam ao compilador do C, basicamente: constantes simbólicas e bibliotecas #include: diz ao compilador para incluir na compilação do
programa outros arquivos. Geralmente estes arquivos contem bibliotecas de funções ou rotinas do usuário.
#define: diz ao compilador quais são as constantes
simbólicas usadas no programa.
Linguagem C Diretivas de compilação: Exemplo: #include <stdio.h> #define PI 3.1415 void main() {
int valor1, valor2, resultado;
scanf("%d",&valor1);
scanf("%d",&valor2);
resultado = valor1 + valor2;
printf("%d",resultado*PI);
}
Linguagem C Diretivas de compilação:
Exemplo:
#include <math.h>
void main()
{
float area, raio = 5.0;
área = M_PI * raio * raio;
}
Linguagem C Funções de entrada e saída de dados:
scanf(), printf()
Linguagem C Funções de entrada e saída de dados:
Biblioteca:stdio.h
getchar(),
putchar(int c)
Biblioteca:conio.h
getch()
getchar()
Linguagem C Funções de entrada e saída de dados:
Biblioteca:stdio.h
getchar(),
putchar(int c)
Biblioteca:conio.h
getch()
getchar()
Linguagem C Funções de entrada e saída de dados: #include <stdio.h>
#include <conio.h>
main() {
float bruto, liq, perc;
clrscr();
printf("Digite o salario bruto:");
scanf("%f",&bruto);
printf("Digite o percentual:");
scanf("%f",&perc);
liq = bruto-(bruto*perc/100);
printf("Salario liquido: ");
printf("%f",liq);
}
Linguagem C Operações aritméticas: • + - * / (inteiros e pto flutuante) % (inteiros) • Atribuição: i = 4; ch = `y'; • Incremento ++, decremento --
• expr1 op = expr2 mais eficiente que expr1 = expr1 op expr2
(i = i + 3 como i += 3)
Linguagem C
Operadores Lógicos • Lógica booleana para a construção de expressões
condicionais • &&, || e ! (AND, OR e NOT)
Linguagem C
Operadores Relacionais
Conversão de tipo (casting) • Algumas vezes queremos, momentaneamente, modificar o tipo de
dado representado por uma variável, isto é, queremos que o dado seja apresentado em um tipo diferente do qual a variável foi inicialmente declarada. Por exemplo: declaramos uma variável como int e queremos, momentaneamente, que seu conteúdo seja apresentado como float. Este procedimento é chamado de conversão de tipo ou casting (moldagem, em inglês).
• Sintaxe: A sintaxe da instrução de conversão de tipo é: (tipo) variável • onde tipo é o nome do tipo ao qual queremos converter o dado
armazenado em variável.
Conversão de tipo (casting) int num; float valor = 13.0; num = (int)valor % 2;
• No exemplo acima a variável valor foi declarada inicialmente como
sendo do tipo float recebendo o valor inicial 13.0. • Logo em seguida o conteúdo de valor é convertido para o tipo int
para realizar a operação módulo (%) com o inteiro 2. Aqui a conversão é necessária pois a operação módulo somente pode ser feita com inteiros. É importante salientar que a conversão de tipo é feita com o dado armazenado em uma variável mas a variável continua tendo o seu tipo original. No exemplo acima a variável valor e os dados nela armazenados continuam sendo do tipo float após a conversão.
Linguagem C
• Controle de Fluxo – Desvio de Fluxo
• Comando if
• Operador condicional ?
• Comando switch
Linguagem C Comando if
(condicional simples)
(a) if (expressão) comando ;
Linguagem C Comando if
(condicional composta)
(b) if (expressão) comando1 else comando2;
(c) if (expressão1)
comando1;
else if (expressão2)
comando2;
else
comando3;
Linguagem C • Comando if
Linguagem C • Comando if-else
Linguagem C O operador condicional ?
• Condição ternária – forma mais eficiente para expressar comandos if simples
expressão1 ? expressão2: expressão3
é equivalente a:
if (expressão1)
expressão2
else expressão3
Linguagem C Comando switch-case
- Comando de desvio múltiplo
Linguagem C Comando switch-case
- Comando de desvio múltiplo
Linguagem C
• Controle de Fluxo – Interação e loop
• Comando for
• Operador condicional while
• Comando do-while
• Comando break e continue
Linguagem C Comando for for (inicialização; condição; incremento){
bloco
}
• Inicialização:expressão de inicialização do contador. • Condição:expressão lógica de controle de repetição. • Incremento: expressão de incremento do contador. • Bloco: um conjunto de instruções a ser executado.
Linguagem C Comando while while(condição){
bloco
}
• condição: expressão lógica ou numérica. • Bloco: conjunto de instruções
Linguagem C Comando do...while do
bloco
} while(condição);
• condição: expressão lógica ou numérica. • Bloco: conjunto de instruções
Linguagem C Comandos break e continue Comandos de controle de loop • break: permite que o fluxo de execução seja desviado para o
enunciado seguinte a um loop controlado por uma instrução switch, while, do-while ou for.
• continue: desvia o fluxo de execução de volta para a expressão de condição associada ao controle do loop.
Linguagem C
Matrizes e strings
• Matrizes unidimensionais
• Matrizes multidimensionais
• Strings
Linguagem C
Matrizes unidimensionais • Listas de informações do mesmo tipo, que são
armazenadas em posições contíguas na memória.
• Declaração: tipo nome_var[tamanho]; • Exemplo:
int listadenumeros[50]; terceironumero = listadenumeros[2]; listadenumeros[5]=100;
Linguagem C
Matrizes multidimensionais • Matriz bidimensional – matriz linha-coluna • Matrizes de três ou mais dimensões: demandam muita quantidade
de memória. • Declaração: tipo nome[a][b][c]…[z].
int tabeladenumeros[50][50];
anumber = tabeladenumeros[2][3]; tabledenumeros[25][16]=100;
Passagem para funções: int m[4][3][6][5]; func1(int m[][3][6][5]) { .... }
Linguagem C
Strings • Matrizes de caracteres
• Ex: char nome[50]; • printf(“%s”,nome); • Caractere ‘\0’: indica término de uma string • Exemplos ilegais: char firstname[50],lastname[50],lastname[100]; firstname="Arnold"; lastname="Mr"+firstname+lastname;
Linguagem C Funções
• Obrigatório: função main() • C Não tem procedures Protótipos de funções:
• Antes de utilizar uma função, C deve ter conhecimento do tipo que ela retorna e os tipos de parâmetro que a função espera:
– Código mais estruturado – Permite ao compilador verificar a sintaxe das chamadas de função
• Na declaração da função, deve-se indicar o tipo de retorno da função, o nome
da função e os tipos de parâmetros na ordem em que eles aparecem na definição da função.
• Lista de argumentos: valores que a função recebe • return(): determina o fim lógico da rotina e o valor de retorno
Linguagem C Funções
Declaração: float media(float a, float b); Definição: float media(float a, float b) { float media; media=(a+b)/2; return(media); } Chamada à função: main() { float a=5, b=15, result; result = media(a,b); printf("media=%f\n",result); }
Linguagem C Funções do tipo void
• Forma de emular procedures do tipo PASCAL. • Não retornam valor • Não usar return
void quad() { int loop; for (loop=1;loop<10;loop++) printf("%d\n",loop*loop); }
main() { quad(); }
Linguagem C Funções e matrizes • passagem de matrizes unidimensionais para funções float media (int size,float list[]){ int i; float sum=0.0; for (i=0;i<size;i++) sum+=list[i]; return(sum/size); }
• float list[ ] informa ao C que list é uma matriz de float. • não especificamos a dimensão da matriz quando ela é um
parâmetro da função.
Linguagem C Funções e matrizes
• passagem de matrizes bidimensionais para funções void printtable (int xsize,int ysize,float table[][5]) { int x,y; for (x=0;x<xsize;x++){ for (y=0;y<ysize;y++) printf("%f",table[x][y]); printf("\n"); } }
• float table[][5] informa ao C que a tabela é uma matriz de dimensão
n x 5 de float. • Deve-se especificar a segunda (e subsequente) dimensão da matriz, MAS
não a primeira dimensão.
Linguagem C Recursividade • A recursividade talvez seja a mais importante vantagem das
funções em C. • • Recursão: processo pelo qual uma função chama a si mesma
repetidamente um numero finito de vezes. Este recurso é muito útil em alguns tipos de algoritmos chamados de algoritmos recursivos.
• Exemplo clássico: calculo do fatorial de um número. Definição de fatorial: n! = n . (n-1) . (n-2) . ... . 3 . 2 . 1 0! = 1
onde n é um numero inteiro positivo
Linguagem C Recursividade
• Uma propriedade (facilmente verificável) dos fatoriais é
que:
n! = n . (n-1)! • Esta propriedade é chamada de propriedade recursiva:
o fatorial de um número pode ser calculado através do fatorial de seu antecessor. Ora, podemos utilizar esta propriedade para escrevermos uma rotina recursiva para o calculo de fatoriais. Para criarmos uma rotina recursiva, em C, basta criar uma chamada a própria função dentro dela mesma, como no exemplo a seguir.
Linguagem C Recursividade
#include <conio.h>
#include <stdio.h>
void main(){ // declaracao da funcao principal
long double n,f; // declaracao de variaveis
long double fat(unsigned n); // declaracao da funcao fat()
clrscr();
do{ // leitura do numero
puts("Digite um numero positivo para calculo do fatorial");
printf("numero: ");
scanf("%Lf",&n);
}while(n < 0.0 || n > 1000.0);
f = fat(n); // chamada a funcao fat()
printf("\n%.0Lf! = %Le",n,f); // impressao do resultado
};
Linguagem C Recursividade
/*rotina fat()*/
long double fat(unsigned n){ // declaracao da funcao
long double valor; // fdeclaracao de variavel temporaria
if(n == 0.0){ // se fim da recursao...
valor = 1.0; // ... calcula ultimo valor.
}else{ // senao...
valor = n * fat(n-1.0); // ... chama fat(n-1).
};
return(valor); // retorna valor.
};
Linguagem C Recursividade
Uma função recursiva cria a cada chamada um novo conjunto de variáveis locais.
Não existe ganho de velocidade ou espaço de memória significativo com o uso de funções recursivas.
Teoricamente uma algoritmo recursivo pode ser escrito de forma iterativa e vice-versa. A principal vantagem destes algoritmos é que algumas classes de algoritmos [de inteligência artificial, simulação numérica, busca e ordenação em arvore binaria, etc.] são mais facilmente implementadas com o uso de rotinas recursivas.
Linguagem C
Tipos definidos pelo usuário:
• Estruturas
• uniões
Linguagem C Estrutura: • Agrupamento de variáveis inter-relacionadas. • Uma vez definido um tipo de estrutura, passa-se a ter um
novo tipo de dado, composto por um conjunto de variáveis, chamadas de membros ou campos, logicamente relacionadas, mas não necessariamente do mesmo tipo.
• A definição de um tipo de estrutura não aloca espaço em memória, apenas define um novo tipo de dado que, posteriormente, poderá ser usado na declaração de estruturas deste tipo, quando, aí sim, a memória será alocada.
Linguagem C
Estrutura: Definição
struct tipo_estrutura
{
tipo_membro_1 nome_membro_1;
tipo_membro_2 nome_membro_2;
...
tipo_membro_n nome_membro_n;
};
Linguagem C
Estrutura: - A declaração de estruturas pode ser feita de duas formas: no momento em que se define o tipo fazendo uso do identificador de um tipo previamente definido.
Linguagem C Estrutura: Referência ao membro da estrutura
struct info { int valor; short dia; short mes; short ano; }informações;
main() { short dados; dado = informações.dia; }
Linguagem C Estrutura: Matrizes de estruturas
struct conta { float saldo; float emprestimo; };
main() { struct conta cliente [10]; cliente[4].saldo = 500.65; }
Linguagem C Estrutura: Inicialização de uma estrutura:
struct dados { int valor_1; char valor_2; }; struct dados informa = { 20 , ‘F’ };
Linguagem C União: • possibilitam que variáveis com diferentes tipos de
dados compartilhem uma mesma região de memória.
• Útil nos casos em que as variáveis são usadas em partes distintas de um programa; não havendo, portanto, nenhum problema em ocuparem uma mesma região de memória.
• A quantidade total de memória alocada é igual à quantidade necessária para o armazenamento do membro que mais espaço ocupa.
Linguagem C União: Definição: union nome_da_união { tipo_do_membro_1 nome_do_membro_1; tipo_do_membro_2 nome_do_membro_2; ... tipo_do_membro_n nome_do_membro_n; }
Linguagem C União: - Matrizes como membros de uma união (economia em relação à
declaração das matrizes em separado) union info { char dado_1[4]; short int dado_2[2]; }informações;
Linguagem C União:
- Matrizes declaradas em separado (sem union)
Linguagem C União: acesso aos membros - Operador ponto (.): espera, à sua esquerda, o nome da união - Operador flexa ()espera, à sua esquerda, um ponteiro para a união. union { int matriz_1 [10]; int matriz_2 [20]; } valores, *ptr; main() { ptr = &valores; valores.matriz_1 [0] = 10; //usando o ponto valores.matriz_2 [0] = 20; ptr -> matriz_1 [1] = 15; //usando a flexa ptr -> matriz_2 [1] = 25; }
Linguagem C União: Inicialização • Apenas o valor a ser atribuído ao primeiro elemento pode ser
especificado. • No exemplo, o membro nome da união participante, será inicializado com
a cadeia especificada:
union { char nome[10]; char dado[15]; } participante = “Fernando”; • As únicas operações válidas sobre uniões são a obtenção do seu
endereço e o acesso a um dos seus membros. Quanto aos ponteiros, podem ser usados com uniões da mesma forma como são com estruturas (visto mais adiante).
Linguagem C União: Uniões como membros de uma estrutura
struct info { char nome[10]; int identidade; union { char ref_fisica [10]; char ref_juridica [10]; }referencia; };
Linguagem C Ponteiros Vantagens: • única forma de expressar algumas computações; • produz código compacto e eficiente; • fornece uma ferramenta muito poderosa; • provê flexibilidade; • um único ponteiro permite acessar diferentes dados em diferentes
posições de memória, bastando para isso trocar o endereço armazenado na variável ponteiro.
• fornecem um mecanismo pelo qual as funções podem retornar mais de um valor, permitem um acesso mais rápido aos elementos de vetores de matrizes e possibilitam a criação de estruturas de dados complexas onde cada elemento da estrutura deve “apontar” para outros elementos da estrutura.
C utiliza ponteiros explicitamente com: matrizes, estruturas e funções.
Linguagem C
Ponteiros
Definição:
• Um ponteiro é uma variável que contém o endereço em memória de outra variável, essa sim, normalmente, contendo o dado a ser manipulado pelo programa.
Linguagem C Ponteiros
Declaração:
• Deve-se declarar um ponteiro com o mesmo tipo (int, char, etc...) do bloco a ser apontado. Por exemplo, se queremos que um ponteiro aponte para uma variável int devemos declará-lo como int também.
• Sintaxe: tipo_ptr *nome_ptr1; ou tipo_ptr* nome_ptr1, nome_ptr2, ...;
tipo_ptr: tipo de bloco para o qual o ponteiro apontará
Linguagem C Ponteiros Declaração - exemplo: int *p; - declara um ponteiro chamado p que aponta para um inteiro. - Este ponteiro aponta para o primeiro endereço de um bloco de quatro
bytes. - Sempre é necessário declarar o tipo do ponteiro. float* s_1, s_2; - declara dois ponteiros (s_1 e s_2) do tipo float. - todos os elementos da lista serão declarados ponteiros. OBS: O ponteiro deve estar associado a um mesmo tipo: Não é permitido
atribuir o endereço de um short int a um long int, por exemplo.
Linguagem C Ponteiros Operadores & e * • O operador unário &: fornece o “endereço de uma variável”
(1º. Byte do bloco ocupado pela variável)
• O operador de indireção (de conteúdo) * fornece o “conteúdo de um objeto apontado por um ponteiro” – o conteúdo de um endereço!!
• scanf("%d", &num); - refere-se ao endereço do bloco ocupado pela variável num. "leia
o buffer do teclado, transforme o valor lido em um valor inteiro (4 bytes) e o armazene no bloco localizado no endereço da variável num".
Linguagem C Ponteiros Operadores & e * • Para se atribuir a um ponteiro o endereço de uma variável: int *p, val=5; // declaração de ponteiro e variável p = &val; // atribuição • Para se atribuir a uma variável o conteúdo de um endereço: int *p = 0x3f8, val; // declaração de ponteiro e variável val = *p; // atribuição
OBS: • O operador endereço (&) somente pode ser usado em uma única variável.
Não pode ser usado em expressões como, por exemplo, &(a+b). • O operador conteúdo (*) somente pode ser usado em variáveis ponteiros.
Linguagem C Ponteiros O que acontece em nível de máquina em memória?
int x = 1, y = 2;
int *ip;
ip = &x;
y = *ip;
x = ip;
*ip=3;
Linguagem C Ponteiros • Quando um ponteiro é declarado, ele não aponta para nenhum lugar. É
necessário fazer com que ele aponte para algum endereço de memória antes de usá-lo.
• Errado!! (mas.. no Compiler Error!!): int *ip; *ip = 100; • Correto!! int *ip; int x; ip = &x; *ip = 100;
Linguagem C Ponteiros Aritmética de ponteiro • É possível fazer operações aritméticas num ponteiro: float *flp, *flq; *flp = *flp + 10; ++*flp; (*flp)++; flq = flp; • A razão de se associar um ponteiro a um tipo de dado é para que
ele saiba com quantos bytes o dado está armazenado. Quando se incrementa um ponteiro, é incrementado o ponteiro por um ‘bloco’ de memória.
Linguagem C Ponteiros Aritmética de ponteiro • para um ponteiro de caracteres: ++ch_ptr adiciona 1 byte ao endereço. • para um float ou int: ++ip ou ++flp adiciona 4 bytes ao endereço. • Considere uma variável float (fl) e um ponteiro para um float (flp):
• flp aponta para f1, logo se o ponteiro for incrementado (++flp) ele se move a posição mostrada 4 bytes adiante. Se, de uma outra forma, somar 2 ao ponteiro, então ele se move duas posições de float. i.e. 8 bytes
Linguagem C Ponteiros e Funções
• Quando C passa argumentos para funções, ele pode passar esses argumentos por valor ou por referência.
• O valor de uma variável var de uma função fun_1() passada para uma outra função fun_2() não pode ser alterado pela função fun_2(). De fato, isto é verdade se passamos o valor da variável var para a função fun_2().
• Mas o valor de var pode ser alterado por fun_2() passando seu endereço (passagem por referência). Outras linguagens também fazem isso (ex. var parameters em PASCAL). C usa ponteiros explicitamente para fazer isto.
Linguagem C Ponteiros e Funções swap (&a, &b); void swap (int *px, int *py) { int temp; temp = *px; //conteúdo do ponteiro *px = *py; *py = temp; } void main() { int a,b; scanf(“%d %d”, &a, &b); swap(&a,&b); /*passagem dos endereços de a e b*/ printf(“%d %d”, a, b); } • A função swap recebe os endereços das variáveis passada pela função
main( ), armazenando-os nos ponteiros px e py. Dentro da função, troca-se os conteúdos dos endereços apontados.
Linguagem C Ponteiros para estruturas
- útil na implementação de organizações de dados que envolvam estruturas como elementos.
- Ao somar 1 a um ponteiro para uma estrutura, o novo valor do ponteiro será a posição na qual se encontra armazenada a próxima estrutura deste mesmo tipo.
• O operador -> permite o acesso a um membro da estrutura apontada por um ponteiro.
Linguagem C Ponteiros para estruturas #include <stdio.h>
struct bloco_controle
{
int ax, bx, cx, dx;
long pc;
long sp;
};
main()
{
struct bloco_controle pcb[10], *ptr;
ptr = pcb;
pcb[0].ax = 10;
pcb[1].ax = 20;
printf(“valor ax do bloco 1 = %d\n”, (*ptr).ax); /*ou ptr->ax*/
printf(“valor ax do bloco 2 = %d\n”, (*(ptr + 1)).ax); /*ou (ptr+1)->ax*/
}
Linguagem C Ponteiros para estruturas - Exemplo de listas encadeadas: typedef struct { int valor; ELEMENTO *próximo; }ELEMENTO;
ELEMENTO n1, n2; n1.próximo = &n2;
• próximo deve ser declarado como um ponteiro para ELEMENTO, já que 4 bytes
podem ser estabelecidos a qualquer ponteiro. Não pode ser do tipo variável pois resultaria numa definição recursiva, o que não é permitido.
Linguagem C Ponteiros e matrizes - Em C, o nome de uma matriz é tratado como o endereço de seu
primeiro elemento.
- Assim ao se passar o nome de uma matriz para uma função está se passando o endereço do primeiro elemento de um conjunto de endereços de memória.
- se mat é uma matriz, então mat e &mat[0] representam o mesmo endereço.
- Podemos acessar o endereço de qualquer elemento da matriz do seguinte modo: &mat[i] é equivalente a (mat + i). (mat + i) não representa uma adição aritmética normal, mas o endereço do i-ésimo elemento da matriz (endereço contado a partir do endereço inicial mat[0]).
Linguagem C Ponteiros e matrizes • Do mesmo modo que se pode acessar o endereço de cada
elemento da matriz por ponteiros, também se pode acessar o valor de cada elemento usando ponteiros. Assim mat[i] é equivalente a *(mat + i).
• Aqui se usa o operador conteúdo (*) aplicado ao endereço do i-ésimo elemento da mat.
Nota: Ponteiros e matrizes são diferentes: • um ponteiro é uma variável. Pode-se fazer pa = a e pa++. • Uma matriz NÃO é uma variável. a = pa e a++ SÃO ILEGAIS!!
e como passar matrizes para funções?
Linguagem C Ponteiros e matrizes • Quando uma matriz é passada para uma função, o que é
realmente passado é a sua localização inicial dos elementos em memória.
strlen(s) ≡ strlen(&s[0]); int strlen ( char s[ ] ); equivale a int strlen ( char *s ); int strlen(char *s) { //função da bib padrão. Retorna o comprimento da string
char *p = s; while (*p != ‘\0’) p++;
return p – s; }
Linguagem C Matrizes de ponteiros • Representações de dados que irão funcionar de maneira
eficiente e conveniente com linhas de texto de comprimentos variáveis.
Como fazer isto? 1 - Armazene linhas fim-a-fim numa grande matriz de char. \n irá
delimitar linhas. 2- Armazene ponteiros numa matriz diferente onde cada ponteiro
aponta para o primeiro char de cada nova linha. 3- Compare duas linhas usando a função de biblioteca padrão
strcmp(). 4- Se duas linhas estão fora de ordem – troque o ponteiro na matriz
de ponteiros (não em texto).
Linguagem C Matrizes de ponteiros Esta solução elimina: • - controle complicado de armazenamento • - alto overhead de se mover linhas.
Linguagem C Matrizes multimendionais e ponteiros • Os elementos de uma matriz são armazenados linha por
linha. • Quando passamos uma matriz bidimensional para uma
função, devemos especificar o número de colunas – o número de linhas é irrelevante. A razão para isto é o ponteiro novamente.
• C precisa saber o número de colunas para poder avançar de linha a linha na memória.
• O compilador C precisa saber o comprimento de cada linha para indexar a matriz corretamente.
Linguagem C
Matrizes multimendionais e ponteiros
Considere int a[5][35] a ser passado numa função:
Podemos fazer:
f(int a[ ][35]) { ..... } ou f(int (*a)[35]) { ..... }
(Precisamos dos parênteses (*a) já que [ ] tem maior precedência que *) int (*a)[35]; //declara um ponteiro para uma matriz de 35 ints. int *a [35]; //declara uma matriz de 35 ponteiros para ints.
Linguagem C
Matrizes multimendionais e ponteiros
Considere int a[5][35] a ser passado numa função:
Podemos fazer:
f(int a[ ][35]) { ..... } ou f(int (*a)[35]) { ..... }
(Precisamos dos parênteses (*a) já que [ ] tem maior precedência que *) int (*a)[35]; //declara um ponteiro para uma matriz de 35 ints. int *a [35]; //declara uma matriz de 35 ponteiros para ints.
Linguagem C Matrizes multimendionais e ponteiros
Diferença sutil entre ponteiros e matrizes. char *name[10]; char Aname[10][20]; Nós podemos legalmente fazer name[3][4] e Aname[3][4] em C.
Entretanto: • Aname é realmente uma matriz bidimensional de 200 elementos
do tipo char. • Acessa elementos via 20*linha + col + endereço_base em
memória. • name tem 10 elementos ponteiro.
Linguagem C Matrizes multimendionais e ponteiros
- Em geral, para qualquer matriz bidimensional: a[j][k] é equivalente a *(a + (j x comprimento da linha) + k). • Nota: Se cada ponteiro em name é dito apontar para uma
matriz de 20 elementos, então, apenas aí, teremos 200 elementos char.
• A vantagem é que cada ponteiro pode apontar para
matrizes de comprimentos diferentes.
Linguagem C
Matrizes multimendionais e ponteiros
char *name[ ] = {“no month”, “jan”, “feb”, ...}; char Aname[ ][15] = {“no month”, “jan”, “feb”,... };
Linguagem C – Alocação Dinâmica
Malloc( ), sizeof( )
• A linguagem C permite alocar dinamicamente (em tempo de execução), blocos de memória usando ponteiros. Dada a íntima relação entre ponteiros e matrizes, isto significa que podemos declarar dinamicamente matrizes de tamanho variável. Isto é desejável caso queremos poupar memória, isto é, não reservar mais memória que o necessário para o armazenamento de dados.
• Para a alocação de memória usamos a função malloc()(memory
allocation) da biblioteca alloc.h. • A função malloc() reserva, sequencialmente, um certo numero de
blocos de memória e retorna, para um ponteiro, o endereço do primeiro bloco reservado.
• Caso não seja possível alocar o espaço requisitado, a função malloc() retorna a constante simbólica NULL.
Linguagem C – Alocação Dinâmica
Malloc( ), sizeof( )
Definição: void *malloc (size_t numero_de_bytes); Sintaxe: pont = (tipo *)malloc(tam); onde: • pont é o nome do ponteiro que recebe o endereço do
espaço de memória alocado. • tipo é o tipo do endereço apontado (tipo do ponteiro). • tam é o tamanho do espaço alocado: numero de bytes.
Linguagem C – Alocação Dinâmica • Malloc( ), sizeof( )
pont = (tipo*)malloc(num*sizeof(tipo)); num é o numero de elementos que queremos poder armazenar no
espaço alocado. Exemplo: Se queremos declarar uma matriz chamada mat, tipo int, com
num elementos podemos usar o trecho abaixo: ... int *mat; // declaração do ponteiro mat = (int*)malloc(num*2); // alocação de num blocos de 2 bytes Ou int *mat; // declaração do ponteiro mat = (int*) malloc(num * sizeof(int));
Linguagem C – Alocação Dinâmica • sizeof( ) É uma boa prática usar sizeof() mesmo sabendo o verdadeiro
tamanho que se quer -- a fim de manter a portabilidade entre programas. • Sizeof pode ser utilizado para encontrar o tamanho de qualquer tipo de
dado, variáveis ou estruturas. Simplesmente deve ser fornecido um desses tipos como argumento para a função. Logo:
int i; struct COORD { float x,y,z };
typedef struct COORD PT; sizeof(int), sizeof(i), sizeof(struct COORD) e sizeof(PT) são todos ACEITÁVEIS.
Linguagem C – Alocação Dinâmica free()
• Libera um bloco de memória previamente alocado. • Possibilita que a memória seja liberada (desalocada) para
estar novamente disponível, possivelmente para outras chamadas de malloc( ).
• Recebe um ponteiro como argumento e libera a memória a qual o ponteiro se refere.
Sintaxe:
free(pont);
• pont é o nome do ponteiro que contém o endereço do início do espaço de memória reservado.
Linguagem C – Alocação Dinâmica calloc( ) e realloc( ) void *calloc (size_t num_elements, size_t n_bytes); void *realloc(void *ptr, size_t n_bytes); • A função calloc( ) aloca em tempo de execução, um espaço na memória,
com tamanho suficiente para o armazenamento de uma matriz com um total de num_elements elementos. Sendo que cada elemento ocupa um número de bytes especificado por n_bytes. A função retorna um ponteiro para a primeira posição do bloco alocado, ou o valor associado ao identificador NULL, caso não tenha sido possível a alocação.
• A quantidade total de memória alocada corresponde ao valor obtido pela multiplicação do número de elementos pelo tamanho de cada elemento:
int *ip; ip = (int *) calloc (100,sizeof(int)); /*aloca 100 inteiros (inicialmente com
zero)*/
Linguagem C – Alocação Dinâmica calloc( ) e realloc( ) void *calloc (size_t num_elements, size_t n_bytes); • Além de reservar a área de memória, a função inicializa as posições
alocadas com o valor 0. Após a conclusão da função, é retornado o endereço da primeira posição de memória do espaço alocado.
void *realloc(void *ptr, size_t n_bytes); • A função realloc() tem por objetivo modificar o tamanho de um bloco
de memória que já tenha sido alocado. Ao ser chamada a função, deve ser informado o endereço inicial do bloco anteriormente alocado, ptr; e o número de bytes a serem ocupados pelo novo bloco, n_bytes. A função retorna um ponteiro para a primeira posição do bloco alocado, ou o valor NULL, em caso de falha.
• Para alterar o tamanho da memória alocada ao ponteiro *ip acima para um bloco de matrizes de 50 inteiros ao invés de 100:
ip = (int *) realloc (ip,50);
Linguagem C – listas encadeadas typedef struct { int value; ELEMENT *next; }ELEMENT; • queremos aumentar a lista dinamicamente: • link = (ELEMENT *) malloc (sizeof(ELEMENT));
Isso irá alocar memória para um novo link. Se quisermos desalocar memória de um ponteiro, usamos
a função free():
free(link)
Linguagem C – listas encadeadas
queue.c
Página 41
C – tópicos de ponteiros avançados char ch; /*um caractere*/ char *pch; /*um ponteiro para um caractere*/ char **ppch; /*um ponteiro para um ponteiro para um caractere. **ppch faz referência ao endereço de memória de *pch que, por
sua vez, faz referência ao endereço de memória da variável ch.
• Ponteiros para ponteiros - Lembrar que char * se refere a uma string terminada com NULL.
C – tópicos de ponteiros avançados Ponteiro para string Ponteiro para várias strings Referência a strings individuais: ppch[0], ppch[1], … Isto é
idêntico a declarar char *ppch[ ]
C – tópicos de ponteiros avançados #include <stdio.h> //Alocação dinâmica de matriz bidimensional: #include <stdlib.h> void main(void) { char **ppch; int n = 100, m = 255, i; char buf[200] = "este e um teste %d\n"; char buf2[255] = " "; ppch = (char **) malloc (sizeof(char *) * n); for (i=0 ; i<n ; i++) { *(ppch+i) = (char *) malloc (sizeof(char)*m); sprintf(buf2,buf,i); strcpy(*(ppch+i),buf2); } for (i=0 ; i<n ; i++) { printf("%s",*(ppch+i)); } }
C – entrada na linha de comando
main (int argc, char ** argv)
• argc é o número de argumentos digitados – incluindo o nome do programa.
• argv é uma matriz de strings contendo cada argumento da linha de comando, incluindo o nome do programa no primeiro elemento da matriz.
C – entrada na linha de comando #include <stdio.h> main (int argc, char **argv) { int i; printf(“argc = %d \n\n”, argc); for (i=0 ; i<argc; ++i) printf(“argv[%d]: %s\n”, i, argv[i]); } prog f1 “f2” f3 4 stop! • argc = 6 //conta o nome do programa • argv[0] = prog //nome do programa • argv[1] = f1 • argv[2] = f2 // “” são ignorados • argv[3] = f3 • argv[4] = 4 //espaços em branco delimitam fim dos argumentos • argv[5] = stop!
C – Ponteiros para funções
• Até agora usamos ponteiros para apontar para endereços de memória onde se encontravam as variáveis (dados).
• Algumas vezes é necessário apontar para funções:
apontar para o endereço de memória que contém o início das instruções de uma função.
Quando assim procedemos, dizemos que usaremos ponteiros para funções.
C – Ponteiros para funções Ponteiros como chamada de função.
• Um uso de ponteiros para funções é passar uma função como
argumento de outra função. Mas também se pode usar ponteiros para funções ao invés de funções nas chamadas normais de funções.
• Sintaxe: A sintaxe de declaração de ponteiro para funções:
tipo_r (*nome_p)(lista);
tipo_r: tipo de retorno da função apontada. nome_p : o nome do ponteiro que apontará para a função. lista:é a lista de argumentos da função.
C – Ponteiros para funções float fun(int a, int b){ ... }
o ponteiro correspondente será: float (*pt)(int,int);
O ponteiro para função deve ser declarado entre parênteses.
O ponteiro e a função retornam o mesmo tipo de dado e tem os mesmos argumentos.
C – Ponteiros para funções Sintaxe: Para atribuir o endereço de uma função para um ponteiro: pont = &função; pont: nome do ponteiro e função: nome da função. • Se um ponteiro contém o endereço de uma função, ele pode ser usado no lugar
da chamada da função. float fun(int a,int b){ ... }
void main(void){ float temp; float (*pt)(int,int); pt = &fun; temp = (*pt)(10,20); /*é o mesmo que: temp = fun(10,20);*/ ... }
C – Ponteiros para funções Aplicação: • A função da biblioteca padrão qsort é uma função muito útil
que é feita para ordenar uma matriz por um valor chave de qualquer tipo em ordem ascendente, desde que os elementos da matriz sejam de tipo fixo.
• O protótipo de qsort (em stdlib.h): void qsort (void *base, size_t num_elements, size_t element_size, int
(*compare) (void const *, void const *));
• O argumento base aponta para a matriz a ser ordenada,
num_elements indica o comprimento da matriz, element_size é o tamanho em bytes de cada elemento da matriz e o último argumento compare é um ponteiro para uma função.
C – Ponteiros para funções Aplicação:
• qsort chama a função compare, que é definida pelo usuário, para comparar o dado quando da sua ordenação.
• Note que qsort mantém sua independência ao dar a responsabilidade de comparação para o usuário.
• A função compare deve retornar alguns valores (inteiros) de acordo com o resultado da comparação:
menor que zero - se o primeiro valor é menor do que o segundo valor
zero: se o primeiro valor é igual ao segundo valor
maior que zero: se o primeiro valor é maior do que o segundo valor.
C – Ponteiros para funções Aplicação: • Algumas estruturas de dados complicadas podem ser ordenadas dessa
maneira. • Por exemplo, para ordenar a estrutura seguinte por chave inteira:
typedef struct { int key; }Record; • Nós podemos escrever uma função de comparação, record_compare:
int record\_compare(void const *a, void const *a) { return( ((Record *) a -> key – (Record *) b ) -> key ); } • Assumindo que temos um array de array_length Records preenchido
apropriadamente com data, podemos chamar qsort desta forma: qsort ( array, arraylength, sizeof(Record), record_compare);
C – Ponteiros para funções Passando uma função como argumento de outra função • Na declaração da função a ser passada: i) apenas a definição normal: tipo nome_p(lista){ float soma(float a,float b){ ... ... } } • Na função receptora: i) Declarar o ponteiro que recebe a função passada na lista
de argumentos:
tipo nome_r (..., tipo (*pt)(lista), ...){ float grad(float x, float y, float (*p)(float,float)){
C – Ponteiros para funções
Passando uma função como argumento de outra função
• Na função receptora: i) Declarar o ponteiro que recebe a função passada na
lista de argumentos: tipo nome_r (..., tipo (*pt)(lista), ...){ float grad(float x, float y, float (*p)(float,float)){ ii) Usar o ponteiro para funções nas chamadas da função
passada: var = (*pt)(lista); valor = (*p)(x,y);
C – Ponteiros para funções
Passando uma função como argumento de outra função
• Na função principal:
i) Passar o nome da função chamada para a função receptora:
var = nome_g(... , nome_p , ...);
g = grad(x,y,soma);
C – Funções de manipulação de arquivos
Streams
• O sistema de arquivos de C é projetado para trabalhar com uma ampla variedade de dispositivos, incluindo terminais, acionadores de disco e acionadores de fita. Embora cada um dos dispositivos seja muito diferente, o sistema de arquivo com buffer transforma-os em um dispositivo lógico chamado de stream. Pelo fato das streams serem amplamente independentes do dispositivo, a mesma função pode escrever em um arquivo em disco ou em algum outro dispositivo, como o console.
C – Funções de manipulação de arquivos
Streams
• Maneira portável para ler e escrever dados. fornecem uma maneira flexível e eficiente de I/O. Uma stream pode ser um arquivo ou um dispositivo físico (ex. impressora ou monitor) que é manipulado com um ponteiro para a stream.
• Existe uma estrutura de dados em C, FILE, que representa todas as streams e é definida em stdio.h. É necessário apenas se referir à estrutura FILE nos programas em C quando estiver fazendo I/O com streams.
• Precisamos apenas declarar um variável ou um ponteiro deste tipo nos nossos programas. NÃo precisamos saber nada mais específico sobre essa definição.
Devemos • abrir um stream antes de qualquer operação de I/O. open • acessá-la. • fechá-la. Close
C – Funções de manipulação de arquivos
Streams
• Stream I/O: BUFFERED. Isto significa que um " bloco" fixo é lido ou escrito para um arquivo via alguma área de armazenamento temporária (o buffer).
• Streams pré-definidas • UNIX define 3 streams pré-definidas (em stdio.h): stdin, stdout, stderr
• Todos eles utilizam texto como método de I/O
C – Funções de manipulação de arquivos
Streams
• Stdin e stdout podem ser usados com arquivos, programas, dispositivos I/O tais como teclado, console, etc.
• Stderr sempre vai para o consocsdss ou tela.
• O console é o default para stdout e stderr. • O teclado é o default para stdin.
• Streams pré-definidas são automaticamente abertas.
C – Funções de manipulação de arquivos
Arquivos • Associa-se um stream a um arquivo específico realizando uma
operação de abertura. Uma vez o arquivo aberto, informações podem ser trocadas entre ele e o programa.
Abrindo um arquivo • A função fopen ( ) abre uma stream para uso e associa um
arquivo a ela. Ela retorna o ponteiro de arquivo associado a esse arquivo.
FILE *fopen(const *char nomearq, const *char modo); • nomearq: ponteiro para uma cadeia de caracteres que forma
um nome válido de arquivo e pode incluir um path. A string apontada por modo determina como o arquivo será aberto. Se o arquivo não puder ser acessado por algum motivo, um ponteiro NULL é retornado.
• Os modos incluem: “r”(read), “w”(write) e “a” (append).
C – Funções de manipulação de arquivos
Arquivos • Para abrir um arquivo, devemos ter um stream (ponteiro para
arquivo) que aponta para uma estrutura FILE. • Logo, para abrir um arquivo, chamado myfile.dat para leitura,
podemos fazer: FILE *stream; stream = fopen("myfile.dat","r"); • É uma boa prática verificar se o arquivo foi aberto
corretamente:
If ( ( stream = fopen("myfile.dat", "r")) == NULL) { printf("arquivo %s nao pode ser aberto\n", myfile.dat"); exit(1); }
C – Funções de manipulação de arquivos
Arquivos Fechando um arquivo • A função fclose() fecha uma stream que foi aberta através
de uma chamada a fopen(). É aconselhável fechar um arquivo antes de abrir outro.
int fclose(FILE *stream);
• A função fflush() atinge o mesmo resultado: int fflush (FILE *stream); fclose() -- fecha um arquivo fflush() -- descarrega um arquivo
C – Funções de manipulação de arquivos
Arquivos Lendo e escrevendo arquivos
int fprintf(FILE *stream, char *format, args…);
int fscanf(FILE *stream, char *format, args…);
• Elas são semelhantes a printf() e scanf() exceto que o dado é lido da stream que deve ter sido aberta com fopen().
• O ponteiro para stream é incrementado automaticamente com TODAS as funções de leitura/escrita de arquivo. Não é necessário se preocupar com isso.
C – Funções de manipulação de arquivos
Arquivos
Lendo e escrevendo arquivos
char *string[80]
FILE *stream;
If ( (stream = fopen(…)) != NULL)
fscanf(stream, "%s", string);
Podemos acessar streams pré-definidas com fprintf(), etc.
fprintf(stderr,"não pode executar!!\n");
fscanf(stdin,"%s",string);
C – Funções de manipulação de arquivos
Arquivos – outras funções
getc() ou fgetc(): lê caracteres de um arquivo aberto no modo leitura por fopen().
int getc(FILE *stream); stream: ponteiro de arquivo do tipo FILE devolvido por fopen(). A função getc() devolve EOF quando o final do arquivo for
alcançado.
do { ch = getc(fp); while (ch != EOF); }
C – Funções de manipulação de arquivos
Arquivos – outras funções
putc( ) ou fputc(): escreve caracteres em um arquivo que foi previamente aberto para escrita através da função fopen( ).
int putc(char ch, FILE *stream)
stream: um ponteiro de arquivo devolvido por fopen() ch: caractere a ser escrito. O ponteiro de arquivo informa a putc() em que arquivo em disco
escrever.
Obs: • getc() e putc() são definidas como MACRO do preprocessador • fgetc() e fputc() são funções de biblioteca do C.
C – Funções de manipulação de arquivos
Arquivos – outras funções
sprintf() e sscanf() - São parecidos com fprintf() e fscanf() exceto que eles
lêem/escrevem para uma string.
int sprintf (char *string, char * format, args…) int sscanf(char *string, char *format, args…)
float tanque_cheio =47.0 /*litros*/ float km = 300; char km_por_llitro[80]; sprintf (km_por_litro, "kilometros por litro = % 2.3f",
km/tanque_cheio);
C – Funções de manipulação de arquivos
Arquivos – outras funções
Encontrando o final do arquivo:
int feof(FILE *stream);
Retorna true se a stream já está no final do arquivo. Logo, para ler uma stream, fp, linha por linha:
while ( !feof(fp) )
fscanf (fp,”%s”, linha);
C – I/O Baixo Nível
• Nessa forma de I/O - UNBUFFERED - cada requisição de leitura/escrita resulta num acesso a disco (ou dispositivo) diretamente para buscar/colocar um número específico de bytes.
• Não há nenhuma facilidade de formatação - estamos lidando com bytes de informação. Isto significa que agora estamos usando arquivos binários (e não texto!).
• Ao invés de ponteiros para arquivo, usamos manipuladores de arquivo de baixo nível ou descritores de arquivo, que fornecem um número inteiro único para identificar cada arquivo.
C – I/O Baixo Nível
Para abrir um arquivo:
int open(char *filename, int flag, int perms) -- retorna um descritor de arquivo ou –1 no caso de falha.
• O flag controla acesso de arquivo e estão definidos em fcntl.h:
O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_WRONLY,...
• perms – pode deixar em 0 para a maioria das nossas aplicações.
C – I/O Baixo Nível
Para fechar um arquivo: int close(int handle);
int read(int handle, char *buffer, unsigned length); int write(int handle, char *buffer, unsigned length); • são usadas para ler/escrever um número específico de bytes
de/para um arquivo(handle) armazenado ou para se colocado na localização de memória especificada por buffer.
• A função sizeof() é usada normalmente para especificar o comprimento.
• read() e write() retornam o número de bytes lidos/escritos ou –1 se falharem.
• (Exemplo na apostila!!)
Linguagem JAVA Tópicos:
1. Tipos primitivos
2. Identificadores
3. Expressões
4. Operações sobre objetos
5. Classes
6. Exceções
7. O ambiente de JAVA
8. ...continua na referência (ver se vale a pena!)
Linguagem JAVA Tipos primitivos:
a) boolean: true ou false 1 bit de armazenamento
default: false
boolean resultado = true;
b) char:
16 bits de armazenamento
default: NUL
char umCaracter = ’A’;
Linguagem JAVA Tipos primitivos:
a) Caracteres representados por sequências de escape:
Linguagem JAVA Tipos primitivos:
c) byte, short, int, long
default: 0
long valor = 100L;
Obs: não existem valores inteiros sem sinal
Valor numérico inteiro Bits de armazenamento (def. na especificação)
Faixa de valores
byte 8 -128 a +127
short 16 -32.768 a +32.767
int 32 -2.147.483.648 a +2.147.483.647
long 64 -9.223.372.036.854.775.808 a +9.223.372.036.854.775.807
Linguagem JAVA Tipos primitivos:
d) float, double
default: 0.0
- Constantes literais do tipo float podem ser identificadas no código Java pelo sufixo f ou F; do tipo double, pelo sufixo d ou D.
Valor real Bits de armazenamento (def. na especificação)
Dígitos significativos de precisão
float 32 nove
double 64 dezoito
Linguagem JAVA Identificadores
• Um nome pode ser composto por letras, por dígitos e pelos símbolos _ e $.
• Um nome não pode ser iniciado por um dígito (0 a 9).
• Letras maiúsculas são diferenciadas de letras minúsculas.
• Uma palavra-chave da linguagem Java não pode ser um identificador.
Case-sensitive: int abel e int Abel são variáveis diferentes!
Linguagem JAVA Identificadores
Convenção padrão:
• Nomes de classes iniciados por letras maiúsculas.
• Nomes de métodos, atributos e variáveis iniciados por letras minúsculas.
• Em nomes compostos, cada palavra do nome é iniciada por letra maiúscula— as palavras não são separadas por nenhum símbolo.
Linguagem JAVA Expressões
• Sentenças terminadas por ;
• envolvendo uma operação aritmética, uma operação
lógica inteira, uma operação lógica booleana, uma avaliação de condições, uma atribuição, um retorno de método, ou ainda operações sobre objetos.
• Expressões retornando valores numéricos, booleanos e outros tipos de expressões.
• Controle do fluxo de execução
Linguagem JAVA
Expressões retornando valores numéricos
• Expressões aritméticas envolvem atributos, variáveis e/ou constantes numéricas (inteiras ou reais).
• Operadores aritméticos definidos em Java:
+ - * / %
++ (incremento)
-- (decremento)
Linguagem JAVA Expressões retornando valores numéricos
• Expressões aritméticas envolvem atributos, variáveis e/ou constantes numéricas (inteiras ou reais). Operadores aritméticos:
+ - * / % ++ --
• Operações lógicas sobre valores inteiros atuam sobre a representação binária do valor armazenado, operando internamente bit a bit. Operadores desse tipo são:
~ Complemento, OR, AND, XOR bit-a-bit, <<,>>,>>>
Linguagem JAVA Expressões retornando valores booleanos • As operações lógicas booleanas operam sobre valores
booleanos. Operadores booleanos incluem: ! Complemento lógico | OR lógico booleano: operador binário infixo: retorna true se
pelo menos um dos dois argumentos é true; ||OR lógico condicional: operador binário infixo: opera como
o |; porém, se o primeiro argumento já é true, o segundo argumento não é nem avaliado;
& AND lógico booleano && AND lógico booleano ^ XOR booleano - Operadores relacionais: >, >=, <, <=, ==, !=
Linguagem JAVA Outros tipo de Expressões • ?: Operador condicional ternário b ? s1 : s2 • = operador binário a += b equivale a a = a + b • Os métodos têm sua execução encerrada de duas maneiras
possíveis. A primeira é válida quando um método não tem um valor de retorno (o tipo de retorno é void): a execução é encerrada quando o bloco do corpo do método chega ao final. A segunda alternativa encerra a execução do método através do comando return. Se o método tem tipo de retorno void, então o comando é usado sem argumentos:
• return; //método com o tipo de retorno void • return expressão; // valor do retorno é o resultado da expressão,
compatível com o tipo de retorno especificado pelo método.
Linguagem JAVA controle do fluxo de execução if if (condição) { bloco_comandos } if (condição) expressão; if (condição) { bloco_comandos_caso_verdade } else { bloco_comandos_caso_falso }
Linguagem JAVA controle do fluxo de execução switch...case switch (variável) { case valor1: bloco_comandos break; case valor2: bloco_comandos break; ... case valorn: bloco_comandos break; default: bloco_comandos }
Linguagem JAVA Controle do fluxo de execução while while (condição) { bloco_comandos } do ... While
do {
bloco_comandos } while (condição); for for (inicialização; condição; incremento) { bloco_comandos }
Linguagem JAVA Operações sobre objetos
Cls obj = new Cls();
• O “método” à direita do operador new é um construtor da classe Cls.
• Construtor: (pseudo-)método especial, definido para cada classe. O corpo desse método determina as atividades associadas à inicialização de cada objeto criado.
• O construtor é apenas invocado no momento da criação do objeto através do operador new.
Linguagem JAVA Operações sobre objetos Construtor • A assinatura de um construtor diferencia-se das assinaturas dos
outros métodos: – não tem nenhum tipo de retorno — nem mesmo void. – o nome do construtor deve ser o próprio nome da classe.
• Pode receber argumentos, como qualquer método. • Usando o mecanismo de sobrecarga, mais de um construtor pode
ser definido para uma classe.
• Toda classe tem pelo menos um construtor sempre definido. (Se nenhum construtor for explicitamente definido pelo programador da classe, um construtor default, que não recebe argumentos, é criado pelo compilador Java)
Linguagem JAVA Operações sobre objetos
Construtor
• No momento em que um construtor é invocado, a seguinte seqüência de ações é executada para a criação de um objeto:
1. O espaço para o objeto é alocado e seu conteúdo é inicializado (bitwise) com zeros.
2. O construtor da classe base é invocado.
3. Os membros da classe são inicializados para o objeto, seguindo a ordem em que foram declarados na classe.
4. O restante do corpo do construtor é executado.
Linguagem JAVA Operações sobre objetos Construtor • Seguir essa seqüência é uma necessidade de forma a garantir
que, quando o corpo de um construtor esteja sendo executado, o objeto já terá à disposição as funcionalidades mínimas necessárias, quais sejam aquelas definidas por seus ancestrais.
• O primeiro passo garante que nenhum campo do objeto terá
um valor arbitrário, que possa tornar erros de não inicialização difíceis de detectar. Esse passo é que determina os valores default para atributos de tipos primitivos
Linguagem JAVA Operações sobre objetos Construtor • Uma vez que um objeto tenha sido criado e haja uma referência
válida para ele, é possível solicitar que ele execute métodos que foram definidos para objetos dessa classe.
• A aplicação de um método meth(int), definido em uma classe Cls, a um objeto obj, construído a partir da especificação de Cls, se dá através da construção
obj.meth(varint);
• onde varint é uma expressão ou variável inteira anteriormente definida.
Linguagem JAVA Operações sobre objetos
• Em uma linguagem tradicional, a forma correspondente seria invocar uma função ou procedimento passando como argumento a estrutura de dados sobre a qual as operações teriam efeito:
meth(obj, varint);
• Esse “atributo implícito” — a referência para o próprio objeto que está ativando o método — pode ser utilizado no corpo dos métodos quando for necessário fazê-lo explicitamente. Para tanto, a palavra-chave this é utilizada.
Linguagem JAVA
Operações sobre objetos
• Quando se declara uma variável cujo tipo é o nome de uma classe, como em
String nome;
• NÃO está se criando um objeto dessa classe,
• MAS simplesmente uma referência para um objeto da classe String, a qual inicialmente não faz referência a nenhum objeto válido.
Linguagem JAVA Operações sobre objetos • Quando um objeto dessa classe é criado, obtém-se uma
referência válida, que é armazenada na variável cujo tipo é o nome da classe do objeto. Por exemplo, quando cria-se uma string como em
nome = new String("POO/Java"); nome é uma variável que armazena uma referência válida para
um objeto específico da classe String — o objeto cujo conteúdo é a string POO/Java. É importante ressaltar que a variável nome mantém apenas a referência para o objeto e não o objeto em si.
Uma atribuição como:
String outroNome = nome; • não cria outro objeto, mas simplesmente uma outra referência
para o mesmo objeto.
Linguagem JAVA Operações sobre objetos • A linguagem Java permite que o programador crie e
manipule objetos explicitamente.
• Porém, a remoção de objetos em Java é manipulada automaticamente pelo sistema, usando um mecanismo de coleta de lixo (garbage collector).
• coletor de lixo: processo de baixa prioridade que permanece verificando quais objetos não têm nenhuma referência válida, retomando o espaço despendido para cada um desses objetos. Dessa forma, o programador não precisa se preocupar com a remoção explícita de objetos.
Linguagem JAVA Operações sobre objetos Verificação de tipo. • Dado um objeto obj uma classe Cls, é possível verificar
dinamicamente (durante a execução do método) se o objeto pertence ou não à classe usando o operador instanceof. Este retorna true se o objeto à esquerda do operador é da classe especificada à direita do operador.
obj instanceof Cls retornaria true se obj fosse da classe Cls.
Linguagem JAVA Operações sobre objetos
Arranjos
• Agregados homogêneos de valores que podem agrupar literais ou objetos.
• Declaração:
int array1[];
• apenas cria uma referência para um arranjo de inteiros!! porém o arranjo não foi criado!!
• Assim como objetos, arranjos são criados com o operador new:
array1 = new int[100];
(cria espaço para armazenar 100 inteiros no arranjo array1.)
Linguagem JAVA Operações sobre objetos Arranjos • Declaração E criação:
int array1[] = new int[100]; • Podem ser alternativamente criados com a especificação de
algum conteúdo: int array2[] = {2, 4, 5, 7, 9, 11, 13};
• O acesso a elementos individuais de um arranjo é especificado
através de um índice inteiro. O elemento inicial, assim como em C e C++, tem índice 0.
int x = array2[3]; (e a variável x receba o valor 7, o conteúdo da quarta posição)
array2[7]; //erro em tempo de execução!!
Linguagem JAVA
Operações sobre objetos
Arranjos
Dimensão: Propriedade length
int y = array2.length;
faz com que y receba o valor 7, o número de elementos no arranjo array2.
Linguagem JAVA Operações sobre objetos Strings • Ao contrário do que ocorre em C e C++, strings em Java não são
tratadas como seqüências de caracteres terminadas por NUL. • São objetos, instâncias da classe java.lang.String. • Uma string pode ser criada como em:
String s = "abc"; • O operador + pode ser utilizado para concatenar strings:
System.out.println("String s: " + s);
• Se, em uma mesma expressão, o operador + combinar valores numéricos e strings, os valores numéricos serão convertidos para strings e então concatenados.
Linguagem JAVA Operações sobre objetos Strings • Ao contrário do que ocorre em C e C++, strings em Java não são
tratadas como seqüências de caracteres terminadas por NUL. • São objetos, instâncias da classe java.lang.String. • Uma string pode ser criada como em:
String s = "abc"; • O operador + pode ser utilizado para concatenar strings:
System.out.println("String s: " + s);
• Se, em uma mesma expressão, o operador + combinar valores numéricos e strings, os valores numéricos serão convertidos para strings e então concatenados.
Linguagem JAVA Operações sobre objetos Strings
String s = "abc"; • A classe String define um conjunto de funcionalidades para
manipular strings através de seus métodos. • Para obter o número de caracteres em uma string, o método
int length() é usado. Assim, a expressão s.length();
retornaria o valor 3. • Para concatenar 2 strings:
String t = s.concat(".java"); faz com que a string t tivesse o conteúdo “abc.java”.
Linguagem JAVA Operações sobre objetos Strings Métodos para comparar o conteúdo de duas strings: (3 formas) a) equals(String outro) compara as duas strings, retornando verdadeiro se
seus conteúdos forem exatamente iguais. t.equals(s); //retorna falso s.equals("abc"); //retorna verdadeiro b) equalsIgnoreCase(String outro) s.equalsIgnoreCase("ABC"); // retorna verdadeiro c) compareTo(String outro) : retorna um valor inteiro igual a zero se as duas
strings forem iguais, negativo se a string à qual o método for aplicado preceder lexicograficamente a string do argumento, ou positivo, caso contrário. Também há compareToIgnore-Case(String str).
Linguagem JAVA Operações sobre objetos
Strings
• Para extrair caracteres individuais de uma string:
charAt(int pos)
que retorna o caráter na posição especificada no argumento (0 é a primeira posição).
• Para obter substrings de uma string:
substring()
duas assinaturas:
String substring(int pos_inicial);
String substring(int pos_inicial, int pos_final);
Linguagem JAVA Operações sobre objetos
Strings
• Para extrair caracteres individuais de uma string:
charAt(int pos)
que retorna o caráter na posição especificada no argumento (0 é a primeira posição).
• Para obter substrings de uma string:
substring()
duas assinaturas:
String substring(int pos_inicial);
String substring(int pos_inicial, int pos_final);
Linguagem JAVA Operações sobre objetos Strings A localização de uma substring dentro de uma string pode
ocorrer de diversas formas.
• O método indexOf(), com quatro assinaturas distintas, retorna a primeira posição na string onde o caráter ou substring especificada no argumento ocorre. É possível também especificar uma posição diferente da inicial a partir de onde a busca deve ocorrer.
• lastIndexOf() busca pela última ocorrência do caráter ou substring a partir do final ou da posição especificada.
Linguagem JAVA
Operações sobre objetos
Strings
• É possível também verificar se uma string começa com um determinado prefixo ou termina com um sufixo através respectivamente dos métodos startsWith() e endsWith().
• Ambos recebem como argumento uma string e retornam um valor booleano.
Linguagem JAVA
classes em JAVA
• Base da programação orientada a objetos.
• Definidas em arquivos fonte (extensão .java)
• Compiladas para arquivos de classes (extensão .class).
• Organizadas em pacotes.
Classes em JAVA
Pacotes
• Organizar as classes de maneira a evitar problemas com nomes duplicados de classes e permitir a localização do código da classe de forma eficiente.
• Em Java, a solução para esse problema está na organização de classes e interfaces em pacotes.
• Um pacote é uma unidade de organização de código que congrega classes, interfaces e exceções relacionadas.
• O código-base de Java está todo estruturado em pacotes e as aplicações desenvolvidas em Java também devem ser assim organizadas.
Classes em JAVA
Pacotes • Uma classe Xyz que pertence a um pacote
nome.do.pacote tem um “nome completo” que é nome.do.pacote.Xyz.
• Se outra aplicação tiver uma classe de mesmo nome não haverá conflitos de resolução, pois classes em pacotes diferentes têm nomes completos distintos.
• A organização das classes em pacotes também serve como indicação para o compilador Java para encontrar o arquivo que contém o código da classe.
Classes em JAVA
Pacotes
• O ambiente Java normalmente utiliza a especificação de uma variável de ambiente CLASSPATH, a qual define uma lista de diretórios que contém os arquivos de classes Java.
• No entanto, para não ter listas demasiadamente longas, os nomes dos pacotes definem Subdiretórios de busca a partir dos diretórios em CLASSPATH.
Classes em JAVA
Pacotes • Ao encontrar no código uma referência para a
classe Xyz, o compilador deverá procurar o arquivo com o nome Xyz.class;
• como essa classe faz parte do pacote nome.do.pacote, ele irá procurar em algum subdiretório nome/do/pacote.
• Se o arquivo Xyz.class estiver no diretório /home/java/nome/do/pacote, então o diretório /home/java deve estar incluído no caminho de busca de classes definido por CLASSPATH.
Classes em JAVA
Pacotes • Para indicar que as definições de um arquivo
fonte Java fazem parte de um determinado pacote, a primeira linha de código deve ser a declaração de pacote:
package nome.do.pacote; • Caso tal declaração não esteja presente, as
classes farão parte do “pacote default”, que está mapeado para o diretório corrente.
Classes em JAVA Pacotes • Para referenciar uma classe de um pacote no código fonte, é possível
sempre usar o “nome completo” da classe; no entanto, é possível também usar a declaração import. Por exemplo, se no início do código estiver presente a declaração:
import nome.do.pacote.Xyz; então a classe Xyz pode ser referenciada sem o prefixo nome.do.pacote
no restante do código.
import nome.do.pacote.*; • indica que quaisquer classes do pacote especificado podem ser
referenciadas apenas pelo nome no restante do código fonte. • A única exceção para essa regra refere-se às classes do pacote java.lang
— essas classes são consideradas essenciais para a interpretação de qualquer programa Java e, por este motivo, o correspondente import é implícito na definição de qualquer classe Java.
Classes em JAVA Definição
[modif] class NomeDaClasse {
// corpo da classe...
}
• A primeira linha é um comando que inicia a declaração da classe.
• Após a palavra-chave class, segue-se o nome da classe, que deve ser um identificador válido para a linguagem.
• O modificador modif é opcional; se presente, pode ser uma combinação de public e abstract ou final.
• A definição da classe propriamente dita está entre as chaves { e }, que delimitam blocos na linguagem Java.
Classes em JAVA Definição [modif] class NomeDaClasse { // corpo da classe... } • Sequência de definição para o corpo da classe: 1. As variáveis de classe (definidas como static), ordenadas
segundo sua visibilidade: iniciando pelas public, seguidos pelas protected, pelas com visibilidade
padrão (sem modificador) e finalmente pelas private. 2. Os atributos (ou variáveis de instância) dos objetos dessa
classe, seguindo a mesma ordenação. 3. Os construtores de objetos dessa classe. 4. Os métodos da classe, geralmente agrupados por
funcionalidade.
Classes em JAVA Definição
• Sintaxe para definir um atributo de um objeto: [modificador] tipo nome [ = default];
• modificador: opcional, especifica a visibilidade diferente da
padrão (public, protected ou private), alteração da modificabilidade (final) e se o atributo está associado à classe (static).
• tipo: um dos tipos primitivos da linguagem Java ou o nome de uma classe;
• nome: identificador válido da linguagem Java;
• o valor default: opcional; se presente, especifica um valor inicial para a variável.
Classes em JAVA Definição
Métodos
• procedimentos que podem manipular atributos de objetos para os quais o método foi definido.
• podem definir e manipular variáveis locais;
• podem receber parâmetros por valor através da lista de argumentos.
Classes em JAVA Métodos
[modificador] tipo nome(argumentos) { corpo do método } • modificador (opcional): combinação de: public,
protected ou private; abstract ou final; e static. • tipo: indicador do valor de retorno, sendo void se o
método não tiver um valor de retorno; • nome: identificador válido na linguagem Java; • argumentos: lista de parâmetros separados por vírgulas,
onde para cada parâmetro é indicado primeiro o tipo e depois (separado por espaço) o nome.
Classes em JAVA Métodos • Boa prática de programação: manter a funcionalidade de um
método simples, desempenhando uma única tarefa. • O nome do método deve refletir de modo adequado a tarefa
realizada. Se a funcionalidade do método for simples, será fácil encontrar um nome adequado para o método.
• Como ocorre para a definição de atributos, a definição de métodos reflete de forma quase direta a informação que estaria presente em um diagrama de classes UML, a não ser por uma diferença vital:
o corpo do método. Métodos de mesmo nome podem co-existir em uma mesma
classe desde que a lista de argumentos seja distinta, usando o mecanismo de sobrecarga.
Classes em JAVA Métodos public class Ponto2D { private int x; private int y; public Ponto2D(int x, int y) { this.x = x; this.y = y; } public Ponto2D( ) { this(0,0); } public double distancia(Ponto2D p) { double distX = p.x - x; //p.x – this.x double distY = p.y - y; //p.y – this.y return Math.sqrt(distX*distX + distY*distY); } }
Classes em JAVA Métodos (relacionado ao exemplo)
• Um objeto dessa classe tem dois atributos privativos que definem as coordenadas do ponto bidimensional, x e y—nesse caso, as coordenadas são valores inteiros.
• A classe tem dois construtores. O primeiro deles recebe dois argumentos, também de nome x e y, que definem as coordenadas do ponto criado. Para diferenciar no corpo do construtor os parâmetros dos atributos, estes têm seu nome prefixado pela palavra-chave this.
• O segundo construtor ilustra outro uso da palavra-chave this. Esse construtor não recebe argumentos e assume portanto o ponto tem como cordenadas a origem, ou seja, o ponto (0,0). O corpo desse construtor poderia ser simplesmente
x = 0; y = 0; • A alternativa apresentada usa a forma this(0,0); • que equivale a “use o construtor desta mesma classe que recebe dois
argumentos inteiros, passando os valores aqui especificados”.
Classes em JAVA Métodos (relacionado ao exemplo)
• Finalmente, a classe define um método público para calcular a distância entre o ponto que invocou o método (this) e outro ponto especificado como o argumento p. Nesse caso, não é preciso usar a palavra-chave this antes dos atributos da coordenada do objeto.
• Assim, as expressões que calculam distX e distY equivalem a double distX = p.x - this.x; double distY = p.y - this.y; • A expressão de retorno ilustra ainda a utilização de um método
estático da classe Math do pacote java.lang para o cômputo da raiz quadrada. Usualmente, métodos definidos em uma classe são aplicados a objetos daquela classe. Há no entanto situações nas quais um método pode fazer uso dos recursos de uma classe para realizar sua tarefa sem necessariamente ter de estar associado a um objeto individualmente.
Classes em JAVA Métodos (relacionado ao exemplo)
• Para lidar com tais situações, Java define os métodos da classe, cuja declaração deve conter o modificador static.
• Um método estático pode ser aplicado à classe e não necessariamente a um objeto.
• Exemplos de métodos estáticos em Java incluem: - os métodos para manipulação de tipos primitivos
definidos nas classes Character, Integer e Double, todas elas do pacote java.lang,
- todos os métodos definidos para a classe Math.
Classes em JAVA O método main • Toda classe pode também ter um método main associado
utilizado pelo interpretador Java para dar início à execução de uma aplicação.
• Ao contrário do que acontece em C e C++, onde apenas uma função main deve ser definida para a aplicação como um todo, toda e qualquer classe Java pode ter um método main definido.
• Apenas no momento da interpretação o main a ser executado é definido através do primeiro argumento (o nome da classe) para o programa interpretador.
Classes em JAVA O método main • método associado à classe e não a um objeto específico da
classe. Logo, definido como um método estático. • Adicionalmente, deve ser um método público para permitir sua
execução a partir da máquina virtual Java. • Não tem valor de retorno, mas recebe como argumento um
arranjo de strings que corresponde aos parâmetros que podem ser passados para a aplicação a partir da linha de comando.
• Essas características determinam a assinatura do método:
public static void main(String[] args) ou
static public void main(String[] args)
Classes em JAVA O método main • Mesmo que não seja utilizado, o argumento de main deve ser
especificado. • Por exemplo, a definição da classe Ponto2D poderia ser complementada
com a inclusão de um método para testar sua funcionalidade:
public class Ponto2D { ... public static void main(String[] args) { Ponto2D ref2 = new Ponto2D(); Ponto2D p2 = new Ponto2D(1,1); System.out.println("Distancia: " + p2.distancia(ref2)); } }
Classes em JAVA O método main • O nome do parâmetro (args) obviamente poderia ser diferente,
mas os demais termos da assinatura devem obedecer ao formato especificado. Esse argumento é um parâmetro do tipo arranjo de objetos da classe String.
• Cada elemento desse arranjo corresponde a um argumento passado para o interpretador Java na linha de comando que o invocou. Por exemplo, se a linha de comando for
java Xyz abc 123 def • o método main(String[] args) da classe Xyz vai receber, nessa
execução, um arranjo de três elementos na variável args com os seguintes conteúdos:
em args[0], o objeto String com conteúdo "abc"; em args[1], o objeto String com conteúdo "123"; em args[2], o objeto String com conteúdo "def".
Classes em JAVA O método main • Como o método main é do tipo void, ele não tem valor de
retorno. No entanto, assim como programas desenvolvidos em outras linguagens, é preciso algumas vezes obter uma indicação se o programa executou com sucesso ou não. Isto é principalmente útil quando o programa é invocado no contexto de um script do sistema operacional.
• Em Java, o mecanismo para fornecer essa indicação é o método System.exit(int). A invocação desse método provoca o fim imediato da execução do interpretador Java. O argumento de exit() obedece à convenção de que ’0’ indica execução com sucesso, enquanto um valor diferente de 0 indica a ocorrência de algum problema.
Classes em JAVA Visibilidade da classe e seus membros • Em Java, a visibilidade padrão de classes, atributos e métodos está restrita
a todos os membros que fazem parte de um mesmo pacote.
• A palavra-chave public modifica essa visibilidade de forma a ampliá-la, deixando-a sem restrições.
• Uma classe definida como pública pode ser utilizada por qualquer objeto de qualquer pacote.
• Em Java, uma unidade de compilação (um arquivo fonte com extensão .java) pode ter no máximo uma classe pública, cujo nome deve ser o mesmo do arquivo (sem a extensão). As demais classes na unidade de compilação, não públicas, são consideradas classes de suporte para a classe pública e têm a visibilidade padrão.
Classes em JAVA Classes derivadas • Sendo uma linguagem de programação orientada a
objetos, Java oferece mecanismos para definir classes derivadas a partir de classes existentes. É fundamental que se tenha uma boa compreensão sobre como bjetos de classes derivadas são criados e manipulados, assim como das restrições de acesso que podem se aplicar a membros de classes derivadas.
• Também importante para uma completa compreensão da utilização desse mecanismo em Java é entender como relacionam-se interfaces e herança.
Classes em JAVA Classes derivadas • A forma básica de herança em Java é a extensão simples entre uma
superclasse e sua classe derivada. Para tanto, utiliza-se na definição da classe derivada a palavra-chave extends seguida pelo nome da superclasse.
• A hierarquia de classes de Java tem como raiz uma classe básica, Object. Quando não for especificada uma superclasse na definição de uma classe, o compilador assume que a superclasse é Object.
Assim, definir a classe Ponto2D como em class Ponto2D { // ... } é equivalente a defini-la como class Ponto2D extends Object { // ... }
Classes em JAVA Classes derivadas • É por esse motivo que todos os objetos podem invocar os métodos da
classe Object, tais como equals() e toString(). • O método equals permite comparar objetos por seus conteúdos. A
implementação padrão desse método realiza uma comparação de conteúdo bit a bit; se um comportamento distinto for desejado para uma classe definida pelo programador, o método deve ser redefinido.
• O método toString() permite converter uma representação interna do objeto em uma string que pode ser apresentada ao usuário.
• Outros métodos da classe Object incluem clone(), um método protegido que permite criar uma duplicata de um objeto, e getClass(), que retorna um objeto que representa a classe à qual o objeto pertence. Esse objeto será da classe Class; para obter o nome da classe, pode-se usar o seu método estático getName(), que retorna uma string.
Classes em JAVA Classes derivadas Para criar uma classe Ponto3D a partir da definição da classe que representa um
ponto em 2 dimensões, a nova classe deve incluir um atributo adicional para representar a 3ª. coordenada:
public class Ponto3D extends Ponto2D { private int z; public Ponto3D(int x, int y, int z) { super(x, y); this.z = z; } public Ponto3D( ) { z = 0; } public static void main(String[] args) { Ponto2D ref2 = new Ponto2D(); Ponto3D p3 = new Ponto3D(1,2,3); System.out.println("Distancia: " + p3.distancia(ref2)); } }
Classes em JAVA Classes derivadas • Nesse exemplo, o método distancia que é utilizado em main
é aquele que foi definido para a classe Ponto2D que, por herança, é um método da classe Ponto3D.
• Esse exemplo ilustra, na definição do primeiro construtor, o
uso da palavra-chave super para invocar um construtor específico da superclasse. Se presente, essa expressão deve ser a primeira do construtor, pois o início da construção do objeto é a construção da parte da superclasse. Caso não esteja presente, está implícita uma invocação para o construtor padrão, sem argumentos, da superclasse, equivalente à forma super().
Classes em JAVA Classes abstratas • Uma classe abstrata NÃO pode ser instanciada, ou seja, não há objetos
que possam ser construídos diretamente de sua definição. abstract class AbsClass { public static void main(String[] args) { AbsClass obj = new AbsClass(); } } geraria a seguinte mensagem de erro: AbsClass.java:3: class AbsClass is an abstract class. It can’t be instantiated. AbsClass obj = new AbsClass(); ^ 1 error
Classes em JAVA Classes abstratas
• Em geral, classes abstratas definem um conjunto de funcionalidades das quais pelo menos uma está especificada mas não está definida — ou seja, contém pelo menos um método abstrato, como em
abstract class AbsClass {
public abstract int umMetodo();
}
Classes em JAVA Classes abstratas • Um método abstrato não cria uma definição, mas apenas uma
declaração de um método que deverá ser implementado em uma classe derivada. Se esse método não for implementado na classe derivada, esta permanece como uma classe abstrata mesmo que não tenha sido assim declarada explicitamente.
• Assim, para que uma classe derivada de uma classe abstrata possa gerar objetos, os métodos abstratos devem ser definidos em classes derivadas:
class ConcClass extends AbsClass { public int umMetodo() { return 0; } }
Classes em JAVA Classes final • indica uma classe que não pode ser estendida final class Mandato { } public class Reeleicao extends Mandato { } ocasionaria um erro de compilação: javac Reeleicao.java Reeleicao.java:4: Can’t subclass final classes: class Mandato public class Reeleicao extends Mandato { ^ 1 error
Classes em JAVA Classes final • A palavra-chave final pode também ser aplicada a métodos e a
atributos de uma classe. • Um método final não pode ser redefinido em classes derivadas. • Um atributo final não pode ter seu valor modificado, ou seja,
define valores constantes. Apenas valores de tipos primitivos podem ser utilizados para definir constantes. O valor do atributo deve ser definido no momento da declaração, pois não é permitida nenhuma atribuição em outro momento.
• A utilização de final para uma referência a objetos é permitida. Como no caso de constantes, a definição do valor (ou seja, a criação do objeto) também deve ser especificada no momento da declaração. No entanto, é preciso ressaltar que o conteúdo do objeto em geral pode ser modificado — apenas a referência é fixa. O mesmo é válido para arranjos.
Classes em JAVA Classes final • A partir de Java 1.1, é possível ter atributos de uma classe
que sejam final mas não recebem valor na declaração, mas sim nos construtores da classe. (A inicialização deve obrigatoriamente ocorrer em uma das duas formas.) São os chamados blank finals, que introduzem um maior grau de flexibilidade na definição de constantes para objetos de uma classe, uma vez que essas podem depender de parâmetros passados para o construtor.
• Argumentos de um método que não devem ser modificados podem ser declarados como final, também, na própria lista de parâmetros.
Classes em JAVA Interface • Sintaxe similar à de classes • contém apenas a especificação da funcionalidade que uma
classe deve conter, sem determinar como essa funcionalidade deve ser implementada.
• Uma interface Java é uma classe abstrata para a qual todos os métodos são implicitamente abstract e public, e todos os atributos são implicitamente static e final. Em outros termos, uma interface Java implementa uma “classe abstrata pura”.
• A sintaxe para a declaração de uma interface é similar àquela para a definição de classes, porém seu corpo define apenas assinaturas de métodos e constantes.
Classes em JAVA
Interface
• Para definir uma interface Interface1 que declara um método met1 sem argumentos e sem valor de retorno, a sintaxe é:
interface Interface1 {
void met1();
}
Classes em JAVA Interface Diferença entre uma classe abstrata e uma interface Java:
• A interface obrigatoriamente não tem um “corpo” associado. Para que uma classe seja abstrata basta que ela seja assim declarada, mas a classe pode incluir atributos de objetos e definição de métodos, públicos ou não.
• Na interface, apenas métodos públicos podem ser declarados — mas não definidos. Da mesma forma, não é possível definir atributos — apenas constantes públicas.
• Enquanto uma classe abstrata é “estendida” (palavra chave extends) por classes derivadas, uma interface Java é “implementada” (palavra chave implements) por outras classes.
Classes em JAVA Interface • Uma interface estabelece uma espécie de contrato que é
obedecido por uma classe. Quando uma classe implementa uma interface, garante-se que todas as funcionalidades especificadas pela interface serão oferecidas pela classe.
• Outro uso de interfaces Java é para a definição de constantes que devem ser compartilhadas por diversas classes. Neste caso, a recomendação é implementar interfaces sem métodos, pois as classes que implementarem tais interfaces não precisam tipicamente redefinir nenhum método:
Classes em JAVA Interface interface Coins { int PENNY = 1, NICKEL = 5, DIME = 10, QUARTER = 25, DOLAR = 100; } class SodaMachine implements Coins { int price = 3*QUARTER; // ... }
Exceções em JAVA • Uma exceção é um sinal que indica que algum tipo de
condição excepcional ocorreu durante a execução do programa.
• Estão associadas a condições de erro que não tinham como ser verificadas durante a compilação do programa.
As duas atividades associadas à manipulação de uma exceção
são: • geração: a sinalização de que uma condição excepcional (por
exemplo, um erro) ocorreu, e • captura: a manipulação (tratamento) da situação excepcional,
onde as ações necessárias para a recuperação da situação de erro são definidas.
Exceções em JAVA • Para cada exceção que pode ocorrer durante a execução do código, um
bloco de ações de Tratamento (um exception handler) deve ser especificado.
• O compilador Java verifica e enforça que toda exceção “não-trivial” tenha um bloco de tratamento associado.
• Mecanismo de manipulação de exceções em Java: adequado à manipulação de erros síncronos, para situações onde a recuperação do erro é possível.
• A sinalização da exceção é propagada a partir do bloco de código onde ela ocorreu através de toda a pilha de invocações de métodos até que a exceção seja capturada por um bloco manipulador de exceção. Eventualmente, se tal bloco não existir em nenhum ponto da pilha de invocações de métodos, a sinalização da exceção atinge o método main(), fazendo com que o interpretador Java apresente uma mensagem de erro e aborte sua execução.
Tratamento de exceções em JAVA Captura e tratamento de exceções: blocos try, catch e finally.
try {
// código que inclui comandos/invocações de métodos
// que podem gerar uma situação de exceção.
}catch (XException ex) {
// bloco de tratamento associado à condição de exceção XException ou a
// qualquer uma de suas subclasses, identificada aqui pelo objeto com
// referência ex
} catch (YException ey) {
// bloco de tratamento para a situação de exceção
// YException ou a qualquer uma de suas subclasses
} finally {
// bloco de código que sempre será executado após o bloco try,
// independentemente de sua conclusão
// ter ocorrido normalmente ou ter sido interrompida
}
Tratamento de exceções em JAVA • onde XException e YException deveriam ser substituídos pelo
nome do tipo de exceção. Os blocos não podem ser separados por outros comandos — um erro de sintaxe seria detectado pelo compilador Java neste caso.
• Cada bloco try pode ser seguido por zero ou mais blocos catch, onde cada bloco catch refere-se a uma única exceção.
• O bloco finally, quando presente, é sempre executado. Em geral, ele inclui comandos que liberam recursos que eventualmente possam ter sido alocados durante o processamento do bloco try e que podem ser liberados, independentemente de a execução ter encerrado com sucesso ou ter sido interrompida por uma condição de exceção. A presença desse bloco é opcional.
Exceções em JAVA Exemplos de exceções já definidas no pacote java.lang: • ArithmeticException: indica situações de erros em
processamento aritmético, tal como uma divisão inteira por 0. A divisão de um valor real por 0 não gera uma exceção (o resultado é o valor infinito);
• NumberFormatException: indica que tentou-se a conversão de uma string para um formato numérico, mas seu conteúdo não representava adequadamente um número para aquele formato. É uma subclasse de IllegalArgumentException;
• IndexOutOfBounds: indica a tentativa de acesso a um elemento de um agregado aquém ou além dos limites válidos. É a superclasse de ArrayIndexOutOfBoundsException, para arranjos, e de StringIndexOutOfBounds, para strings;
Exceções em JAVA Exemplos de exceções já definidas no pacote
java.lang:
• NullPointerException: indica que a aplicação tentou usar uma referência a um objeto que não foi ainda definida;
• ClassNotFoundException: indica que a máquina virtual Java tentou carregar uma classe mas não foi possível encontrá-la durante a execução da aplicação.
Exceções em JAVA Além disso, outros pacotes especificam suas exceções,
referentes às suas funcionalidades.
• Por exemplo, no pacote java.io define-se IOException, que indica a ocorrência de algum tipo de erro em operações de entrada e saída.
• É a superclasse para condições de exceção mais específicas desse pacote, tais como EOFException (fim de arquivo ou stream), FileNotFoundException
(arquivo especificado não foi encontrado) e InterruptedIOException (operação de entrada ou saída foi interrompida).
Exceções em JAVA • Uma exceção contém pelo menos uma string que a
descreve, que pode ser obtida pela aplicação do método getMessage(), mas pode eventualmente conter outras informações.
• Por exemplo, InterruptedIOException inclui um atributo público do tipo inteiro, bytesTransferred, para indicar quantos bytes foram transferidos antes da interrupção da operação ocorrer.
• Outra informação que pode sempre ser obtida de uma exceção é a seqüência de métodos no momento da exceção, obtenível a partir do método printStackTrace().
Exceções em JAVA • Como exceções fazem parte de uma hierarquia de classes,
exceções mais genéricas (mais próximas do topo da hierarquia) englobam aquelas que são mais específicas.
• A forma mais genérica de um bloco try-catch é
try { ... }
catch (Exception e) { ... }
pois todas as exceções são derivadas de Exception. Se dois blocos catch especificam exceções em um mesmo ramo da hierarquia, a especificação do tratamento da exceção derivada deve preceder aquela da mais genérica.
Erros e exceções de runtime em JAVA • Exceções consistem de um caso particular de um objeto da
classe Throwable. Apenas objetos dessa classe ou de suas classes derivadas podem ser gerados, propagados e capturados através do mecanismo de tratamento de exceções.
• Além de Exception, outra classe derivada de Throwable é a classe Error, que é a raiz das classes que indicam situações que a aplicação não tem como ou não deve tentar tratar. Usualmente indica situações anormais, que não deveriam ocorrer.
• Entre os erros definidos em Java, no pacote java.lang, estão StackOverflowError e OutOfMemoryError. São situações onde não é possível uma correção a partir de um tratamento realizado pelo próprio programa que está executando.
Erros e exceções de runtime em JAVA
• Há também exceções que não precisam ser Explicitamente capturadas e tratadas. São aquelas derivadas de RuntimeException, uma classe derivada diretamente de Exception. São exceções que podem ser geradas durante a operação normal de uma aplicação para as quais o compilador Java não irá exigir que o programador proceda a algum tratamento (ou que propague a exceção).
• Entre essas incluem-se
ArithmeticException,
IllegalArgumentException,
IndexOutOfBoundsException e
NullPointerException.
Propagando Exceções em JAVA • Embora toda exceção que não seja derivada de
RuntimeException deva ser tratada, nem sempre é possível tratar uma exceção no mesmo escopo do método cuja invocação gerou a exceção.
• Nessas situações, é possível propagar a exceção para um nível acima na pilha de invocações.
• Para tanto, o método que está deixando de capturar e tratar a exceção faz uso da cláusula throws na sua declaração:
void métodoQueNãoTrataExceção() throws Exception { invoca.métodoQuePodeGerarExceção(); }
Propagando Exceções em JAVA void métodoQueNãoTrataExceção() throws Exception { invoca.métodoQuePodeGerarExceção(); } • métodoQueNãoTrataExceção() reconhece que em seu corpo há a
possibilidade de haver a geração de uma exceção MAS não se preocupa em realizar o tratamento dessa exceção em seu escopo. Ao contrário, ele repassa essa responsabilidade para o método anterior na pilha de chamadas.
• Eventualmente, também o outro método pode repassar a exceção adiante. Porém, pelo menos no método main() as exceções deverão ser tratadas ou o programa pode ter sua interpretação interrompida.
Definindo e gerando exceções em JAVA • Exceções são classes - é possível que uma aplicação defina suas próprias
exceções através do mecanismo de definição de classes. • Por exemplo, considere que fosse importante para uma aplicação
diferenciar a condição de divisão por zero de outras condições de exceções artiméticas. Neste caso, uma classe DivideByZeroException poderia ser criada:
public class DivideByZeroException extends ArithmeticException { public DivideByZeroException() { super("O denominador na divisão inteira tem valor zero"); } } • Neste caso, o argumento para o construtor da superclasse especifica a
mensagem que seria impressa quando o método getMessage() fosse invocado para essa exceção. Essa é a mesma mensagem que é apresentada para um RuntimeException que não é capturado e tratado.
Definindo e gerando exceções em JAVA
• Para gerar uma condição de exceção durante a execução de um método, um objeto dessa classe deve ser criado e, através do comando throw, propagado para os métodos anteriores na pilha de execução:
public double calculaDivisao (double numerador, int denominador)
throws DivideByZeroException {
if (denominador == 0)
throw new DivideByZeroException();
return numerador / denominador;
}
Definindo e gerando exceções em JAVA • O mesmo comando throw pode ser utilizado para repassar uma
exceção após sua captura—por exemplo, após um tratamento parcial da condição de exceção:
public void usaDivisao()
throws DivideByZeroException {
...
try {
d = calculaDivisao(x, y);
} catch (DivideByZeroException dbze) {
...
throw dbze;
}
...
}
Definindo e gerando exceções em JAVA
• Nesse caso, a informação associada à exceção (como o seu ponto de origem, registrado internamente no atributo do objeto que contém a pilha de invocações) é preservada. Caso fosse desejado mudar essa informação, uma nova exceção poderia ser gerada ou, caso a exceção seja a mesma, o método fillInStackTrace() poderia ser utilizado, como em
throw dbze.fillInStackTrace();
O ambiente de JAVA
• Nesse caso, a informação associada à exceção (como o seu ponto de origem, registrado internamente no atributo do objeto que contém a pilha de invocações) é preservada. Caso fosse desejado mudar essa informação, uma nova exceção poderia ser gerada ou, caso a exceção seja a mesma, o método fillInStackTrace() poderia ser utilizado, como em
throw dbze.fillInStackTrace();
Palavras chave de JAVA • As palavras a seguir são de uso reservado em Java e não
podem ser utilizadas como nomes de identificadores:
Linguagem C++
• Programação orientada a objetos
Programação orientada à objetos Facilita o desenvolvimento e entendimento de programas de grande porte. Possibilita a reutilização do código, reduzindo custos de desenvolvimento. Conceitos: objetos, herança e polimorfismo.
Classes e Objetos - possibilidade de combinar num único registro campos que conterão
dados e campos que são funções para operar os campos de dados do registro. Uma unidade assim definida é chamada classe.
- Classe: tipo de dado como os tipos que existem predefinidos em compiladores de diversas linguagens de programação. Assim como é possível declarar várias variáveis do tipo int, pode-se também declarar várias variáveis de uma classe já definida.
- Uma variável de uma classe é chamada objeto e conterá campos de
dados e funções.
Programação orientada à objetos • As funções de um objeto são chamadas funções-membro
ou métodos e, de modo geral, são o único meio de acesso aos campos de dados também chamados variáveis de instância.
• Em linguagens orientadas a objetos baseadas em classes, tal como o C++, a definição de um tipo de objeto é normalmente chamada de definição de uma classe.
• Uma classe especifica características dos objetos que serão posteriormente criados com este tipo. Ao ser criado um objeto, é necessário que seja especificada a sua classe. Objetos de uma mesma classe apresentam comportamento uniforme.
Programação orientada à objetos • Na definição de uma classe, são declaradas as
variáveis e os procedimentos a serem usados para acessá-las.
• Uma vez definida uma classe, podemos criar instâncias desta classe, ou seja, podemos criar objetos desta classe.
• Cada objeto criado passa a ter as suas próprias
variáveis, cujos tipos são aqueles especificados na definição da classe, e a compartilhar o código dos procedimentos especificados para acesso às variáveis.
Programação orientada à objetos Encapsulamento • Se o programa necessita atribuir um valor a alguma
variável de instância, deve chamar uma função membro que recebe o valor como argumento e faz a alteração. Não podemos acessar variáveis de instância diretamente.
• Dessa forma, os campos de dados estarão escondidos, o que previne alterações acidentais. Dizemos então que os campos de dados e suas funções estão encapsuladas numa única entidade.
Programação orientada à objetos Encapsulamento • Se alguma modificação ocorrer em variáveis de instância de um
objeto, sabemos exatamente quais funções interagiram com elas: são as funções-membro do objeto. Nenhuma outra função pode acessar esses dados. Isso simplifica a escrita, manutenção e alteração de programas.
• Um programa em C++ consiste em um conjunto de objetos que se comunicam por meio de chamadas às funções-membro.
• A frase "chamar uma função-membro de um objeto" pode ser dita como "enviar uma mensagem a um objeto".
Programação orientada à objetos Encapsulamento • Objetos: tipos abstratos de dados nos quais encontramos
tanto variáveis, usadas para armazenamento das informações, quanto o código que pode atuar sobre as mesmas.
• As variáveis encontram-se encapsuladas no objeto, e a única forma de acessá-las é através das chamadas às funções do próprio objeto.
Objeto composto por dados e procedimentos necessários à
descrição de um elemento no sistema.
Programação orientada à objetos Encapsulamento • O resultado de uma operação sobre as variáveis de um objeto,
depende tanto dos valores dos argumentos, caso a função seja parametrizada, quanto dos valores já armazenados nas variáveis envolvidas.
• Vantagens do uso de objetos na implementação de um programa: – os objetos são entidades lógicas de mais fácil compreensão e uso do
que os elementos que os compõem; – a implementação de um objeto pode ser alterada sem que seja
necessária a alteração de outras partes do código da aplicação que o usa;
– para se usar o objeto, não é necessário que se tenha conhecimento de como este foi implementado.
Programação orientada à objetos Herança • Relacionar classes umas com as outras por meio de
hierarquias. • Quando dividimos classes em subclasses, cada subclasse
herda as características da classe da qual foi derivada. Além das características herdadas, cada subclasse tem suas características particulares.
• herança : O conceito de subclasse ou processo de classes derivadas.
• Em C++, é definida uma classe-base quando são identificadas características comuns em um grupo de classes derivadas.
• Uma classe que é derivada de uma classe-base pode, por sua vez, ser a classe-base de outra classe.
Programação orientada à objetos Herança
O uso de uma biblioteca de classes oferece uma grande vantagem sobre o uso de uma biblioteca de funções:
o programador pode criar classes derivadas de classe-base de biblioteca - sem alterar a classe-base, é possível adicionar a ela características diferentes que a tornarão capaz de executar exatamente o que desejarmos.
Programação orientada à objetos Herança
Simples ou múltipla.
- Herança simples: uma classe pode herdar características de apenas uma outra classe.
- herança múltipla: extensão da herança simples, e uma classe pode herdar características de duas ou mais classe, logicamente relacionadas, ou não.
Programação orientada à objetos Herança Vantagens: • Possibilidade de se criar classes especializadas sem ter que
alterar a implementação das classes básicas, aumentando, desta forma, a possibilidade de reutilização do código e, consequentemente, obtendo uma otimização do processo de desenvolvimento dos programas.
• Facilidade de se obter uma melhor organização do
programa, simplificando, consequentemente, a evolução do sistema, já que facilita o processo de especialização e modificação do código, além de reduzir a duplicação de código e de dados no sistema.
Programação orientada à objetos Polimorfismo e sobrecarga
Polimorfismo: possibilidade de um mesmo nome poder ser usado
para identificar diferentes procedimentos. nomes usados para representar operações
genéricas, e não procedimentos específicos. Em C++, chamamos de polimorfismo a criação de uma
família de funções que compartilham do mesmo nome, mas cada uma com código independente.
Programação orientada à objetos Polimorfismo e sobrecarga
Sobrecarga: • Tipo particular de polimorfismo. • Exemplo: o operador aritmético +. • O computador executa operações completamente
diferentes para somar números inteiros e números reais. • A utilização de um mesmo símbolo para a execução de
operações distintas é chamada sobrecarga. • Em C++, além das sobrecargas embutidas na linguagem,
podemos definir outras com capacidades novas. O polimorfismo e a sobrecarga são habilidades únicas em
linguagens orientadas ao objeto.
Programação orientada à objetos Polimorfismo e sobrecarga
Polimorfismo:
• O resultado da ação de cada uma das funções da família é o mesmo. A maneira de se atingir esse resultado é distinta.
• Exemplo: calcular o salário líquido de todos os funcionários de uma empresa. Nessa empresa há horistas, mensalistas, os que ganham por comissão etc.
• Criamos um conjunto de funções em que cada uma tem um método diferente de calcular o salário.
• Quando a função é chamada, C++ saberá identificar qual é a função com o código adequado ao contexto.
Classes e objetos
• Classes: muito mais poderosas que as estruturas e uniões.
• As classes são ditas 'completas' pois são formadas por dados-membro e funções-membro que operam esses dados.
• Uma definição de classe sempre começa pela
palavra-chave class, de seu corpo delimitado por chaves e finalizado com um ponto-e-vírgula.
Classes e objetos Exemplo de uma classe:
//retangulo.cpp #include <iostream.h> class Retangulo { //define a classe private: int bas,alt; //dados membro public: //funções membro para inicializar os dados void init (int b, h) {bas=h; alt=h;} void printdata() { cout << “\nBase = “ <<bas<< “Altura = ”<<alt; cout << “\nArea= “ <<(bas*alt); } };
Classes e objetos Exemplo de uma classe: void main () { Retangulo x,y; //declara dois objetos
x.init(5,3); //chama função membro que inicializa
y.init(10,6); //os dados dos dois objetos
x.printdata(); //chama função membro que imprime a
y.printdata(); //área do retângulo x e do retângulo y
} - A classe Retangulo é composta por dois itens de dados e duas
funções. Estas funções são o único meio de acesso aos itens de dados. A primeira atribui valores aos itens de dados, a segunda calcula e imprime a área do retângulo.
Classes e objetos Objetos
• Enquanto uma instância de um estrutura ou de uma união
é chamada variável, uma instância de uma classe é chamada objeto.
• Uma variável de um tipo classe é dita um objeto.
Definindo a Classe • Antes de criar um objeto de uma certa classe, é necessário
que ela esteja definida. • Esse conceito é o mesmo que usamos com estruturas e
uniões: antes de criar uma variável de um tipo estrutura, é necessário definir a estrutura.
Membros privados e membros públicos • O corpo da definição da classe contém as palavras
private e public seguidas de dois-pontos. Elas especificam a visibilidade dos membros.
• Os membros definidos após private: são chamados
de parte privada da classe e podem ser acessados pela classe inteira, mas não fora dela.
• Um membro privado não pode ser acessado por
meio de um objeto – conceito equivalente à relação existente entre uma função e as suas variáveis locais; elas não estarão visíveis fora da função.
Membros privados e membros públicos
• Geralmente, na parte privada da classe são colocados os membros que conterão dados. Dizemos então que esses dados estarão "escondidos", ou seja, não poderão ser alterados por funções que não pertençam a classe.
• Se uma função-membro é declarada private,
somente outras funções-membro da mesma classe poderão acessá-la.
Membros privados e membros públicos
• A seção pública da classe é formada pelos membros definidos após public: e pode ser acessada por qualquer função-membro e por qualquer outra função do programa onde um objeto foi declarado.
• Os membros públicos fazem a interface entre o programador e a classe.
• Geralmente, na parte pública de uma classe são colocadas as funções que irão operar os dados.
Membro protegido
• Além de privado ou público, um membro pode ser declarado como protegido, através da palavra-chave protected.
• O motivo para a declaração de um membro como protegido, está relacionado ao mecanismo de herança de classes.
• Uma classe pode herdar características de outras classes. Quando isto ocorre, a nova classe não pode acessar os membros declarados como privados à classe herdada, a não ser que, em vez de serem declarados como privados, sejam declarados como protegidos.
Membros declarados como protegidos continuam só podendo ser
acessados por funções membros, mas o direito de acesso passa a poder ser herdado pelas funções membros das classes
derivadas.
Definição de classe
class nome_da_classe { public: variáveis e funções públicas private: variáveis e funções privadas protected: variáveis e funções protegidas };
As funções membro de uma classe • Funções-membro, também chamadas métodos, são as que estão
dentro da classe. Na classe Retangulo, definimos dois métodos: init() e printdata(). Estas funções executam operações comuns em classes: atribuem valores aos dados-membro e imprimem certos resultados.
Definição: 1ª. Forma: definir a função dentro da própria classe:
class nome_da_classe { tipo-retorno nome_da_função (lista de parâmetros) { corpo da função } };
As funções membro de uma classe Definição: 2ª. Forma: definir a função fora do escopo da definição da classe uso do operador de identificação de escopo, representado pelo símbolo
(::): class nome_da_classe { tipo_retorno nome_da_função (lista de parâmetros); }; tipo_retorno nome_da_classe :: nome_da_função (lista de parâmetros) { corpo da função } - Uma vez definida, a função pode ser chamada a partir de um método da
mesma classe, ou no caso do método ser público, a partir de qualquer outra função dentro do escopo de um objeto deste tipo de classe.
A chamada à uma função membro • Os membros públicos de um objeto são acessados por meio do
operador ponto. Sintaxe similar à de acesso aos membros de uma estrutura.
• Uma função-membro age sobre um objeto particular e não sobre a classe como um todo.
A instrução: • x.init(5,3); • executa a função-membro init() do objeto x. • Esta função atribui o valor 5 à variável bas e 3 à variável alt do
objeto x.
A instrução • y.init(10,6); • executa a mesma tarefa sobre o objeto y.
A chamada à uma função membro
As instruções:
• x.printdata();
• y.printdata();
imprimem os dados de cada retângulo e as suas áreas.
Mensagem: instrução de chamada a uma função-membro
A instrução:
• x.printdata();
• envia uma mensagem ao objeto x solicitando que ele imprima os dados e a área do retângulo.
Dados-membro static • Muitas vezes é necessário que todos os objetos de uma mesma
classe acessem um mesmo item de dado. Este item faria o mesmo papel de uma variável externa, visível a todos os objetos daquela classe.
• Quando um dado membro é declarado como static, é criado um único item para a classe como um todo, não importando o número de objetos declarados. A informação de um membro static é compartilhada por todos os objetos da mesma classe.
• Um membro static tem características similares às variáveis estáticas normais. É visível somente dentro da classe e é destruído somente quando o programa terminar.
Funções construtoras e destrutoras Funções construtoras: usadas para inicializar um objeto quando da sua criação. tem o mesmo nome da classe da qual é membro, sendo
automaticamente chamada quando for criado um objeto desta classe.
O seu uso é opcional, sendo útil quando um objeto, ao ser criado, precise executar algum processo de inicialização.
Um construtor não deve retornar nenhum valor. O motivo é ele ser chamado diretamente pelo sistema e portanto não há como recuperar um valor de retorno.
Um construtor pode perfeitamente chamar uma função-membro. O seu código é igual ao de qualquer outra função, exceto pela falta de tipo.
Funções construtoras e destrutoras
Funções construtoras:
Como exemplo:
- considerar a definição de uma classe que modela uma estrutura de dados tipo pilha.
- A classe contém, na sua parte privada, uma matriz para armazenamento das informações e uma variável para contagem do número de elementos inseridos.
- Na parte pública encontramos as funções-membro e a função construtora, responsável por inicializar a variável de contagem quando for criado um objeto desta classe.
Funções construtoras e destrutoras Exemplo: #define SUCESSO 1 #define FALHA 0
//definição da classe pilha class pilha { int dados[100]; int conta; public: //função construtora pilha (void); //funções-membro int insere (int valor); int remove (int *valor); };
//definição da função construtora pilha::pilha() { conta = 0; } //definição da função-membro
insere: int pilha::insere(int valor) { if (conta < 100) { dados[conta++] = valor; return SUCESSO; }else return FALHA; }
Funções construtoras e destrutoras Exemplo: #define SUCESSO 1 #define FALHA 0
//definição da classe pilha class pilha { int dados[100]; int conta; public: //função construtora pilha (void); //funções-membro int insere (int valor); int remove (int *valor); };
//definição da função-membro remove
int pilha::remove(int *valor);
{
if (conta == 0)
return FALHA;
else {
*valor = dados [--conta];
return SUCESSO;
}
}
Funções construtoras e destrutoras Funções destrutoras • Além de ser criado, um objeto pode também ser destruído
(liberado da memória), por exemplo, quando a execução do programa desloca-se para um bloco de código fora do escopo do objeto.
• A função destruidora é chamada quando uma variável da classe deixa de existir.
• A identificação de uma função destrutora é feita pelo nome da classe precedido pelo símbolo (~).
• Da mesma forma que um construtor, os destrutores não podem ter valor de retorno.
• O destrutor também não pode receber argumentos nem pode ser chamado explicitamente pelo programador.
Funções construtoras e destrutoras
Definição de um destrutor
class nome_da_classe
{
public:
~nome_da_classe (void); //função destrutora
};
Funções construtoras e destrutoras Destrutor da classe REC: decrementa o
contador de objetos sempre que um objeto é liberado da memória.
class REC { private: static int n; public: //construtor REC () { n++; } //destrutor ~REC () { n-- ; } int getrec() const {return n; } }; int REC :: n = 0;
void main() { REC r1, r2, r3; cout << "\nNumero de objetos: "
<<r1.getrec(); { REC r4, r5; cout <<"\nNumero de objetos:”
<<r1.getrec(); } cout << "\nNumero de objetos: "
<<r1.getrec(); }
Membros static Públicos - Acessado quando precedido do nome da classe seguido do
operador (::). class facil { private: int x, y, z; public: static int p; facil() { } ~facil() { } }; para se atribuir 5 ao membro p: facil :: p=5;
Membros static Públicos
• Um membro static é criado na definição da classe e existe independentemente de qualquer objeto.
• Um membro static não pode ser inicializado por um construtor da classe, visto que o construtor é chamado toda vez que um objeto é criado.
Objetos e Alocação de Memória • Para cada objeto declarado, é reservado um espaço de
memória em separado para armazenamento de seus dados-membro.
• Entretanto, todos os objetos da classe utilizam as mesmas funções.
• Logo, é armazenado espaço de memória para armazenar somente uma cópia de cada função-membro de uma classe particular, independente de quantos objetos tiverem sido criados!!!
• Funções-membro são criadas e colocadas na memória somente uma vez para a classe toda.
• As funções-membro são compartilhadas por todos os objetos da classe. Os itens de dado, por sua vez, armazenam diferentes valores para cada objeto, então é necessário que sejam criados novos membros de dados sempre que um objeto for declarado.
Objetos como argumentos de funções-membro
#include <iostream.h> #include <iomanip.h> class venda { private: int npecas; float preco; public: venda() { } //construtor sem args venda(int np, float p) //construtor
//com args { npecas = np; preco = p; }
void getvenda() { cout << "Insira No. Pecas: "; cin >> npecas; cout << "Insira Preco: "; cin >> preco; } void printvenda() const; void add_venda(venda v1, venda v2) { npecas = v1.npecas+v2.npecas; preco = v1.preco + v2.preco; } };
Objetos como argumentos de funções-membro
void venda::printvenda() const { cout << setiosflags(ios::fixed) //nao notacao científica << setiosflags(ios::showpoint)
//ponto decimal << setprecision(2) //duas casas << setw(10) << npecas;
//tamanho 10 cout << setw(10) << preco << "\n"; }
void main() { venda A(58,12734.53), B, Total; B.getvenda(); Total.add_venda(A,B);
cout << "Venda A.......";
A.printvenda(); cout << "Venda B.......";
B.printvenda(); cout << "Totais....... ";
Total.printvenda(); }
Funções que retornam um objeto #include <iostream.h> #include<iomanip.h> class venda { private: int npecas; float preco; public: venda() { } //construtor sem args venda(int np, float p); //construtor
//com args void getvenda(); void printvenda() const;
venda add_venda(venda v) const; };
venda venda::add_venda(venda v) const
{ venda temp; //variavel temporaria temp.npecas = npecas + v.npecas; temp.preco = preco + v.preco; return temp; } void main() { venda A(58,12734.53), B, Total; B.getvenda(); Total = A.add_venda(B);
cout << "Venda A......."; A.printvenda(); cout << "Venda B......."; B.printvenda(); cout << "Totais....... "; Total.printvenda(); }
Construtores de corpo vazio
• Muitas vezes, pode-se querer ter um construtor para inicializar os dados e também definir algum objeto sem que este seja inicializado. Para tanto, deve-se criar um construtor de corpo vazio.
• O corpo vazio indica que a função não faz nada e pertence à classe somente para que o outro construtor não seja executado automaticamente.
Sobrecarga de Operadores • Transformar expressões obscuras e complexas em outras
mais intuitivas Exemplo: Total.add_venda(A,B); Podem ser escritas como: Total = A + B; • Uma das deficiências das linguagens de programação
tradicionais ocorre quando elas precisam lidar com espécies de dados criadas pelo programador.
• Esses tipos de dados não podem ser manipulados com os mesmos operadores que funcionam com os tipos básicos. Por exemplo, você utiliza a adição (+) e a subtração (-) com tipos internos como int e float, mas não pode somar automaticamente estruturas e classes.
Sobrecarga de Operadores • Sobrecarregar um operador significa redefinir o seu símbolo
pelo modo como os operadores C++ trabalham, pode mudá-los conforme as suas necessidades, por meio de sobrecargas.
• Sobrecarregar significa utilizar um mesmo nome para tarefas parecidas, mas de códigos de programa diferentes. Várias funções com o mesmo nome podem apresentar diferentes trechos de programas.
• A implementação de sobrecargas de operadores é definida por meio de funções chamadas operadoras. Estas funções podem ser criadas como membros de classes ou como funções globais, acessíveis a todo programa.
Sobrecarga de Operadores Limitações • A sobrecarga de operadores permite uma nova implementação
da maneira como um operador funciona. Entretanto, é necessário respeitar a definição original do operador. Por exemplo, não é possível mudar um operador binário (que trabalhe com dois operandos) para criar um operador unário (que trabalha com um único operando).
• Não é permitido também estender a linguagem inventando novos operadores representados com símbolos novos. Por exemplo, não se pode inventar uma operação usando o símbolo ##. Este símbolo não é um operador válido em C++.
• A definição de operadores deve obedecer à precedência
original do operador. Não é possível modificar a precedência de operadores por meio de sobrecargas.
Sobrecarga de Operadores
Operadores que não podem ser sobrecarregados:
- o operador ponto de acesso a membros(.),
- o operador de resolução de escopo(::)
- operador condicional ternário (?:).
Sobrecarga de Operadores Operadores Unários e Binários
• Os operadores unários operam sobre um único operando.
Exemplos de operadores unários são o de incremento(++), decremento(--) e menos unário (-).
• Os operadores binários operam sobre dois operandos (+,-,*,/,>,+= etc.).
Obs: os operadores unários, definidos como funções-membro de classes, não recebem nenhum argumento, enquanto operadores binários recebem um único argumento.
A sobrecarga dos operadores de incremento e decremento apresenta uma dificuldade maior pelo fato de eles operarem de forma diferente quando prefixados ou pós-fixados.
Sobrecarga de Operadores Exemplo de uma classe que define um ponto de duas coordenadas inteiras e contém uma
sobrecarga do operador de incremento prefixado.
class ponto {
private:
int x, y;
public:
ponto (int x1 = 0, int y1 = 0) //construtor
{ x = x1; y = y1; }
ponto operator ++() { //função operadora prefixada
++x;++y;
ponto temp; //cria objeto temporário
temp.x = x; temp.y = y;
return temp;
}
void printpt() const { //imprime ponto
cout << ‘(‘ << x << ‘,’ << y<< ‘)’;
}
};
Sobrecarga de Operadores void main()
{
ponto p1, p2 (2, 3), p3; //declara e inicializa
cout << “ \n p1 = “; p1.printpt(); //imprime
cout << “ \n p2 =”; p2.printpt();
cout << “\n ++p1 = “;
(++p1).printpt(); //incrementa e imprime novamente
cout << “ \n++p2 = “;
(++p2).printpt();
p3 = ++p1; //incrementa e atribui
cout <<” \n p3 = “; p3.printpt();
}
Sobrecarga de Operadores • Para sobrecarregar um operador, simplesmente definimos uma função que o
compilador chama quando esse operador é usado. O nome da função é sempre igual ao símbolo do operador precedido da palavra chave operator.
Se p1 é um objeto da classe ponto, a instrução
++p1;
é equivalente a
p1.operator++();
• A função operator++() cria um objeto temporário da classe ponto para ser usado como valor de retorno. Logo, pode-se escrever instruções como: p3 = ++p1;
• No programa anterior, foi criado um objeto temporário da classe ponto, chamado temp a fim de fornecer um valor de retorno à função operator++().
Sobrecarga de Operadores Forma mais conveniente de retornar objetos de funções:
//retorna um objeto temporário sem nome
ponto operator ++()
{
++x; ++y;
return ponto(x,y);
}
A instrução
return ponto(x,y);
• Cria um objeto da classe ponto. Este objeto não tem nome e é inicializado pelo construtor com os valores fornecidos como argumentos (é necessário um construtor que recebe argumentos). Ele só existe no instante em que for retornado.
Sobrecarga do operador de incremento pós-fixado
• Se a função operator++() for definida sem nenhum parâmetro, ela será chamada sempre que a operação prefixada for encontrada.
• Se a função operator++(int) for definida com um único parâmetro do tipo int, ela será chamada sempre que a operação pós-fixada for encontrada. Este parâmetro serve apenas para que o compilador possa diferenciar as duas formas de uso.
Sobrecarga do operador de incremento pós-fixado
//mostra sobrecarga do operador ++ Pré e Pós fixados #include <iostream.h> class ponto { private: int x, y; public: ponto(int x1 = 0, int y1 = 0) //Construtor { x = x1; y = y1; } ponto operador ++() //função operadora prefixada { ++x;++y; return ponto(x,y); }
Sobrecarga do operador de incremento pós-fixado
//função operadora pós-fixada
ponto operador ++(int)
{
++x;++y;
return ponto(x-1, y-1);
}
void printpt() const //imprime ponto
{ cout << ‘(‘ << x << ‘,’ << y << ‘)’; }
};
Sobrecarga do operador de incremento pós-fixado
void main() { ponto p1, p2(2,3), p3; //declara e inicializa
cout << “ \n p1 = “; p1.printpt(); cout << “ \n p2 = “; p2.printpt();
cout << “ \n++p1 = “; (++p1).printpt(); //incrementa e depois usa cout << “ \np2++ = “; (p2++).printpt(); //usa e depois incrementa cout << “\n p2 = “; p2.printpt(); p3 = p1++; //atribui e depois incrementa cout << “\n p3 = “;p3.printpt(); cout << “\n p1 = “; p1.printpt(); }
Sobrecarga do operador de incremento pós-fixado
• Para a sobrecarga de operadores unários, a função operadora não tem argumentos, exceto no caso do operador de incremento ou decremento pós-fixado.
• A operação pré ou pós-fixada deve ser implementada pelo programador.
• O compilador não reconhece automaticamente a operação pré ou pós-fixada.
Sobrecarga de operadores binários
• Quando se sobrecarrega operadores binários, a função operadora tem um argumento.
• Genericamente, a função operadora (se declarada como membro da classe) sempre necessita de um argumento a menos do que o número de operandos daquele operador.
Sobrecarga de operadores binários
Sobrecarga de Operador Aritmético + #include <iostream.h> #include <iomanip.h> class venda { private: int npecas; float preco; public: venda() { } //Construtor sem args venda(int np, float p) //Construtor com args { npecas = np; preco = p; }
Sobrecarga de operadores binários
Sobrecarga de Operador Aritmético + venda operator + (venda v) const; //funcao operadora void getvenda() { cout << "Insira numero de pecas: "; cin >> npecas; cout << "Insira preco : "; cin >> preco; } void printvenda() const; }; void venda: printvenda() const { cout << setiosflags(ios::fixed) //não notação científica << setiosflags(ios::showpoint) //ponto decimal << setprecision(2) //duas casas << setw(10) << npecas; //tamanho 10 cout << setw(10) << preco << "\n"; }
Sobrecarga de operadores binários Sobrecarga de Operador Aritmético + venda venda::operator + (venda v) const //soma duas vendas { int pec = npecas + v.npecas; float pre = preco + v.preco; return venda(pec, pre); } void main() { venda A(58,12734.53), B, C(30,6000.3), T, Total; B.getvenda(); T = A + B; //A.operator+(B); Total = A + B + C; //somas múltiplas cout << "Venda: A...................."; A.printvenda(); cout << "Venda: B...................."; B.printvenda(); cout << "Venda: A+B.................."; T.printvenda(); cout << "Totais:A+B+C................"; Total.printvenda(); }
Sobrecarga de operadores binários - A função venda operator + (venda v) const retorna um objeto venda e
recebe um único argumento da classe venda. A chamada à função operadora é executada na instrução:
T = A + B;
• O objeto à esquerda do operador (A) é o objeto do qual a função operadora é membro.
• O objeto à direita do operador (B) é fornecido como argumento para a função operadora.
• Na função, o objeto (A) é acessado diretamente, visto que este é o objeto do qual a função operadora é membro.
• O operando da direita (B) é acessado como argumento da função, por meio do operador ponto (v.npecas, v.preco). O valor retornado por esta função é o valor da expressão (A+B) e será atribuído a T. A instrução anterior é equivalente a:
T = A.operator+(B);
Sobrecarga de operadores binários Somando objetos da classe ponto – aicionando uma função operadora que sobrecarrega o
operador + binário.
#include <iostream.h> class ponto { private: int x, y; public: ponto(int x1 = 0, int y1 = 0) //Construtor { x = x1; y = y1; }
ponto operator +(ponto p) const //Soma dois objetos { return ponto(x+p.x, y + p.y); }
ponto operator +(int n) const //Soma um número { return ponto(x+n, y+n); }
void printpt() const //imprime ponto { cout << ‘(‘ << x << ‘,’ << y << ‘)’;} };
Sobrecarga de operadores binários Somando objetos da classe ponto – aicionando uma função
operadora que sobrecarrega o operador + binário. void main() { ponto p1(5,1), p2(2,3), p3; //Declara e inicializa cout << “\n p1 = “;p1.printpt(); cout << “\n p2 = “;p2.printpt(); p3 = p1 + p2; //p3 = p1.operator+(p2); cout << “\n p3 = “;p3.printpt(); p3 = p1 + 5; //p3 = p1.operator+(5); cout << “\n p3 = “;p3.printpt(); }
Sobrecarga de operadores binários
• Uma função operadora não deve obrigatoriamente ter objetos dos dois lados do operador.
• Podemos adicionar um objeto ponto e um inteiro escrevendo uma função que descreve como a soma deve ser efetuada.
• Observe que p3 = 5 + p1 está ERRADO (obviamente um inteiro não tem funções-membro que podem ser chamadas para executar uma adição.
Criação de operadores para manipular strings
• Em C++, o operador + e o operador += não podem ser usados para a operação de acrescentar novos caracteres a uma cadeia de caracteres.
• A sobrecarga do operador + e += do exemplo seguinte
permite escrever instruções como: str1 = str2 + str3; str1 += str2; onde str1, str2, str3 são objetos da classe string.
Criação de operadores para manipular strings
//Sobrecarga do operador + com strings #include <iostream.h> #include <string.h> const MAX = 80; class string { private: char str[MAX]; public: string() { str[0] = ‘\0’;} //Construtor 1 string(char s[ ]) //construtor 2 { strcpy(str,s); }
string operator += (string s) //concatena { string temp;
if (strlen (str) + strlen(s.str) < MAX)
{ strcpy(temp.str,str); strcat(temp.str,s.str); strcpy(str, temp.str); }else cout << “ memória lotada!! “; return temp; } string operator + (string s) //concatena { string temp(str); temp+=s.str; return temp; } void print() const { cout << str; } //imprime };
Criação de operadores para manipular strings void main() { string s1(“Feliz Aniversario!”), s2(“Sam.”), s3(“Bom dia! “),s4, s5;
cout << “\ns1 = “; s1.print(); cout << “\ns2 = “; s2.print();
s3 += s2; s4 = s1 + s2; s5 = s1 + s1; cout << “\ns3 = “; s3.print(); cout << “\ns4 = “; s4.print(); cout << “\ns5 = “; s5.print(); }
Criação de operadores para manipular strings
• As funções operadoras recebem um argumento da classe string e retornam um objeto da mesma classe.
• O processo de concatenação envolve a criação de um objeto temporário e o uso das funções de biblioteca strcpy() e strcat().
• Os objetos envolvidos não são alterados; toda alteração é feita no objeto temporário que é retornado pelo comando return.
Comparando cadeias de caracteres Sobrecarga de operadores relacionais. Exemplo de um programa que ordena uma
lista de nomes por meio de uma função que utiliza o operador < sobrecarregado para a comparação das cadeias de caracteres.
//strsort.cpp //mostra sobrecarga dos operadores
relacionais #include <iostream.h> #include <string.h> #include <stdio.h> const MAX=80; enum Boolean { False, True }; class string { private: char str[MAX];
public: string() { str[0] = ‘\0’; } string(char s[ ]) { strcpy(str,s);} string getstr() { gets(str); return string(str); } Boolean operator < (string s) const { return (strcmp(str,s.str) < 0) ? True:
False; } Boolean operator == (string s) const { return (strcmp(str,s.str)==0) ? True:
False; } void print() const { cout << str; } }; void ordena (string s[ ], int n);
Comparando cadeias de caracteres void main()
{
string s[MAX];
for (int n=0; n<=80; n++) {
cout << "\nDigite nome ou [ENTER] para terminar: “ << endl;
if (s[n].getstr() == " ") break;
}
cout << "\n\nLista original:";
for (int i = 0; i < n; i++) {
cout << "\ns["<<i<< "] = ";
s[i].print();
}
ordena (s,n);
cout << "\nLista ordenada: ";
for (i=0; i< n; i++) {
cout << "\ns[" << i << "] = ";
s[i].print();
}
}
void ordena (string s[], int n) { string temp; for (int i=0; i<n; i++) { for (int j=i+1; j< n; j++) if (s[j] < s[i]) { temp = s[i]; s[i] = s[j]; s[j] = temp; } } }
Comparando cadeias de caracteres • Em main(), a instrução
if (s[n].getstr() == " ") break;
• Compara a entrada do usuário com uma cadeia nula. Se o usuário pressionar a tecla [ENTER] sem escrever qualquer nome, o programa entende que a entrada terminou.
Sobrecarga de funções • Permite que o nome de uma função seja usado para
declarar diversas funções que diferem apenas no tipo de dados em que operam.
• O compilador usa automaticamente a versão correta da função baseado no tipo dos argumentos.
• É possível usar o nome abs() para chamar a função de valor absoluto, indiferente do tipo de dados utilizados.
• O compilador C++ deve ser avisado antecipadamente, usando a palavra-chave overload, que uma função será sobrecarregada. De outra forma, ele apenas assume que você está cometendo um erro de definição múltipla.
Sobrecarga de funções Exemplo: criar duas versões de abs(): overload abs; int abs(int); long abs(long); int abs(int i) { return i < 0 ? -i : i; } long abs(long i) { return i < 0 ? -i : i; }
Sobrecarga de funções Exemplo: criar duas versões de uma
função que devolve o valor de um número elevado a um expoente inteiro.
overload power; int power(int b, int e); double power(double b, int e); void main() { printf("%d\n", power(10,2)); printf("%f", power(10.12,2)); }
//potência inteira int power (int b, int e) { int t; t = b; for (e--; e; e--) b *= t; return b; } //potência com double double power(double b, int e) { double t; t = b; for (e--; e; e--) b* = t; return b; }
Herança • Processo para criação de novas classes - classes derivadas -
baseadas em classes existentes ou classes-base. • Para usar uma classe ou mesmo para derivar uma classe, só é
necessário ter o arquivo de código-objeto ou uma biblioteca que contenha seu código-objeto; não é preciso ter o código-fonte. Isto permite que bibliotecas de classes sejam compradas ou distribuídas e sejam facilmente adaptadas a situações não-previstas ao serem elaboradas.
• O processo de herança vai além da derivação simples. Uma classe derivada pode herdar características de mais de uma classe-base. Classes derivadas não estão limitadas a herança única.
• Para derivar uma nova classe de uma classe existente, basta indicar o nome da classe-base após o nome da nova classe, separado por dois-pontos(:) - não confundir com o operador de resolução de escopo (::). O nome da classe-base pode ser precedido da palavra public ou da palavra private.
Herança Membros protected • A parte pública da classe-base está disponível à classe derivada
e a qualquer objeto desta classe. Qualquer membro público da classe-base é automaticamente um membro da classe derivada. Entretanto, nenhum membro privado da classe-base pode ser acessado pela classe derivada. Para resolver este problema, C++ permite o uso de uma seção chamada protegida.
• Membros protected são semelhantes a membros private, exceto pelo fato de que são visíveis a todos os membros de uma classe derivada.
• Membros protected não podem ser acessados por nenhum objeto declarado fora dessas classes.
Herança Herança pública e privada • A declaração public indica que os membros públicos da classe-
base serão membros públicos da classe derivada e os membros protected da classe-base serão membros protected da classe derivada. Os membros públicos da classe-base podem ser acessados por um objeto da classe derivada.
• A declaração private pode ser usada no lugar de public e indica que tanto os membros públicos quanto os protegidos da classe-base serão membros privados da classe derivada. Estes membros são acessíveis aos membros da classe derivada, mas não aos seus objetos. Portanto, um objeto da classe derivada não terá acesso a nenhum objeto da classe-base.
Herança Herança pública e privada //privpub.cpp //mostra acesso público, privado e protegido à classe-base #include <iostream.h> class BASE { protected: int secreto; private: int super-secreto; public: int publico; }; class DERIV1 : public BASE { public: void print1() {int a = secreto;} //OK void print2() (int b = super-secreto;} //ERRO: não acessível void print3() {int c = publico;} //OK };
Herança Herança pública e privada //privpub.cpp //mostra acesso público, privado e protegido à classe-base #include <iostream.h> class BASE { protected: int secreto; private: int super-secreto; public: int publico; }; class DERIV2 : private BASE { public: void print1() {int a = secreto;} //OK void print2() (int b = super-secreto;} //ERRO: não acessível void print3() {int c = publico;} //OK };
Herança Herança pública e privada void main() { int x;
DERIV1 obj1; //DERIV1 é public x = obj1.a; //ERRO: não acessível x = obj1.b; //ERRO: não acessível x = obj1.c; //OK DERIV2 obj2; //DERIV2 é private x = obj2.a; //ERRO: não acessível x = obj2.b; //ERRO: não acessível x = obj2.c; //ERRO: não acessível }
Reescrevendo Funções membro da classe-base • Quando você usa uma classe predefinida e pré-compilada como classe-base,
nem sempre as funções membros dessa classe atendem completamente ao que você precisa. Muitas vezes, você gostaria de modificar uma das funções para acrescentar alguma nova característica.
• É possível criar funções-membro de uma classe derivada que tenham o mesmo
nome de funções membro da classe-base Isto faz com que a sintaxe da chamada a elas, por meio de um objeto, seja a mesma, independentemente de tratar-se de um objeto da classe-base ou da classe derivada. A nova função pode chamar a função da classe-base ou da classe derivada. A nova função pode chamar a função da classe-base por meio do operador de resolução de escopo (::).
• Se duas funções de mesmo nome existem, uma na classe-base e outra na classe
derivada, a função da classe derivada será executada se for chamada por meio de um objeto da classe derivada.
• Se um objeto da classe-base é criado, usará sempre funções da própria classe-
base pois não conhece nada da classe derivada.
Reescrevendo Funções membro da classe-base
#include <iostream.h>
//definição da classe base class classe_1 { public: int x; classe_1 (void) { x = 10; } }; //definição da classe derivada class classe_2: public classe_1 { public: int x; void atribui (int valor) { x = valor; } int soma (void); };
int classe_2::soma(void) { return (classe_1::x + x); } main() { classe_2 obj; obj.atribui (15); cout << "soma dos valores = "
<<obj.soma(); }
Reescrevendo Funções membro da classe-base
• Neste exemplo, acessamos a variável x, membro da classe base classe_1,
a partir da função soma(), membro da classe derivada classe_2.
• Caso a classe base contenha funções construtoras, ou destrutoras, estas funções serão chamadas quando da criação, ou destruição, de um objeto com o tipo da classe derivada. Além disto, caso ambas as classes contenham estes tipos de funções, quando da criação, ou da destruição, de um objeto do tipo da classe derivada, será executada, tanto a função membro da classe derivada, quanto a função membro da classe base.
• Ao ser criado um objeto com o tipo da classe derivada, a função construtora da classe derivada será executada após a execução da função construtora da classe base. Ao ser destruído um objeto com o tipo da classe derivada, a função destrutora da classe derivada será executada antes da execução da função destrutora da classe base.
Hierarquia de Classes
Exemplo:
• Classe-base conta: usada para criar as classes derivadas: conta simples, contaEspecial e Poupança.
• A base de dados armazena o nome do cliente, a identificação numérica da conta e o saldo, independentemente de sua categoria. Entretanto, contas especiais necessitam de um dado adicional: o limite; e contas-poupança necessitam da taxa de reajuste.
Hierarquia de Classes
Hierarquia de Classes
Hierarquia de Classes
Hierarquia de Classes
Conversões de tipos entre classe-base e classe derivada
• Conversão implícita de um objeto da classe derivada num objeto da classe-base. Exemplo:
conta C; contaEspecial CE; CE.getdata(); C = CE; //converte contaEspecial em conta • Todos os membros do objeto C da classe-base recebem os valores dos
membros correspondentes do objeto CE da classe derivada. Entretanto, a atribuição inversa é proibida:
CE = C; //erro! Não pode ser convertido Como CE tem membros que C não tem, seus valores ficariam
indefinidos!!
Níveis de Herança
• Uma classe pode ser derivada de outra classe, que, por sua vez, é também uma classe derivada.
class X { }; class Y : public X { }; class Z : public Y { };
Herança Múltipla
• A construção de hierarquias de herança múltipla envolve mais complexidade do que as hierarquias de herança simples. Esta complexidade diz respeito ao desenho da construção das classes e não a sintaxe de uso. A sintaxe de múltiplas heranças é similar à de uma única herança.
class X { }; class Y { }; class Z : public X, public Y //Z é derivada de X e de Y { }; • As classes-base das quais Z é derivada devem sem escritas após
os dois-pontos e separadas por vírgula.
Ponteiros para Classes
• Podemos declarar ponteiros para classes através do operador de indireção (*).
• Uma vez declarado um ponteiro para uma classe, podemos usar o operador de endereço (&) de modo a especificar qual o objeto apontado.
• Tendo atribuído um valor ao ponteiro, podemos passar a acessar os membros públicos da classe através do operador flexa.
• Os membros acessados são aqueles relativos à instância representada pelo objeto apontado.
• Exemplo: usar ponteiros na chamada às funções membros da classe. Antes das chamadas às funções, o valor armazenado no ponteiro é atualizado com o endereço do objeto sobre o qual desejamos que a função atue. A função soma() atua sobre as variáveis do objeto dado_a, e a função subt() atua sobre as variáveis do objeto dado_b;
Ponteiros para Classes class coordenadas { int x, y; public: coordenadas (void) { x = 0; y =
0; } void soma (int a, int b); void subt (int a, int b); }; void coordenadas::soma (int a, int b) { x = x + a; y = y + b; }
void coordenadas::subt (int a, int b) { x = x - a; y = y - b; } void main() { coordenadas dado_a, dado_b,
*ptr; ptr = &dado_a; ptr ->soma (1,2); ptr = &dado_b; ptr ->subt (2, 4); }
Ponteiros para Classes • Um ponteiro para uma classe base, pode também ser usado
para acessar membros de uma classe derivada, desde que sejam membros herdados da classe base.
• Considerando classe_a como sendo a classe base, classe_b como sendo a classe derivada, e func() como sendo uma função membro herdada da classe base pela classe derivada, a seguinte sequência de comandos é válida:
classe_a obj_a; //cria um objeto tipo classe_a classe_a* ptr; //ponteiro para tipo classe_a classe_b obj_b; //cria um objeto tipo classe_b ptr = &obj_b; //ptr aponta para obj_b ptr -> func(); //chama função membro
Ponteiros para Classes
• Exemplo: declarar uma matriz de objetos do tipo da classe coordenadas e um ponteiro para esta classe.
• O ponteiro é inicializado como o endereço do primeiro objeto da matriz e posteriormente incrementado.
• Uma vez que os resultados das operações aritméticas sofrem um processo de escala proporcional ao tamanho do objeto, o novo valor do ponteiro indica o endereço do segundo objeto da matriz.
Ponteiros para Classes void main() { coordenadas dado[2], *ptr;
ptr = dado; //inicializa ponteiro ptr -> soma (1, 2); //atua sobre dado[0] ptr -> apresenta(); //valor impresso sera 1 2
ptr = ptr + 1; //incrementa ponteiro ptr -> subt (2, 4); //atua sobre dado [1] ptr -> apresenta(); //valor impresso será -2 -4 }
void coordenadas::apresenta(void) { cout << " valor x = " << x << "\n"; cout << " valor y = " << y << "\n"; }
Ponteiros para Membros - Especifica-se o tipo do membro da classe, o tipo da
classe e o identificador da variável, precedido pelos operadores de identificação de escopo e de indireção.
- Uma vez declarado um ponteiro para um membro de uma classe, podemos fazer uso do mesmo de modo a acessar membros de objetos deste tipo de classe.
- No exemplo seguinte, declaramos um ponteiro para o membro x da classe coordenadas, e acessamos os membros dos objetos criados com este tipo de classe:
Ponteiros para Membros #include <iostream.h> class coordenadas { public: int x, y; coordenadas(void) { x=1;y=2; } };
void main() { coordenadas dado_1, dado_2; int valor; int coordenadas::*ptr; ptr = &coordenadas::x; valor = dado_1.*ptr; cout << "Valor do membro x = "
<< valor; ptr = &coordenadas::y; valor = dado_2.*ptr; cout << "Valor do membro y = "
<< valor; }