sexta-feira, 30 de janeiro de 2015

[P2] Nascimentos no Hospital Moinhos de Vento (HMV), Porto Alegre, de 16/12/2013 a 16/12/2014


Descrição do problema e origem dos dados

     Os dados a seguir contem os nascimentos no HMV, em Porto Alegre, de 16/12/2013 a 16/12/2014. Os dados são PÚBLICOS, e foram coletados diretamente a partir do site do hospital (Maternidade do Hospital Moinhos de Vento).

     São 3490 registros, com a seguinte estrutura (CSV, ou comma-separated-values, valores separados por vírgulas), com o seguinte cabeçalho:
DiaNascimento;DiaSemana;Gênero;NomeFilho;NomeMae;NomePai
DiaSemana Descrição
1 Domingo
2 Segunda-Feira
3 Terça-Feira
4 Quarta-Feira
5 Quinta-Feira
6 Sexta-Feira
7 Sábado

     O gênero pode ser M (masculino) ou F (feminino).

     Cabe ressaltar que o arquivo não representa de forma alguma o total de nascimentos do intervalo, pois só podem ser divulgados na Internet os dados dos pais que assinaram um termo específico para esta divulgação. Então, é fato que aconteceram muito mais do que 3490 nascimentos no período acima.

Arquivos necessários

Faça o download do arquivo neste link.

Questões interessantes deste projeto

     Questões que podem ser abordadas (muito provavelmente, você precisará utilizar a Tabela ASCII):
  1. como abrir este arquivo?
  2. quais estruturas de dados podem ser usadas?
  3. qual a linguagem de programação mais interessante de ser usada?
  4. como ler os dados do arquivo?
  5. como popular as estruturas de dados a partir do arquivo?
  6. consultas (é importante que você faça as consultas na ordem solicitada):
    1. quantas crianças existem no arquivo?
    2. informe quantas crianças não possuem o nome completo (apenas o primeiro nome)
    3. quantas crianças são do gênero masculino (M) e quantas são do gênero feminino (F)?
    4. quais dias nasceram mais crianças de um mesmo gênero?
    5. qual dia nasceu todas as crianças de um mesmo gênero?
    6. quantas pessoas possuem os caracteres ã,õ,ê,ô,ç,á,é,í,ó,ú,ñ no nome?
    7. quantas crianças possuem apóstrofos no nome?
    8. quais crianças (nome completo) não tem nome do pai associado?
    9. faça uma tabela mostrando o dia da semana e o número total de nascimentos ocorridos neste dia.
    10. qual é o nome da criança com mais caracteres? (conte apóstrofos e espaços entre os nomes)
    11. descubra os nomes únicos da listagem, ou seja, os que existem em apenas uma ocorrência, para nome do filho, mãe e pai
    12. faça uma lista com o primeiro nome da criança e o total de nascimentos (por exemplo, Ana=360, Maria=123, João=88, ...)
    13. faça uma lista que conta o total de nomes do arquivo, considerando-se apenas o nome do filho/mãe/pai e desconsiderando-se os acentos agudos ou circunflexos, por exemplo, Abelardo=455, Joaquim=12, Maria=67
    14. crie um novo arquivo contendo os nomes dos filhos/mães/pais abreviados, por exemplo, Tiago Silva da Silva ficaria Tiago S. da S. (cuidado com nomes com apóstrofos, com de/da/do/dos/das/etc, e outros casos especiais)
    15. informe, para cada nome, usando o seguinte formato: NomeFilho/TamanhoFilho;NomeMae/TamanhoMae;NomePai/TamanhoPai (onde Tamanho equivale ao total de caracteres de cada nome, incluindo os espaços)
    16. faça um novo arquivo sem cabeçalho, espaços, apóstrofos, letras com acentos (agudo e circunflexo) e ponto-e-vírgulas
    17. conte todos os tamanhos combinados de NomeFilho/NomeMãe/NomePai, ou seja, João Silva da Silva;Maria Silva;José da Silva possui 43 caracteres: quantos outros nomes combinados possuem 43 caracteres? E 20? E 30? (faça para todos os tamanhos e desconsidere os acentos agudos ou circunflexos)
    18. quantas crianças com o mesmo nome nasceram no mesmo dia? (mostre o dia e o nome e desconsidere os acentos agudos ou circunflexos)
    19. existem gêmeos na listagem? quais são os nomes das crianças?
    20. [DESAFIO] vamos tentar prever o sobrenome completo da criança a partir dos nomes dos pais (para aqueles que possuem nome da mãe e do pai completos no arquivo). Sua tarefa é pegar o nome do pai, combinar com o nome da mãe, tentar adivinhar o nome do filho e depois mostrar o nome do filho original e o nome do filho calculado por você, em um novo arquivo.
      1. proponha uma fórmula para calcular a distância entre estes dois nomes, mostrando que os nomes corretamente previstos são explicados pela fórmula e os nomes que ficaram diferentes também (dica: use a Tabela ASCII)
    21. [DESAFIO] quais dias nasceram mais pessoas do gênero masculino E do gênero feminino repetidos? (por exemplo: 3 Marias e 4 Josés no mesmo dia)
    22. [DESAFIO] construa uma fórmula para associar um identificador único a cada pessoa a partir do seu nome (e do nome da sua mãe, se necessário), garantindo que não existam dois identificadores repetidos mesmo se o nome e o nome da mãe forem repetidos)
      1. crie um arquivo novo e coloque o identificador no cabeçalho e para cada criança (no início, ou seja, na primeira coluna)

