Projeto final de SOP - turmas 2070114 e 2070115

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

Assuntos específicos da linguagem C

Abaixo estão alguns artigos sobre assuntos específicos da linguagem C, e que serão necessários ao longo da disciplina:

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:

  1. Criar agenda: criar uma nova agenda, que deve ter um nome único.
  2. Ler agenda: carregar para a memória uma agenda que reside em um arquivo.
  3. Gravar agenda: gravar em um arquivo a agenda que reside em mempória.
  4. Inserir evento: adicionar um novo evento à agenda, que não pode conflitar com o horário de outro evento que já exista.
  5. Mostrar eventos: mostrar em ordem cronológica todos os eventos da agenda.
  6. Remover evento: remover um evento específico.
  7. 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.

Obs: se quiser executar os exemplos mostrados aqui copie-os para dentro de um arquivo com extensão .c. Lembre de acrescentar a seguinte linha no topo do arquivo:

#include <stdio.h>

Depois compile e execute o programa exemplo.

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 - maiores detalhes no curso de C da UGMF). 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", x);

      // executa a ação correspondente à opção escolhida

    } while (x != 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 e 30/10

Representando eventos dentro do programa com struct

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 Descriçã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".
// Essa função recebe como parâmetro um valor do tipo "struct Pessoa", e esse
// valor será copiado para dentro da variável "pessoa". Essa variável existe somente
// dentro desta função (quer dizer, é uma variável local).
// A função não devolve nenhum valor, por isto seu tipo de retorno é "void".

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;
}

