Programação em C++
Declarações e definições
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações vs Definições
•
•
•
Declarar uma entidade, consiste em anunciar ao compilador que uma
entidade com esse nome foi ou vai ser definida.
Definir uma entidade, consiste em reservar espaço de memória para esta
residir, especificar o seu comportamento e eventualmente inicia-la com um
valor. A maior parte das declarações são também definições.
Num programa, deve haver uma única definição para cada nome, mas
poderão haver várias declarações de um mesmo nome, desde que todas
elas sejam concordantes com o tipo da entidade declarada.
•
Qualquer declaração que especifica um valor é uma definição.
•
A declaração de uma variável de um tipo básico, de um tipo derivado, ou
de um tipo definido pelo utilizador (Complex, Matrix, BigInt, etc.) só não
será simultaneamente uma definição, quando antecedida da palavra
chave extern, que informa o compilador que essa variável já se encontra
definida algures.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-2
Declarações
struct User;
Declara User como nome de um novo tipo de objectos definido
pelo programador.
Complex sqrt(Complex);
Declara sqrt() como o nome de uma função que devolve um
valor do tipo Complex e recebe um parâmetro também do tipo
Complex.
extern int errorNumber;
Declara errorNumber como o nome de um objecto tipo int,
definido algures.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-3
Definições
char c;
Declara c como nome que designa um char e simultaneamente define c, isto é, reserva espaço
em memória onde c passa a residir.
int n = 10
Além de declarar n, define a variável e inicia-a com o valor 10,
struct Complex { double real; double img; };
Além de declarar a estrutura Complex, define qual a sua composição
typedef Complex Point;
Além de declarar Point como o nome de um tipo, define que esse tipo é o Complex, pelo que os
dois nomes se tornam equivalentes.
char* name1 = "Toto";
Declara e define name1 como apontador para char, reserva espaço em memória com a
dimensão necessária para conter a string de iniciação, promove a própria iniciação desse string
e afecta o apontador nome1 com o valor do primeiro endereço do espaço reservado ao string.
char name2[] = "Toto";
Idêntica à instrução anterior, salvo que name2 fica a ser um apontador constante, isto é, name2
não pode ser posto a apontar para outro endereço que não seja aquele com que foi iniciado.
char name2[8] = "Toto";
Idêntica à instrução anterior, salvo que o espaço reservado para o string é neste caso 8
bytes,independente da dimensão do string de iniciação.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-4
Alcance de um nome (Scope)
Uma declaração introduz
um nome com um
alcance. Esse nome só
tem validade na parte
específica do programa
em que tenha sido
int x;
// x global
void f() {
//Esta declaração ocultou o nome global x.
int x=1;
// atribuição realizada para a variável global x.
::x=2;
}
declarada.
Isto é um
erro, porque
estão duas
variáveis
declaradas
com o mesmo
nome no
mesmo scope.
void f( int x ) {
//Esta declaração ocultou o nome global x
int x=1;
// Atribuição realizada para a variável global x
::x=2
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-5
Tempo de existência de um objecto (1)
•
Um objecto é uma entidade resultante de uma definição, ao qual está
associado um nome, um tipo e para o qual foi reservado espaço adequado
em memória.
•
A menos que o programador especifique em contrário, um objecto é criado e
iniciado quando da sua definição e é destruído quando o programa ultrapassa o seu
alcance.
•
Os objectos com nomes globais, são criados e iniciados uma vez, e vivem até
que o programa termine.
•
Objectos declarados antecedidos da palavra chave static, comportam-se da
mesma maneira que objectos globais, no que se refere a tempo de existência.
•
Uma variável estática que não
implicitamente iniciada a zeros.
•
Usando os operadores new e delete, podem criar-se objectos cujo tempo de
vida é directamente controlado pelo programa.
seja
explicitamente
iniciada,
fica
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-6
Tempo de existência de um objecto (2)
// x e p criados e iniciados no inicio do programa e destruídos no final
// do programa.
int x = 10; // Inteiro iniciado com 10.
int *p;
// Apontador iniciado com 0 (NULL).
int *f() {
// y criado e iniciado com a valor aleatório quando
// função f() é invocada e destruído quando ela termina.
long y;
// l criado no início do programa, iniciado com 10 na
// primeira chamada a f() e destruído no fim do programa.
static int l = 10;
p = new int(3); // Criar um inteiro com valor 3. Este não é destruído
// quando a função termina.
}
void main() {
int *pi = f();
delete pi;
}
// pi e afectado com o apontador retornado pela função
// Destruir o inteiro apontado por pi.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-7
Constantes
Em C++ existem 4 formas
de definir constantes:
Adicionando a keyword const à definição
Exº : const pi=3.14159265
•Por enumerados
Exº : enum Escapes {BELL=‘\a’, TAB=‘\t’;
enum Meses {Jan=1, Fev, Mar, Abr, Mai,
Jun, Jul, Ago, Set, Out, Nov, Dez };
•Nomes de arrays
Exº : char buf[10];
++buf; // erro: buf é um apontador constante
•Nomes de funções
Exº : int funcao(char c);
Apontadores
constantes
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-8
Constantes numéricas
O tipo das constantes numéricas pode ser
explicitado pelo modo como são escritas:
• Para os tipos inteiros, o sufixo U designa unsigned, o sufixo L designa long e o sufixo UL designa
unsigned long.
if ( value == 30500L )
• Para os tipos reais existem os sufixos F para float e L para long double
flag = ( result == 1.5L );
• A representação das constantes reais, envolvem um ponto decimal ou a letra e seguida de um
valor inteiro, significando a potência de base 10 pela qual se deve multiplicar o número situado à
sua esquerda. Esta última notação designa-se por notação científica.
// Representação em ponto decimal.
const double pi = 3.14159265;
// Representação científica.
const double pi = 314159265e-08;
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4-9
Caracteres especiais
Sequências de escape
Caracter
\a
som da campainha (bell )
\b
retrocesso (back space)
\f
inicio de página
\n
mudança de linha
\r
inicio de linha
\t
tabulação horizontal
\v
tabulação vertical
\\
back slash
\’
plica
\”
aspas
\000
número em octal
\xhh
número em hexadecimal
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 10
Enumerados
enum Meses {JAN=1, FEV, MAR, ABR, MAI, JUN, JUL, AGO, SET, OUT, NOV, DEZ };
Meses mes;
// ...
switch ( mes ) {
void f() {
Meses m=JAN;
int i=FEV; // Conversão implícita.
m= i ;
case JAN: // …
// Erro!..(tem que se explicitar a conversão).
m=Meses(i);
// A conversão explicita é necessária.
I = m;
// OK (conversão implícita).
m=4;
// Erro!(tem que se explicitar a conversão).
m = Meses(4);// A conversão explicita é necessária.
break;
case FEV: // ...
break;
}
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 11
Tipos fundamentais
Tipo de valores armazenados
Caracteres
Inteiros
Reais
Lógicos
char
unsigned char
signed char
int
unsigned
short
unsigned short
long
unsigned long
float
double
long double
bool
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 12
Conversão implícita de tipos
•
Quando numa expressão estão envolvidas variáveis de tipos fundamentais
diferentes, os tipos das variáveis são automaticamente convertidos para um
mesmo tipo, que passa a constituir o tipo do valor da expressão.
•
Em expressões aritméticas, sempre que a um operador estejam presentes
operandos de diferentes tipos, ocorre uma conversão implícita do operando do
cuja representação em memória é de menor dimensão, no de representação
com maior dimensão.
•
Em igualdade de espaço de representação prevalece o tipo com maior alcance
de representação. Assim, entre float e long prevalece float. E entre qualquer
dos tipos a variante unsigned prevalece sobre a variante signed.
int n; long int i; char c; double d; float f;
n+I // n é convertido em long int e a expressão toma o valor long int.
n+c // c é convertido em int e a expressão torna-se do tipo int.
c+d // c é convertido em double e a expressão torna-se do tipo double.
-1L < 1U // 1U é convertido long
-1L > 1UL // -1L é convertido num unsigned long
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 13
Tipos derivados
Operadores:
*
&
[]
()
Apontador -> operador unário prefixo
Referência -> operador unário prefixo
Array
-> operador unário posfixo
função
-> operador unário posfixo
Definições:
estruturas
classes
int* a;
//
float v[10];
//
char* p[10];
//
typedef char* p[10]; //
void f(int);
//
struct Str {
//
short length;
char* p;
};
Apontador para um inteiro.
Array de 10 elementos tipo float.
Array de 10 apontadores para char.
Tipo array de 10 apontadores para char.
Função com um argumento tipo int.
Estrutura com dois campos.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 14
Apontadores (1)
char** cpp;
//Apontador para apontador para carácter.
int (*vp)[10];
//Apontador para array de 10 inteiros.
int (*fp)(char, char*); /* fp apontador para função que retorna inteiro e que
recebe dois parâmetros, um do tipo char e outro do tipo
apontador para char
*/
Ende re ço
0x0000
char c, //c é um carácter.
*p; //p é um apontador para
carácter.
p = &c;
*p ='a';
// p agora aponta para
0x03C5 c
M e m ória
RAM
.
.
.
'a'
.
.
.
c.
0x15F2 p
// c = 'a'
0xFFFF
0x03C5
.
.
.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 15
Apontadores (2)
O que faz esta função ?
int strlen(const char* p) {
char* q=p;
}
while (*q++); // Enquanto o caracter apontado por q for
// diferente de '\0', incrementa o apontador.
return q-p-1; // Retorna número de caracteres do string.
void maior(int* pa, int *pb) {
int tmp;
}
if(*pa < *pb) {
tmp = *pa;
*pa = *pb;
*pb = tmp;
}
void main() {
int a = 5, b = 6;
}
Quais os valores de a e b
após a chamada à função
maior(…) ??
maior(&a, &b);
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 16
Desreferência
int *p[5]; // Array de 5 apontadores para inteiro
...
// afectação do array p com apontadores de inteiros.
int a=1;
// Inteiro iniciado com o valor 1.
*p[a+3]=7;// Afecta com 7 o inteiro apontado por p[a+3].
P+1
P+2
P+3
P+4
P
P[0]
ou
*p
*p[0] ou **p
P[1]
ou
*(p+1)
P[2]
ou
*(p+2)
P[3]
ou
*(p+3)
*p[2] ou **(p+2)
*p[1] ou **(p+1)
P[4]
ou
*(p+4)
*p[4] ou **(p+4)
*p[3] ou **(p+3)
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 17
Tipo void e void *
void
•
•
//Função que não tem valor de retorno.
Aplica-se como o nome de um tipo,
apesar de não existir nenhum
objecto do tipo void.
Serve para referir que uma função
não retorna nenhum valor.
void f();
//Apontador para um objecto de tipo desconhecido.
void* pv;
//Função sem argumento equivalente a int g().
int g(void);
void *
•
•
•
Serve para referir um tipo de apontador
que aponta para objectos de tipo
desconhecido.
Qualquer tipo de apontador pode afectar
uma variável do tipo void*. O inverso não
é permitido sem fazer cast explicito.
Um apontador tipo void* é útil para
poder guardar um endereço genérico,
podendo ser explicitamente convertido
num apontador para um dado tipo de
objecto.
void* malloc(unsigned size);
void free(void *);
void f() {
//Conversão explicita.
int* pi=(int*)malloc(10*sizeof(int));
//Conversão explicita.
char* pc=(char *)malloc(10);
// ...
free(pi); // Conversão implícita.
free(pc); // Conversão implícita.
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 18
Referências (1)
• O C++ permite passagem de parâmetros a funções por referência (&),
mais fáceis de usar do que os apontadores.
• Tal como um apontador, uma referência corresponde a um local onde
se situa outra variável.
• Mas tal como uma variável normal, não requer operador especial de
desreferênciação.
Exº:
int x;
int& y=x;
• Ambas as variáveis referem o conteúdo da mesma localização de
memória, e são de facto a mesma variável.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 19
Referências (2)
•
Uma referência é um nome alternativo (alias) para um objecto.
•
Na definição de referência, esta tem que ser sempre iniciada.
•
O valor de uma referência não pode mudar após iniciação e refere
sempre o objecto para o qual foi iniciado, ou seja uma referência não é
um objecto.
int i=1;
int& r=i; //r e i referem-se ao mesmo
int
int x=r; //x=1
r=2;
//i=2
equivalente
int i=1;
int* r = &i;
int x=*r; //x=1
*r=2; //i=2
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 20
Referências (3)
• Quando um objecto referenciado não é um lvalue
( não é um objecto do qual se pode tomar o
endereço).
– É aplicada uma conversão de tipo se necessário.
– O valor resultante é colocado numa variável temporária.
– O endereço dessa variável é usado como valor do iniciador.
A declaração :
double& dr=1;
Envolve as seguinte acções:
const double* drp;
double temp;
temp=double(1);
drp = &temp;
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 21
Referências vs apontadores
Acções inválidas para referências e válidas para apontadores.
–
–
–
–
–
–
Apontar para uma referência.
Tomar o endereço de uma referência.
Comparar referências.
Afectar referências.
Realizar operações aritméticas sobre referências.
Modificar referências.
Se qualquer destas operações for efectuada sobre
referências, o que acontece na realidade é que iremos
actuar sobre o objecto com a qual a referência está
relacionada, ou seja, o objecto referenciado.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 22
Passagem de parâmetros a funções
•
Os identificadores dos argumentos que constam na declaração da função, dizem-se
parâmetros formais.
•
Quando se evoca a função, os parâmetros que se lhe associam dizem-se
parâmetros actuais, e irão iniciar os parâmetros formais (ou argumentos).
•
O corpo de uma função nunca afecta os parâmetros actuais com que seja evocada, a
menos que o parâmetro formal seja do tipo referência para o parâmetro actual.
Qual a diferença
entre os seguintes
troços de código
???
void f(int val, int &ref) {
val++;
ref++;
}
void f(int val, int *pt) {
++val;
++(*pt);
}
void main() {
int n1 = 5, n2 = 5;
f(n1, n2);
cout << "n1 = " << n1 << " n2 = "
<< n2 << endl;
}
void main() {
int n1 = 5, n2 = 5;
f(n1, &n2);
cout << "n1 = "<< n1 << " n2 = "
<< n2 << endl;
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 23
Definição de estruturas
Definição de
uma
estrutura :
Acesso aos campos
da estrutura.
struct Data {
int ano;
int mes;
int dia;
};
Data d;
…
d.ano = 1997;
d.mes = 1;
d.dia=23;
Acesso aos campos da estrutura através de um pointer
Data *pd = &d;
…
pd->ano = 1997;
pd->mes = 1;
pd->dia=23;
Equivalente
Data *pd = &d;
…
(*pd).ano = 1997;
(*pd).mes = 1;
(*pd).dia=23;
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 24
Referência como argumento de funções (1)
struct Grande { // estrutura de grande dimensão.
int item;
char text[1000]; // espaço com fartura ...
};
Grande bo={123,"isto é uma estrutura de grandes dimensoes..."};
// Três funções que têm a estrutura Grande como argumento.
void funcval(Grande v1);
// Chamada por valor.
void funcptr(const Grande *p1) ;// Chamada por apontador.
void funcref( const Grande &r1);// Chamada por referência.
void main() {
funcval(bo); // Passagem do próprio objecto.
funcptr(&bo); // Passagem do endereço do objecto.
funcref(bo); // Passagem por referência
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 25
Referência como argumento de funções (2)
void funcval(Grande v1) {
//Notação de valor.
cout << '\n' << v1. item;
cout << '\n' << v1.text;
}
void funcptr ( const Grande *p1) {
//Notação de apontador.
cout << '\n' << p1->item;
cout << '\n' << p1->text;
}
void funcref ( const Grande &r1) {
//Notação de referência.
cout << '\n' << r1.item;
cout << '\n' << r1.text;
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 26
Prefixo const
• Prefixando a declaração da referência com const torna constante o objecto
referenciado, o que significa que o valor do objecto referenciado por esse
argumento não pode ser alterado pela função.
• Prefixando a declaração de um apontador com const torna constante o objecto
apontado, o que significa que o valor do objecto apontado por esse argumento não
pode ser alterado pela função.
int x=1, y;
int next(const int& aa) {
return aa++; // ERROR
}
y = next(x);
int x=1, y;
int next(const int& aa) {
return aa+1; // OK
}
y = next(x); // y = 2 e x=1
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 27
Referência como retorno de funções
O retorno de funções por referência, pode ser usado para definir funções que se
possam situar do lado esquerdo de uma expressão (lvalue).
Verifique a semelhança
entre os seguintes troços
de programa.
int v[20];
int& g(int i) {return v[i];}
g(3)=7;
Equivalente
int v[20];
int* g(int i) {return &v[i];}
*g(3)=7;
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 28
Sobrecarga (overloading) de funções
• Em C++, várias funções podem partilhar o mesmo nome (function
overloading), desde que cada uma delas se possa distinguir das outras
pelo número e tipo dos argumentos com que foi definida.
• A sobrecarga do nome de uma função permite em C++ criar uma
família de funções diferentes, partilhando o mesmo nome mas com
código distinto, exigindo para isso que tenham pelo menos um
argumento de tipo diferente.
Por exemplo:
int f(int);
double f(double);
// ...
f(3); // invoca f(int);
f(3.14)//invoca f(double);
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
Declarações e definições
4 - 29
Funções inline
• Quando uma função é definido como inline, o compilador insere o
próprio código da função em todos os pontos em que essa função seja
invocada pelo programa (se de um ficheiro fonte constam três
chamadas a uma função definida como inline, o
código
dessa
função será três vezes repetido no ficheiro objecto).
• A vantagem das funções inline reside no facto de terem maior
rapidez de execução, dado que ficam libertas dos mecanismos
de chamada, de passagem de parâmetros e retorno de valores. Esses
mecanismos, no caso da função envolver poucas instruções, constituem
um acréscimo significativo em tempo de execução.
• O uso criterioso de funções inline implica que sejam
simples
e curtas, para que se justifique a sua adopção.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
4 - 30
Funções inline
Usar o prefixo inline na definição
das funções.
inline int greate(int a, int b) {
return a > b;
}
inline void swap(int &a, int &b) {
int aux = a; a =b; b = aux;
}
void main() {
int x, y;
cout << “Escreva dois inteiros ->”;
cin >> x >> y;
cout << “O maior e´o “
<< (greate( y, x ) ? “segundo” : “primeiro”);
if ( greate( y , x ) swap( x, y );
cout << x << “ e’ maior que << y << endl;
}
É equivalente a inserir o código da
função em todos os pontos em que é
evocada .
void main() {
int x, y;
cout << “Escreva dois inteiros ->”;
cin >> x >> y;
cout << “O maior e´o “
<< y > x ? “segundo” : “primeiro”);
if ( y > x ) {
int aux = x; x = y; y = aux;
}
cout << x << “ e’ maior que << y << endl;
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
4 - 31
Algoritmos recursivos
• Uma função diz-se recursiva, quando se invoca a si própria
na consecução dos objectivos a que se propõe.
• Algoritmos recursivos advêm normalmente de definições
por relações de recorrência.
• Um algoritmo na forma recursiva, traduz-se numa função
contendo uma referência explícita a si própria
(recursividade directa) ou uma referência a outra função,
que no seu corpo a venha a referenciar directa ou
indirectamente (recursividade indirecta).
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
4 - 32
Algoritmos recursivos (factorial)
Um exemplo paradigmático de
um algoritmo recursivo é a
função para calcular o factorial.
Definição:
Função factorial em C++ que
respeita a definição:
int factorial( int n ) {
if (n == 0)
return 1;
else
return factorial(n-1)* n;
}
•O factorial de zero é um.
( 0! = 1 )
Factorial de n é o factorial de (n-1)*n.
( n! = (n -1)! * n )
Esquema de execução da
função factorial, quando
for pedido o factorial(4).
fact(4)
24 = 6 * 4
6=2* 3
fact(3)
2=1* 2
fact(2)
1=1* 1
Chamada
fact(1)
fact(0)
1
Retorno
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
4 - 33
Algoritmo de ordenação-quicksort
• É um dos algoritmos de ordenação mais eficientes.
• O quicksort pode ser descrito da seguinte forma:
1. O array está ordenado se tiver menos que 2 elementos
(condição terminal);
2. Escolher um dos elementos do array para pivot;
3. Decompor o array em duas partes disjuntas. Na parte
esquerda colocar elementos menores ou iguais ao pivot,
e na parte direita os maiores ou iguais ao pivot;
4. Ordenar a parte esquerda do array.
5. Ordenar a parte direita do array.
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
4 - 34
Algoritmo de ordenação-quicksort
inline void swap( int &a, int &b ){
int aux= a; a= b; b= aux;
}
void quickSort( int a[], int min, int max ) {
if ( min >= max ) return; // O array está ordenado.
int i = min, j = max;
int pivot = a[ (i+j)/2 ]; // valor do elemento central.
do { // Dividir em duas partes.
while( a[i] < pivot ) ++i;
while( a[j] > pivot ) --j;
if ( i<=j ){
if ( a[i] != a[j] )
swap( a[i], a[j] );
++i; --j;
}
} while( i <= j );
quickSort( a, min, j ); // Ordenar parte esquerda.
quickSort( a, i, max ); // Ordenar parte direita.
}
Programação em C++
Pimenta Rodrigues
Pedro Pereira
Manuela Sousa
4 - 35
Download

ALCANCE DE UM NOME (SCOPE)