Tópicos em
C++
Claudio Esperança
Paulo Roma Cavalcanti
 2002 LCG/UFRJ. All rights reserved.
1
Classes e Objetos
• C++ é mais do que C com uns poucos sinos e
apitos adicionais.
• O mecanismo básico para atingir
programação orientada a objeto em C++ é o
conceito de classe.
▪ Fornece formas de encapsulamento e proteção
de informação.
▪ Permite reutilização de código.
 2002 LCG/UFRJ. All rights reserved.
2
O que é OO?
• Programação orientada a objeto foi o
paradigma dominante dos anos 90.
• Criou o conceito de objeto, que é um tipo de
dado com uma estrutura e um estado.
▪ Cada objeto define um conjunto de operações
que podem acessar ou manipular esse estado.
▪ Tipos definidos pelo usuário devem se
comportar da mesma maneira de tipos prédefinidos (fornecidos pelo compilador).
 2002 LCG/UFRJ. All rights reserved.
3
Tipos de Dados
• Um tipo básico de dados de uma linguagem,
como inteiro, real, ou caractere, fornece certas
coisas:
▪ Podem-se declarar novos objetos, com ou sem
iniciação.
▪ Pode-se copiar ou testar quanto à igualdade.
▪ Pode-se executar a entrada e a saída de dados
com esses objetos.
 2002 LCG/UFRJ. All rights reserved.
4
Objetos
• Um objeto é uma unidade atômica.
▪ Não pode ser dissecado por um programador.
• Proteção de informação torna os detalhes de
implementação inacessíveis.
• Encapsulamento é o agrupamento de dados,
e operações que se aplicam a eles, para
formar um agregado.
▪ Mas escondendo a os detalhes de
implementação.
• Uma classe é o mesmo que uma estrutura,
mas com os membros protegidos por default.
 2002 LCG/UFRJ. All rights reserved.
5
Encapsulamento
• O princípio de atomicidade é conhecido por
encapsulamento.
▪ O usuário não tem acesso direto às partes de um
objeto ou a sua implementação.
▪ O acesso é feito indiretamente, através de funções
fornecidas com o objeto.
• É uma forma de imitar a vida real.
▪ Aparelhos eletrônicos possuem o seguinte aviso:
“Não abra – não há partes consertáveis por um
usuário”.
▪ Pessoas sem treinamento que tentam consertar
equipamentos com esses avisos acabam quebrando
mais do que consertando.
 2002 LCG/UFRJ. All rights reserved.
6
Reutilização de Código
• A idéia é usar os mesmos componentes
sempre que possível.
• Quando o mesmo objeto é a solução fica fácil.
▪ O difícil é quando se necessita de um objeto
ligeiramente diferente.
• C++ fornece diversos mecanismos para isso.
▪ Se a implementação for idêntica exceto pelo
tipo básico do objeto, pode-se usar uma
template.
▪ Herança é o mecanismo que permite estender
a funcionalidade de um objeto.
 2002 LCG/UFRJ. All rights reserved.
7
Classes
• Classes são usadas para encapsular
informação.
• Já que as funções que manipulam o estado do
objeto são membros da classe, elas são
acessadas pelo operador “.”, como em uma
estrutura de C.
• Quando se chama uma função de uma classe,
na realidade se está passando uma
mensagem para o objeto.
• A diferença básica entre OO de C++ e o velho
C é puramente filosófica: em C++ o objeto
tem o papel principal.
 2002 LCG/UFRJ. All rights reserved.
8
Para Não Esquecer Jamais
• Se você deseja usar OO, acostume-se a
esconder todos os dados das classes.
▪ C++ não é uma linguagem para preguiçosos.
• Para isto existe uma seção de dados
privados.
▪ O compilador se esforçará ao máximo para
mantê-los inacessíveis ao “mundo exterior”.
• Toda classe possui um construtor e um
destrutor, que dizem como uma instância do
objeto deve ser iniciada e destruída.
▪ Procure escrevê-los sempre. Evite defaults.
 2002 LCG/UFRJ. All rights reserved.
9
10
Construtor
class memoryCell {
public:
memoryCell ( ) { value = 0; }
int read ( ) const { return value; }
void write ( int x ) { value = x; }
Exemplo Bobo
private:
int value;
};
value está
protegido
 2002 LCG/UFRJ.
