Maratona de Programação: Encerrando Minha Participação

Padrão

Participei ontem, dia 17 de setembro, da etapa regional da Maratona de Programação. Pra quem não conhece, a Maratona é uma competição voltadas pra estudantes de Computação e Ciências Matemáticas, que consiste em desenvolver programas que resolvam aos problemas propostos. A equipe que resolver mais problemas durante as 5 horas de competição, é a vencedora. Aparentemente simples, a competição envolve muito, mas muito mesmo da sua capacidade de abstração e do uso de conhecimentos em diversas áreas, principalmente da matemática.

Participei pela equipe da FAI, na etapa regional realizada em Passos. Neste ano conseguimos um ótimo resultado, terminando na posição, ficando atrás de um time da Unifei. Como a sede em que participamos contava com apenas uma vaga para a final brasileira, infelizmente não nos classificamos… mas valeu! Esta foi minha 3ª participação no evento (em 2009 como reserva, e em 2010 e 2011 como competidor), e encerrando com chave de prata ouro. 😀

Equipe da FAI na Maratona de Programação 2011

Gostaria de agradecer imensamente os nobres guerreiros que batalharam junto ontem durante o dia todo. Grande abraço pro William, Aduílio e Leandro, além, claro, ao nosso coach Roberto Porto pelo apoio!

Valeu! 😀

Arquivos

Padrão

Olá pessoal! Aqui estamos nós novamente. Dessa vez, para o último post da série de programação para iniciantes. Sim, sim, após 18 posts encerramos essa primeira série. Mas é por uma boa causa: em breve começaremos a série de posts sobre POO (Programação Orientada a Objetos)! Se você acompanhou essa série desde o início, poderá seguir a próxima série como um complemento desta.

Mas sem mais demoras, vamos ao tema desse último post. Nele falaremos sobre a gravação de dados em arquivos, pois de que vale um bom aplicativo se ele não guarda dados? Fechou o programa, perdeu tudo?? Negativo. Após esse post, você poderá criar programas realmente funcionais, gravando dados em arquivos (binários ou de texto).

Antes de começarmos a ver na programação como as coisas funcionam, precisamos entender o que é um arquivo. O arquivo nada mais é que um conjunto sequencial de dados em sua forma binária (0 e 1). Na programação, usualmente salvamos o arquivo em dois formatos diferentes: o formato ASCII e o formato binário. No formato ASCII, os dados são gravados na forma de caracteres, ou seja, caso você abra o arquivo em um editor de textos (como o Bloco de Notas ou o Gedit), você poderá ver os dados normalmente. Tais tipos de arquivos são úteis ao exportar um log, por exemplo. Assim, caso o usuário abra o arquivo, poderá ler os dados normalmente. Já no tipo binário, os dados são gravados na forma que se encontram na memória. Por exemplo, vamos supor que um inteiro seja representado por 0x0001A047 na memória. E vamos supor que isso valha 80. Ao gravar no arquivo, você gravará o correspondente binário de 80 (alguma coisa parecida com esse número hexadecimal). Dessa forma, se alguém abrir o arquivo com um editor de textos, não conseguirá distinguir muita coisa. Esse tipo de arquivo é bom para gravarmos os dados que o aplicativo irá manipular, dificultando alterações externas por usuários “espertinhos”. Ficou clara a diferença?

Na programação, os arquivos são controlados geralmente por um ponteiro (em C) ou por uma estrutura própria (em Pascal). Ao abrir um arquivo, o ponteiro localiza-se automaticamente no início do arquivo. A partir daí, vamos então lendo os dados, especificando a quantidade de informação a ser lida, até chegar ao fim do arquivo. Tal condição é obtida através de uma função que indica esse fim do arquivo.

Para começarmos a colocar a mão na massa, vamos fazer diferente dessa vez. Vamos criar um programa completo, em Pascal e em C, que demonstrará todos os conceitos que pretendo passar neste post. Será uma agenda eletrônica, que guardará o nome, a data de aniversário e o telefone do contato. Nesse aplicativo, será possível inserir um contato, buscar um contato, editar um contato e excluir um contato (conceito básico de CRUD – Create, Read, Update e Delete). Este aplicativo também será um tipo de revisão geral de tudo que vimos até aqui! Logo em seguida, vou explicá-lo passo a passo.

Primeiro, o programa em Pascal:

(*
 * Aplicativo de Agenda Eletrônica - versão em Pascal
 * Desenvolvido por Rafael D. Toledo
 * (rafaeldtoledo@gmail.com)
 * Baixado de http://striker07.wordpress.com
 * Compartilhe, use à vontade, mas cite a fonte! =)
 *)

program
  agendaEletronica;

uses
  crt, sysutils;

type
  contato = record
    nome: string[30];
	diaAniversario: integer;
	mesAniversario: integer;
	telefone: string[15];
  end;

function exibirMenu: integer;
var
  opcao: integer;
begin
  clrscr;

  writeln('     AGENDA ELETRONICA - striker07.wordpress.com');
  writeln;
  writeln(' 1. Inserir Contato');
  writeln(' 2. Buscar Contato');
  writeln(' 3. Atualizar Telefone');
  writeln(' 4. Excluir Contato');
  writeln(' 5. Encerrar');
  writeln;
  write(' Digite a opcao desejada: ');
  readln(opcao);

  exibirMenu := opcao;
end;

procedure gravarContato;
var
  meuContato: contato;
  arquivo: file of contato;
begin
  clrscr;

  writeln('     AGENDA ELETRONICA - INSERIR CONTATO');
  writeln;
  write(' Nome: ');
  readln(meuContato.nome);
  write(' Dia do Aniversario: ');
  readln(meuContato.diaAniversario);
  write(' Mes do Aniversario: ');
  readln(meuContato.mesAniversario);
  write(' Telefone: ');
  readln(meuContato.telefone);

  assign(arquivo, 'contatos.dat');
  {$I-} reset(arquivo); {$I+};
  if ioresult <> 0 then
    rewrite(arquivo);

  if filesize(arquivo) > 0 then
    seek(arquivo, filesize(arquivo));

  write(arquivo, meuContato);
  close(arquivo);
end;

procedure buscarContato;
var
  encontrado: boolean;
  busca: string[30];
  meuContato: contato;
  arquivo: file of contato;
begin
  encontrado := false;
  clrscr;

  writeln('     AGENDA ELETRONICA - BUSCAR CONTATO');
  writeln;
  write(' Nome do Contato: ');
  readln(busca);

  assign(arquivo, 'contatos.dat');
  {$I-} reset(arquivo); {$I+};
  if ioresult <> 0 then
  begin
    writeln;
	write(' Nada encontrado no arquivo!');
	sleep(1000);
  end
  else
  begin
    seek(arquivo, 0);
    while not eof(arquivo) do
    begin
      read(arquivo, meuContato);
	  if meuContato.nome = busca then
	  begin
	    encontrado := true;
	    writeln;
	    writeln(' Nome: ', meuContato.nome);
	    writeln(' Aniversario: ', meuContato.diaAniversario, '/', meuContato.mesAniversario);
	    write(' Telefone: ', meuContato.telefone);
	    sleep(2000);
	  end;
    end;

	if encontrado = false then
	begin
	  writeln;
	  write(' Contato nao encontrado!');
	  sleep(1000);
	end;

	close(arquivo);
  end;
end;

procedure atualizarTelefone;
var
  encontrado: boolean;
  nome: string[30];
  telefone: string[15];
  contatoTemporario: contato;
  arquivoAntigo, arquivoAtualizado: file of contato;
