Programação de Computadores II
José Romildo Malaquias
14 de abril de 2011
Sumário
1 Mecanismos de passagem de parâmetro
1-1
1.1 Referências independentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1-1
1.2 Passagem de parâmetros por valor . . . . . . . . . . . . . . . . . . . . . . . . . . 1-1
1.3 Passagem de parâmetros por referência . . . . . . . . . . . . . . . . . . . . . . . 1-2
2 Classes e Objetos
2.1 Registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Classes e Objetos . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Construtores . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4 Especificadores de acesso . . . . . . . . . . . . . . . . . . . .
2.5 Vetores: uma versão melhorada . . . . . . . . . . . . . . . .
2.6 Exemplo usando classes: simulação de um caixa automático
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2-1
2-1
2-2
2-3
2-4
2-5
2-6
3 Sobrecarga
3-1
3.1 Sobrecarga de funções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-1
3.2 Argumentos padrão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3-4
2
1
Mecanismos de passagem de parâmetro
1.1
Referências independentes
Uma referência é o nome de uma posição de armazenamento.
Exemplo 1.1. Considere o seguinte segmento de código em C++.
int roberto = 14;
int& beto = roberto;
• beto é uma referência à posição de armazenamento da variável roberto
• beto é um nome alternativo, um apelido, um alias da variável roberto
• qualquer alteração em beto também será feita em roberto, e vice-versa
1.2
Passagem de parâmetros por valor
• Uma cópia do argumento é feita e passada (na pilha de chamadas de função) para a
função chamada.
• Alterações na cópia não afetam o valor da variável original no chamador.
• Não há restrição na forma da expressão usada como argumento na chamada.
• Poder ser ineficiente para argumentos grandes.
Exemplo 1.2. No programa a seguir, é definina uma função para o calcula do fatorial de um
número inteiro. Esta função usa o mecanismo de passagem de parâmetros por valor.
# include <iostream>
using namespace std;
int fatorial(int n)
{
int fat = 1;
while (n > 0)
{
fat *= n;
n --;
}
return fat;
}
1-1
1-2
CAPÍTULO 1. MECANISMOS DE PASSAGEM DE PARÂMETRO
int main()
{
int num
cout <<
cout <<
cout <<
}
1.3
= 6;
"número: " << num << endl;
"fatorial: " << fatorial(num) << endl;
"número: " << num << endl;
Passagem de parâmetros por referência
• O argumento na chamada da função deve ser uma variável.
• O parâmetro formal é uma referência para o argumento.
• Não há cópia do argumento, e por isto é mais eficiente.
• Alterações na referência alteram a variável original.
• Para impedir alterações no parâmetro (e conseqüentemente na variável original) pode-se
utilizar o qualificador const.
Exemplo 1.3. O programa a seguir é similar ao anterior, porém a função que calcula o fatorial
utiliza o mecanismo de passagem por referência.
# include <iostream>
using namespace std;
int fatorial(int& n)
{
int fat = 1;
while (n > 0)
{
fat *= n;
n --;
}
return fat;
}
int main()
{
int num
cout <<
cout <<
cout <<
}
= 6;
"número: " << num << endl;
"fatorial: " << fatorial(num) << endl;
"número: " << num << endl;
2
Classes e Objetos
2.1
Registros
Registro é uma estrutura de dados formada por componentes, chamados campos do registro, que podem ser de diferentes tipos. Cada componente é identificador por um nome, que
é especificado na declaração do tipo do registro.
Em C++ os tipos registros são também chamados de estruturas.
O exemplo a seguir apresenta um tipo para representar uma conta bancária. Uma conta
bancária contém as seguintes informações: o número da conta, o nome do titular da conta, e o
saldo da conta.
struct conta
{
int numero;
string titular;
double saldo;
};
O código a seguir cria um valor do tipo conta:
int main()
{
conta a = { 2134, "Maria Antonieta", 56.32 };
cout << "conta : " << a.numero
<< "titular: " << a.titular
<< "saldo : " << a.saldo;
<< endl;
a.saldo -= 30.02;
cout << "novo saldo: " << a.saldo
}
Vamos definir uma operação para exibir dos dados de uma conta:
void mostra_conta(conta& c)
{
cout << "conta : " << c.numero
<< "titular: " << c.titular
<< "saldo : " << c.saldo;
<< endl;
}
Outras operações comuns com conta bancária são saque e deposito:
void deposito(conta& c, double valor)
{
if (valor > 0)
2-1
2-2
CAPÍTULO 2. CLASSES E OBJETOS
c.saldo += valor
}
Exercício 2.1. Defina uma função para implementar a operação de saque em uma conta
bancária. A função deve verificar se o valor a ser sacado é válido (ou seja, o valor a ser secado
não pode ser negativo), e como a conta não é uma conta especial, a função também deve
verificar se há saldo suficiente para o saque. Teste a função.
A esta maneira de programar em que definimos um tipo de dados e um conjunto de operações
sobre este tipo de dados de forma independente, chamamos de programação procedimental1 .
Observe que qualquer função pode acessar os componentes de uma conta e alterá-los. Isto pode
deixar a conta em um estado inconsistente, com saldo negativo2 .
2.2
Classes e Objetos
É possível combinarmos a representação de um tipo de dados com as operações sobre este
tipo em uma única definição, chamada definição de classe:
class conta
{
public:
int numero;
string titular;
double saldo;
void mostra()
{
cout << "conta : " << numero
<< "titular: " << titular
<< "saldo : " << saldo;
<< endl;
}
}
int main()
{
conta a = { 987, "Pedro Manuel", 166.0 };
a.mostra();
}
O tipo assim definido é chamado de classe e representa um conjunto de valores semelhantes
(com a mesma forma de representação e com o mesmo conjunto de operações).
Um valor de um tipo classe é chamado de objeto ou instância. Assim a variável a no
código anterior contém um objeto que é uma instância da classe conta.
A realização de uma operação com um objeto é feita através de uma mensagem que é
enviada ao objeto. No exemplo anterior é operação para exibir uma conta é realizada através
do código a.mostra() . Dizemos que estamos enviando a mensagem mostra() para o objeto
armazenado na variável a.
Um campo de uma classe é mais comumente conhecido como atributo, membro de dados,
ou variável de instância. Uma função definida em uma classe para implementar uma operação
sobre as instância da classe é chamada de método ou função-membro. Um membro de uma
classe é um membro de dados da classe, ou uma função-membro da classe.
1
2
Às vezes as funções também são chamadas de procedimentos.
Lembre-se de que não estamos lidando com contas especiais, cujo saldo pode ser negativo
2.3. CONSTRUTORES
2-3
Na definição de um método o uso de um nome de atributo refere-se ao atributo do objeto
que recebe a mensagem quando uma mensagem é enviada a um objeto.
O especificador de acesso public indica que os atributos e métodos da classe podem ser
acessados em qualquer parte da aplicação.
Exercício 2.2. Acrescente à classe que representa contas bancárias as operações de saque e
depósito, e teste estas operações na função main.
2.3
Construtores
Normalmente quando um objeto é alocado na memória, ele precisa ser inicializado corretamente, de forma a não ficar em um estado inconsistente. É possível definir métodos especiais
em classe a fim de realizar a inicialização de objetos assim que eles são alocados na memória.
A estes métodos chamamos de métodos construtores.
Em C++ um método construtor sempre tem o mesmo nome da classe e não é permitido
especificar um tipo de retorno.
Para o exemplo da conta bancária teremos:
class conta
{
public:
int numero;
string titular;
double saldo;
conta(int num, string tit)
: numero(num),
titular(tit),
saldo(0.0)
{
}
// os outros metodos foram omitidos
}
Observe que o método construtor tem o mesmo nome da classe: conta. Observe também
que existe uma sintaxe especial para a inicialização dos atributos individuais do objeto: Logo
depois da lista de parâmetros formais do construtor colocamos a lista dos atributos a serem
inicializados, e em seguida o corpo do método (que neste caso é vazio). Observe também que
usamos o símbolo de dois pontos (:) antes da lista de inicialização dos atributos, e que os
atributos são separados por uma vírgula. Também observe que o valor de inicialização de um
atributo é colocado entre parênteses.
Neste método construtor o número da conta e o nome do titular são passados como parâmetros, e o saldo sempre é inicializado com zero.
Um objeto pode ser inicializado de diferentes formas utilizando o método construtor como
vemos no segmento de código que se segue:
int main()
{
conta a = conta(9745, "Marcos Paulo");
a.mostra();
conta b(1001, "Serafim dos Santos");
b.mostra;
}
Exercício 2.3. Faça um programa para testar os exemplos propostos.
2-4
2.4
CAPÍTULO 2. CLASSES E OBJETOS
Especificadores de acesso
Considere a classe para representar contas bancárias discutida anteriormente. Analise o
seguinte segmento de código:
int main()
{
conta a(123456, "Eduardo do Nascimento");
a.mostra();
a.deposito(435.50);
a.saldo -= 500.00;
a.mostra();
}
Observe que ao realizarmos a operação de subtração combinada com atribuição a.saldo -= 500.00 ,
deixamos o objeto em um estado inconsistente, pois realizamos uma operação equivalente a um
saque sem saldo suficiente.
Este exemplo ilustra que geralmente é desejável não permitir que outras partes do programa
tenham acesso aos atributos de um objeto, pois isto pode levar a alterações nos atributos que
são realizadas de forma incorreta. Neste exemplo é necessário verificar se há saldo suficiente
antes de se realizar um saque.
Para evitar este tipo de problema é possível especificar como os membros de uma classe
podem ser acessados, através dos especificadores de acesso.
Até agora no nosso exemplo foi utilizado o especificador de acesso public, que não impõe
nenhum limitação de acesso aos membros declarados na classe. Isto tornou possível a alteração
do saldo diretamente na função main do exemplo anterior.
Para restringirmos o acesso a um atributo apenas ao código contido na própria classe,
usamos o especificador de acesso private. Come este especificador apenas os métodos da
própria classe poderão manipular o atributo. Outras partes do programa só poderão manipular
uma instância da classe através do envio de mensagens.
Exercício 2.4. Modifique o exemplo da conta bancária de forma que os três atributos da classe
sejam privados, e todos os métodos sejam públicos. Verifique que o compilador apontará um
erro ao tentarmos modificar o saldo da conta diretamente, como fizemos na função main no
exemplo anterior.
Exercício 2.5. Desenvolva uma aplicação para calcular o conceito de um aluno em uma disciplina.
1. Defina uma classe para representar alunos de uma turma, contendo o nome do aluno, o
número de matrícula, e as três notas do aluno obtidas no semestre.
2. Defina um método para calcular a média aritméticas das três notas do aluno.
3. Defina um método para determinar o conceito obtido pelo aluno na disciplina, segundo a
tabela que se segue.
média das notas
igual ou superior a 9.0
igual ou superior a 8.0 e menor que 9.0
igual ou superior a 6.0 e menor que 8.0
inferior a 6.0
conceito
A
B
C
D
4. Defina um método que solicita ao usuário para digitar os dados de um aluno, e leia estes
dados e atualize o objeto aluno com os dados digitados pelo usuário.
5. Defina um método que exiba para o usuário os dados do aluno.
2.5. VETORES: UMA VERSÃO MELHORADA
2-5
6. Defina uma função main onde o usuário irá informar o número de alunos da turma, e
em seguida os dados destes alunos, que deverão ser armazenados em um vetor. Após a
entrada dos dados, exiba os dados dos alunos, incluindo o conceito obtido.
2.5
Vetores: uma versão melhorada
Vetor é uma estrutura de dados que permite armazenar uma sequência de elementos do
mesmo tipo, e acessá-los através de sua posição na sequência.
Já conhecemos a estrutura básica de vetor, ilustrada no exemplo que se segue.
void main()
{
int vet[4] = { 10, 20, 30, 40 };
// exibe o terceiro elemento da sequencia: 30
cout << vet[2] << endl;
}
Estes vetores tem tamanho fixo, definido quando o mesmo é alocado na memória.
Em C++ existe uma outra forma de trabalhar com vetores, utilizando a biblioteca vector.
Os vetores definidos através desta biblioteca são mais flexíveis, uma vez que o seu tamanho pode
variar durante a execução do programa. Além disto, quando o vetor é criado, não é necessário
informar o seu tamanho.
O tipo destes vetores é especificado usando a classe vector seguida do tipo dos elementos do
vetor colocado entre colchetes angulares (< e >). Assim o tipo dos vetores de inteiros é escrito
como vector<int>, e o tipo dos vetores de contas bancárias é escrito como vector<conta>.
Pode-se inserir elementos no final vetor usando o método push_back, cujo argumento é o
elemento a ser inserido no vetor.
Para acessar um elemento do vetor pode-se usar a mesma operação de indexação dos vetores
normais, colocando o índice entre colchetes logo depois do nome do vetor.
Para obter o número de elementos armazenados no vetor (ou seja, o tamanho do vetor),
utiliza-se o método size, sem nenhum argumento.
O exemplo a seguir ilustra o uso destes vetores aprimorados.
# include <iostream>
# include <vector>
using namespace std;
int main()
{
// cria um vetor
vector<int> vet;
// acrescenta elementos no final do vetor
vet.push_back(10);
vet.push_back(20);
vet.push_back(30);
// exibe os elementos e seus quadrados
for (int i = 0; i < vet.size(); i++)
cout << vet[i] << ’\t’ << vet[i]*vet[i] << endl;
}
2-6
2.6
CAPÍTULO 2. CLASSES E OBJETOS
Exemplo usando classes: simulação de um caixa automático
Para ilustrar o uso de classes no desenvolvimento de aplicações vamos trabalhar com a simulação de um caixa automático, onde o usuário poderia realizar operações em contas bancárias
como abertura de conta, saque e transferência.
Os objetos relevantes nesta aplicação são:
1. cliente: Representa um cliente do banco. Os atributos relevantes são:
• nome do cliente, e
• número do cpf
Serão necessárias as operações:
• consultar o nome
• consultar o cpf
2. conta: Representa uma conta bancária. Os atributos relevantes são:
• cpf do titular
• número da conta
• senha de acesso
• saldo
As operações relevantes são:
• consultar o número da conta
• consultar o cpf do titular
• consultar o saldo
• efetuar saque
• efetuar depósito
3. banco: Representa um banco. Os atributos são:
• cadastro de clientes
• conjunto de contas abertas no banco
As operações são:
• cadastro de um novo cliente
• abertura de uma nova conta
• consulta de saldo em uma determinada conta do banco
• realização de saque em uma determinada conta do banco
• realização de depósito em uma determinada conta do banco
• realização de transferência entre duas contas do banco
4. caixa automático: Representa um caixa automático que atende um determinado banco.
O único atributo relevante é:
• o banco atendido pelo caixa automático
2.6. EXEMPLO USANDO CLASSES: SIMULAÇÃO DE UM CAIXA AUTOMÁTICO
2-7
A operação oferecida pelo caixa automático é:
• exibição de um menu de opções, onde o usuário poderá escolher uma operação a ser
realizada no banco.
O programa abaixo, também disponível em caixa-automatico.cpp, apresenta uma implementação parcial desta aplicação. Sua tarefa é completar a aplicação.
/*
Programação de Computadores II
Classes e Objetos
Exemplo: caixa automático
*/
# include <iostream>
# include <vector>
using namespace std;
class cliente
{
private:
string nome;
int cpf;
public:
cliente(string n, int doc)
: nome(n), cpf(doc)
{ }
string get_nome()
{
return nome;
}
int get_cpf()
{
return cpf;
}
};
class conta
{
private:
int numero;
int cpf;
int senha;
double saldo;
public:
conta(int num, int doc, int password)
: numero(num),
cpf(doc),
senha(password),
2-8
CAPÍTULO 2. CLASSES E OBJETOS
saldo(0.0)
{ }
int get_numero()
{
return numero;
}
int get_senha()
{
return senha;
}
double get_saldo()
{
return saldo;
}
void mostra()
{
cout << "conta: " << numero << endl
<< "cpf do titular: " << cpf << endl
<< "saldo: " << saldo << endl;
}
void depositar(double valor)
{
if (valor >= 0)
saldo = saldo + valor;
else
cout << "valor do depósito não pode ser negativo" << endl;
}
void sacar(double valor)
{
if (valor >= 0)
if (saldo >= valor)
saldo = saldo - valor;
else
cout << "saldo insuficiente" << endl;
else
cout << "valor do saque não pode ser negativo" << endl;
}
};
class banco
{
private:
vector<cliente> cadastro_clientes;
vector<conta> cadastro_contas;
public:
2.6. EXEMPLO USANDO CLASSES: SIMULAÇÃO DE UM CAIXA AUTOMÁTICO
void cadastrar_cliente()
{
cout << endl
<< "Cadastro de cliente" << endl
<< "–––––––––––––––––––" << endl;
cout << "Nome do cliente: ";
string nome_cliente;
cin >> nome_cliente;
cout << "Número do CPF : ";
int cpf_cliente;
cin >> cpf_cliente;
cadastro_clientes.push_back(cliente(nome_cliente, cpf_cliente));
}
void abrir_conta()
{
cout << endl
<< "Abertura de conta corrente" << endl
<< "–––––––––––––––––––" << endl;
int numero = cadastro_contas.size() + 1;
cout << "Número da conta: " << numero << endl;
cout << "CPF do cliente : ";
int cpf;
cin >> cpf;
cout << "Senha
: ";
int senha;
cin >> senha;
cadastro_contas.push_back(conta(numero, cpf, senha));
}
void consultar_saldo()
{
cout << endl
<< "Consulta de saldo" << endl
<< "–––––––––––––––––––" << endl;
int num;
cout << "Número da conta: ";
cin >> num;
// verificar se a conta existe
for (int i = 0; i < cadastro_contas.size(); i++)
if (cadastro_contas[i].get_numero() == num)
{
int senha;
cout << "Senha
: ";
cin >> senha;
cout << endl;
if (cadastro_contas[i].get_senha() == senha)
2-9
2-10
CAPÍTULO 2. CLASSES E OBJETOS
cout << "Saldo
: "
<< cadastro_contas[i].get_saldo() << endl;
else
cout << "senha inválida!";
return;
}
cout << "conta inválida" << endl;
}
};
class caixa_automatico
{
private:
banco b;
public:
caixa_automatico(banco meu_banco)
: b(meu_banco)
{ }
void menu()
{
int opcao;
do
{
cout << endl
<< "CAIXA AUTOMÁTICO" << endl
<< "––––––––" << endl
<< "1. Cadastrar novo cliente" << endl
<< "2. Abrir nova conta" << endl
<< "3. Consultar saldo" << endl
<< "0. Sair" << endl
<< endl
<< "Escolha uma opção: ";
cin >> opcao;
cout << endl;
switch (opcao)
{
case 1:
b.cadastrar_cliente();
break;
case 2:
b.abrir_conta();
break;
case 3:
b.consultar_saldo();
break;
case 0:
break;
default:
cout << "Opção inválida!" << endl;
}
2.6. EXEMPLO USANDO CLASSES: SIMULAÇÃO DE UM CAIXA AUTOMÁTICO 2-11
} while (opcao != 0);
}
};
int main()
{
banco ban;
caixa_automatico caixa = caixa_automatico(ban);
caixa.menu();
}
3
Sobrecarga
3.1
Sobrecarga de funções
A linguagem C++ permite que várias funções diferentes sejam definidas usando o mesmo
nome, contando que essas funções tenham conjuntos diferentes de parâmetros no que diz respeito
• aos tipos dos parâmetros,
• ao número de parâmetros, ou
• à ordem dos tipos dos parâmetros.
Esta capacidade é chamada de sobrecarga de funções.
Quando uma função sobrecarregada é chamada, o compilador seleciona a função adequada
examinando o número, os tipos e a ordem dos argumentos na chamada.
O tipo do resultado de uma função não é utilizado para selecionar a função sobrecarregada
que deve ser executada em uma chamada de função. Assim não se pode sobrecarregar duas ou
mais definições que diferem apenas no tipo do valor de retorno.
A sobrecarga de funções permite que o programador reutilize nomes com forte apelo intuitivo
em uma grande variedade de situações. Ela é comumente utilizada para definir várias funções
utilizando o mesmo nome que realizam tarefas semelhantes, mas em tipos de dados diferentes.
Exemplo 3.1. Funções sobrecarregadas para calcular a média de dois números e de três números. Observe que as duas funções foram definidas com o mesmo nome: media .
# include <iostream>
using namespace std;
// calcula a média de dois números reais
double media(double n1, double n2)
{
cout << "média: double,double" << endl;
return (n1 + n2)/2.0;
}
double media(float n1, float n2)
{
cout << "média: float,float" << endl;
return (n1 + n2)/2.0;
}
3-1
3-2
CAPÍTULO 3. SOBRECARGA
double media(int a, double b)
{
cout << "média: int,double" << endl;
return (a + b)/2.0;
}
// calcula a média de três números reais
double media(double n1, double n2, double n3)
{
cout << "média: double,double,double" << endl;
return (n1 + n2 + n3)/3.0;
}
// função principal
int main()
{
double x, y, z;
cout << "Digite dois números reais: ";
cin >> x >> y;
cout << "Média: "
<< media(x, y) // chamada da primeira função
<< endl << endl;
cout << "Digite três números reais: ";
cin >> x >> y >> z;
cout << "Média: "
<< media(x, y, z) // chamada da terceira função
<< endl << endl;
float a, b;
cout << "Digite dois números reais: ";
cin >> a >> b;
cout << "Média: "
<< media(a, b) // chamada da terceira função
<< endl << endl;
cout << "Média: "
<< media(3, 9.8) // chamada da segunda função
<< endl;
}
// FIM
Exemplo 3.2. Funções sobrecarregadas para calcular o quadrado de um número inteiro e de
um número real.
# include <iostream>
using namespace std;
// calcula o quadrado de um número inteiro
int quadrado(int x)
3.1. SOBRECARGA DE FUNÇÕES
3-3
{
cout << "calculando o quadrado do inteiro " << x << endl;
return x * x;
}
// calcula o quadrado de um número real
double quadrado(double x)
{
cout << "calculando o quadrado do real " << x << endl;
return x * x;
}
// função principal
int main()
{
int a;
cout << "Digite um número inteiro: ";
cin >> a;
cout << "Quadrado: "
<< quadrado(a) // chamada da primeira função
<< endl << endl;
double b;
cout << "Digite um número real: ";
cin >> b;
cout << "Quadrado: "
<< quadrado(b) // chamada da segunda função
<< endl;
}
A assinatura de uma função é o nome da função com a sequência de tipos na lista de
parâmetros formais. Quando um nome de função é sobrecarregado, as funções devem ter
assinaturas diferentes.
Exercício 3.1. Suponha que você tenha duas definições de função com as seguintes declarações:
double placar(double tempo, double distancia);
int placar(double pontos);
Que definição de função seria usada na seguinte chamada de função e por que seria esta a
usada?
double x = 23.3;
double placar_final = placar(x);
Exercício 3.2. Suponha que você tenha duas definições de função com as seguintes declarações:
double aResposta(double dado1, double dado2);
double aResposta(double tempo, int contagem);
Que definição de função seria usada na seguinte chamada de função e por que seria esta a
usada?
double x, y = 1.5;
x = aResposta(y, 6.0);
3-4
3.2
CAPÍTULO 3. SOBRECARGA
Argumentos padrão
Pode-se especificar um argumento padrão para um ou mais parâmetros chamados por valor
em uma função. Se o argumento correspondente for omitido, então o argumento padrão é
utilizado.
Uma função pode ter vários argumentos padrão, mas eles sempre ser os argumentos mais à
direita. Havendo mais de um argumento padrão na declaração da função, quando a função é
chamada pode-se omitir argumentos a começar da direita.
Exemplo 3.3. A função exibe_volume no programa que se segue calcula o volume de uma
caixa a partir de seu comprimento, largura e altura. Se nenhuma altura for dada, presume-0se
que altura seja 1. Se nem a largura e nem a altura forem dadoas, presume-se que ambas sejam
1.
# include <iostream>
using namespace std;
void exibe_volume(double comprimento,
double largura = 1,
double altura = 1)
{
cout << "O Volume de uma caixa com" << endl
<< " comprimento = " << comprimento << endl
<< " largura
= " << largura << endl
<< " altura
= " << altura << endl
<< "é " << comprimento * largura * altura << endl;
}
int main()
{
exibe_volume(4, 6, 2);
exibe_volume(4, 6);
exibe_volume(4);
}
Argumentos padrão podem ser fornecidos apenas para argumentos mais à direita na lista
de argumentos.
Exemplo 3.4. Algumas declarações de
int f(int, int=0, char=’y’);
int g(int=0, int=0, char);
int h(int=0, int, char);
funções com argumentos padrão válidas e inválidas.
// ok
// erro
// erro
Exercício 3.3. Esta atividade propõe a implementação de um tipo (classe) para representar
um conjunto de números naturais com algumas operações básicas sobre conjuntos.
Neste exercício você vai trabalhar os conceitos de classe, objeto e mensagens. Também será
explorada a sobrecarga de funções membro ao definir várias versões de métodos construtores
para a classe proposta. Será usada também a classe vector da biblioteca do C++.
1. Crie uma classe chamada conjunto para representar um conjunto de números naturais.
O conjunto deve ser representado por um vetor de valores booleanos (verdadeiro ou falso).
O elemento do vetor na posição i é verdadeiro se e somente se o número natural i pertencer
ao conjunto. Utilize a classe vector da biblioteca do C++, pois ela é mais flexível do
que os arrays, uma vez que não impõe um tamanho máximo ao vetor.
3.2. ARGUMENTOS PADRÃO
3-5
2. Crie um método insere para inserir um valor x no conjunto:
void insere(int x)
A implementação deste método deverá verificar se o valor x a ser inserido no conjunto
é um índice válido no vetor. Caso x seja um índice inválido (isto é, x é maior ou
igual ao tamanho do vetor), o vetor deve ser expandido de forma a tornar x um índice
válido. Para tanto acrescente o valor falso ao final do vetor (enviando uma mensagem
push_back ) tantas vezes quantas forem necessárias. Sendo x um índice válido, basta
então colocar o valor verdadeiro na posição x do vetor.
3. Crie um outro método insere para inserir os valores de um intervalo fechado x – y no
conjunto:
void insere(int x, int y)
Para implementar este método basta fazer um comando de repetição para todos os valores do intervalo, inserindo cada valor no conjunto através do método do item anterior.
Observe que o nome deste método é o mesmo do item interior. Portanto você está fazendo
sobrecarga de funções.
4. Forneça um construtor sem argumentos (também conhecido como construtor padrão
ou construtor default) que inicializa o conjunto como sendo o conjunto vazio. Observe
que este método não precisa fazer nada.
5. Forneça um construtor com um único argumento que inicializa o conjunto como sendo
o conjunto unitário contendo este argumento. Utilize o método insere para inserir o
elemento no conjunto.
6. Forneça um construtor com um dois argumentos que inicializa o conjunto como sendo o
intervalo fechado dos números naturais cujo limite inferior é o primeiro argumento, e cujo
limite superior é o segundo argumento. Utilize o método insere de dois argumentos
para inserir os elementos do intervalo no conjunto. Observe que ao definir estes três
métodos construtores, estamos fazendo sobrecarga de funções.
7. Defina um método remove para remover um elemento do conjunto:
void remove(int x)
Para remover um elemento x do conjunto é suficiente verificar se o elemento é um índice
válido (isto é, o elemento é menor do que o tamanho do vetor), e neste caso colocar o
valor falso nesta posição do vetor.
8. Crie um método pertence para verificar se um valor x pertence ao conjunto:
bool pertence(int x)
É suficiente verificar se x é um índice válido do vetor e se o valor armazenado nesta
posição é verdadeiro.
9. Crie um método uniao para realizar a operação de união entre dois conjuntos: o conjunto
que recebe a mensagem, e outro conjunto que será passado como argumento para o
método.
conjunto uniao(conjunto c)
O resultado será um terceiro conjunto formado pelos elementos que pertencem ao primeiro
ou ao segundo conjunto. Na implementação deste método utilize uma variável local
3-6
CAPÍTULO 3. SOBRECARGA
para armazenar um novo conjunto (inicialmente vazio) que será o resultado da operação.
Verifique se cada índice válido dos vetores de cada um dos dois conjuntos pertence ao
respectivo conjunto (utilizando o método pertence ), e em caso afirmativo, insira o
índice no novo conjunto (usando o método insere ).
10. Crie um método intersecao para realizar a operação de interseção entre dois conjuntos:
o conjunto que recebe a mensagem, e outro conjunto que será passado como argumento
para o método.
conjunto intersecao(conjunto c)
O resultado será um terceiro conjunto formado pelos elementos que pertencem aos dois
conjuntos. Na implementação deste método utilize uma variável local para armazenar um
novo conjunto (inicialmente vazio) que será o resultado da operação. Verifique se cada
índice válido do vetor do primeiro conjunto pertence ao primeiro e ao segundo conjunto
(utilizando o método pertence ), e em caso afirmativo, insira o índice no resultado
(usando o método insere ).
11. Crie um método diferenca para realizar a operação de diferença entre dois conjuntos:
o conjunto que recebe a mensagem, e outro conjunto que será passado como argumento
para o método.
conjunto diferenca(conjunto c)
O resultado será um terceiro conjunto formado pelos elementos do primeiro conjunto que
não pertencem ao segundo conjunto. Na implementação utilize uma variável local para
armazenar um novo conjunto (inicialmente vazio) que será o resultado da operação. Verifique se cada índice válido do vetor do primeiro conjunto pertence ao primeiro conjunto e
não pertence ao segundo conjunto (utilizando o método pertence ), e em caso afirmativo,
insira o índice no resultado (usando o método insere ).
12. Crie um método exibe para exibir um conjunto na saída padrão do sistema ( cout ).
void exibe()
Os elementos do conjunto devem ser exibidos separados por vírgula e colocados entre
chaves.
Uma possível implementação deste método é:
void exibe()
{
cout << "{";
int k = 0;
while (k < vet.size() && ! vet[k])
k++;
if (k < vet.size())
{
cout << k;
for (k++; k < vet.size(); k++)
if (vet[k])
cout << ’,’ << k;
}
cout << "}";
}
Nesta implementação o vetor utilizado para representar o conjunto foi chamado de vet .
3.2. ARGUMENTOS PADRÃO
13. Utilize a função principal ( main ) a seguir para testar a classe.
int main()
{
conjunto a(3,7);
conjunto b(5,10);
a.insere(12);
b.insere(1);
cout << "a: ";
a.exibe();
cout << endl;
cout << "b: ";
b.exibe();
cout << endl;
cout << "a uniao com b: ";
a.uniao(b).exibe();
cout << endl;
cout << "a intersecao com b: ";
a.intersecao(b).exibe();
cout << endl;
cout << "a - b: ";
a.diferenca(b).exibe();
cout << endl;
}
3-7
Download

Roteiro: classes e objetos