All rights reserved.
main ( ) {
memoryCell m;
m.write ( 5 );
cout << “Conteúdo da
#if 1
<< m.read ( )
#else
<< m.value
#endif
<< ‘\n’;
return 0;
Instancia a
classe
Armazena
célula é: “
um valor
Maneira
correta
Erro!!!
value é
privado!!!
11
Uso da Classe
}
Diretiva de préprocessamento
 2002 LCG/UFRJ.
All rights reserved.
Interface
• A interface descreve o que pode ser feito com o
objeto.
▪ Necessita de uma boa convenção de nomes.
▪ Exemplo: primeira letra de nome de classe ou função
minúscula, demais palavras do nome com primeira
letra maiúscula (sem “_”).
▪ memoryCell.
▪ Dados: ponteiros com prefixo ptr e pode usar “_”.
▪ Constantes e tipos enumeráveis em caixa alta:
◦ GL_FLOAT, GLUT_DOUBLE;
▪ Prefixo com o nome do pacote.
◦ glLoadIdentity(), glVertex3f.
 2002 LCG/UFRJ. All rights reserved.
12
Documentação
• É fundamental uma documentação mínima
em todo o arquivo de um sistema.
• Existem ferramentas muito boas de domínio
público, como o Doxygen.
▪ Basta colocar diretivas na forma de
comentários na própria interface.
▪ É melhor acrescentar as diretivas durante a
confecção da interface para não ter de voltar
depois de “má vontade”.
 2002 LCG/UFRJ. All rights reserved.
13
// memoryCell.h
14
// evita incluir a interface de novo se ela já foi
// incluída por outro arquivo.
#ifndef __MEMORY_CELL__
#define __MEMORY_CELL__
/**
* A very simple interface for a generic class.
* Doxygen will generate the documentation,
* in html, rtf and/or latex automatically.
*/
class memoryCell {
public:
/** empty construtor.
* Sets the content of this cell to value.
*
* @param value new value for this cell.
*/
memoryCell ( int value = 0 );
Interface
memoryCell
/// destrutor. Não faz nada.
~memoryCell ( ) { }
 2002 LCG/UFRJ.
All rights reserved.
15
/** the correct way of returning the content of this cell.
* @return the content of this cell.
*/
Não altera
int read ( ) const;
dados desta
classe
Altera
dados desta
cell. classe
/** sets a new value for this cell.
* @param x new value.
*/
void write ( int x );
private:
/// holds the content of this
int cell_value;
};
#endif
Interface
memoryCell
 2002 LCG/UFRJ.
All rights reserved.
Implementação
• A implementação contém os detalhes de
como a interface foi codificada, de forma a
atender as especificações do projeto.
• As declarações dos nomes das funções ficam
na declaração da classe e as implementações
são definidas depois, normalmente num
arquivo separado (.C, .cpp, ou .cc).
▪ Usam a sintaxe de função mais o nome da
classe e o operador de escopo “::”.
 2002 LCG/UFRJ. All rights reserved.
16
// memoryCell.C
#include memoryCell.h
17
Construtor
memoryCell::memoryCell ( int value ) {
this->cell_value = value;
Membro
}
int memoryCell::read ( ) const { desta classe
return this->cell_value; getter
}
void memoryCell::write ( int x ) {
this->cell_value = x;
setter
}
Implementação
memoryCell
 2002 LCG/UFRJ.
All rights reserved.
Construtores e Destrutores
• O construtor diz como o objeto é declarado e
iniciado.
▪ Se a iniciação não casar com nenhum
construtor, o compilador reclamará.
• O destrutor diz como o objeto será destruído
quando sair de escopo.
▪ No mínimo deve liberar a memória que foi
alocada por chamadas “new”no construtor.
▪ Se nenhum destrutor for declarado será
gerado um default, que aplicará o destrutor
correspondente a cada dado da classe.
 2002 LCG/UFRJ. All rights reserved.
18
Construtor de Cópia
• O construtor de cópia é chamado sempre que
o objeto for passado ou retornado por valor.
• Se nenhum for declarado será criado um
default, que aplicará o construtor de cópia
correspondente a cada dado da classe.
• Se a declaração for privada, o construtor de
cópia será desativado (não poderá ser
invocado).
• Ou escreva um construtor de cópia decente
ou então desative-o.
 2002 LCG/UFRJ. All rights reserved.