begin
  encontrado := false;

  clrscr;

  writeln('     AGENDA TELEFONICA - ATUALIZAR TELEFONE');
  writeln;
  write(' Nome do Contato: ');
  readln(nome);
  write(' Novo Telefone: ');
  readln(telefone);

  assign(arquivoAntigo, 'contatos.dat');
  {$I-} reset(arquivoAntigo); {$I+};
  if ioresult = 0 then
  begin
    assign(arquivoAtualizado, 'temp.dat');
	{$I-} rewrite(arquivoAtualizado); {$I+};
	if ioresult <> 0 then
	begin
	  writeln;
	  write(' Falha na atualizacao do contato!');
	  close(arquivoAntigo);
	  sleep(1000);
	end
	else
	begin
	  seek(arquivoAntigo, 0);
	  while not eof(arquivoAntigo) do
	  begin
	    read(arquivoAntigo, contatoTemporario);
		if nome = contatoTemporario.nome then
		begin
		  contatoTemporario.telefone := telefone;
		  encontrado := true;
		end;
		write(arquivoAtualizado, contatoTemporario);
	  end;
	  close(arquivoAntigo);
	  close(arquivoAtualizado);
	  erase(arquivoAntigo);
	  rename(arquivoAtualizado, 'contatos.dat');

	  writeln;
	  if encontrado then
	    write(' Atualizacao realizada com sucesso!')
	  else
	    write(' Nao foi possivel encontrar o contato...');
	  sleep(1000);
	end;
  end
  else
  begin
    writeln;
	write(' Nenhum contato encontrado!');
	sleep(1000);
  end;
end;

procedure excluirContato;
var
  encontrado: boolean;
  nome: string[30];
  contatoTemporario: contato;
  arquivoAntigo, arquivoAtualizado: file of contato;
begin
  encontrado := false;

  clrscr;

  writeln('     AGENDA TELEFONICA - EXCLUIR TELEFONE');
  writeln;
  write(' Nome do Contato: ');
  readln(nome);

  assign(arquivoAntigo, 'contatos.dat');
  {$I-} reset(arquivoAntigo); {$I+};
  if ioresult = 0 then
  begin
    assign(arquivoAtualizado, 'temp.dat');
	{$I-} rewrite(arquivoAtualizado); {$I+};
	if ioresult <> 0 then
	begin
	  writeln;
	  write(' Falha na exclusao do contato!');
	  close(arquivoAntigo);
	  sleep(1000);
	end
	else
	begin
	  seek(arquivoAntigo, 0);
	  while not eof(arquivoAntigo) do
	  begin
	    read(arquivoAntigo, contatoTemporario);
		if nome <> contatoTemporario.nome then
		begin
		  write(arquivoAtualizado, contatoTemporario);
		end
		else
		  encontrado := true;
	  end;
      close(arquivoAntigo);
      close(arquivoAtualizado);

      erase(arquivoAntigo);
	  rename(arquivoAtualizado, 'contatos.dat');

	  writeln;
	  if encontrado then
	    write(' Exclusao realizada com sucesso!')
	  else
	    write(' Nao foi possivel encontrar o contato...');
	  sleep(1000);
	end;
  end
  else
  begin
    writeln;
	write(' Nenhum contato encontrado!');
	sleep(1000);
  end;
end;

var
  opcao: integer;

begin
  repeat
    opcao := exibirMenu;

	case opcao of
	  1: gravarContato;
	  2: buscarContato;
	  3: atualizarTelefone;
	  4: excluirContato;
	  5:
	    begin
		  writeln;
	      write(' Encerrando...');
		end
	  else
	    begin
		  writeln;
		  write(' Opcao invalida!');
		end
	end;
	sleep(1000);
  until opcao = 5;
end.

E em C:

/*
 * Aplicativo de Agenda Eletrônica - versão em C
 * Desenvolvido por Rafael D. Toledo
 * (rafaeldtoledo@gmail.com)
 * Baixado de http://striker07.wordpress.com
 * Compartilhe, use à vontade, mas cite a fonte! =)
 */

#include <stdio.h>
#include <stdlib.h> // Para system() e sleep() (Linux)
#include <string.h>
#include <windows.h> // Para Sleep() - somente se for Windows

struct contato
{
    char nome[30];
    int diaAniversario;
    int mesAniversario;
    char telefone[15];
};

int exibirMenu(void);
void gravarContato(void);
void buscarContato(void);
void atualizarTelefone(void);
void excluirContato(void);

int main(void)
{
    int opcao;

    do
    {
        opcao = exibirMenu();

        switch (opcao)
        {
        case 1:
            gravarContato();
            break;
        case 2:
            buscarContato();
            break;
        case 3:
            atualizarTelefone();
            break;
        case 4:
            excluirContato();
            break;
        case 5:
            printf("\n Encerrando...");
            break;
        default:
            printf("\n Opcao invalida!");
        }
        Sleep(1000); // Se estiver usando Linux, é sleep(1);
    } while (opcao != 5);

    return 0;
}

int exibirMenu(void)
{
    int opcao;

    system("cls"); // Se estiver usando Linux, é system("clear");

    printf("\tAGENDA ELETRONICA - striker07.wordpress.com\n\n");
    printf(" 1. Inserir Contato\n");
    printf(" 2. Buscar Contato\n");
    printf(" 3. Atualizar Telefone\n");
    printf(" 4. Excluir Contato\n");
    printf(" 5. Encerrar\n\n");
    printf(" Digite a opcao desejada: ");
    scanf("%d", &opcao);

    return opcao;
}

void gravarContato(void)
{
    struct contato meuContato;
    FILE *arquivo;

    system("cls"); // Se estiver usando Linux, é system("clear");

    printf("\tAGENDA ELETRONICA - INSERIR CONTATO\n\n");
    printf(" Nome: ");
    fflush(stdin);
    gets(meuContato.nome);
    printf(" Dia do Aniversario: ");
    scanf("%d", &meuContato.diaAniversario);
    printf(" Mes do Aniversario: ");
    scanf("%d", &meuContato.mesAniversario);
    printf(" Telefone: ");
    fflush(stdin);
    gets(meuContato.telefone);

    arquivo = fopen("contatos.dat", "ab");
    if (!arquivo)
    {
        arquivo = fopen("contato.dat", "wb");
    }

    fwrite(&meuContato, 1, sizeof(struct contato), arquivo);

    fclose(arquivo);
}

void buscarContato(void)
{
    int encontrado = 0;
    char busca[30];
    struct contato meuContato;
    FILE *arquivo;

    system("cls"); // Se estiver usando Linux, é system("clear");

    printf("\tAGENDA ELETRONICA - BUSCAR CONTATO\n\n");
    printf(" Nome do Contato: ");
    fflush(stdin);
    gets(busca);

    arquivo = fopen("contatos.dat", "rb");

    if (arquivo)
    {
        fread(&meuContato, 1, sizeof(struct contato), arquivo);
        while (!feof(arquivo))
        {
            if (strcmp(busca, meuContato.nome) == 0)
            {
                printf("n Nome: %s\n", meuContato.nome);
                printf(" Aniversario: %d/%d\n", meuContato.diaAniversario, meuContato.mesAniversario);
                printf(" Telefone: %s", meuContato.telefone);
                Sleep(2000); // Se estivar usando Linux, é sleep(2);
                encontrado = 1;
                break;
            }
            fread(&meuContato, 1, sizeof(struct contato), arquivo);
        }

        if (!encontrado)
        {
            printf("\n Contato nao encontrado!");
            Sleep(1000); // Se estivar usando Linux, é sleep(1);
        }
        fclose(arquivo);
    }
    else
    {
        printf("\n Nada encontrado no arquivo!");
        Sleep(1000); // Se estivar usando Linux, é sleep(1);
    }
}

void atualizarTelefone(void)
{
    int encontrado = 0;
    char nome[30], telefone[15];
    struct contato contatoTemporario;
    FILE *arquivoAntigo = NULL, *arquivoAtualizado = NULL;

    system("cls"); // Se estiver usando Linux, é system("clear");

    printf("\tAGENDA TELEFONICA - ATUALIZAR TELEFONE\n\n");
    printf(" Nome do Contato: ");
    fflush(stdin);
    gets(nome);
    printf(" Novo Telefone: ");
    fflush(stdin);
    gets(telefone);

    arquivoAntigo = fopen("contatos.dat", "rb");
    if (arquivoAntigo)
    {
        arquivoAtualizado = fopen("temp.dat", "wb");
        if (!arquivoAtualizado)
        {
            printf("\n Falha na atualizacao do contato!");
            Sleep(1000); // Se estivar usando Linux, é sleep(1);
            return;
        }

        fread(&contatoTemporario, 1, sizeof(struct contato), arquivoAntigo);
        while (!feof(arquivoAntigo))
        {
            if (strcmp(contatoTemporario.nome, nome) == 0)
            {
                strcpy(contatoTemporario.telefone, telefone);
                encontrado = 1;
            }
            fwrite(&contatoTemporario, 1, sizeof(struct contato), arquivoAtualizado);
            fread(&contatoTemporario, 1, sizeof(struct contato), arquivoAntigo);
        }

        fclose(arquivoAntigo);
        fclose(arquivoAtualizado);

        remove("contatos.dat");
        rename("temp.dat", "contatos.dat");

        if (!encontrado)
        {
            printf("\n Nao foi possivel encontrar o contato...");
        }
        else
        {
            printf("\n Atualizacao realizada com sucesso!");
        }

        Sleep(1000); // Se estiver usando Linux, é sleep(1);
    }
    else
    {
        printf("\n Nenhum contato encontrado!");
        Sleep(1000); // Se estivar usando Linux, é sleep(1);
    }
}