sexta-feira, 16 de janeiro de 2015

[P1] Numerônimo

Referência da Wikipédia: http://en.wikipedia.org/wiki/Numeronym

     Assim como existem os sinônimos e antônimos existem os "numerônimos". O nome deste blog é um numerônimo para o meu nome completo (Ricardo Melo Czekster), ou seja, entre o primeiro 'R' e o último 'R' do meu nome existem 17 caracteres (o que é interessante, pois muitas pessoas dizem que meu nome é impronunciável mas, acreditem, existem sobrenomes poloneses/russos bem piores de pronunciar que este...):

00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18
R i c a r d o m e l o c z e k s t e R

     Existem dois casos interessantes para se usar um numerônimo: (a) para formar uma abreviação que ao ser pronunciada se parece com a palavra, no caso de K9 ("kay" + "nine" ou canine unit, em inglês) e (b) para substituir as letras entre a primeira e a última e utilizar números para representar as letras omitidas, como é o caso de i18n, um numerônimo para InternationalizatioN ('I' seguido de 18 letras seguido de 'N').

     Outros numerônimos intressantes são W3C (World Wide Web Consortium), 101 para introdução básica a qualquer assunto (mais utilizado nos Estados Unidos da América, por exemplo, Curso: Jogos 101), ou mesmo quando números são utilizados para substituir letras similares (caso de H4CK3D para significar HACKED).

     Exemplos: a11y (accessibility), G20 (as vinte maiores economias do mundo) e Y2K (year 2000, bug do milênio, do ano 2000).

Introdução à Interface de Linha de Comando

     A Interface de Linha de Comando (Command Line Interface - CLI) é extremamente importante em programação. Através desta modalidade os programadores podem compilar programas que aceitam argumentos (ou parâmetros) fornecidos pela linha de comando (no GNU/Linux trata-se do terminal e no MS-Windows do cmd.exe).

     Uma curiosidade interessante: o Sistema Operacional UNIX foi escrito em C e usava extensivamente o conceito de programas com passagem de parâmetros.

Linguagem C

     A Linguagem C (e C++ também) oferece mecanismos para tratar a linha de comando através da função principal main.
// tudo após // na mesma linha não será lido pelo compilador //////////

// cabeçalho C necessário para a função printf
#include <stdio.h>

int main(int argc, char *argv[]) {
   int i;
   printf("Total de argumentos: %d\n", argc);
   for (i = 0; i < argc; i++) {
      printf("argv[%d] = %s\n", i, argv[i]);
   }
   return 0;
}
     Estamos aqui usando a função printf que imprime dados na tela, %d para representar a impressão de números inteiros, %s para imprimir textos (strings) e \n para realizar quebra de linha (return). O número de caracteres % na função indica o número de parâmetros necessários que devem ser chamados (observe as duas chamadas para printf e veja suas diferenças).

     Vamos dar o nome deste arquivo de teste.c e compilar através da linha de comando:
gcc teste.c -o teste
teremos um executável chamado teste (parâmetro de linha de comando -o). Observe que o gcc (GNU C Compiler, o compilador da Linguagem C) também aceita parâmetros pela linha de comandos. Caso -o não seja chamado, o gcc criará um executável chamado a.out (no GNU/Linux) e a.exe no MS-Windows.

     Neste ponto, basta executar o programa e observar a saída produzida, passando diferentes parâmetros:
./teste Isto eh um teste!
     O ./ acima é necessário apenas no GNU/Linux, e informa que deseja-se executar um programa do diretório corrente. Sem isso não funciona. A saída produzida é a seguinte:
Total de argumentos (argc): 5
argv[0] = teste
argv[1] = Isto
argv[2] = eh
argv[3] = um
argv[4] = teste!
     Mas porque 5 e não 4 argumentos de linha de comando? Porque em C, ele conta o nome do programa (no caso, teste).


     Mas e se quiséssemos usar um número da linha de comando no nosso programa, por exemplo, calcular a raiz quadrada de um número passado por parâmetro pela linha de comando. O programa só deve funcionar se for passado UM parâmetro. Como seria a implementação?
// função printf
#include <stdio.h>

// função exit(int status) - sair do programa
// função int atoi(char* v) - converte um texto para um inteiro
#include <stdlib.h>

// função double sqrt(double n) - raiz quadrada de um número (square root)
#include <math.h>

int main(int argc, char *argv[]) {
   int i;
   int num;           // número passado pela linha de comando
   double resposta;   // raiz quadrada do número

   if (argc != 2) {
      printf("Erro.");
      printf("Voce nao informou o numero de parametros necessarios.\n");
      printf("Tente novamente\n");
      printf("Uso:\n\t./teste NUMERO\n"); // \t insere TABULAÇÃO
      exit(1);
   }

   // converte um texto (a) para um inteiro (i)
   //     passando um texto por parâmetro: argv[1]
   num = atoi(argv[1]); // argv[1] pois argv[0] é o nome do programa
   resposta = sqrt(num);
   printf("A raiz quadrada de %d eh %2.2f\n", num, resposta);

   return 0;
}
     Chame este arquivo de raiz.c, compile e execute-o para diferentes valores.


     No entanto, uma pergunta ainda persiste: como C interpreta múltiplos parâmetros de linha de comando?

     Vamos supor que desejamos passar a seguinte sequência de caracteres por linha de comando:
./teste2 -o 1 -s international insolence intrusion
     Neste exemplo, gostaríamos que -o representasse o tipo da saída (aceitando apenas 0 ou 1), -s sendo mostrar os textos inseridos, seguidos dos textos que desejamos inserir.

     A Linguagem C, por utilizar char *argv[] (que é uma matriz de caracteres), considera a entrada como sendo:

argc=7 00 01 02 03 04 05 06 07 08 09 10 11 12 13
argv[0] 't' 'e' 's' 't' 'e' '2' '\0'              
argv[1] '-' 'o' '\0'                      
argv[2] '1' '\0'                        
argv[3] '-' 's' '\0'                      
argv[4] 'i' 'n' 't' 'e' 'r' 'n' 'a' 't' 'i' 'o' 'n' 'a' 'l' '\0'
argv[5] 'i' 'n' 's' 'o' 'l' 'e' 'n' 'c' 'e' '\0'        
argv[6] 'i' 'n' 't' 'r' 'u' 's' 'i' 'o' 'n' '\0'        

     Ou seja, argv[4][4]='r' e argv[0][5]='2' (lembre-se que é uma matriz de caracteres). Observe que \0 é um caractere de controle em C que representa o final da string. Observe também que os espaços entre os argumentos foram consumidos pelo programa.

Descrição do projeto
     Vamos implementar um programa que, dado qualquer texto da linha de comando, este calculará seu numerônimo. Por exemplo:
./numeronimo pneumonoultramicroscopicsilicovolcanoconiosis
irá produzir a seguinte saída:
Numerônimo: p43s
     Somente um parâmetro deverá ser passado para o programa. Como você faria este programa?