19
20
memoryCell ( const memoryCell& m ) {
*this = m;
Operador de
}
atribuição
Exemplo de
Construtor de
Cópia
Construtor
de Cópia
Uso:
memoryCell m1 ( 4 );
memoryCell m2 ( m1 );
 2002 LCG/UFRJ.
All rights reserved.
Operador de Atribuição
• É usado para copiar objetos do mesmo tipo.
▪ O operador de cópia cria um novo objeto, mas o
operador “=“ age sobre um objeto já existente.
• Operadores de atribuição retornam, em geral,
referências constantes. Retorno por valor não é uma
boa idéia ...
• Retornar uma referência não constante torna
(A = B) = C válido, mas sem sentido.
▪ O resultado é atribuir C à referência A, sem nunca ter
atribuído à B.
• Se não for definido haverá uma cópia membro a
membro dos dados da classe por default.
 2002 LCG/UFRJ. All rights reserved.
21
22
const memoryCell& memoryCell::operator = (
const memoryCell& m ) {
this->cell_value = m.cell_value;
return *this;
Referência
}
para este
objeto
Exemplo de
Operador =
Operador de
atribuição
Uso:
memoryCell m1 ( 4 );
memoryCell m2 = m1;
 2002 LCG/UFRJ.
All rights reserved.
Iniciação X Construtor
memoryCell ( int value = 0 ) : cell_value ( value ) { }
• A seqüência depois de “:” é a lista de iniciação.
• Os membros são iniciados na ordem em que são
declarados e não na ordem da lista.
• O ideal é iniciar cada membro da classe pelo seu
próprio construtor.
• É preferível iniciar os membros da classe usando
listas de iniciação ao invés de atribuir no construtor.
▪ Cada membro não especificado na lista é iniciado
pelo seu construtor vazio. Só depois é que as
atribuições são feitas (trabalho dobrado).
▪ Se a iniciação não for simples, só aí usa-se o corpo do
construtor.
 2002 LCG/UFRJ. All rights reserved.
23
Sobreposição de Operadores
• Em C++ todos os operadores podem ser
sobrepostos, a exceção de: “.”, “.*”, “?” e
“sizeof”.
▪ Precedência e aridade não são alteradas.
• Um operador binário retorna, em geral, um
objeto por valor, porque o resultado é
armazenado em um temporário.
▪ Também pode ser implementado invocando o
operador de atribuição.
 2002 LCG/UFRJ. All rights reserved.
24
25
const memoryCell& memoryCell::operator += (
const memoryCell& m ) {
this->cell_value += m.cell_value;
return *this;
Referência
}
para este
objeto
Exemplo de
Sobreposição
memoryCell memoryCell::operator + (
const memoryCell& m ) const {
memoryCell temp ( *this );
Adiciona o
temp += m;
segundo
return temp;
Retorna
operando
}
temp por
valor
 2002 LCG/UFRJ.
All rights reserved.
Templates
• Templates servem para escrever rotinas que
funcionam para tipos arbitrários.
• O mecanismo baseado em typedef permite criar
rotinas genéricas.
▪ Mas não é suficiente se queremos rotinas que
funcionem com dois tipos diferentes.
• Uma template não é uma função comum, mas sim
um padrão para criar funções.
• Quando uma template é instanciada com um tipo
particular, uma nova função é criada.
• A template é expandida (como uma macro) para
prover uma função real.
▪ O compilador gera código a partir da template para
cada combinação diferente de parâmetros.
 2002 LCG/UFRJ. All rights reserved.
26
Exemplo de Função Template
template <class Etype>
inline const Etype& Max ( const Etype& a,
const Etype& b ) {
return a > b ? a : b;
}
•
•
•
•
Etype define o tipo do parâmetro da template.
O código da template pode ser substituído como
uma macro, caso se use a opção inline.
Deve-se retornar uma referência, pois não se sabe
de antemão o tamanho do dado retornado.
O operador “>”deve estar definido no tipo Etype.
 2002 LCG/UFRJ. All rights reserved.
27
Classes Template
• A sintaxe é similar ao de uma função
template.
• Não há sentido em gerar uma biblioteca de
templates.
▪ Lembre-se que o compilador não sabe que
tipos serão necessários. Logo, não gera
código algum.
▪ Ou todo o código da template está junto com
a interface, ou a template deve ser instanciada
de antemão para cada tipo a ser usado na
aplicação.
 2002 LCG/UFRJ. All rights reserved.