void excluirContato(void)
{
    int encontrado = 0;
    char nome[30];
    struct contato contatoTemporario;
    FILE *arquivoAntigo = NULL, *arquivoAtualizado = NULL;

    system("cls"); // Se estiver usando Linux, é system("clear");

    printf("\tAGENDA TELEFONICA - EXCLUIR TELEFONE\n\n");
    printf(" Nome do Contato: ");
    fflush(stdin);
    gets(nome);

    arquivoAntigo = fopen("contatos.dat", "rb");
    if (arquivoAntigo)
    {
        arquivoAtualizado = fopen("temp.dat", "wb");
        if (!arquivoAtualizado)
        {
            printf("\n Falha na exclusao do contato!");
            fclose(arquivoAntigo);
            Sleep(1000); // Se estivar usando Linux, é sleep(1);
            return;
        }

        fread(&contatoTemporario, 1, sizeof(struct contato), arquivoAntigo);
        while (!feof(arquivoAntigo))
        {
            if (strcmp(contatoTemporario.nome, nome) == 0)
            {
                encontrado = 1;
            }
            else
            {
                fwrite(&contatoTemporario, 1, sizeof(struct contato), arquivoAtualizado);
            }
            fread(&contatoTemporario, 1, sizeof(struct contato), arquivoAntigo);
        }

        fclose(arquivoAntigo);
        fclose(arquivoAtualizado);

        remove("contatos.dat");
        rename("temp.dat", "contatos.dat");

        if (!encontrado)
        {
            printf("\n Nao foi possivel encontrar o contato...");
        }
        else
        {
            printf("\n Exclusao realizada com sucesso!");
        }

        Sleep(1000); // Se estiver usando Linux, é sleep(1);
    }
    else
    {
        printf("\n Nenhum contato encontrado!");
        Sleep(1000); // Se estivar usando Linux, é sleep(1);
    }
}

Como podem perceber, é um programa razoavelmente grande (quase 300 linhas, em ambas as versões). Para facilitar as explicações, vamos analisar função por função, começando pela função principal (Pascal – linha 254; C – linha 28).

A função principal do aplicativo não tem nenhum segredo. Declaramos uma variável chamada opção que armazenará a escolha do usuário sobre o que fazer. Se você não entendeu alguma coisa do que foi feito aqui, não hesite em voltar nos posts anteriores e reler para entender. Aqui, estou assumindo que você já compreendeu as técnicas utilizadas anteriormente nessa série de posts. Então, como eu ia dizendo, fazemos uma escolha de acordo com a opção do usuário, chamando a função correspondente ao que ele escolheu. Essa opção é retornada pela função exibirMenu (Pascal – linha 23; C – linha 62). Esta função limpa a tela, também muito simples, exibe o menu na tela com as opções, lê a opção digitada pelo usuário e retorna. Somente isso.

Vamos começar a falar da parte importante desse programa, que é a manipulação de arquivos em si. Vamos começar pela função de inclusão, a função gravarContato (Pascal – linha 43; C – linha 80). Nesta função (estou usando a palavra função pra generalizar, mas gravarContato é um procedimento!), temos duas variáveis: um registro, do tipo contato, que declaramos com os campos nome, diaAniversario, mesAniversario e telefone (Pascal – linha 15; C – linha 14), e uma outra variável representando o nosso arquivo. Em Pascal, definimos que tipo de dados guardaremos no arquivo logo na sua declaração – no nosso caso, file of contato, já que guardaremos os contatos nele. Já em C, o tipo arquivo é mais genérico. Declaramos um ponteiro para o tipo FILE (sim, todas em maiúsculo). A partir dele, o controle do fluxo para o arquivo todo é de nossa responsabilidade. Prosseguindo, na primeira parte, simplesmente lemos os dados do usuário e o colocamos dentro do registro. No ponto em que estamos, não deve haver dúvidas sobre isso. O próximo passo, é realizarmos a abertura do arquivo. O processo de abertura é essencial, pois com ele tomamos o arquivo, impedindo que outras aplicações o acessem enquanto o estamos manipulando. Em Pascal, isso ocorre neste trecho:

assign(arquivo, 'contatos.dat');
{$I-} reset(arquivo); {$I+};
if ioresult <> 0 then
  rewrite(arquivo);

Primeiramente, ligamos a nossa variável a um arquivo físico através da função assign. Logo em seguida, fazemos uma chamada a função reset, que serve para abrir um arquivo já existente. Circundamos essa chamada com as diretivas de compilação {$I-} e {$I+}, para verificarmos posteriormente se houve erro. Ao usarmos tais diretivas, temos o resultado da operação na variável ioresult. Caso o valor presente seja diferente de 0, é sinal que houve algum erro – provavelmente porque o arquivo ainda não existe. Dessa forma, realizamos a abertura com a função rewrite. Essa função cria um novo arquivo, independentemente se existe ou não outro com mesmo nome. Caso exista, o mesmo será sobrescrito – por isso, tome certo cuidado ao utilizar tal função, ou poderá perder todos os seus dados!

Pronto! O nosso arquivo está, então, aberto e está aguardando realizarmos alguma coisa com ele. Vamos ver como fica esse mesmo procedimento na linguagem C:

arquivo = fopen("contatos.dat", "ab");
if (!arquivo)
{
    arquivo = fopen("contato.dat", "wb");
}

Temos o nosso ponteiro chamado arquivo, que receberá o retorno da função fopen. Esta função tem como argumentos o nome do arquivo (no nosso caso, contatos.dat) e em seguida o modo de abertura. Neste caso, utilizamos o modo ab – append & binary, ou seja, queremos adicionar alguma coisa após o fim do arquivo, que se trata de um arquivo binário. Caso o retorno da função seja nulo (NULL) – indicando que o arquivo não existe, por isso não é possível adicionar nada ao seu fim, abrimos então o arquivo para escrita, usando o modo wb – write & binary. Da mesma forma como em Pascal, esse modo sobrescreverá ou criará um novo arquivo.

Para gravarmos o nosso dado no arquivo, fazemos chamada a uma função de escrita. Em Pascal, chamamos a função write, passando pra ela onde iremos gravar (no caso, no arquivo), e o que iremos gravar (no caso, o nosso registro). Porém, antes disso, posicionamos o arquivo no seu fim, através da função seek. Verificamos se o tamanho do arquivo é maior que 0 (valor em bytes), e caso positivo, o posicionamos em seu final. Por fim, fechamos o arquivo usando a função close, que recebe o arquivo como argumento.

Em C, não precisaremos realizar o reposicionamento, pois caso o arquivo seja aberto no modo append, ele já estará automaticamente posicionado no fim do arquivo, e caso seja aberto no modo write, é porque não há arquivo, ou seja, o dado será gravado na posição 0. A função para a gravar o dado é um pouco mais complexa que em Pascal. Em C, utilizamos a função fwrite. Para ela, passamos primeiramente o endereço onde está o dado que desejamos gravar (por isso o &), logo em seguida, quantas vezes iremos gravar a informação (no caso, 1 vez), o tamanho da informação que será gravada (do tamanho de um registro do tipo contato – obtemos tal valor com a função sizeof) e por fim, onde tal informação será gravada – no arquivo. Por fim, fechamos o arquivo com a função fclose.

Pronto! A nossa informação já está gravada no arquivo contato.dat!

