Mudanças entre as edições de "Projeto final de SOP - turmas 2070114 e 2070115"
Linha 319: | Linha 319: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Note que o acesso aos campos de uma variável do tipo ''struct'' se faz com a sintaxe ''identificador.campo'' (lembre que ''identificador'' é o nome de uma variável). Veja este exemplo para mostrar os valores dos campos de uma variável do tipo ''struct Pessoa'': | + | Note que o acesso aos campos de uma variável do tipo ''struct'' se faz com a sintaxe ''identificador.campo'' (lembre que ''identificador'' é o nome de uma variável, neste caso o da variável do tipo ''struct''). Veja este exemplo para mostrar os valores dos campos de uma variável do tipo ''struct Pessoa'': |
<syntaxhighlight lang=c> | <syntaxhighlight lang=c> |
Edição das 20h46min de 27 de outubro de 2009
Etapa 1
Período: 23/10 a 06/11
Descrição
Interface com o usuário da agenda, modelagem de eventos e armazenamento da agenda em memória
Interface
A agenda deve apresentar uma interface de texto. Nessa interface o usuário pode escolher uma das seguintes opções, com as respectivas operações a serem desencadeadas:
- Criar agenda: criar uma nova agenda, que deve ter um nome único.
- Ler agenda: carregar para a memória uma agenda que reside em um arquivo.
- Gravar agenda: gravar em um arquivo a agenda que reside em mempória.
- Inserir evento: adicionar um novo evento à agenda, que não pode conflitar com o horário de outro evento que já exista.
- Mostrar eventos: mostrar em ordem cronológica todos os eventos da agenda.
- Remover evento: remover um evento específico.
- Sair: sair do programa.
Eventos
Os eventos devem ser modelados para que contenham as seguintes informações:
- Título
- Descrição
- Data e horário de criação
- Data e horário de início
- Data e horário de término
- Categoria
- Local
- Status
- Recorrência
No programa um evento deve ser guardado em uma variável do tipo struct. Uma struct funciona como um registro, em que diferentes valores logicamente relacionados são agrupados e podem ser manipulados usando uma única variável.
23/10: início
Focou-se a criação da interface, que mostra as opções para o usuário e aguarda que ele digite o número da opção escolhida. Para que a interface repita até que o usuário escolha a opção correspodente a sair do programa, usou-se uma estrutura de repetição while. Para o processamento da opção escolhida discutiu-se que a forma mais conveniente é usar a estrutura de decisão switch ... case (equivalente ao Escolhe ... caso do Portugol). Um resumo sobre isto pode ser visto nas transparências.
Usando while e do ... while
No Portugol existe a estrutura de repetição Enquanto condição faz, como mostrado abaixo:
Inicio
inteiro x
escrever "Vou mostrar os números pares entre 0 e 100\n"
x <- 0
Enquanto x < 101 faz
escrever x, " "
x <- x + 2
fimEnquanto
fim
Em linguagem C há o equivalente while (condição). O exemplo a seguir é o equivalente ao algoritmo em Portugol acima:
int main() {
int x;
puts("Vou mostrar os números pares entre 0 e 100");
x = 0;
while (x < 101) {
printf("%d ", x);
x = x + 2;
}
}
Além da estrutura while, a linguagem C oferece também do {} while (condição);. Diferente do while (condição) , a condição é avaliada ao final da execução do laço. Assim, com do {} while (condição); as instruções contidas no laço são sempre executadas ao menos uma vez. Isto pode ser útil na criação da interface da agenda. Veja o exemplo abaixo:
int main() {
int opcao;
do {
printf("1. Fazer algo\n");
printf("2. Sair\n");
printf("Opção: ");
scanf("%d", &opcao);
// aqui se deve testar a opção digitada, e executar a ação correspondente
} while (opcao != 2);
printf("Fui !\n");
}
Mais informações podem ser encontradas nestas transparências, e também no curso de linguagem C da UFMG.
Testando diferentes valores de uma variável
A interface da agenda implica a apresentação de um menu de opções, a leitura da opção a ser executada, e em seguida a identificação de qual opção o usuário escolheu. Para saber que opção foi digitada, o programa deve comparar o valor da variável correspondente com os valores das opções disponíveis. Por exemplo, se o usuário digitar 1, então deve-se criar uma nova agenda, se ele digitar 4, deve-se inserir um novo envento na agenda, e se ele digitar 7, deve-se terminar o programa. Uma forma direta de fazer isto é usar a estrutura de decisão switch .. case, que equivale a Escolhe .. caso do Portugol. Veja este exemplo do Portugol:
inicio
inteiro opcao
escrever "Opção: "
ler opcao
escolhe opcao
caso 1:
escrever "Você escolheu a opção 1\n"
caso 2:
escrever "Você escolheu a opção 2\n"
defeito:
escrever "Opção desconhecida !\n"
fimEscolhe
Fim
O equivalente em linguagem C é:
int main() {
int opcao;
printf("Opção: ");
scanf("%d", &opcao);
switch (opcao) {
case 1:
puts("Você escolheu a opção 1");
break;
case 2:
puts("Você escolheu a opção 2");
break;
case 1:
puts("Opção desconhecida !");
break;
}
}
Mais informações podem ser encontradas nestas transparências, e também no curso de linguagem C da UFMG.
O problema do scanf, quando se digita um valor inválido
Algumas equipes identificaram um problema na leitura da opção pelo teclado. Se o usuário digitar algo que não seja um número inteiro, a interface entra em loop, sempre mostrando o menu de opções. Para exemplificar, veja abaixo a leitura de um número inteiro feita com scanf:
int main() {
int opcao, lidos;
do {
// aqui mostra as opções do menu
printf("Digite uma opção: ");
lidos = scanf("%d", &opcao);
printf("Opção escolhida=%d\n", opcao);
// executa a ação correspondente à opção escolhida
} while (opcao != 7); // neste exemplo, a opção 7 faz a saída do programa
}
Este video mostra o problema do loop quando se digita um valor inválido.
Lembre que o scanf tenta ler o valor digitado de acordo com a especificação contida no formato (no exemplo, "%d" que corresponde a um número inteiro). Se ele conseguir, retorna a quantidade de valores lidos (no exemplo isto é guardado na variável lidos). Assim, se scanf no exemplo acima não conseguir ler o valor digitado, irá retornar 0. O problema é que neste caso o valor que foi digitado fica pendente em memória, quer dizer, o scanf não descarta esse valor, apesar de não ter conseguido lê-lo. Na próxima vez que o scanf for chamado ele vai tentar novamente ler esse mesmo valor, e se der erro o valor vai continuar pendente. Isto só se reverterá quando o scanf especificar um tipo de dado compatível com o valor pendente, ou então se o formato especificar que se deve descartá-lo. Uma possível correção seria portanto testar se o scanf conseguiu ler o valor, e em caso negativo descartar tudo o que foi digitado. Veja assim o exemplo abaixo com a correção:
int main() {
int x, n;
do {
// aqui mostra as opções do menu
// o laço abaixo faz com que se repita a leitura pelo teclado até que se digite um
// número inteiro.
do {
printf("Digite uma opção: ");
n = scanf("%d", &x);
// se n == 0, então scanf não conseguiu ler o valor, porque ele não é do tipo inteiro.
// Então o valor digitado deve ser descartado. Para descartar tudo o que foi digitado, deve-se
// chamar de novo o scanf, mas com o formato "%*[^\n]". Isto joga fora toda a linha digitada.
// Uma explicação sobre formatos do scanf está na sua página de manual (execute "man scanf").
if (n == 0) {
n = scanf("%*[^\n]");
}
} while (n == 0);
printf("Opção escolhida=%d\n", opcao);
// executa a ação correspondente à opção escolhida
} while (opcao != 7); // neste exemplo, a opção 7 faz a saída do programa
}
Com a correção acima, o problema do loop desaparece, o que se pode ver neste outro video.
Esse problema da leitura de valores com scanf pode aparecer em outras partes da agenda. Então seria mais prático se o algoritmo contido no programa acima, que repete a leitura do teclado até que se consiga ler uma valor válido, pudesse ser reaproveitado. Isto é um bom exemplo do uso de funções da linguagem C. Uma função é um algoritmo a ser reutilizado. Como todo algoritmo, deve ter dados de entrada, uma sequência de instruções, e fornecer dados de saída como resultado. Veja o exemplo abaixo, que se baseia na leitura do teclado com scanf:
// Função meu_scanf:
// Dados de entrada: parâmetro "char * mensagem" é o texto a ser mostrado antes de ler do teclado.
// Dado de saída: meu_scanf devolve (retorna) um número inteiro que corresponde ao valor lido.
int meu_scanf(char * mensagem) {
int x, n;
do {
printf(mensagem);
n = scanf("%d", &x);
// se n == 0, então scanf não conseguiu ler o valor, porque ele não é do tipo inteiro.
// Então o valor digitado deve ser descartado. Para descartar tudo o que foi digitado, deve-se
// chamar de novo o scanf, mas com o formato "%*[^\n]". Isto joga fora toda a linha digitada.
// Uma explicação sobre formatos do scanf está na sua página de manual (execute "man scanf").
if (n == 0) {
n = scanf("%*[^\n]");
}
} while (n == 0);
// aqui meu_scanf retorna o valor lido, que está contido na variável "x"
return x;
}
int main() {
int opcao;
do {
// aqui mostra as opções do menu
// chama a função "meu_scanf" para ler um número inteiro do teclado.
opcao = meu_scanf("Digite uma opção: ");
printf("Opção escolhida=%d\n", opcao);
// executa a ação correspondente à opção escolhida
} while (opcao != 7); // neste exemplo, a opção 7 faz a saída do programa
}
No final destas transparências há um resumo sobre funções. Mais informações estão no curso de linguagem C da UFMG.
28/10 (turma 2070115)
A representação do que é um evento dentro do programa da agenda implica descrever todas as características de um evento, conforme descritas na seção [Introdução]. Há mais de uma forma de fazer isto dentro do programa, sendo a mais elementar (mas não a melhor !) simplesmente criar uma variável para cada dado que compõe um evento. Se for adotada essa abordagem, o resultado seria parecido com o exemplo abaixo:
int main() {
char titulo[64];
char descricao[1024];
char local[64];
int inicio; // o timestamp da data e horario de inicio
int termino; // o timestamp da data e horario de término
int criacao; // o timestamp da data e horario de criacao
char categoria[32];
char status[32];
char recorrencia[32];
// corpo do programa da agenda, com as instrucoes
}
Parece simples, mas fazer como mostrado acima traz várias dificuldades. A maior delas é não ser possível tratar um evento como um dado único dentro do programa, já que ele passa a ser definido por um conjunto de variáveis. Imagine a dificuldade de criar um vetor de eventos, ou de criar funções que recebam eventos como parâmetro. Isto leva a considerar que a possibilidade de um evento ser descrito por uma única variável facilitaria bastante a escrita do programa. No entanto, como conciliar isto com o fato de que um evento possui múltiplas características ? Uma solução pode ser obtida usando-se o que se chama de struct na linguagem C. Para entender o que é um struct, veja o exemplo abaixo sobre um cadastro de pessoas::
// Define uma struct Pessoa, que contem os dados
// (atributos) de uma pessoa, do ponto de vista deste programa.
// Repare que "struct Pessoa" passa a ser um novo tipo de dados !
struct Pessoa {
char nome[32];
char sobrenome[32];
char endereco[128];
int numero;
char bairro[32];
char cidade[64];
};
int main() {
// Declara uma variável do tipo "struct Pessoa"
struct Pessoa umaPessoa;
printf("Nome: ");
scanf("%s", umaPessoa.nome);
printf("Sobrenome: ");
scanf("%s", umaPessoa.sobrenome);
printf("Endereco: ");
scanf("%s", umaPessoa.endereco);
printf("Numero da residencia: ");
scanf("%d", &umaPessoa.numero);
printf("Bairro: ");
scanf("%s", umaPessoa.bairro);
printf("Cidade: ");
scanf("%s", umaPessoa.cidade);
// mais instruções do programa que cria o cadastro
return 0;
}
Nesse exemplo, uma pessoa é descrita por seu nome e sobrenome, endereco, número de residência, bairro e cidade. Para representar uma pessoa, cria-se um novo tipo de dados chamado struct Pessoa, que encapsula todas essas informações. Assim, as variáveis do tipo struct Pessoa serão capazes de conter todos esses dados, porém guardados de forma a ficarem facilmente vinculados. Note que na declaração de struct Pessoa estão declarações parecidas com variáveis, uma para cada característica de pessoa. Essas declarações contidas na struct se chamam campos.
// Define uma struct Pessoa, que contem os dados
// (atributos) de uma pessoa, do ponto de vista deste programa.
// Repare que "struct Pessoa" passa a ser um novo tipo de dados !
struct Pessoa {
char nome[32]; // campo nome
char sobrenome[32]; // campo sobrenome
char endereco[128]; // campo endereço
int numero; // campo número
char bairro[32]; // campo bairro
char cidade[64]; // campo cidade
};
Note que o acesso aos campos de uma variável do tipo struct se faz com a sintaxe identificador.campo (lembre que identificador é o nome de uma variável, neste caso o da variável do tipo struct). Veja este exemplo para mostrar os valores dos campos de uma variável do tipo struct Pessoa:
printf("Nome: %s\n", pessoa.nome);
printf("Sobrenome: %s\n", pessoa.sobrenome);
printf("Endereco: %s\n", pessoa.endereco);
printf("Numero da residencia: %d\n", pessoa.numero);
printf("Bairro: %s\n", pessoa.bairro);
printf("Cidade: %s\n", pessoa.cidade);
Finalmente, uma variável do tipo struct pode ser passada como parâmetro de uma função. Isto demonstra inclusive que a variável do tipo struct é tratada como uma coisa única, apesar de conter muitos valores de diferentes tipos internamente:
struct Pessoa {
char nome[32]; // campo nome
char sobrenome[32]; // campo sobrenome
char endereco[128]; // campo endereço
int numero; // campo número
char bairro[32]; // campo bairro
char cidade[64]; // campo cidade
};
// Uma funcao para mostrar os dados de uma variavel do tipo "struct Pessoa".
void mostra_pessoa(struct Pessoa pessoa) {
printf("Nome: %s\n", pessoa.nome);
printf("Sobrenome: %s\n", pessoa.sobrenome);
printf("Endereco: %s\n", pessoa.endereco);
printf("Numero da residencia: %d\n", pessoa.numero);
printf("Bairro: %s\n", pessoa.bairro);
printf("Cidade: %s\n", pessoa.cidade);
}
int main() {
struct Pessoa umaPessoa;
printf("Nome: ");
scanf("%s", umaPessoa.nome);
printf("Sobrenome: ");
scanf("%s", umaPessoa.sobrenome);
printf("Endereco: ");
scanf("%s", umaPessoa.endereco);
printf("Numero da residencia: ");
scanf("%d", &umaPessoa.numero);
printf("Bairro: ");
scanf("%s", umaPessoa.bairro);
printf("Cidade: ");
scanf("%s", umaPessoa.cidade);
printf("\n\nVoce digitou o seguinte:\n\n");
mostra_pessoa(umaPessoa);
return 0;
}