28
// memoryCell.h
#ifndef __MEMORY_CELL__
#define __MEMORY_CELL__
/**
* A very simple interface for a template class.
*/
template <class Etype>
class memoryCell {
public:
/** empty construtor.
* Sets the content of this cell to value.
*/
memoryCell ( const Etype& value = Etype() ) {
cell_value = value;
}
Especificação
template antes
da classe
29
Template
memoryCell
/** the correct way of returning the content of this cell.
* @return the content of this cell.
*/
const Etype& read ( ) const { return cell_value; }
Referência
this cell.
constante
/** sets a new value for
* @param x new value.
*/
void write ( const Etype& x ) { cell_value = x; }
private:
/// holds the content of this cell.
Etype cell_value;
};
#endif
Pode assumir
qualquer tipo
 2002 LCG/UFRJ.
All rights reserved.
main ( ) {
memoryCell <int> m;
m.write
cout <<
#if 1
<<
#else
<<
#endif
<<
30
Instancia a
template
com int
( 5 );
“Conteúdo da célula é: “
m.read ( )
Uso da Template
memoryCell
m.cell_value
‘\n’;
return 0;
}
 2002 LCG/UFRJ.
All rights reserved.
Herança
• Talvez o principal objetivo de programação
orientada a objeto seja a reutilização de
código.
• Templates são apropriadas quando a
funcionalidade básica do código é
independente de tipo.
• Herança serve para estender a
funcionalidade de um objeto.
▪ Criam-se novos tipos com propriedades
restritas ou estendidas do tipo original.
 2002 LCG/UFRJ. All rights reserved.
31
Polimorfismo
• Polimorfismo permite que um objeto possa
armazenar vários tipos diferentes de objetos.
• Quando uma operação for aplicada a um
objeto polimorfo será selecionada
automaticamente aquela adequada ao tipo
armazenado.
• Sobreposição (overload) de funções e
operadores é um exemplo de polimorfismo.
▪ Neste caso, a seleção da função é feita em
tempo de compilação, o que limita o
comportamento polimorfo.
 2002 LCG/UFRJ. All rights reserved.
32
Reutilização
• Em C++ é possível postergar a seleção da função até
que o programa esteja sendo executado.
▪ O mecanismo básico usa herança.
• Freqüentemente, no projeto de uma nova classe,
descobre-se que há uma classe similar escrita
anteriormente.
▪ Reutilização de código sugere que não se comece do
zero, mas que se escreva uma nova classe baseada na
classe existente.
• Em C++ o mecanismo é criar uma classe abstrata
polimorfa que possa armazenar os objetos das
classes similares.
 2002 LCG/UFRJ. All rights reserved.
33
Exemplos
• Classe Vetor: Vetores limitados.
• Classe Data: Calendários diversos
(Gregoriano, Hebreu, Chinês).
• Impostos: vários tipos de contribuintes
(solteiro, casado, separado, cabeça de casal).
• Formas: (círculos, quadrados, triângulos).
 2002 LCG/UFRJ. All rights reserved.
34
Três Opções
• Definir classes completamente independentes.
▪ Implica em escrever vários pedaços de código idênticos.
▪ Aumenta a chance de erro e cada mudança deve ser replicada.
• Usar uma única classe e controlar as funções por if / else e switches.
▪ Manutenção é difícil, pois cada alteração requer recompilar todo
o código.
▪ Extensão das classes por um programador não é possível a
menos que todo o fonte esteja disponível.
▪ If / else gasta tempo de execução, mesmo que o resultado seja
conhecido em tempo de compilação.
▪ Não há segurança de tipos, pois se toda forma está contida em
uma única classe, pode-se atribuir um círculo a um triângulo.
 2002 LCG/UFRJ. All rights reserved.
35
Derivação
• Derivar classes a partir de uma classe base
(herança).
▪ Uma classe derivada herda todas as
propriedades da classe base.
▪ Cada classe derivada é uma nova classe. Logo,
a classe base não é afetada por mudanças nas
classes derivadas.
▪ Uma classe derivada é compatível por tipo
com a base, mas o contrário é falso.
▪ Classes irmãs não são compatíveis por tipo.
 2002 LCG/UFRJ. All rights reserved.