Vamos agora analisar o funcionamento da função buscarContato (Pascal – linha 73; C – linha 110). Temos uma variável que indica se o contato buscado foi encontrado ou não, uma variável que terá o nome do contato que está sendo buscado, um registro que armazenará os dados lidos do arquivo e o nosso arquivo. No início da função, pedimos ao usuário que digite o nome do contato a ser buscado. Em seguida, abrimos o arquivo para leitura. Em Pascal, caso não seja possível executar o comando reset, significa que o arquivo não existe – exibimos então uma mensagem de erro ao usuário (Nada encontrado no arquivo). Em C, abrimos o arquivo no modo rb – read & binary. Caso a função de abertura retorne NULL, significa que o arquivo não existe, portanto não é possível lê-lo – exibimos então a mensagem de erro. Caso tudo ocorra dentro dos conformes, vamos começar a varrer o arquivo em busca do contato digitado pelo usuário.

O que limitará a quantidade de acessos ao arquivo, será a função eof (Pascal) ou feof (C). Essa função retorna verdadeiro caso seja atingido o fim do arquivo. Porém, vale ressaltar uma diferença básica entre o funcionamento das duas funções. Em Pascal, a função retornará verdadeiro ao ler-se o último registro, ou seja, eu terei em meu registro o conteúdo do último registro do arquivo. Já em C, a função retorna verdadeiro quando eu tento ler dados que já ultrapassariam o fim do arquivo. Por exemplo, ao ler o último registro do arquivo, ela não retornaria verdadeiro. Apenas se eu tentar ler mais uma vez! Assim, quando a função feof me retornar verdadeiro, eu terei lixo no meu registro. É por esse motivo que, em Pascal utilizamos o while, com a leitura no início do corpo da repetição. Já em C, realizamos uma primeira leitura fora da repetição, e utilizamos o do-while, com a leitura ao final do corpo da repetição.

Em Pascal, para lermos o nosso arquivo, chamamos a função read, que recebe como parâmetros o arquivo de onde os dados serão lidos e o registro onde os dados lidos serão armazenados. Já em C, chamamos a função fread, que possui os mesmos parâmetros da função fwrite, com a uma leve diferença de significado – o registro onde serão armazenados os dados lidos do arquivo, a quantidade de leituras (sempre achei isso meio estranho…), a quantidade de dados lidos do arquivo, e o arquivo no qual os dados serão lidos. Caso o registro lido corresponda ao que o usuário está procurando, exibimos os dados na tela e setamos a variável encontrado (nota: achou estranho em C, essa variável ser inteira? Em C, como nativamente não existe o tipo booleano, as variáveis inteiras podem ser consideradas como booleanas, levando-se em consideração a regra: Falso = 0; Verdadeiro != 0. Porém, a partir de sua última especificação, o C99, o tipo bool foi especificado, adicionando-se o arquivo de cabeçalho stdbool.h). Após atingir o fim do arquivo, verificamos se o registro não foi encontrado – para exibirmos a mensagem ao usuário – e por fim fechamos o arquivo.

Vamos analisar, então, as últimas duas funções – atualizarTelefone (Pascal – linha 124; C – linha 157) e excluirContato (Pascal – linha 190; C – linha 221). Essas funções permitem diversas formas de implementação. Esta que lhes apresento não é a melhor, nem a mais otimizada, mas funciona e acredito que seja a mais simples de entender. Vou explicar a lógica antes de explicar o código. Vamos utilizar dois arquivos. O primeiro, será o arquivo de entrada, onde estão todos os registros já gravados. O segundo, será o arquivo de saída, que iremos regravar os dados. Na atualização, lemos um registro do arquivo de entrada, verificamos se é o arquivo a ser atualizado. Se não for, gravamos no arquivo de saída. Se for, atualizamos o telefone e também gravamos no arquivo de saída. Já na exclusão, o processo é bastante semelhante. Lemos um registro do arquivo de entrada, verificamos se é o registro a ser excluído. Se não for, gravamos no arquivo de saída. Se for, simplesmente o ignoramos e seguimos para o próximo. Ao final do processo, excluímos o arquivo original e renomeamos o arquivo atualizado para o nome que o outro arquivo tinha. Confuso? Talvez um pouco agora no início. Mas com a prática, você entenderá melhor a lógica e poderá criar a sua própria lógica – que pode ser infinitamente melhor que a minha.

Analisando o código, temos a variável indicadora se a atualização/exclusão ocorreu com sucesso, o nome do registro a ser atualizado/excluído, o telefone a ser atualizado (somente na função atualizarTelefone), o registro temporário que armazenará os dados a medida com que forem lidos do arquivo, e as duas variáveis representando os arquivos. Iniciamos a função pedindo os dados do usuário, abrimos o arquivo contatos.dat para leitura e o arquivo temp.dat para escrita. Assim, lemos todos os registros, realizamos a análise, e gravamos tudo no arquivo de saída (temp.dat). Após isso, fechamos os dois arquivos para que possamos realizar o processo de excluir e renomear. Em Pascal, utilizamos as funções erase que recebe uma variável vinculada a um arquivo – esse vínculo foi criado ao chamarmos a função assign lá no topo da função. Já para renomearmos, chamamos a função rename, passando o arquivo a ser renomeado e o seu novo nome. Em C, chamamos a função remove, passando o nome do arquivo a ser excluído, e em seguida a função rename, passando o nome do arquivo a ser renomeado, seguido pelo novo nome do arquivo.

Bom, pode parecer um bocado de informação de uma só vez, mas como já disse antes: somente com a prática você vai se familiarizar com tais técnicas. Eu mesmo posso dizer: quando estava aprendendo, isso me pareceu extremamente complicado, complexo, pra não dizer sem sentido. Mas posso dizer também que, ao superar essa etapa (ou este medo?), você estará preparado para seguir em frente e começar a desbravar temas mais “complexos”.

Agora, pra fecharmos esse post, vou mostrar um exemplo simples de como escrever e ler de um arquivo texto.

Em Pascal:

program
  arquivoTexto;

uses
  crt;

var
  texto: string[255];
  arquivo: text;

begin
  writeln('Vamos escrever uma frase no arquivo.');

  assign(arquivo, 'arquivo.txt');
  rewrite(arquivo);
  writeln(arquivo, 'Testando arquivos txt.');
  close(arquivo);

  writeln('Va ate o diretorio e verifique o arquivo.txt');
  write('Pressione uma tecla para continuar. . .');
  readkey;

  writeln;
  writeln('Agora vamos ler o conteudo gravado.');
  reset(arquivo);
  readln(arquivo, texto);
  writeln('Conteudo do arquivo: ');
  writeln(texto);
  close(arquivo);

  write('Pressione uma tecla para continuar. . .');
  readkey;
end.

Em C:

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

int main(void)
{
	char texto[255];
	FILE *arquivo;

	arquivo = fopen("arquivo.txt", "w");
	fprintf(arquivo, "Testando arquivos txt.");
	fclose(arquivo);

	printf("Va ate o diretorio e verifique o arquivo.txt\n");
	system("pause");

	printf("\nAgora vamos ver se o conteudo foi gravado.\n");
	arquivo = fopen("arquivo.txt", "r");

	fgets(texto, 255, arquivo);
	printf("Conteudo do arquivo:\n%s\n", texto);
	fclose(arquivo);

	system("pause");
	printf("\n");

	return 0;
}

Bom, neste exemplo, simplesmente gravamos uma string em um arquivo e depois lemos novamente e exibimos na tela. Em Pascal, a única “novidade” seria o tipo text. Esse tipo representa o arquivo texto, fazendo com que o manipulemos como se fosse a própria tela ou a entrada do teclado, ou seja, utilizando as funções writeln e readln. Em C, o que diferencia para o uso de arquivos binários, primeiramente, é que no modo de abertura, não temos a letra b. Assim, ao abrir o arquivo para leitura, usamos r, para escrita, usamos w e assim por diante. Para escrever e ler do arquivo, temos versões das funções printf e scanf, com o prefixo f. Dessa forma, usamos fprintf e fscanf da mesma forma que usamos normalmente, só que passando também o arquivo como parâmetro. Nesse exemplo em específico, eu usei fgets, já que a nossa frase continha espaços (que o scanf interpreta como fim de linha). Nessa chamada, passamos a variável que receberá o texto, o tamanho máximo de bytes a serem lidos (pra que não ocorra o problema de estourar a capacidade de nossa string), e o arquivo de onde o texto será lido. Com o gets, a leitura é interrompida quando encontra-se um “enter” ou quebra de linha. No mais, nada de novidade nos programas acima.