Questões interessantes deste projeto
  1. implemente o numerônimo de qualquer argumento passado por linha de comando (conforme acima), sendo que o programa só deve aceitar um argumento, resultado em erro caso contrário
  2. vamos supor que múltiplos parâmetros sejam passados na linha de comando
    1. faça um programa que mostra uma matriz de caracteres (com aspas simples) de todos os parâmetros, inclusive o \0
    2. implemente um programa que mostra o numerônimo destes múltiplos parâmetros, separando-os por linha
    3. implemente um programa que concatena (junta as strings separadas por espaço) todos os múltiplos parâmetros fornecidos (se foram fornecidos) em um único numerônimo
  3. considere que a=1,b=2,c=3,d=4 e assim por diante, até z=26 (faça funcionar para o alfabeto: abcdefghijklmnopqrstuvwxyz); faça um programa que acumula estes valores para calcular o numerônimo ao invés de apenas contar o total de caracteres entre o primeiro e o último caractere
    1. faça uma opção de linha de comando (-c) que calcula o numerônimo conforme o alfabeto acima (caso -c não for informado, ele retorna o numerônimo sem calcular conforme o alfabeto)
  4. [DESAFIO] separe os números conforme o total de vogais e o total de consoantes, por exemplo, internationalization, ficaria: i9-9n, pois tem 9 vogais e 9 consoantes e pneumonoultramicroscopicsilicovolcanoconiosis ficaria p23-20s (dica: teste os caracteres do parâmetro individualmente, por exemplo: if (argv[5][0] == 'a')...)

sexta-feira, 2 de janeiro de 2015

[P0] Compilação, Estruturas de Dados, Arquivos e Funções


     Tudo que é necessário para começar a programar os problemas são descritos a seguir.
     A linguagem escolhida será a Linguagem C Simplificada (minha definição), com a utilização de um subconjunto de comandos, tipos e estruturas de dados da Linguagem C original.

OBSERVAÇÃO: a Linguagem C é muito mais rica e completa do que a versão simplificada vista aqui - investigue outros detalhes da linguagem por conta própria!


Programa Principal

     Para começar a programar em C é fácil (ao contrário do que dizem). Basta baixar a versão binária (última) do CodeBlocks (um port do GCC - GNU C Compiler para Windows) ou então programar diretamente na linha de comando (no GNU/Linux o GCC (GNU C Compiler) vem com qualquer distribuição). O caractere '\n' equivale a uma nova linha. Aspas simples (' ') são caracteres e são apenas mostradas na saída (na tela).
      SÓ PODE HAVER UMA OCORRÊNCIA DE int main() EM UM PROJETO. Um executável sempre inicia sua execução pela função main.
#include <stdio.h>

int main() {
   printf("Me recuso a escrever 'hello world!' aqui!\n");
}
     Salve este arquivo como main.c e compile-o, através da IDE do CodeBlocks ou diretamente na linha de comando:
gcc main.c
     Este comando irá produzir um executável chamado a.out
     Para alterar o nome de saída do executável, utilize a diretiva de compilação -o, por exemplo:
gcc main.c -o main.exe
     Para executar, basta chamar o main.exe (clicando duas vezes), ou da linha de comando (no MS-Windows, basta ir na pasta, no GNU/Linux, tem que ir para a pasta e ainda colocar ./main.exe para executar, além de não precisar necessariamente terminar o executável com .exe):
./main.exe

Comandos de seleção e repetição

     Vamos utilizar os comandos de seleção (if () {}-then {}-else {}) e repetição (for(inicio;teste;incremento) {}) sempre que possível com início e final de bloco (chaves, {}).
...
  int i;
  int a = 5;
  int b = 0;
...
  if (a == 5) {     // se a for igual a 5
      a = a * 5;    // então a agora vale 5 * 5 = 25
  } else {          // senão
      a = a * 10;   // a vale 5 * 10 = 50
  }
...
  for (i=0; i < a; i++) { // para i de 0 até a (que vale 25, neste caso)
     b = (b + a) * 2;
  }
...
     Os parênteses alteram a ordem de execução dos comandos (primeiro é calculado o que está entre parênteses). Sempre inicie as variáveis em C! Se b não fosse iniciado para zero, muitos problemas iriam ocorrer!!


Declaração de constantes

     Para declarar uma constante, basta utilizar a diretiva de pré-compilação #define de C:
// tudo após // não será lido pelo compilador, ou seja, é um *comentário*
#include <stdio.h>

#define DIASDASEMANA 7

int main() {
   printf("Dias da Semana = %d\n", DIASDASEMANA);
}
     C irá pré-compilar seu programa e substituir, em tempo de compilação, as ocorrências de DIASDASEMANA pelo número 7 (por isso esta etapa é conhecida por pré-compilação). Como boa prática e estilo de programação, sempre utilize letras maiúsculas para constantes. Programe sempre de forma clara e concisa.
#include <stdio.h>

#define DIASDASEMANA 7
#define DOMINGO      0
#define SEGUNDAFEIRA 1
#define TERCAFEIRA   2
#define QUARTAFEIRA  3
#define QUINTAFEIRA  4
#define SEXTAFEIRA   5
#define SABADO       6

int main() {
   int dia = DOMINGO;
   printf("Dias da Semana = %d\n", DIASDASEMANA);
   ...
   if (dia == SABADO) { // BEM melhor que if (dia == 6) {
      ...
   }
   ...
}


Declaração de variáveis

     Em C, declaram-se variáveis sempre no início das funções. Os tipos a serem utilizados são: inteiros, reais e caracteres (inicialmente). Utilize sempre nomes de variáveis relevantes para o seu problema.
     A função printf sempre pede uma string de caracteres em aspas duplas (" ").
#include <stdio.h>

int main() {
   // declaração de números INTEIROS, negativos, zero ou positivos
   int acum;

   // declaração de números REAIS, em ponto flutuante, 
      // com alta precisão, usando-se base e mantissa
   double media;

   // declaração de CARACTERES, representados em C 
      // por números inteiros de 0 a 255 e aspas simples ('')
   char carac;

   printf("Funcionou!");
}
     A seguir, são mostrados os números representados por cada tipo simplificado visto acima:
 tipo sizeof(tipo)Intervalo
 char 1 byte-128 até 127
 int 4 bytes-2.147.483.648 até 2.147.483.647
 double 8 bytes2,3E-308 até 1,7E+308
#include <stdio.h>
int main() {
   printf("char : %d bytes\n", sizeof(char));
   printf("int : %d bytes\n", sizeof(int));
   printf("double : %d bytes\n", sizeof(double));
}
     Estes valores podem ser diferentes para sua arquitetura. O que são os caracteres %? Como funciona a impressão na tela em C? Veja a seguir.


Impressão formatada na tela

     Para se imprimir as variáveis criadas usando formatação, deve-se usar o caractere reservado %:
#include <stdio.h>

int main() {
   // declaração de números INTEIROS, negativos, zero ou positivos
   int acum;

   // declaração de números REAIS, em ponto flutuante, 
      // com alta precisão, usando-se base e mantissa
   double media;

   // declaração de caracteres, representados em C
      // por números de 0 a 255, e aspas simples ('')
   char carac;

   acum = 100;
   media = 55.22;
   carac = 'A';

   printf("O número inteiro é igual a %d ", acum);
   printf("e o real é igual a %2.2f e o ", media);
   printf("caractere é igual a %c.\n", carac);

   // ou descomente a linha a seguir...
   // printf("Inteiro=%d, Real=%f, Caractere=%c\n", acum, media, carac);
}
     Cada caractere % corresponde à um parâmetro que deve ser passado para a função printf (após as aspas duplas).

     A impressão pode ser utilizada para mostrar os valores das variáveis em momentos chave da programação (debugging).


Entrada de Dados para programas

     A entrada de dados pode ser feita de duas formas (de forma introdutória): via teclado e função scanf ou via arquivo e funções especiais para tratar com arquivos. Ambas são vistas em detalhes a seguir.

Entrada de Dados via teclado

     A entrada de dados via teclado envolve o uso de ponteiros, por isso que deve-se passar o endereço das variáveis por parâmetro. Por exemplo:
#include <stdio.h>