36
Análise
• As primeiras duas opções são típicas de programação
procedural.
• A terceira opção é aquela que deve ser adotada em
programação orientada a objeto.
class Derivada: public Base {
// membros não listados são herdados.
public:
// construtores e destrutores (em geral
// diferentes da base)
// membros da base sobrepostos
// novos membros públicos
private:
// dados adicionais (geralmente privados)
// funções privadas adicionais
// membros da base a serem desativados
};
 2002 LCG/UFRJ. All rights reserved.
37
Uso de Ponteiros com Herança
• Ponteiro para classe base
▪
▪
▪
▪
▪
classeBase* bPtr = NULL;
classeBase bVar;  Objeto do tipo base.
bPtr = &bVar;  Trivialmente válido.
bPtr = &dVar;  dVar é do tipo classeDerivada.
bPtr->print();
 Chama print da classe base.
• Ponteiro para classe derivada
▪
▪
▪
▪
▪
classeDerivada* dPtr = NULL;
classeDerivada dVar;  Objeto do tipo derivado.
dPtr = &dVar;
 Trivialmente válido.
dPtr = &bVar;
 ERRO!!
dPtr->print();
 Chama print da derivada.
 2002 LCG/UFRJ. All rights reserved.
38
Apontar Ponteiro Derivado para Classe
Base
• Gera erro.
▪ Classe derivada pode ter métodos e variáveis
que não existem na classe base.
▪ Objeto da classe base não é do mesmo tipo do
objeto da classe derivada.
 2002 LCG/UFRJ. All rights reserved.
39
Apontar ponteiro Base para Classe
Derivada
• Mecanismo válido.
▪ Mas só podem ser chamados métodos da
classe base.
▪ Chamada a métodos da classe derivada gera
erro.
• Tipo de um ponteiro ou referência define os
métodos que podem ser invocados.
 2002 LCG/UFRJ. All rights reserved.
40
Funções Virtuais
• Objetos (e não os ponteiros) definem os
métodos que devem ser invocados.
• Suponha círculo, triângulo e retângulo
derivados de uma classe forma.
▪ Cada um com seu próprio método de desenho
(draw).
• Para desenhar qualquer forma, chama-se
draw a partir de um ponteiro para forma.
▪ O programa determina, em tempo de
execução, qual draw chamar (formas são
tratadas de maneira genérica).
 2002 LCG/UFRJ. All rights reserved.
41
Declaração Virtual
• Draw deve ser declarada virtual na classe
base.
▪ Sobreponha-se draw em cada classe derivada.
▪ As assinaturas devem ser idênticas.
▪ Um vez declarada virtual, será virtual em
todas as classes derivadas.
▪ É boa prática manter a declaração virtual nas
classes derivadas, embora não seja
mandatário.
 2002 LCG/UFRJ. All rights reserved.
42
Tipos de Herança
• Pública: todos os membros públicos da
classe básica permanecem públicos.
▪ Membros privados permanecem privados.
▪ Relacionamento É-Um (IS-A).
• Privada: mesmo membros públicos são
escondidos.
▪ Relacionamento Tem-Um (HAS-A).
▪ É preferível usar composição do que herança
privada.
▪ É a herança default.
 2002 LCG/UFRJ. All rights reserved.
43
Herança de Construtores
• O estado de público ou privado dos
construtores, construtor de cópia, e operador
de atribuição são herdados.
▪ Se eles forem herdados, mas não definidos na
classe derivada, os operadores
correspondentes são aplicados a cada
membro.
 2002 LCG/UFRJ. All rights reserved.
44
Binding Estático e Dinâmico
• No binding estático a decisão de que função
invocar para resolver uma sobreposição é
tomada em tempo de compilação.
• No binding dinâmico a decisão é tomada em
tempo de execução.
• C++ usa binding estático por default, porque
se achava no passado que o overhead seria
significativo.
• Para forçar o binding dinâmico o
programador deve especificar a função como
virtual.
 2002 LCG/UFRJ. All rights reserved.