Bom pessoal, é isso. O post ficou longo, demorou pra ficar pronto, mas acho que deve ajudar o pessoal aí. Assim, encerramos essa primeira série de posts. Fico imensamente grato a todos que visitam o blog. Acho que atingi meu primeiro objetivo, que era o de passar o conhecimento que eu tenho em programação de uma forma descomplicada pra quem está começando. Lá no primeiro post, em dezembro de 2008, mal sabia eu o quão certo essa ideia daria. Hoje, já temos mais de 16.000 visitas e pretendo aumentar cada vez mais a quantidade de pessoas ajudadas por esse blog.

Deixo, por ora, meu muito obrigado, e que continuem acessando, pois em breve, teremos novidades por aqui.

Abraço a todos 😀

Ponteiros

Padrão

Olá pessoal! Aqui estou eu continuando a nossa série de posts sobre programação para iniciantes para tratarmos de um assunto bastante delicado: os temidos Ponteiros.

Ponteiros são capazes tanto de te dar poder total sobre a sua aplicação, possibilitando manipulações de memória, casting, e uma porção de coisas, como também podem destruir sua aplicação facilmente (e em casos extremos causar erros de memória inclusive no sistema operacional).

Hoje em dia, algumas linguagens mais novas (como o Java), aboliram o uso de ponteiros, retirando do programador toda a responsabilidade de alocar memória, desalocar após o uso (o Java possui um Garbage Collector, ou coletor de lixo).

Mas, o que seriam estes tais ponteiros? Ponteiros nada mais são do que variáveis cujo o seu conteúdo é um endereço de memória. Como assim? Ele guarda, não um valor propriamente dito… é uma referência a algum lugar na memória que, provavelmente, contém algum dado. Mas que tipo de dado? Para o tipo de dados definido na declaração do ponteiro. O que isso significa? Significa que os ponteiros necessitam de um tipo para serem definidos. Em C, podemos definir um ponteiro sem tipo, genérico (utilizando o “tipo” void). Porém, o uso desse artifício deve ser bastante ponderado, pois pode acarretar em um comportamento equivocado ou mesmo em erros de memória.

Bom, primeiramente, como os ponteiros referem-se a memória, é um pouco estranho pensarmos nisso no algoritmo. Se vocês estão acompanhando a nossa série desde o começo, já devem estar conseguindo fazer alguns programas bastante funcionais, e devem ter percebido que nem sempre precisamos fazer o algoritmo todo do programa. Processamentos banais, estruturas de repetição simples, naturalmente surgem na cabeça do programador. E com o tempo, ao se familiarizarem-se com alguma linguagem de programação, irão pensar já no código. Voltando aos ponteiros, eles servirão para nos auxiliarem em algumas tarefas mais “cabeludas” onde precisaremos modificar alguma coisa “na unha”, ou o que a sua imaginação quiser. Alguns dos usos mais comuns de ponteiros são na passagem de parâmetros por referência e na alocação dinâmica (em tempo de execução) de memória.

Pra começar, então, podemos ver um exemplo de ponteiros em Pascal e em C. No seguinte trecho, vamos simplesmente criar um ponteiro, alocar memória para ele, atribuir um valor à variável e escrever na tela. Para fins didáticos, também vou colocar em pseudo-código (algoritmo), mas como eu disse acima, na minha opinião manipulação de ponteiros em forma algorítmica é algo meio estranho… mas enfim, vamos criar aqui uma notação própria. Lembre-se que o algoritmo deve ser claro para descrever uma ideia. Portanto, fique à vontade para adaptar sua própria notação.

Algoritmo Alocacao_Dinamica

[Declaração de Variáveis]
   ponteiro : ^inteiro

[Processamento]
   alocar(ponteiro)
   ponteiro^ ← 10
   escreva("Variável alocada dinamicamente com o valor ", ponteiro^)
   desalocar(ponteiro)
[Fim]
program alocacao_dinamica;

uses crt;

var
   ponteiro: ^integer;

begin
   new(ponteiro);
   ponteiro^ := 10;
   writeln('Variável alocada dinamicamente com o valor ', ponteiro^);
   dispose(ponteiro);
end.
// Alocação dinâmica de memória
#include <stdio.h>
#include <stdlib.h> // Necessário para malloc() e free()

int main(void)
{
   int *ponteiro;

   ponteiro = malloc(sizeof(int));
   *ponteiro = 10;
   printf("Variável alocada dinamicamente com o valor %d", *ponteiro);
   free(ponteiro);

   return 0;
}

Vamos conhecer a sintaxe e o funcionamento das funções que utilizamos. Primeiramente, declaramos uma variável ponteiro que, como foi dito acima, é declarada de um tipo correspondente. Isso ocorre porque os bits na memória são lidos de maneira diferente dependendo do tipo. Em C, podemos através de cast ponteiros, podemos setar um ponteiro de float para ser lido como inteiro… umas doideras assim (que eu, particularmente, nunca utilizei). Fazendo algumas brincadeiras com isso, podemos ver como um descuido desses pode causar reações adversas no programa.

Bom, a sintaxe da declaração é da mesma forma que uma variável comum, porém adicionando-se o caractere referente ao ponteiro, que em Pascal é representado pelo caracter ^ (acento circunflexo), que na declaração é colocado antes do tipo, e na utilização da variável, vai após o nome dela. Já em C, a sintaxe consiste no * (asterisco) precedendo o nome da variável. Perceba que em alguns pontos do código, também utilizamos a variável sem o caractere. Bom, antes de explicarmos o porquê, também temos de falar de outro caractere relativo aos ponteiros: o caractere de referência. Em Pascal, esse caractere é o @ (arroba) e em C é o & (ê comercial).

Com essa sopa de caracteres, vamos agora explicar como funcionam e pra que servem tais notações. Primeiramente, temos o ponteiro sem caractere nenhum. Neste caso (como na primeira linha de código propriamente dito do nosso exemplo), a variável sem nenhum dos dois caracteres, indica o endereço de memória. Ou seja, naquela linha (utilizando o new ou o malloc), estamos reservando uma área de memória apontada pela variável ponteiro. Então, fixando: sem qualquer caractere, o ponteiro representa um endereço de memória. Por exemplo 0x00AC1D. Em um segundo caso, temos o caractere de conteúdo: ^ em Pascal e * em C. Esse caractere indica o conteúdo de. Como assim? Em vez de indicar o local, você vai utilizar o valor que está armazenado no local indicado pelo ponteiro. Se não tiver nada (ou tiver lixo, como acontece sempre), ele simplesmente vai interpretar o que está lá de acordo com o tipo do ponteiro. Por isso é sempre bom anular ou inicializar o ponteiro, através das constantes nil em Pascal ou NULL em C. Já já veremos exemplos de código utilizando isso. Por fim, temos o caractere de referência @ em Pascal e & em C. Ao utilizar este caractere, obtemos o endereço onde tal variável armazenada, ou seja, o caractere indica o endereço de alguma variável. Utilizamos bastante ao atribuirmos, por exemplo, o endereço de uma variável estática a um ponteiro (veremos isso em alguns exemplos logo mais). Se utilizarmos tal caractere em um ponteiro, por exemplo, teremos o endereço onde a variável do tipo ponteiro foi criada, e não o que ela aponta. Meio confuso? Os exemplos a seguir devem ajudar a clarear um pouco as ideias. (nota mental: meu cérebro ainda não se acostumou a escrever ideia sem acento!)

var
   x : integer;
   p, q : ^integer;

begin
   x := 10;
   p := @x;

   q := p;
   q^ := p^ + 10;
   writeln('Resultado: ', p^);
end.
int main(void)
{
   int x, *p, *q;

   x = 10;
   p = &x;

   q = p;
   *q = *p + 10;
   printf("Resultado: %d", *p);
   return 0;
}