Atenção: declarações de novos tipos de dados com struct devem ser globais (fora da função main', e também de qualquer outra função).

Mais um exemplo do uso de struct e funções: data e horário

Neste novo exemplo se deseja fazer um programa que leia e mostre datas. Uma data é definida por: dia, mês, ano, hora, minuto e segundo. Assim, foi definido um novo tipo de dados para representar datas no programa:

struct DataHorario {
  int dia, mes, ano;
  int hora, minuto, segundo;
};

O novo programa deve ler uma data do teclado, e mostrar na tela a data lida. Assim dois algoritmos são necessários: um para ler a data, e outro para mostrá-la. Esses algoritmos foram implementados em funções, como mostrado abaixo:

// Função mostra_data: mostra uma data no formato dia/mes/ano hora:minuto:segundo
// dado de entrada: um valor do tipo "struct DataHorario", a ser passado no parâmetro "umaData"
// valor de retorno: não tem (por isto é do tipo "void")

void mostra_data(struct DataHorario umaData) {
  printf("%d/%d/%d %d:%d:%d", umaData.dia, umaData.mes, umaData.ano,
                              umaData.hora, umaData.minuto, umaData.segundo);
}

// Função le_data: lê uma data, que deve ser digitada no formato dia/mes/ano hora:minuto:segundo
// dado de entrada: não tem
// valor de retorno: um valor do tipo "struct DataHorario", que corresponde a data lida

struct DataHorario le_data() {
  struct DataHorario umaData;

  scanf("%d/%d/%d %d:%d:%d", &umaData.dia, &umaData.mes, &umaData.ano,
                              &umaData.hora, &umaData.minuto, &umaData.segundo);

  return umaData;
}

Agora pode-se reunir a declaração de struct DataHorario e das funções le_data e mostra_data para escrever o programa completo que lê data e a mostra na tela:

#include <stdio.h>

struct DataHorario {
  int dia, mes, ano;
  int hora, minuto, segundo;
};

// Função mostra_data: mostra uma data no formato dia/mes/ano hora:minuto:segundo
// dado de entrada: um valor do tipo "struct DataHorario", a ser passado no parâmetro "umaData"
// valor de retorno: não tem (por isto é do tipo "void")

void mostra_data(struct DataHorario umaData) {
  printf("%d/%d/%d %d:%d:%d", umaData.dia, umaData.mes, umaData.ano,
                              umaData.hora, umaData.minuto, umaData.segundo);
}

// Função le_data: lê uma data, que deve ser digitada no formato dia/mes/ano hora:minuto:segundo
// dado de entrada: não tem
// valor de retorno: um valor do tipo "struct DataHorario", que corresponde a data lida

struct DataHorario le_data() {
  struct DataHorario umaData;

  scanf("%d/%d/%d %d:%d:%d", &umaData.dia, &umaData.mes, &umaData.ano,
                              &umaData.hora, &umaData.minuto, &umaData.segundo);

  return umaData;
}

int main() {
  struct DataHorario aData;

  printf("Informe a data no formato dia/mes/ano hora:minuto:segundo: ");
  aData = le_data();

  printf("Voce informou: ");
  mostra_data(aData);
  puts("");
}

Testando a validade da data

No exemplo acima, a função le_data não testa a validade da data lida. No entanto, é desejável verificar se a data digitada é válida. Com isto, le_data poderia pedir novamente que fosse digitada uma data, até que o usuário fornecesse uma que fosse aceitável. O exemplo abaixo contém as modificações que seriam necessárias no programa para se obter esse efeito:

int data_valida(struct DataHorario data) {
  int ok;
  int bissexto;

  // Testa dia e mes
  if ((data.dia <= 0) || (data.ano <= 0)) return 0;

  switch (data.mes) {
    case 4:
    case 6:
    case 9:
    case 11:
      if (data.dia > 30) return 0;
      break;
    case 2:
      if (data.dia > 29) return 0;
      // se dia 29 e ano nao eh bissexto ...
      bissexto=((data.ano % 4 == 0) && (data.ano % 100 != 0) ||(data.ano % 400 == 0));
      if ((data.dia == 29) && ! bissexto)
        return 0;
      break;

    default:
      if (data.mes > 12) return 0;
      else 
       if (data.dia > 31) return 0;
      break;
  }

  // Testa hora e minuto
  if ((data.hora < 0) || (data.hora > 23)) return 0;
  if ((data.minuto < 0) || (data.minuto > 59)) return 0;

  // se chegou ateh aqui, entao estah tudo certo !
  return 1;
}


struct DataHorario le_data(char * mensagem) {
  struct DataHorario umaData;
  int ok;
  char linha[128];

  do {
    printf("%s", mensagem);
    // Le uma linha do teclado. Essa linha deve conter a data e horario no
    // formato esperado.
    scanf(" %[^\n]", linha);
    // Extrai os valores da data e horario da linha que foi lida do teclado.
    ok = sscanf(linha, "%d/%d/%d %d:%d", &umaData.dia, &umaData.mes, &umaData.ano,
                              &umaData.hora, &umaData.minuto);

    // Se foram digitados valores para todos os campos de struct DataHorario, deve-se
    // testar a validade da data.
    if (ok == 5)
      if (!data_valida(umaData)) ok = 0;

  } while (ok < 5);
  return umaData;
}

A função le_data foi alterada para conferir a validade da data. O teste a ser feito corresponde a conferir se foram digitados cinco números (dia, mes, ano, hora, minuto), e caso afirmativo verificar se esses números foram uma data válida. Essa verificação final foi implementada na função data_valida, que retorna 1 (verdadeiro) se a data for válida, e 0 (falso) caso contrário. A função data_valida efetua uma série de testes para dia, mes, ano, hora e minuto, incluindo o caso de anos bissextos.

Um detalhe em le_data pode chamar sua atenção: a leitura da data foi realizada de forma diferente dos exemplos anteriores. Abaixo isto está destacado:

scanf(" %[^\n]", linha);
ok = sscanf(linha, "%d/%d/%d %d:%d", &umaData.dia, &umaData.mes, &umaData.ano,&umaData.hora, &umaData.minuto);

A leitura foi realizada em duas etapas:

  1. Leitura de uma string do teclado (linha 1): faz-se a leitura de uma linha completa, porém sem intepretar o que foi digitado. Quer dizer, o usuário pode digitar qualquer coisa.
  2. Interpretação do que foi digitado (linha 2): tentam-se extrair da linha digitada os números correspondentes à data e horário. Note o uso de uma nova função, chamada sscanf. Essa função tem funcionamento idêntico a scanf, mas em vez de ler os valores do teclado ela tenta extraí-los de uma string (passada como primeiro parâmetro).

A alternativa seria ler de uma vez só, como mostrado abaixo:

  do {
    printf("%s", mensagem);
    ok = scanf("%d/%d/%d %d:%d", &umaData.dia, &umaData.mes, &umaData.ano,
                              &umaData.hora, &umaData.minuto);
    if (ok == 5) {
      if (!data_valida(umaData)) ok = 0;
    }
    else scanf(" %*[^\n]");
  } while (ok < 5);

Mas por que foi feito dessa maneira ? Talvez a melhor forma de justificar essa mudança seja com uma comparação. Veja o que acontece se o exemplo com a leitura da linha e sua posterior interpretação (com sscanf) for executado:

Informe a data no formato dia/mes/ano HH:MM : 1/1/2009 10:67
Informe a data no formato dia/mes/ano HH:MM : 1/1/2009
Informe a data no formato dia/mes/ano HH:MM : sdcdcd
Informe a data no formato dia/mes/ano HH:MM : 1/3
Informe a data no formato dia/mes/ano HH:MM : 1/1/2009 10:
Informe a data no formato dia/mes/ano HH:MM : 1/1/2009 10:59
Voce informou: 01/01/2009 10:59

Agora veja o que acontece se for executado o programa acima, porém fazendo o scanf ler diretamente os valores do teclado:

Informe a data no formato dia/mes/ano HH:MM : 1/1/2009 10:67
Informe a data no formato dia/mes/ano HH:MM : 1/1/2009

xaxasx
Informe a data no formato dia/mes/ano HH:MM : 1/


1/2009
0:0
Voce informou: 01/01/2009 00:00

Se primeiro for lida uma linha, para depois ser intepretada (como no primeiro caso), a detecção de uma data inválida é imediata. Quer dizer, assim que o usuário teclar ENTER o programa já pode avaliar o que ele digitou. Já no segundo caso, se a data for lida diretamente do teclado, o scanf vai esperar que sejam digitados os cinco números no formato especificado. Se o usuário teclar ENTER, mesmo assim o scanf continuará esperando os dados restantes. Assim, o scanf somente concluirá quando tiver lido cinco números, ou quando o usuário digitar algo não aceitável (ex: letras ao invés de números).

Recomendações discutidas em aula

Na etapa 1 do projeto final deve-se criar a interface da agenda com o usuário, e armazenar a agenda em memória. A agenda é composta de um conjunto de eventos, sendo que cada evento possui um certo número de características (ou atributos). Para a realização dessa etapa algumas sugestões foram dadas:

A interface deve ser orientada a menu

A interface com o usuário deve apresentar um menu com opções numeradas. O usuário deve digitar o número da opção escolhida para executar a ação correspondente. O menu deve ser parecido com este abaixo:

 1. Criar uma agenda
 2. Carregar uma agenda de um arquivo
 3. Gravar a agenda em seu arquivo
 4. Inserir um evento
 5. Mostrar os eventos
 6. Remover um evento
 7. sair
 Digite o número da opção: 

Ações sobre a agenda devem ser implementadas em uma função em separado

A opção do menu escolhida por um usuário deve ser identificada para então se executar a ação correspondente. Dentro do programa da agenda isto fica parecido com o código abaixo:

int main() {
  int opcao;

  do {
    // mostra as opções do menu
    // lê a opção escolhida

    switch (opcao) {
      case 1:
        // ações para criar a agenda
        break;
      case 2:
        // ações para ler a agenda de um arquivo
        break;
      case 3:
        // ações para gravar a agenda em um arquivo
        break;
      case 4:
        // ações para inserir um novo evento na agenda
        break;
      case 5:
        // ações para mostrar os eventos contidos na agenda
        break;
      case 6:
        // ações para remover um evento da agenda
        break;
      case 7:
        // ações para sair do programa
        break;
      default:
        // erro: opção inválida
        break;
    }
  } while (opcao != 7);
}

Ao invés de escrever os algoritmos correspondentes a cada uma das ações acima dentro do próprio case, devem-se criar funções. Dentro dessas novas funções serão escritos os algoritmos:


void criar_agenda() {
  // algoritmo para criar uma agenda
}

void carregar_agenda() {
  // algoritmo para carregar uma agenda de um arquivo
}

void gravar_agenda() {
  // algoritmo para gravar uma agenda em um arquivo
}

void inserir_evento() {
  // algoritmo para inserir um novo evento em uma agenda
}

void mostrar_eventos() {
  // algoritmo para mostrar eventos de uma agenda
}

void remover_evento() {
  // algoritmo para remover um evento de uma agenda
}

int main() {
  int opcao;

  do {
    // mostra as opções do menu
    // lê a opção escolhida

    switch (opcao) {
      case 1:
        criar_agenda();
        break;
      case 2:
        carregar_agenda();
        break;
      case 3:
        gravar_agenda();
        break;
      case 4:
        inserir_evento();
        break;
      case 5:
        mostrar_eventos();
        break;
      case 6:
        remover_evento();
        break;
      case 7:
        // ações para sair do programa
        break;
      default:
        // erro: opção inválida
        break;
    }
  } while (opcao != 7);
}

As funções acima referenciadas podem ter parâmetros e valores de retorno. Isto não foi mostrado porque cada equipe está fazendo um programa diferente, e deve assim descobrir que parâmetros serão necessários.

Por fim, pela discussão acima o programa da agenda deve seguir o modelo mostrado no fluxograma abaixo:

Agenda.png

Eventos devem ser modelados como um novo tipo de dados

Um evento possui várias características, e a melhor forma de representar isto dentro de um programa é com a criação de um novo tipo de dados:

struct Evento {
  // campos para representar as características de um evento
};

Lembre-se que a declaração de um struct deve ser global: fora da função main e de qualquer outra função !

06/11 - uma visão geral sobre funções na linguagem C

Nesse dia se fez uma apresentação sobre funções, usando um conjunto de exemplos para demonstrar o que são, como declarar e como usar funções. Também foi comentado o exemplo do cadastro de pessoas, mostrado na próxima subseção, pois ele tem similaridade com a agenda.

Outros programas exemplos

  1. Lendo o relógio do computador: isto pode ser útil para definir a data de criação de um evento.
  2. Lendo o relógio do computador, mas convertendo a data e horário para uma string: uma variação do exemplo anterior
  3. Criando um pequeno cadastro de pessoas
    • O cadastro usa um vetor de struct Pessoa para guardar até 10 pessoas em memória.
  4. O mesmo cadastro de pessoas, mas criando funções para ler e para mostrar os dados de uma pessoa
    • Observe a simplificação obtida dentro da função main, pois os algoritmos para ler uma pessoa e para mostrar uma pessoa foram retirados dali e colocados dentro de funções (no caso, ler_pessoa e mostra_pessoa)
  5. O mesmo cadastro, mas lendo nomes e sobrenomes que podem ter espaços no meio
    • A única diferença em relação ao exemplo anterior é na leitura do nome e sobrenome da pessoa, que aqui podem ter espaços no meio (ex: nome = "maria aparecida").
  6. O mesmo cadastro, mas criando um tipo de dados struct Cadastro
    • Nesta nova versão do cadastro foram feitas mudanças significativas. O cadastro é descrito por um novo tipo de dados chamado struct Cadastro:
       struct Cadastro {
        struct Pessoa pessoas[10]; // guarda até 10 pessoas
        int cadastradas; // conta quantas pessoas estão cadastradas
      };
      
      Foram também criadas duas novas funções, que adicionam pessoas no cadastro e mostram seu conteúdo:
      // Funcao para mostrar na tela todo o conteúdo de um cadastro
      void mostra_cadastro(struct Cadastro umCadastro) {
        int i;
      
        i = 0;
        while (i < umCadastro.cadastradas) {
          mostra_pessoa(umCadastro.pessoas[i]);
          puts("");
          i = i + 1;
        }
      }
      
      // Funcao para adicionar uma pessoa ao cadastro
      int adiciona_pessoa(struct Cadastro * umCadastro) {
        if (umCadastro->cadastradas < 10) {
          umCadastro->pessoas[umCadastro->cadastradas] = ler_pessoa();
          umCadastro->cadastradas = umCadastro->cadastradas + 1;
      
          return 1;
        } else {
          return 0;
        }
      }
      
      Em particular a função adiciona_pessoa possui uma novidade: qualquer modificação feita na variável umCadastro, dentro da função adiciona_pessoa, será na verdade feita na variável cadastro da função main. A chave para explicar esta diferença em relação a outros exemplos vistos até então é o conceito de passagem de parâmetro de função, explicado no artigo sobre Funções na linguagem C.

09 a 13/11: continuação da etapa 1

Continuação do projeto, na expectativa de que a etapa 1 seja concluída em 13/11.

Etapa 2

A etapa 2 se resume a escrever os dados da agenda em um arquivo, e em ler uma agenda de um arquivo. Essas tarefas dependem de saber trabalhar com arquivos na linguagem C.

Nesta etapa serão implementadas as seguintes opções do menu:

  • Criar agenda: deve-se criar uma nova agenda, que possui um nome. O nome da agenda corresponde ao nome do arquivo em disco onde ela deverá ser armazenada. Essa nova agenda deve estar vazia. Após ser criada, ela estará pronta para conter novos eventos.
  • Ler agenda: deve-se ler uma agenda que já existe, e está guardada em um arquivo identificado pelo nome da agenda. O arquivo deve ser lido, e os eventos lá contidos devem ser carregados para memória. Após a leitura da agenda, ela estará pronta para ser usada.
  • Gravar agenda: deve-se criar um arquivo e nele escrever os eventos da agenda. O nome do arquivo deve ser o mesmo que o nome da agenda.

Um aspecto chave é a representação dos dados da agenda no arquivo. Esses dados devem ser escritos forma a serem facilmente recuperados. Portanto, eles podem ser escritos no arquivo de uma forma mais simplificada. Assim, imaginando-se uma agenda com dois eventos, o arquivo onde ela foi gravada pode ter o seguinte formato:

titulo_do_evento_1
descricao_do_evento_1
data_inicio_do_evento_1
data_criacao_do_evento_1
duracao_do_evento_1
categoria_do_evento_1
local_do_evento_1
recorrencia_do_evento_1
titulo_do_evento_2
descricao_do_evento_2
data_inicio_do_evento_2
data_criacao_do_evento_2
duracao_do_evento_2
categoria_do_evento_2
local_do_evento_2
recorrencia_do_evento_2

Como se pode ver, os dados do evento podem ser guardados de forma contínua, escrevendo-se um dado por linha. Isto facilita a leitura da agenda, pois basta ler os dados dos eventos sequencialmente. Afinal, o programa sabe a ordem em que os dados foram escritos.

Para implementar a escrita e leitura da agenda em arquivos serão necessários pelo menos as funções fopen, fclose, fprintf, fscanf e feof, que estão descritas e exemplificadas no artigo sobre arquivos na linguagem C.

16 e 18/11: Início

20/11: continuação

23 e 25/11: continuação

27/11: um pequeno teste

Para estimar como anda o aprendizado de cada aluno, hoje será feito este pequeno teste no início da aula.

Teste 2 de SOP

Complete o algoritmo abaixo para descobrir em que posição do vetor "v" foi escrito um valor != 0:

#include <stdio.h>
#include <stdlib.h>

// Uma função para inicializar o vetor.
// O vetor é zerado (todas suas posições são preenchidas com 0),
// mas em uma posição aleatória é colocado o valor 1.

void inicia(int * vetor, int comprimento) {
  time_t t;
  int i;

  // zera todas as posições do vetor
  for (i = 0; i < comprimento; i++) vetor[i] = 0;

  // inicializa o gerador de números pseudo-aleatórios
  t = time(NULL);
  srandom(t % 100);

  // sorteia um número, que será a posição do vetor onde se
  // colocará um valor != 0
  i = random() % comprimento;
  vetor[i] = 1; 
}


int main() {
  int v[100];

  inicia(v, 100);

  // aqui você deve incluir seu algoritmo para descobrir em que 
  // posição do vetor "v" há um valor != 0.
  // essa posição deve ser mostrada na tela, juntamente com 
  // o valor nela contido.

}

Etapa 3

Na terceira etapa do projeto a agenda deve ganhar novas operações:

  • Busca de eventos: o usuário deve poder selecionar e mostrar eventos que atendam determinados critérios, tais como intervalo de ocorrência, título, parte da descrição, local e status.
  • Remoção de eventos: deve ser possível remover eventos selecionados.
  • Recorrência: eventos devem poder ser recorrentes (repetitivos). Para atender esse requisito deve-se usar o campo recorrência de um evento, usando-se esta codificação:

    periodicidade;dias_da_semana;data_final

    ... sendo que cada uma dessas informações deve seguir esses formatos:

    periodicidade: palavras-chaves diário, semanal, mensal, anual ou uma string vazia
    dias_da_semana: uma lista (separada por vírgula) dos dias em que o evento deve ocorrer, sendo que os dias são identificados por seg, ter, qua, qui, sex, sab, dom.
    data_final: a data da última ocorrência do evento.

A implementação dessas novas funcionalidades na agenda implica basicamente a manipulação do vetor de eventos que está em memória.

27/11: Início

 Abortada !!!

Mudança de rumos: fixação de conceitos básicos

27/11 a 02/12

Lista de exercícios sobre linguagem C

O objetivo desta lista é ajudar na fixação de conceitos básicos da linguagem C.

  1. Traduza os seguintes algoritmos em Portugol para a linguagem C (todos eles foram obtidos na página de exemplos de SOP). Para cada algoritmo, execute tanto a versão em Portugol e em C, e compare os resultados:
  2. Faça um programa que leia um número inteiro do teclado, e mostre na tela todos os números cujos valores absolutos sejam menores ou iguais a ele. Ex: para o número 4, seria mostrado -4 -3 -2 -1 0 1 2 3 4.
  3. Faça um programa que leia um número inteiro do teclado e mostre quantos algarismos ele possui. Obs: um número inteiro na linguagem C pode ter até 10 algarismos (porque ele pode assumir valores entre -2147483648 e 2147483647).
  4. Faça um programa que leia o dia, mes e ano do teclado como números inteiros, e mostre-os com os seguintes formatos: dia/mes/ano, dia/mes ano, anomesdia. Ex: se o usuário digitou 3, 4, 2009, devem ser mostrados 03/04/2009, '03/04 2009, 20090403.
  5. Faça um programa que leia o dia, mes e ano do teclado como números inteiros, e mostre a data no formato: dia de nome_do_mes ano. Ex: se o usuário digitar 3, 4, 2009, deve ser mostrado 3 de abril 2009.
  6. Faça um programa que leia uma palavra do teclado, e em seguida calcule todos os anagramas possíveis com suas letras. Ex: a palavra "sop" teria os anagramas "SOP", "SPO", "POS", "PSO", "OSP", "OPS".
  7. Faça um programa que leia até 10 números inteiros do teclado, guarde-os em um vetor, e então remova dele todos os números que sejam negativos. Ao final, o vetor deve ser mostrado na tela. Experimente resolver esse problema criando uma função para limpar o vetor. Ex:
    #include <stdio.h>
    
    int limpa(int * v) {
      // aqui escreva o algorito que limpa o vetor guardado no parâmetro "v".
      // Esta função deve retornar a quantidade de números que sobraram no vetor (quer dizer, a quantidade de números não negativos)
    }
    
    int main() {
      int v[10]; // o vetor onde cabem 10 números inteiros
      int comprimento; // aqui será guardado o comprimento do vetor após a limpeza
    
      // lê 10 números do teclado
    
      // Limpa o vetor
      comprimento = limpa(v); 
    
      // mostra o vetor
    }
    

Lista de exercícios para avaliação sobre programação em C

A lista de exercícios deve ser resolvida e entregue impreterivelmente até dia 11/12 (6a feira), no início da aula. Nesse dia cada equipe deverá apresentar para o professor sua resolução da etapa 1 da Agenda, e responder a questionamentos sobre o programa apresentado. Poderão ser feitas questões como "para que serve essa variável ?", "por que você faz essa comparação aqui ?", ou "o que aconteceria se eu tirasse essa linha do programa ?". Em seguida, cada membro da equipe estará sujeito individualmente a perguntas sobre sua resolução da lista de exercícios.

A avaliação sobre linguagem C será composta pela etapa 1 da Agenda e pela lista de exercícios acima indicada.

Caso não se obtenha conceito para aprovação, na 2a feira 14/12 será feita uma prova prática.

04/12 a 10/12: Strings

Na última semana de aula serão trabalhados conceitos sobre strings na linguagem C.

12 e 14/12: Finalização

Uma maratona de avaliações, para aferir o aprendizado de todos quanto a conceitos básicos da linguagem C, e também sobre a participação no projeto da agenda.

Imagino que alguns estejam curiosos para ver resolvidos os exercícios da lista. Então resolvi deixar aqui a minha resolução ... lembrem que cada pessoa resolve de um jeito, então há muitas formas de resolver o mesmo problema ! Há alguns programas adicionais, que deixei disponíveis para quem se interessar:

  • Uma implementação do problema das Torres de Hanoi, que demonstra o caráter recursivo da solução.
  • O gerador de anagramas
  • Quatro versões para o desafio da lista 5 (problema do caminhoneiro). Esse é um problema clássico conhecido como Problema do Caixeiro Viajante, e tem muitas aplicações na vida real (ex: distribuição de combustíveis por caminhões-tanque, coleta de lixo). As versões estão numeradas da mais simples (caminhoneiro_v0.c) à mais sofisticada (caminhoneiro_v3.c). No início de cada programa há uma breve explicação sobre que melhoria ele possui em relação à versão anterior. Obs: para as versões caminhoneiro_v2.c e caminhoneiro_v3.c, experimente usar os arquivos tabela.txt e tabela2.txt (essa versões pedem um arquivo).

Por fim, BOAS FÉRIAS !!!