45
Porque binding dinâmico?
• Quando se declara um ponteiro para uma classe
como argumento de uma função, normalmente o
tipo do ponteiro é o da classe base.
▪ Assim, pode ser passada qualquer uma das classes
derivadas e o tipo do objeto apontado é determinado
em tempo de execução.
▪ Virtualidade é herdada.
▪ Funções redefinidas em classes derivadas devem ser
declaradas virtuais.
▪ Uma decisão em tempo de execução só é necessária
quando o objeto for acessado através de ponteiros.
 2002 LCG/UFRJ. All rights reserved.
46
Construtores e Destrutores Virtuais?
• Construtores nunca são virtuais.
• Destrutores devem ser virtuais na classe
base apenas.
• O tipo de um construtor sempre pode ser
determinado em tempo de compilação.
• O destrutor deve ser virtual para garantir
que o destrutor do objeto real é chamado.
▪ A classe derivada pode ter membros
adicionais que foram alocados
dinamicamente, que só podem ser
desalocados pelo destrutor da classe derivada.
 2002 LCG/UFRJ. All rights reserved.
47
Custo
• Polimorfismo tem um custo que impacta na
performance.
▪ STL não usa polimorfismo por questão de
eficiência.
• Cada classe com função virtual tem uma
tabela vtable.
▪ Para cada função virtual, vtable tem um
ponteiro para função apropriada.
▪ Se a classe derivada tiver a mesma função
virtual da classe base, o ponteiro da vtable
aponta para a função na base.
 2002 LCG/UFRJ. All rights reserved.
48
Casting (compatibilização)
• Downcasting
▪ Operador dynamic_cast
◦ Determina o tipo do objeto em tempo de
execução.
◦ Retorna 0 se não for o tipo certo (não pode
sofrer cast)
NewClass *ptr = dynamic_cast < NewClass *>
objectPtr;
▪ Pode compatibilizar endereço de objeto do
tipo base para ponteiro para classe derivada.
 2002 LCG/UFRJ. All rights reserved.
49
Informação de Tipo
• Keyword typeid
▪ Header <typeinfo>
▪ Uso: typeid(object)
◦ Retorna type_info do objeto
◦ Tem informação sobre o tipo do operando,
incluindo nome.
◦ typeid(object).name()
 2002 LCG/UFRJ. All rights reserved.
50
Tipos de Classe
• Classes abstratas
▪ Único propósito: ser uma classe base (chamada classe
base abstrata).
▪ Incompleta: possui métodos sem código.
◦ Classes derivadas preenchem “pedaços omitidos“.
▪ Objetos não podem ser instanciados a partir de
classes abstratas.
◦ No entanto, pode haver ponteiros e referências.
• Classes concretas
▪ Podem instanciar objetos.
▪ Implementam todas as funções que definem.
▪ Provêem detalhes.
 2002 LCG/UFRJ. All rights reserved.
51
Função Virtual Pura e Regular
• Classes abstratas não são necessárias, mas são úteis.
▪ Ponteiros para classes base abstratas são úteis no
polimorfismo.
• Para criar uma classe abstrata:
▪ Precisa-se de uma ou mais função virtual "pura"
◦ Declare-se função com inicializador 0
virtual void draw() const = 0;
▪ Função virtual regular
◦ Possui implementações, e sobreposição é opcional.
▪ Função virtual pura
◦ Nenhuma implementação, mas deve ser sobreposta.
▪ Classes abstratas podem ter dados e funções
concretas.
◦ Devem ter uma ou mais funções virtuais pura.
 2002 LCG/UFRJ. All rights reserved.
52
Herdando Interface e Implementação
getArea
getVolume
getName
print
Shape
0.0
0.0
= 0
= 0
Point
0.0
0.0
"Point"
[x,y]
Circle
pr2
0.0
"Circle"
center=[x,y];
radius=r
2pr2 +2prh
pr2h
"Cylinder"
center=[x,y];
radius=r;
height=h
Cylinder
 2002 LCG/UFRJ. All rights reserved.
53
Funções de Desenho
• Exemplo clássico em OO: método draw.
▪ Colocar funções de desenho nas classes,
requer acesso a um pacote gráfico nas classes
(OpenGL,PHIGS,GKS,Direct X...).
▪ Será que é a forma adequada do ponto de
vista de portabilidade?
• Alternativa melhor pode ser passar os objetos
para uma classe de desenho.
▪ Apenas a pessoa que escreve essa classe
precisa conhecer Computação Gráfica.
 2002 LCG/UFRJ. All rights reserved.
54
Download

Tópicos em C++ - LCG