Transcript Pilha

Programação aplicada de
computadores
Profa. Andréa Maria Pedrosa Valli
www.inf.ufes.br/~avalli
2012/1 – Engenharia Elétrica
Objetivo da Disciplina:
Proporcionar uma visão geral sobre os conceitos definidos em
programação orientada a objetos através do aprendizado da
linguagem C++.
Conteúdo:
• Introdução
• Princípios da Programação Estruturada
• Classes e Objetos
• Composição e Herança
• Polimorfismo
• Exceções
Livro Texto:
H.M. Deitel e P.J. Deitel, C++: como Programar, 5 ed., Bookman,
2006.
Avaliação:
Através de exercícios computacionais (Exerc) e provas escritas
(PrEscr). A média parcial final é calculada da seguinte forma:
0.5 * Exerc * Entrevista + 0.5 * PrEscr,
onde cada um dos termos corresponde a média aritmética dos
exercícios e provas respectivamente. A nota da entrevista varia entre
0 e 1 e Exerc = 0.2 * Lista + 0.8 * Trabalho.
A Segunda Chamada é para quem faltou alguma prova e justificou
(essa prova pode ser escrita ou aplicada no computador).
Homepage da Disciplina:
www.inf.ufes.br/~avalli/programacao/paginas/prog_eletrica012.html
Introdução: Panorâmica da programação
orientada a objetos
Referência:
Antonio Mendes da Silva Filho, Introdução à Programação Orientada
a Objetos com C++, Rio de Janeiro: Elsevier, 2010.
Objetivos:
• Entender o porquê do surgimento do paradigma de programação
orientada a objetos (PPO).
• Contextualizar as diferenças entre programação orientada a
objetos e a programação estruturada.
• Apresentar as principais características das linguagens de
programação orientada a objetos.
• Discutir as principais diferenças entre as linguagens C e C++.
Linguagens: C++,C#, Object Pascal, Java, Python, SmallTalk, Matlab
Cada programa consiste em um conjunto organizado de instruções
que operam sobre um conjunto de dados, processando-os, a fim
de realizar alguma funcionalidade e produzir uma saída.
Obs:
• A medida que os sistemas crescem, cresce a complexidade
associada a eles.
• Antes da POO, o programador era ensinado a raciocinar sobre os
problemas em termos de blocos de códicos (funções) ou
procedimentos e sobre a forma como esses blocos atuavam sobre
os dados (linguagens procedimentais).
• A POO é uma linguagem de programação que foi criada, baseada
no raciocínio de classificação e manipulação do ambiente em que
vivemos.
Linguagens Procedimentais: Fortran, Pascal, C, Basic
• Um programa em uma linguagem procedimental é uma lista de
instruções organizada em termos de funções e módulos (um
agrupamento de várias funções em uma entidade maior).
• Dividir um programa em funções e módulos é um dos
fundamentos da programação estruturada e os dados constituem a
essência de um sistema.
Questão: O que acontece com os dados no paradigma de
programação orientada a procedimentos?
Variáveis globais
Variáveis locais
Função F1
Acessível apenas à
função F1
Acessível a qualquer
função
Variáveis locais
Função F2
Acessível apenas à
função F2
Características das linguagens orientadas a objetos
•
Encapsulamento de dados: Os dados e funções são
encapsulados em uma entidade – o objeto. As funções de um
objeto em C++ são chamadas funções-membros e oferecem
uma única forma de acesso aos dados. Não se pode acessar
os dados diretamente (ocultação dos dados).
Objeto
Objeto
dados
dados
Função-membro
Função-membro
Função-membro
Função-membro
Objeto
dados
Função-membro
Função-membro
Vantagens do encapsulamento de dados:
• Uma grande vantagem do encapsulamento é que toda parte
encapsulada pode ser modificada sem que os usuários da classe em
questão sejam afetados. Ex: liquidificador
• O encapsulamento protege o acesso direto (referência) aos atributos
(variáveis) de uma instância (molde) fora da classe onde estes foram
declarados (use get e set para retornar e modificar o valor do atributo
de um objeto).
• Encapsular atributos também auxilia a garantir que o estado e o
comportamento de um objeto se mantenha coeso. Ex: semáforo
Obs: Em termos intuitivos, uma classe é vista como um "molde" que
gera instâncias de um certo tipo, já objeto é algo que existe
fisicamente moldado a partir desse molde.
• Raciocínio em termos de objetos: diz-se que os objetos são
membros de classes. Ex: um fusca é um objeto da classe Automóveis.
Duas Rodas
Biclicletas
Estrada
Quatro Rodas
Terra
Ferroviário
Motocicletas
Trem
Automóveis
Bonde
Caminhões
Ônibus
Metrô
Navios
Passageiros
Aéreo
Cargueiros
Gerra
Marítimo
Aviões
Submarinos
Pesquisas
Balões
Helicópteros
Pesca
Barcos
Transporte
Transporte
Combate
• Classe: uma classe serve como um padrão, modelo ou template. Ela especifica
quais dados e quais funções serão incluídos nos objetos daquela classe. Definir
uma classe não cria quaisquer objetos, assim como a simples existência de um tipo
de dados int não cria quaisquer variáveis. Uma classe é, portanto, um conjunto de
objetos similares.
• Herança: o princípio de herança considera a divisão de uma classe em
subclasses que compartilham características comuns com a classe da qual ela é
derivada.
classe base
A
B
classes derivadas
A
A
C
B
D
E
B
• Reusabilidade: uma vez que uma classe tenha sido criada, escrita e
depurada, ela pode ser usada por outros programadores em seus programas.
Mais ainda, o programador pode adicionar novas características a ela, sem
modificá-la.
• Criação de novos tipos de dados: um dos benefícios dos objetos é que
eles oferecem ao programador uma forma conveniente de construir novos tipos
de dados. Ex: trabalhar com coordenadas x e y e operações aritméticas normais
do tipo posicao1 = posicao2 + origem, onde as variáveis posicao1, posicao2 e
origem representam coordenadas.
• Polimorfismo e Overloading (Sobrecarga): podemos definir novas
operações para serem usados em novos tipos de dados. No exemplo acima, os
operadores = (igual) e + (soma) são definidos pelo programador para a classe
Posicao. Usar operadores ou funções de formas diferentes, dependentes do que
eles estão operando, é denominado polimorfismo. Quando um operador
existente tem a capacidade de ser usado em novos tipos de dados, diz-se que ele
está sobrecarregado (overloaded). Overloading é um tipo de polimorfismo e mais
um das características de C++.
• Diferenças entre C e C++: C++ é uma linguagem derivada de C. Portanto,
C++ retém a maioria das características da linguagem C e oferece suporte à maior
quantidade de estilos de programação, tornando-a mais versátil e flexível. Os
elementos mais importantes adicionados ao C para criar o C++ compreendem
classes, objetos e a POO. Além disso, inclui uma abordagem melhorada de
entrada e saída (E/S) e nova forma para comentários.
Introdução: Um passeio por C++
O que é C++ segundo Bjarne Stroustrup (o criador de
C++, A Linguagem de Programação C++, 3ed., Bookman):
• é uma C melhor
• suporta abstração de dados,
• suporta programação orientada a objetos e
• suporta programação genérica.
Objetivo desta introdução:
Fornecer uma visão geral de C++ e as principais técnicas para usála mas omitindo os detalhes da programação. Concentre-se em
técnicas de programação e não em recursos de linguagem.
Conteúdo
1. Paradigmas de programação
2. Programação orientada a procedimentos
3. Programação modular
4. Abstração de dados
5. Programação orientada a objetos
6. Programação genérica
1. Paradigmas de programação
• Programação
orientada a objetos é uma técnica de
programação - uma paradigma para escrever “bons” programas
para um conjunto de problemas. A linguagem fornece bons
mecanismo de suporte ao estilo de programação orientada a
objetos.
• Uma linguagem suporta um estilo de programação se ela
fornece facilidades que tornem conveniente (razoavelmente
fácil, seguro e eficiente) usar aquele estilo.
• Uma linguagem não é necessariamente melhor que uma outra
porque ela possui um recurso que a outra não tem, mas se os
recursos que ela tem são suficientes para suportar os estilos de
programação desejados nas áreas de aplicações pretendidas.
Princípios (estética e lógica, minimalismo, “o que você não sabe
não pode feri-lo”):
1. Todos os recursos devem ser claros e elegantemente
integrados nas linguagem;
2. Deve ser possível usar recursos combinados;
3. Deve haver pouco recursos espúrios e de propósito especial;
4. A implementação de um recurso não deve impor uma
significativa sobrecarga a programas que não exigem seu uso;
5. Um usuário necessita saber apenas o subconjunto da
linguagem explicitamente usado para escrever um programa.
C++ foi projetada para suportar abstração de dados, programação
orientada a objetos e programação genérica adicionalmente a
técnicas de programação tradicional de C.
2. Programação orientada a procedimentos
Paradigma:
Decida quais procedimentos você quer; use os melhores algoritmos
que você puder encontrar.
O foco é no procedimento – o algoritmo necessário para executar a
computação desejada. A linguagem possui facilidades para
passar/retornar argumentos para funções.
Recursos para expressar computações:
• variáveis e aritmética
• testes e laços
• ponteiros e arrays
#include <iostream>
#include <cmath>
using namespace std;
const double PI = 3.14159; // define global constant PI
// calculates volume of a sphere
inline double sphereVolume( const double radius )
{
return 4.0 / 3.0 * PI * pow( radius, 3 );
} // end inline function sphereVolume
int main()
{
double radiusValue;
// prompt user for radius
cout << "Enter the length of the radius of your sphere: ";
cin >> radiusValue; // input radius
// use radiusValue to calculate volume of sphere and display result
cout << "Volume of sphere with radius " << radiusValue
<< " is " << sphereVolume( radiusValue ) << endl;
return 0; // indicates successful termination
} // end main
3. Programação modular
Com os anos, o enfoque mudou para a organização dos dados.
Quando não existem agrupamentos de procedimentos com dados
relacionados, a programação orientada a procedimentos é
suficiente. Um conjunto de procedimentos relacionados com os
dados que eles manipulam é denonimado módulo.
Paradigma:
Decida quais módulos você quer; particione o programa de modo a
esconder os dados dentro dos módulos (o princípio da ocultação de
dados).
Exemplo de um módulo: uma pilha
namespace Pilha { // interface
void empilha( char );
char desempilha( );
}
# include “pilha.h”
void f( )
{
Pilha::empilha(‘c’);
if( Pilha::desempilha( ) != ‘c’ ) error( “impossivel” );
}
#include “pilha.h”
namespace Pilha { // implemetacao
const int tamanho_max = 200; );
char v[tamanho_max];
int topo = 0;
-----> arquivo pilha.h
-----> arquivo usuario.c
-----> arquivo pilha.c
void empilha( char c ) { /* testa transbordamento e empilha c */}
void desempilha( ) { /* testa esvaziamento e desempilha */ }
}
Na definição da pilha devemos:
• fornecer uma interface com o usuário para a pilha;
• assegurar que a representação da pilha possa ser acessada somente através
de sua interface com o usuário;
• assegurar que a pilha seja inicializada antes de ser usada pela primeira vez.
Compilação separada:
C++ suporta a noção de compilação separada de C, ou seja,
pode ser usado para organizar um programa como um conjunto
de trechos semi-independentes.
Tratamento de exceções:
• À medida que os programas crescem e bibliotecas são extensivamente usadas,
padrões para tratamento de erros (ou, de modo mais geral, “circunstâncias
especiais”) se tornam importantes.
namespace Pilha { // interface
void empilha ( char );
char desempilha ( );
class Transbordou { };
// tipo representando excecoes de transbordamento
}
void Pilha::empilhar( char c )
{
if ( topo == tamanho_max ) throw Transbordou( );
// empilha c
}
void f( ) {
// . . .
try {
// excecoes que sao tratadas pelo tratador definido abaixo
while ( true ) Pilha::empilha ( ‘c’ );
}
catch ( Pilha::Transbordou ) {
// oops: transbordamento de pilhas; tome a acao apropriada
}
// . . .
}
4. Abstração de dados
C++ permite que o usuário defina diretamente tipos que se
comportam (aproximadamente) do mesmo modo que os tipos
primitivos.
Paradigma:
Decida quais tipos você quer; providencie um conjunto completo de
operações para cada tipo.
Podemos ter por exemplo:
• definição de tipos em módulos- Ex: namespace Pilha com struct
• tipos definidos pelo usuário- Ex: classe de número complexos
• tipos concretos - Ex: class Pilha
• tipos abstratos (a interface isola mais completamente um
usuário dos detalhes de implementação) – Ex: class Pilha
• funções virtuais – tabela de funções virtuais
1. Definição de tipos em módulos
Exemplo: definir um gerenciador de pilhas
namespace Pilha {
struct Rep;
typedef Rep& pilha;
pilha cria ( );
void destroi ( pilha s );
// definicao do layout da pilha est’a em algum lugar
// fazer uma nova pilha
// destruir s
void empilha ( pilha s, char c ); // inserir c em s
char desempilha ( pilha s );
// desempilha em s
}
Uso:
struct Error_desempilha{ };
void f ( )
{
Pilha::pilha s1 = Pilha::cria ( );
Pilha::pilha s2 = Pilha::cria ( );
// fazer uma nova pilha
// fazer uma outra nova pilha
Pilha::empilha ( s1, ‘c’ );
Pilha::empilha ( s2, ‘k’ );
if ( Pilha::desempilha ( s1 ) != ‘c’ ) throw Desempilha_mal ( );
if ( Pilha::desempilha ( s2 ) != ‘k’ ) throw Desempilha_mal ( );
Pilha::destroi ( s1 );
Pilha::destroi ( s2 );
}
2. Tipos definidos pelo usuário: números complexos é um exemplo
class complex {
double re, im;
public:
complex ( double r, double I ) { re = r; im = I; }
complex ( double r ) { re = r, im = 0; }
complex ( ) { re = im = 0; }
friend complex operator+ ( complex, complex );
friend complex operator- ( complex, complex );
friend complex operator- ( complex );
friend complex operator* ( complex, complex );
friend complex operator/ ( complex, complex );
friend bool operator== ( complex, complex );
friend bool operator!= ( complex, complex );
// . . .
// constroi complexo a partir de dois escalares
// constroi complexo a partir de um escalar
// constroi default (0,0)
// binario
// unario
// igual
// diferente
};
complex operator+ ( complex a1, complex a2 ) { return complex ( a1.re + a2.re, a1.im + a2.im ); }
void f ( complex z ) {
complex a = 2.3;
complex b = 1/a;
complex c = a + b*complex(1,2.3);
// …
if ( c!= b ) c = - (b/a) + 2*b;
// . . .
}
3. Tipos concretos: como exemplo, considere um tipo Pilha definido pelo usuário da
seguinte forma:
class Pilha {
char* v;
int topo;
int tamanho_max;
public:
Pilha ( int s ) // construtor
~Pilha ( )
// destruidor
class Transbordou { };
// usada como excecao
class Esvaziou { };
// usada como excecao
class Erro_tamanho { }; // usada como excecao
void empilha ( char c );
char desempilha ( );
};
Uso:
Pilha s_var1 ( 10 );
void f ( Pilha& s_ref, int i ) {
Pilha s_var2 ( i );
Pilha* s_ptr = new Pilha ( 20 );
s_var1.empilha ( ‘a’ );
s_var2.empilha ( ‘b’ );
s_ref.empilha ( ‘c’ );
s_ptr->empilha ( ‘d’ );
// . . .
// pilha global com 10 elementos
// referencia a Pilha
// pilha local com i elementos
// ponteiro para Pilha alocada na memoria livre
4. Tipos abstratos: Ex: uma pilha definida como um tipo abstrato de dados.
class Pilha {
public:
class Transbordou { };
class Esvaziou { };
// usada como excecao
// usada como excecao
virtual void empilha ( char c ) = 0;
virtual char desempilha ( ) = 0;
};
Uso:
void f( Pilha& s_ref )
{
s_ref.empilha ( ‘c’ );
if ( s_ref.desempilha ( ) != ‘c’ ) throw Erro_desempilha ( ) ;
}
A palavra virtual significa em C++ “que pode ser redefinida posteriormente em uma
classe derivada a partir desta”. A classe derivada de Pilha fornece uma implementação
para a interface de Pilha e a sintaxe = 0 indica que alguma classe derivada de Pilha deve
definir esta função. Desta forma, esta Pilha pode servir como uma interface para
qualquer classe que tenha uma implementação das funções empilha() e desempilha(). É
um tipo polimórfico, ou seja, fornece a interface para diversas outras classes.
Implementações:
class Array_pilha : public Pilha {
char* p;
int topo;
int tamanho_max;
public:
Array_pilha ( int s );
~Array_pilha ( );
// Array_pilha implementa Pilha
void empilha ( char c ) ;
char desempilha ( ) ;
};
class Lista_pilha : public Pilha {
list<char> lc;
public:
Lista_pilha ( ) { }
// Lista_pilha implementa Pilha
void empilha ( char c ) { lc.push_front ( c ); }
// . . .
Uso:
void g ( ) {
Array_pilha as( 200 );
f( as );
};
void h ( ) {
Lista_pilha ls;
f( ls );
};
5. Funções virtuais:
void f( Pilha& s_ref )
{
s_ref.empilha ( ‘c’ );
if ( s_ref.desempilha ( ) != ‘c’ ) throw Erro_desempilha ( ) ;
}
Como pode a chamada s_ref.empilha ( ) ser resolvida em relação à definição da função certa?
Quando f() é chamada em g(), Array_pilha::empilha() deve ser chamada.
Quando f() é chamada em h(), Lista_pilha::empilha() deve ser chamada.
Tabela de funções virtuais (vtbl):
5. Programação orientada a objetos
Paradigma:
Decida quais tipos você quer; providencie um conjunto completo de
operações para cada classe; explicite as coisas em comum através
do uso de herança.
Ex: problemas com tipos concretos
class Ponto { /* . . . */ }
class Cor { /* . . . */ }
enum Tipo { circulo, triangulo, quadrado };
class Forma {
Tipo k;
// campo de tipo
Ponto centro;
Cor co;
// . . .
public:
void desenha ( );
void rotacao ( int );
// . . .
};
void Forma::desenha ( ) {
switch ( k ):
case circulo:
// desenha um circulo
break;
case triangulo:
// desenha um circulo
break;
case quadrado:
// desenha um circulo
break;
}
}
Hierarquia de classes
class Forma {
Ponto centro;
Cor co;
// . . .
public:
Ponto onde ( ) { return centro; }
void move ( Ponto to ) { centro = to; /* . . . */ desenha ( ); }
virtual void desenha ( ) = 0;
virtual void rotacao ( int angulo ) = 0;
// . . .
};
• Para definir uma forma particular, precisamos dizer que é uma
Forma e especificar suas propriedades particulares (incluindo as
funções virtuais):
class Circulo : public Forma { // classe derivada da classe base Forma
int raio;
public:
void desenha ( ) { /* . . . */ }
void rotacao ( int ) { } // a funcao nula ;
};
• Diz-se que a classe derivada herda membros de sua classe base, de
forma que o uso das classes base e derivadas é habitualmente
chamado de herança.
• Encontrar a quantidade de propriedades em comum entre tipos
que possam ser explorados usando-se herança e funções virtuais
não é um processo trivial na escrita de um projeto em POO.
• O projetista experiente aplica diversos paradigmas, de acordo com
as necessidades.
6. Programação genérica
Paradigma:
Decida que algoritmos você quer; parametrize-os de forma que
eles funcionem para diversos tipos e estruturas de dados
desejados.
Ex: uma pilha de qualquer coisa, substituindo o tipo específico char
por um parâmetro de gabarito:
template<class T> class Pilha {
T* v;
int tamanho_max;
int topo;
public:
Pilha ( int s );
// constutor
~Pilha ( );
// destruidor
class Esvaziou { };
class Transportou { };
void empilha ( T );
T desempilha ( );
};
As funções membro podem ser definidas de forma similar:
template<class T> void Pilha<T>::empilha( T c )
{
If ( topo == tamanho_max ) throw Transbordou ( );
v[topo] = c;
topo = topo + 1;
}
template<class T> T Pilha<T>::desempilha( )
{
If ( topo == 0 ) throw Esvaziou ( );
topo = topo - 1;
return v[topo];
}
Uso:
Pilha<char> sc( 200 );
Pilha<complex> scplx ( 30 );
Pilha< list<int> > sli ( 45 );
// pilha de 200 caracteres
// pilha de 30 numeros complexos
// pilha de 45 listas de inteiros
void f ( )
{
sc.empilha ( ‘c’ );
if ( sc.desempilha ( ) != ‘c’ ) throw Erro_desempilha ( );
scplx.empilha ( complex ( 1, 2 ) );
If ( scplx.desempilha ( ) != complex( 1, 2 ) ) throw Erro_desempilha ( );
}
• Similarmente, podemos definir listas, vetores, etc., como gabaritos.
Gabaritos são um mecanismo de tempo de compilação, de modo
que seu uso não implica em nenhuma sobrecarga em tempo de
execução quando comparado com “com código escrito a mão”.
• Uma classe contendo uma coleção de elementos de algum tipo é
habitualmente chamada de contêiner ou simplemente contêiner. A
biblioteca padrão C++ oferece uma variedade de contêineres e os
usuários podem escrever seus próprios contêineres.
• Conselhos dados pelo autor, Bjarne Stroustrup:
1. Não se assuste! Tudo se tornará claro a tempo;
2. Você não precisa conhecer cada detalhe de C++ para escrever
bons programas;
3. Concentre-se em técnicas de programação e não em recursos da
linguagem.