Acima, temos dois exemplos simples demonstrando tudo o que eu falei. Temos x como variável estática e p e q como ponteiros. Percebam que em C, ao contrário de Pascal, é possível declarar ponteiros e variáveis estáticas na mesma linha. Iniciando nosso programa, atribuímos o valor 10 para a variável x, da maneira convencional mesmo. Em seguida, fazemos com que p aponte para x, passando para p (sem o ^ ou o *) o endereço de x, através do operador @ (Pascal) e & (C). Em seguida, q recebe p, ou seja, agora os dois ponteiros são iguais (ambos apontam para x). Depois, o endereço de memória apontado por q (que é o mesmo que x), recebe o que tem no endereço apontado por p (também x, ou seja, 10), mais 10. Ou seja, agora, x vale 20, e q e p continuam apontando para x. Para ilustrar a situação, elaborei o desenho abaixo que pode ajudar a compreender o que aconteceu.

x recebe 10. q e p ainda não foram inicializados

p agora aponta para x.

q recebe p. Agora q e p apontam para x.

o local apontado por q (ou seja, x), receberá o valor contido no local apontado por p (também x, ou seja, 10) mais 10. No final, x valerá 20, pois é o local apontado por q.

Sim, as imagens foram feitas no Paint. Mas enfim, percebam que as referências a uma variável por dois ponteiros pode ficar realmente confusa! Hahahaha. Mas espero que com essas ilustrações, o entendimento do código descrito acima tenha ficado melhor.

Veremos agora, como ficaria a alocação dinâmica de um registro. Percebam que ocorre da mesma maneira que uma variável comum, porém com a diferença que em C, os campos do registro são referenciados não mais com o . mas sim com o operador seta ->. Abaixo, um exemplo de uso de um registro alocado dinamicamente, em Pascal e em C.

type
   reg_aluno = record
      nome : string[30];
      idade : integer;
   end;

var
   aluno : ^reg_aluno;

begin
   new(aluno);
   write('Nome do aluno: ');
   readln(aluno^.nome);
   write('Idade: ');
   readln(aluno^.idade);

   writeln('Dados lidos.');
   writeln('Nome: ', aluno^.nome, ' - Idade: ', aluno^.idade);
   dispose(aluno);
end.
typedef struct {
   char nome[30];
   int idade;
} reg_aluno;

int main(void)
{
   reg_aluno *aluno;

   aluno = malloc(sizeof(reg_aluno));
   printf("Nome do aluno: ");
   scanf("%s", aluno->nome);
   printf("Idade: ");
   scanf("%d", &aluno->idade);

   printf("Dados lidos.\n");
   printf("Nome: %s - Idade: %d\n", aluno->nome, aluno->idade);

   return 0;
}

A única ressalva no código acima, é que alguns compiladores podem “chiar” na utilização do malloc(), atribuido à nossa struct. Caso isso aconteça, faça a conversão de ponteiros (cast). Dessa forma, a linha ficaria assim:

aluno = (reg_aluno*) malloc(sizeof(reg_aluno));

Um outro ponto que devemos frisar aqui, é a maneira de “zerarmos” um ponteiro. Ao fazermos isso, evitamos deixar o ponteiro com “lixo”, o que pode ocorrer dele acessar alguma área de memória não permitida, e consequentemente ter o programa encerrado de maneira forçada pelo sistema operacional. Dessa forma, ao declararmos um ponteiro, recomenda-se inicializá-lo assim:

var
   ponteiro: ^integer;

begin
   ponteiro := nil;