int main() {
   int ano;
   printf("Informe o ano corrente: ");
   scanf("%d", &ano);
   printf("Você digitou %d para o ano corrente.\n", ano);
}
     A entrada de dados via scanf segue algumas regras da função printf, por exemplo, deve-se fornecer o tipo da variável de entrada (no caso, %d). Uma importante diferença é passar o endereço da variável, através do caractere &, conforme mostrado no exemplo. Observe que apenas a função scanf pede o endereço, a função printf não utiliza este artifício (pelo menos não agora). Isso será melhor explicado quando falarmos em ponteiros e passagem de parâmetros para funções por referência.

Entrada de Dados via arquivos

     A entrada de dados via arquivos é muito mais interessante do ponto de vista da programação, pois permite lidar com múltiplos dados sem ter que ficar lendo do teclado. Esta forma de leitura será amplamente utilizada neste blog.

     Para ler múltiplas linhas de um arquivo texto e colocar estes caracteres lidos em variáveis, vamos precisar utilizar o conceito de vetores (ou arrays). Vetores são tipos homogêneos, que guardam uma série de variáveis do mesmo tipo. Por exemplo, um caractere chamado genero que guarda o gênero de uma pessoa poderia usar um caractere, por exemplo:
...
char genero;
genero = 'F';   // gênero de UM aluno
//ou
genero = 'M';
...
Pergunta: se fosse necessário guardar o gênero de uma turma de 20 alunos, como poderíamos fazer isso?
Resposta: poderíamos criar 20 variáveis do tipo char, cada variável correspondendo a um aluno diferente.
char genero1,genero2,genero3,genero4,genero5,...,genero20;
     Esta declaração, no entanto, não é elegante. Não deseja-se apenas programar; deseja-se programar bem.

     Poderíamos criar um vetor de caracteres de tamanho 20 onde cada posição do vetor guarda um aluno, da seguinte maneira:
char generos[20];

generos[0] = 'F';  // gênero do primeiro aluno
generos[1] = 'M';  // gênero do segundo aluno
generos[2] = 'M';  // gênero do terceiro aluno
generos[3] = 'F';  // gênero do quarto aluno
...
generos[19] = 'M';  // gênero do vigésimo aluno
     Esta é a principal vantagem de se utilizar vetores (ou arrays), ou tipos homogêneos, para se representar dados. O conceito de índice é necessário para percorrer as posições do vetor e realizar computações. Em C, os índices sempre iniciam em zero e vão até TAMANHO_DO_VETOR - 1. É comum o uso de estruturas de repetição para se trabalhar com vetores.

     Em C, um vetor de caracteres é chamado de string. As strings são terminadas por um caractere especial chamado '\0', que indica o final dos caracteres válidos em uma string.
#include <string.h>
...
char nome[10];
nome[0]='R';
nome[1]='i';
nome[2]='c';
nome[3]='a';
nome[4]='r';
nome[5]='d';
nome[6]='o';
nome[7]='\0';
...
// que equivale a (usando funções da biblioteca string.h) a: 
strcpy(nome, "Ricardo"); // a função já coloca o '\0' no final da string

// para saber o TAMANHO de uma string, use a função strlen:
int tam = strlen(nome); // retorna o tamanho de uma string, até o '\0'

// para IMPRIMIR um vetor de caracteres, utilize %s:
printf("Nome: %s\n", nome);
     Observe que foi instanciada estaticamente uma variável de tamanho 10, mas somente 8 posições foram utilizadas (de 0 a 7, incluindo o '\0' sempre); em C, isso é permitido, as posições não inicializadas contém 'lixo' de memória, ou seja, valores da memória).

     Para criação de arquivos, iremos utilizar uma série de conceitos:
  • instanciar um vetor de caracteres (line[512] ou seja, uma variável chamada line de tamanho 512 caracteres) para guardar uma linha do arquivo (este será o buffer que lê linha a linha do arquivo)
  • abrir um arquivo para leitura (usando o parâmetro "r"), usando a função fopen
  • percorrer cada linha do arquivo até encontrar o sinal de final de arquivo (EOF - end of file)
  • mostar a linha lida na tela (usando a função printf)
  • fechar o arquivo (usando a função fclose)

     Sempre abra (fopen) e feche (fclose) um arquivo. Não observar esta regra pode ser bem problemático. Observe o exemplo a seguir. Desconsidere neste momento a utilização de ponteiros (FILE* fp), isto será explicado no futuro. A função sizeof informa o tamanho que foi alocado para a variável. A função fgets (file get string) retorna na variável line uma linha do arquivo. Observe o código a seguir:
#include <stdio.h>

int main() {
    FILE* fp;
    int len = 0;
    // buffer para guardar uma linha (até encontrar o '\n')
    char line[512];
    fp = fopen("teste.txt", "r");
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("Linha = %s\n", line);
    }
    fclose(fp);
}
     De forma bastante introdutória, isto é tudo que precisamos para trabalhar com arquivos. Um buffer de caracteres de tamanho 512 foi criado para guardar os dados de cada linha. Pode parecer exagerado (e é...) mas neste momento não queremos nos preocupar com gasto de memória. Cada linha é um vetor de caracteres (uma string), então estes podem ser processados caractere a caractere (e um comando de repetição do tipo for):
...
   int i;
   for (i=0; i < 512; i++) {
      printf("Caractere na posição %d = %d.\n", i, line[i]);
   }
...
     Como o comando for vai de zero a 512, você verá bastante 'lixo' na tela (isso é normal em C...). A forma correta de se percorrer uma string (assumindo que sempre irá existir um '\0' para auxiliá-lo, é da seguinte forma:
...
   #include <string.h>
...
   int i;
   for (i=0; i < strlen(line); i++) {
      printf("Caractere na posição %d = %d.\n", i, line[i]);
   }
...
Funções em C
     Uma função é uma maneira de modularizar um programa, ou seja, quebrá-lo em partes menores. A função principal de um programa é a função main, vista anteriormente.
     Podemos escrever nossas próprias funções, por exemplo:
#include <stdio.h>
#include <math.h>

double raizquadrada(double valor) {
   double r;
   r = sqrt(valor);
   return (r);
}

int main() {
   double numero = 112.5;
   double resposta;

   resposta = raizquadrada(numero);
   printf("Raiz de %f = %f\n", numero, resposta);
}
     A função raizquadrada recebe um valor por parâmetro e faz uma chamada a uma outra função de nome sqrt (esta, da biblioteca math.h, que foi incluída no programa). A variável valor, neste caso, é um parâmetro da função raizquadrada e esta função retorna uma variável do tipo double.


OBSERVAÇÃO IMPORTANTE: as funções SEMPRE devem ser declaradas ANTES da função main. Pelo menos um protótipo (uma declaração da função com nome e tipos dos parâmetros) para a função deve existir antes da função main, conforme a seguir:
#include <stdio.h>
#include <math.h>

double raizquadrada(double valor); // PROTÓTIPO DA FUNÇÃO raizquadrada

int main() {
   double numero = 112.5;
   double resposta;

   resposta = raizquadrada(numero);
   printf("Raiz de %f = %f\n", numero, resposta);
}

// IMPLEMENTAÇÃO DA FUNÇÃO raizquadrada após a função main
double raizquadrada(double valor) {
   double r;
   r = sqrt(valor);
   return (r);
}

     Para se trabalhar com funções que passam vetores por parâmetro, deve-se utilizar ponteiros.
#include <stdio.h>
#include <string.h>

// conta o total de ocorrências do caractere carac na string str
// observe a passagem de parâmetro via ponteiro da string str
int totaldecaracteres(char carac, char* str) {
   int resposta;
   int i;
   resposta = 0;
   for (i=0; i < strlen(str); i++) {
      if (str[i] == carac)
         resposta = resposta + 1;
   }
   return (resposta);
}

int main() {
   char nome[20];
   int total;
   strcpy(nome, "aarao");

   total = totaldecaracteres('a', nome);
   printf("Total de 'a' em %s = %f\n", nome, total);
}
     Observe que não foi necessário passar o endereço da variável nome, pois esta variável é um vetor de caracteres, e vetores possuem uma referência implícita, não necessitando colocar o caractere &. É como se escrevessemos o seguinte para a função:
...
   total = totaldecaracteres('a', &nome[0]);
...
     Ou seja, estamos passando por referência o primeiro elemento do vetor de caracteres chamado nome.

Exemplo de estudo: lista dos papas
     Observe o arquivo chamado lista-de-papas.txt que contém uma listagem dos papas desde o início do cristianismo até os dias atuais (observação: preferências religiosas à parte, este exemplo não tem nenhum cunho religioso, trata-se apenas de uma listagem com dados interessantes).

 Faça o download do arquivo de dados neste link.

     O arquivo possui a lista dos papas (um por linha), com o seguinte cabeçalho (seis colunas):
Nome;Local Nascimento;Ano Inicial;Ano Final;Nome em latim;Duração (dias)
     As primeiras cinco linhas contém os seguintes dados (apenas para se ter uma ideia dos conteúdos do arquivo):
São Pedro;Betsaida;32;67;Petrus;13514
São Lino;Túscia;67;76;Linus;4383
Santo Anacleto;Roma;76;88;Anacletus;3287
São Clemente I;Roma;88;97;Clemens;3288
Santo Evaristo;Grécia;97;105;Evaristus;3651
     O objetivo é ler o arquivo e processar todas as linhas, separando cada coluna em uma variável específica conforme os ';' (delimitador de coluna). Inicialmente, a leitura é de caracteres. Quando for necessário, os caracteres (vetores) deverão ser convertidos para valores inteiros.
     Para trabalhar com este arquivo, a ideia inicial é abrir, processar cada linha e fechar o arquivo. Cada linha será lida em um vetor de caracteres, que serão 'quebrados' a cada caractere ';'.
#include <stdio.h>
#include <string.h>

int main() {
    FILE* fp;
    int i;
    int idx;
    int coluna;
    int linha = 0;
    // buffer para guardar uma linha (até encontrar o '\n')
    char line[512];
    // buffer auxiliar para guardar cada coluna
    char buffer[50];
    fp = fopen("lista-de-papas.txt", "r");
    while (fgets(line, sizeof(line), fp) != NULL) {
        idx = 0;
        coluna = 0;
        printf("Linha: %d\n", linha);
        // para cada linha, quebra as colunas conforme o caractere ';'
        for (i=0; i < strlen(line); i++) {
           // se o caractere não for ';', vai adicionando no buffer...
           if (line[i] != ';') {
              buffer[idx] = line[i];
              idx = idx + 1;
           } else { // se for, então coloca '\0', zera idx e mostra
              buffer[idx] = '\0';
              printf("Coluna[%d] = %s\n", coluna, buffer);
              coluna = coluna + 1;
              idx = 0;
           }
        }
        linha = linha + 1;
    }
    fclose(fp);
}
DISSEQUE este código-fonte. Entenda CADA LINHA. Modifique este código-fonte.
Não continue a programar sem entender tudo que está neste código-fonte.

     Uma vez que temos os dados em caracteres, podemos converte-los para inteiros, usando a função atoi da biblioteca stdlib.h, por exemplo:
...
#include <stdlib.h>
...
int ano_inicial;
char buffer[128];
...
ano_inicial = atoi(buffer);
printf("Ano inicial = %d\n", ano_inicial);
...

Questões interessantes deste projeto
     Implemente as atividades a seguir utilizando os conceitos vistos anteriormente, tais como constantes, variáveis, declaração de vetores de caracteres e inteiros e arquivos (somente leitura).
  1. quantos papas existem no arquivo?
  2. mostre uma listagem mostrando o nome do papa em latim e seu local de nascimento
  3. quantos 'santos' existem? e quantos 'são'? quantos ainda não foram canonizados? qual é a regra que determina se o papa (se for canonizado) será um 'santo' ou um 'são'?
  4. implemente um programa que mostra quantos anos durou cada papado
  5. faça um programa que conta e mostra o total de 'edições' de papas que existiram até o momento, por exemplo: Bento=4; Clemente=12; João Paulo=5 (valores de exemplo)
  6. mostre o total de papas por cidade, contando quantos papas cada cidade produziu na história da igreja
  7. mostre quanto tempo demorou o menor e o maior papado da história e as informações dos papas em questão (em caso de empate, mostre todos)
  8. qual o valor médio, em dias, que cada papa permaneceu no seu papado? E em anos?
  9. [DESAFIO] faça uma função onde se passa um vetor de caracteres (correspondendo a uma linha de um arquivo), um caractere que corresponde ao separador utilizado (aqui, no caso, ';', mas poderia ser qualquer outro caractere) e um índice de coluna e a função retorna um char* (um ponteiro para uma string) com os dados da coluna.