int main(void)
{
   int *ponteiro = NULL; // sim, o NULL é em caixa alta!

Para encerrarmos o post, mostrarei um exemplo de como fazer funções que recebem parâmetros como refência. Não sabe o que é um parâmetro passado como referência? Bom, vamos a uma breve explicação: quando chamamos uma função, os parâmetros são chamados por cópia ou por referência. Os parâmetros por cópia, como foram os já ensinados aqui anteriormente, utilizam uma cópia do valor passado por argumento em seus cálculos. Ou seja, se você passa uma variável y para uma função, seu valor não será alterado, mesmo que y sofra alterações dentro da função. Já nos parâmetros por referência, a variável passada como argumento sofre alterações caso seja alterada dentro da função. Isso é útil quando queremos que uma função retorne mais de um valor (já que com o retorno convencional podemos devolver somente um valor).

A seguir, exemplos em Pascal e em C de funções que recebem 2 números e devolvem por referência os números somados e subtraídos.

var
   num1, num2, soma, subtracao : integer;

procedure aplicarOperacoes(n1, n2: integer; var soma, subt: integer);
begin
   soma := n1 + n2;
   subt := n1 - n2;
end;

begin
   num1 := 5;
   num2 := 3;

   aplicarOperacoes(num1, num2, soma, subtracao);

   writeln('A soma de ', num1, ' e ', num2, ' é ', soma, ' e a subtraçao é ', subtracao);
end.
void aplicarOperacoes(int n1, int n2, int *soma, int *subt)
{
   *soma = n1 + n2;
   *subt = n1 - n2;
}

int main(void)
{
   int num1, num2, soma, subtracao;

   num1 = 5;
   num2 = 3;

   aplicarOperacoes(num1, num2, &soma, &subtracao);

   printf("A soma de %d e %d é %d e a subtração é %d.\n", num1, num2, soma, subtracao);
   return 0;
}

Apesar do post sobre ponteiros, perceba que em Pascal não utilizamos ponteiros pra fazermos os parâmetros serem passados como referência. Para isso, basta adicionar a palavra var antes das variáveis que serão passadas como referência. Já em C, declaramos os parâmetros de referência como ponteiros, e ao chamarmos a função, utilizamos o operador & para passarmos a referência das variáveis de recepção da função. Bom, amigos, finalizamos então nosso post sobre os temidos ponteiros. Eles são um assunto delicado na programação, por vezes nos dando imenso controle sobre a aplicação, e por outras vezes nos tirando todo o controle do programa (e a paciência também). Mas é somente com a prática que vocês conseguirão domá-los de uma vez (até hoje apanho deles de vez em quando).

Bom Carnaval a todos, e muito juízo! Hahaha!!! Até a próxima! 😀

Funções e Procedimentos

Padrão

Estou ficando preocupado. Praticamente todos os meus posts começam com “Olá pessoal!“. Preciso urgentemente de uma nova maneira de começar meus posts e saudar meus leitores. Mas, enfim, até o próximo post eu penso em alguma coisa. Quem sabe alguma gíria, algum termo informático… ah, sei lá. Aguardemos os próximos capítulos.

Bom, hoje vamos tratar de um assunto de extrema importância na programação estruturada: as Funções e Procedimentos. Sem eles, a programação seria praticamente impossível de ser realizada à medida que os programas crescessem em tamanho. Além disso, seria praticamente impossível trabalhar em equipe (entenderemos isso já já), e a reutilização de código também seria inimaginável, ou seja, toda vez que escrevêssemos nossos programas, teríamos que fazer tudo a partir do zero. A própria linguagem não teria suas funções e procedimentos, e teríamos que fazer toda a manipulação de teclado, vídeo, E/S, tudo a partir do zero. Entenderam a importância? Veremos aqui como constitui-se uma função/procedimento, as diferenças de um para o outro, o que é um argumento e um parâmetro, diferenças entre um e outro, o que é um retorno… ufa! Como deu para perceber, o post tá bem recheado! Então vamos que vamos!

Para começarmos a falar sobre procedimentos e funções, vamos dar uma olhada no conceito da máquina de von Neumann, onde os sistemas computacionais são constituídos de:

ENTRADA -> PROCESSAMENTO -> SAÍDA

Através desse conceito, podemos generalizar que todo programa recebe uma entrada de Dados, realiza seus cálculos no processamento e, como saída, temos a Informação que é o dado processado. Da mesma forma, os procedimentos e funções recebem dados de entrada, processam-os e retornam algo para quem os chamou. Podemos começar diferenciando um procedimento de uma função pela maneira como é feito o retorno. Na Função, o valor de retorno é Explícito, enquanto que no Procedimento, o retorno é realizado de maneira Implícita. Veremos isso na prática mais adiante. Da mesma forma que temos a saída na forma do retorno, também temos a saída na forma dos parâmetros ou argumentos de nossa função ou procedimento. Mas qual a diferença entre um Parâmetro e um Argumento? De maneira simplificada, podemos dizer que uma função (vou utilizar apenas o termo função para designar função e procedimento, apenas para poupar digitação) recebe de quem a chamou, em seus Parâmetros, os dados a serem processados. Já quando chamamos uma função, passamos Argumentos para que ela execute o seu processamento. Dessa forma, os Parâmetros são os dados aguardados pela função, enquanto que os Argumentos são os dados que passamos à função. Pode parecer um pouco confuso agora, mas pretendo espairecer melhor essas ideias quando discutirmos a parte prática da coisa. Então, para fechar essa parte mais “conceitual”, podemos dizer que as funções são pequenos programas. Ou seja, a partir de agora, podemos “quebrar” o nosso programa em partes menores. Dessa forma, podemos, dentre outras coisas, reutilizar funções já escritas em programas novos, dividir trabalho entre uma equipe, onde cada um escreve um trecho do código, ou seja, cada um escreve suas funções e depois junta-se em um único programa. Legal, hein!

Antes de começarmos a ver as Funções e Procedimentos na prática, precisamos ter uma noção sobre Variáveis Globais e Variáveis Locais.

O conceito de Variáveis, já foi explorado em um post anterior (variável é um célula de memória que armazena um valor, toda variável precisa de um tipo, etc). Uma Variável Global é uma variável que pode ser acessada, alterada, enfim, ela pode ser vista por todo o programa. Como assim? As variáveis são todas globais então? Bom, nos programas que fizemos até agora, sim. A partir de agora, veremos que os procedimentos e funções também possuem suas próprias variáveis, que só são conhecidas dentro de seu escopo (dentro do procedimento ou função). Ou seja, uma Variável Local é uma variável que só é conhecida por um trecho do programa, não podendo ser acessada fora dele. Por exemplo, eu declarei uma variável chamada X dentro de uma função. Se eu mencioná-la fora da função onde foi declarada, o compilador em questão irá acusar um erro, dizendo que a variável não foi declarada. Com isso, você pode perceber que podemos ter variáveis de mesmo nome em nosso programa, porém cada uma dentro de seu escopo, sem nenhuma ligação uma com a outra.

Bom, já falamos bastante da parte “teórica” de funções e procedimentos. Então vamos ver alguns exemplos de como fazê-los, tanto nos algoritmos quanto na linguagem Pascal.

Primeiramente, vamos criar um programa que lê dois números e os somam. Para isso iremos criar uma função chamada somar, que receberá dois números inteiros como parâmetros e devolverá também um inteiro contendo o resultado da soma.

Algoritmo Somar_com_Funcoes

[Declaração de Variáveis]
   valor1, valor2, resultado : inteiro

Função somar(n1, n2 : inteiro) : inteiro
[Início]

[Declaração de Variáveis Locais]
   resultado : inteiro

[Processamento]
   resultado ← n1 + n2
   retorne(resultado)

[Fim]

[Processamento Principal - Início]
   escreva("Digite o primeiro membro da soma: ")
   leia(valor1)
   escreva("Digite o segundo valor da soma: ")
   leia(valor2)
   resultado ← somar(valor1, valor2)
   escreva("O resultado é ", resultado)
[Fim]

 

program Somar_com_Funcoes;

uses
   crt;

var
   valor1, valor2, resultado : integer;

function somar(n1, n2 : integer) : integer;
var
   resultado : integer;
// Logo a seguir vou explicar algumas particularidades da linguagem Pascal

begin
   resultado := n1 + n2;
   somar := resultado;
end;

begin
   write('Digite o primeiro membro da soma: ');
   readln(valor1);
   write('Digite o segundo membro da soma: ');
   readln(valor2);
   resultado := somar(valor1, valor2);
   writeln('O resultado é ', resultado);
   readkey;
end.

Vimos acima um exemplo bastante simples, onde declaramos uma função chamada somar que recebe dois valores inteiros (n1 e n2) e retorna um valor inteiro, contendo a soma dos dois valores passados como argumento. Você pode pensar: “que coisa inútil… é muito mais fácil usar o operador + e pronto”. Calma, calma… esse foi só um exemplo. Obviamente não iremos criar funções que já existem. As funções servem pra que possamos modularizar o código, dividindo tarefas e deixando o código mais legível. Por exemplo, podemos criar uma função que calcule o imposto de renda, que calcule as raízes de uma equação de 2º grau, dentre outros. Com isso, se tivermos outro programa que também necessite de tais cálculos, podemos reutilizar o código!

Podemos perceber em nosso código que declaramos algumas variáveis globais e locais. Nesse caso, declaramos as variáveis globais: valor1, valor2 e resultado, enquanto que declaramos as variáveis locais: n1, n2 e resultado. Epa! Surgiram duas dúvidas agora! Primeiro: n1 e n2 são parâmetros e não variáveis locais, certo? Errado. Os parâmetros que declaramos em nossas funções e procedimentos também são variáveis locais, apesar de não terem sido declarados na área de variáveis locais. A segunda dúvida é: você declarou duas variáveis de nome ‘resultado’! Por que o compilador não acusou erro? Na verdade eu só percebi isso agora, mas serviu pra explicar um detalhe da linguagem Pascal: quando temos duas variáveis de mesmo nome, sendo uma global e uma local, o programa sempre vai levar em conta a variável local. Ele acusaria erro se declarássemos suas variáveis de mesmo nome dentro de um mesmo escopo.

Bom, eu comentei o código ali em cima, dizendo que iria explicar algumas particularidades da linguagem Pascal. No algoritmo, quando vamos retornar o valor que a função calculou, utilizamos simplesmente retorne(<valor>). Já na linguagem Pascal, fazemos isso de uma modo diferente. Atribuímos o valor a uma variável com o mesmo nome da função. Essa variável não precisa ser declarada, pois é criada automaticamente para representar a função. No nosso exemplo, atribuímos o que queremos retornar à variável somar.

Pra encerrar, vou mostrar um exemplo do uso de Procedimentos, também para somar dois valor. Vocês perceberão que eles funcionam exatamente da mesma maneira, porém, sem o valor de retorno:

Algoritmo Somar_com_Procedimentos

[Declaração de Variáveis]
   valor1, valor2, resultado : inteiro

Procedimento somar(n1, n2 : inteiro)
[Início]

[Declaração de Variáveis Locais]
   resultado : inteiro

[Processamento]
   resultado ← n1 + n2
   escreva("O resultado é ", resultado)

[Fim]

[Processamento Principal - Início]
   escreva("Digite o primeiro membro da soma: ")
   leia(valor1)
   escreva("Digite o segundo valor da soma: ")
   leia(valor2)
   somar(valor1, valor2)
[Fim]

 

program Somar_com_Procedimentos;

uses
   crt;

var
   valor1, valor2, resultado : integer;

procedure somar(n1, n2 : integer);
var
   resultado : integer

begin
   resultado := n1 + n2;
   writeln('O resultado é ', resultado);
end;

begin
   write('Digite o primeiro membro da soma: ');
   readln(valor1);
   write('Digite o segundo membro da soma: ');
   readln(valor2);
   somar(valor1, valor2);
   readkey;
end.

Perceberam como é simples? E acaba auxiliando e muito no desenvolvimento de aplicações. Quem sabe eu ainda reescrevo o nosso jogo da velha em Pascal, recodificando-o de forma modularizada? Vou pensar nisso…

Ah, e antes de encerrar, vale lembrar que uma função ou procedimento pode, também, não ter nenhum parâmetro. Para isso, é só deixar os parênteses vazios ou até omiti-los.

Post grande, de certa forma cansativo, mas de um tema bastante interessante! No próximo post vamos começar a nos aventurar por um outro mundo: A Linguagem C! Ela vai servir pra nos mostrar que, tendo um algoritmo bem feito, podemos implementá-lo facilmente, independente da linguagem.

Abraço, pessoal e bons estudos!

Registros

Padrão

Olá pessoal! Mais uma vez estou eu aqui em um post, depois de mais um bom período sem postar nada. Fim de semestre na faculdade é sempre complicado, cheio de provas e ainda estive estudando muitas coisas (C++, Java, Delphi, OpenGL e ainda pretendo dar uma olhada em C#)… pra melhorar viajei e voltei mal, passei uns dias no hospital e tudo… mas chega de desculpas, finalmente estou no 3º ano da faculdade. Cheguei à metade. Sem perder tempo, vamos ao que importa: programação!

Hoje vamos falar sobre Registros, também chamados de Estruturas. Bom, no último post, vimos como criar agrupamentos de dados de um mesmo tipo, no caso, com os Vetores. Agora, suponhamos que queremos agrupar alguns dados referentes a uma pessoa… um aluno para exemplificar. Para um aluno podemos ter uma grande diversidade de dados correspondente a ele: Nome, Idade, Endereço, Telefone, Notas dos Bimestres, Situação (aprovado/reprovado/em exame). No caso, como os dados são de tipos diferentes, não seria possível agrupá-los em um vetor. Para isso existem os Registros!

Porém, antes de entrarmos na definição dos registros no algoritmo, precisamos dar uma passada na definição de novos tipos. Os tipos, nada mais são do que modelos de dados. Por exemplo, quando criamos uma variável do tipo inteiro, estamos definindo que teremos um dado com o formato de um inteiro. É como se o tipo fosse o molde ou a forma (sem acento, de acordo com a nova regra ortográfica, hahaha) e a variável fosse o… bolo ou o que quer que seja. Quando criamos o nosso registros, estamos definindo um novo tipo, ou um novo molde de dados. As variáveis criadas com esse novo tipo terão o seu aspecto, definido na área de definição de tipo. A seguir, vamos ao exemplo algoritmico e na linguagem Pascal de uma definição de um tipo novo, criando um registro simples:

[Definição de Tipos]
   Aluno = registro
      nome : literal[30]
      idade : inteiro
   fim-registro

[Declaração de Variáveis]
   umAluno : Aluno

 

type
   Aluno = record
      nome : string[30];
      idade : integer;
   end;

var
   umAluno : Aluno;

Utilizar os registros dessa maneira é que você define um tipo, e ele fica sendo reconhecido em todo o seu programa, além da vantagem de você poder passar ou retornar um conjunto de variáveis de uma vez só em funções (calma, no próximo post veremos funções e procedimentos!). Lembrando que, em Pascal, a definição de tipos deve ser feita logo após a área Uses (ou a Program, no caso do PascalZim).

Porém, caso você for utilizar o registro somente uma vez, você pode declará-lo diretamente na área de variáveis da seguinte maneira:

[Declaração de Variáveis]
   Bola : registro
      cor : literal[20]
      peso : real
      diametro : real
   fim-registro

 

Var
   Bola : record
      cor : string[20];
      peso : real;
      diametro : real;
   end;

Beleza, tenho o meu registro. Mas como eu acesso os seus campos separadamente? Através do <nome do registro>.<nome do campo>. Dessa forma, apesar do registro ser uma única variável, ele nos oferece a possibilidade de acessar cada um dos seus campos separadamente. Um pequeno exemplo disso pode ser visto abaixo:

[Declaração de Variáveis]
   Aluno : registro
      nome : literal[30]
      idade : inteiro
   fim-registro

[Processamento]
   Aluno.nome ← "Rafael"
   Aluno.idade ← 20
   escreva("O aluno ", Aluno.nome, " tem ", Aluno.idade, " anos.")
[Fim]

 

var
   Aluno : record
      nome : string[30];
      idade : integer;
   end;

begin
   Aluno.nome := 'Rafael';
   Aluno.idade := 20;
   writeln('O aluno ', Aluno.nome, ' possui ', Aluno.idade, ' anos.');
   readkey;
end.

Bom, então é isso. Os registros podem assustar um pouco no começo, mas são simples e de grande utilidade. Qualquer dúvida, como eu já disse, é só deixar um comentário ou mandar um e-mail. No próximo post teremos Funções e Procedimentos, elementos essenciais na programação estruturada. Até lá! 🙂

Jogo da Velha em Pascal

Padrão

Olá pessoal! Aqui estou eu de volta. E como prometido no final do último post, teremos uma “brincadeirinha”. Utilizando os conceitos que vocês já conhecem, elaborei um Jogo da Velha bem simples, mas bastante divertido, que roda em modo console. Com isso, vocês irão perceber que o que mais importa quando se vai programar é a IMAGINAÇÃO e a CRIATIVIDADE! 🙂

tela-jogo-velha

Bom, o link com o código-fonte está aqui. Escrevi e compilei ele na IDE do Free Pascal, mas acredito que vocês conseguirão compilá-lo sem problema nenhum tanto no Pascal Zim, quanto no Turbo Pascal. Pra compilar no Pascal Zim, é só retirar a área que contém as Uses crt; e no Turbo Pascal, se caso estiver utilizando a versão Windows, colocar wincrt em vez de crt.

Vou então explicar as partes principais do código (que também está com alguns comentários)

var
   campo : array[1..3, 1..3] of integer;
   i, j, vencedor, vez, jogada, numero_jogadas : integer;
   jogada_ok : boolean;

Na declaração de variáveis, temos uma matriz 3×3 chamada campo, que representará o nosso campo de jogo. Logo em seguida, temos i e j, que são os índices que utilizaremos em algumas partes para varrer a matriz. vez representa de quem é a vez de jogar (no caso, jogador 1 ou jogador 2) . Jogada, irá armazenar o número inteiro correspondente a uma jogada (número de 1 a 9). Numero_jogadas será um contador de jogadas, pois o jogo, invariavelmente, acaba após 9 jogadas, mesmo que não haja vencedores. Por último, temos a variável booleana jogada_ok, que será o indicador para a próxima jogada. Nela colocaremos um valor verdadeiro ou falso após conferir se o número digitado pelo jogador resulta em uma jogada válida (número de 1 a 9 e se o campo selecionado já não foi marcado em uma jogada anterior).

(* Inicializando o campo de jogo *)
for i := 1 to 3 do
begin
   for j := 1 to 3 do
   begin
      campo[i, j] := 0;
   end;
end;

No início do processamento, inicializamos o campo de jogo, com dois fors encadeados, setando todas as posições da matriz com o valor 0, que indicará ‘vazio’ em nosso jogo. Após isso são exibidas as instruções aos jogadores e espera-se que uma tecla seja pressionada para continuar, através da função readkey.

Logo em seguida, realizamos algumas inicializações de variáveis. Setamos o valor 0 para a variável vencedor. Esta variável irá armazenar o valor 1 ou 2 dependende de quem ganhar o jogo, ou ainda o valor 3 caso o jogo termine empatado. Inicializamos também a variável vez, definindo o jogador 1 como quem vai iniciar o jogo, e a variável numero_jogadas, que será o nosso contador de jogadas, com o valor 0.

Prosseguindo, entramos dentro do looping principal do jogo, controlado pela variável vencedor (enquanto igual a zero, ou seja, enquanto não houver vencedor). Dentro do looping, limpamos a tela com a função clrscr (clear screen), e mostramos o campo de jogo atual.

Em seguida, temos uma consistência. Consistência vem a ser um método de “forçar” o usuário em um programa a inserir uma entrada desejada. Com o uso de consistências, evita-se que o programa reaja de maneira indesejada, devido a entradas inesperadas. Neste caso do nosso jogo, obtemos a leitura de uma jogada e verificamos se ela é válida, conferindo se a célula selecionada está livre e se o valor digitado corresponde a uma célula válida. Dessa forma, enquanto não tivermos uma jogada válida, ficaremos em looping.

Caso a jogada seja válida, incrementamos o contador de jogadas e conferimos se existe algum ganhador. Passamos a vez para o outro jogador e voltamos ao início do looping principal, até que haja um vencedor, ou o número máximo de jogadas seja atingido. Ao final, exibimos na tela o campo de jogo novamente, e apresentamos o resultado final.

Bom, pessoal, o jogo foi feito de maneira bem simples (pode não ser a maneira mais eficiente de se fazer), mas fiz com o intuito de mostrar a vocês um exemplo do que já podem programar com o conhecimento que já sabem. Apesar de não ter nenhuma interface gráfica, ele já serve pra gastar uns minutinhos aí se divertindo e ainda mostrar pros seus amigos.

Fica aí, então, a dica: que tal fazer um RPG de opções? Com o conteúdo dos próximos posts, vocês verão que o principal para se desenvolver um jogo não é o conhecimento da linguagem (obviamente, é necessário um certo conhecimento…), mas a criatividade e a capacidade de abstração (transformar o pensamento humano em lógica de programação!).

Bom, então até o próximo post, com um conteúdo bastante útil para a programação: Registros (estruturas). Abraço a todos e até a próxima!

C:Músicas LitúrgicasLiturgia XI – Tempo Comum Ano Cvence