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! 😀

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

Free Pascal

Padrão


Olá pessoal!

Esse post é bem curto, e serei direto: apesar de programar em Pascal há pouco mais de um ano, não conhecia a interessantíssima comunidade do Free Pascal. Lá é possível encontrar fóruns, downloads, notícias e muito mais. Na seção de downloads, é possível encontrar uma ótima IDE para programar em Pascal. A Free Pascal IDE, apesar de seu visual “console”, é super completa e oferece muitos recursos interessantes. Além disso, ao baixar o pacote da versão 2.2.4, você encontrará também vários arquivos de documentação sobre a linguagem.

Pra quem utiliza (ou já utilizou) o Delphi, pode também dar uma conferida na IDE chamada Lazarus. Utilizei a versão Linux no Ubuntu, e me espantei pela quantidade de recursos. A disposição dos menus, barras de ferramentas, componentes, enfim, tudo muito parecido com o Delphi/C++ Builder. E o melhor de tudo: totalmente gratuito!

Então é isso pessoal, fica aí a dica pra quem gosta da linguagem Pascal!

Abraço a todos! 🙂


Laços de Repetição

Padrão

Olha eu aqui denovo. Apesar dos sumiços periódicos, até que o blog está caminhando bem… E estou gostando também que, mesmo tendo um nível bem baixo de atualizações, o blog está com uma média de 15 a 20 visitas por dia. Saber que o blog está sendo útil para algumas pessoas me deixa muito contente. Ah, gostaria também pedir desculpas por não estar respondendo os e-mails nas últimas semanas… é que estou muito sem tempo mesmo… iniciação científica, projeto da FAITEC (a feira tecnológica da minha faculdade), seminários, palestras, e agora ainda tem a Maratona de Programação, além também de uns problemas pessoais aqui. Mas de boa, podem mandar as dúvidas que eu responderei na medida do possível. Bem, então vamos ao que interessa!

Hoje iremos tratar de um tema de suma importância na programação: Os Laços de Repetição. Em nossos programas já estamos lendo dados do usuário. Imagine o seguinte: vamos fazer uma soma de números. Lemos dois números e realizamos a soma. E se quiséssemos fazer uma soma de, por exemplo, 100 números, ou mesmo uma quantidade X definida pelo usuário? No caso da soma de 100 números, teríamos 100 vezes o comando Leia()? E como faríamos pra ler uma quantidade X de vezes? Sem as estruturas de repetição, sem dúvida, seria bem complicado. Além disso, teríamos um código extremamente grande e repetitivo, com comandos iguais pra fazer as mesmas coisas. Os Laços de Repetição foram criados para solucionar estes problemas, deixando os códigos mais compactos, mais legíveis e mais rápidos de serem desenvolvidos.

Os Laços de Repetição que iremos tratar aqui são o Enquanto, Faça e o Para. Descrevendo-os resumidamente, temos o seguinte:

  • Enquanto: testa a condição antes de executar o bloco de código
  • Faça: executa o bloco de código e depois verifica a condição
  • Para: executa um bloco de código um determinado número de vezes

Vamos começar, então, falando do Enquanto. O enquanto possui um funcionamento bem simples, porém é de grande utilidade. Ele compara uma sentença e executa o bloco de código se a condição for verdadeira. Da mesma forma que, por exemplo, no Se, você pode colocar diversas condições, com o uso dos operadores lógicos e e ou, além do não. A seguir temos o exemplo da sintaxe em algoritmo…

enquanto (nota > 70) faça
   comandos...
fim-enquanto

… e em Pascal…

while (Nota > 70) do
begin
   comandos...
end;

Dessa forma, utilizamos o Enquanto quando vamos executar uma repetição sem sabermos necessariamente o número de vezes que ele será executado. O looping pode, ainda, não ser executado nenhuma vez, caso a condição não seja satisfeita, ou seja, no Enquanto, testa-se a condição antes de executar o bloco de código.

Outra estrutura de repetição importante é o Repita. Ela possui praticamente o mesmo funcionamento que o Enquanto, porém a condição é testada após a execução do bloco de código. Abaixo, temos a sua sintaxe algoritmica…

repita
   comandos...
enquanto(nota <= 70) e (nota >= 30)

… e em Pascal…

repeat
   comandos...
until(nota <= 70) and (nota >= 30);

Neste exemplo acima, utilizei o operador lógico e para condicionar a repetição. Dessa forma, o looping será executado somente enquanto a variável Nota estiver na faixa entre 30 e 70 (para mais detalhes sobre os operadores lógicos, dê uma olhada no post sobre eles).

Por último, mas não menos importante, temos o Para. Esta estrutura de repetição se diferencia das outras por executar a repetição um determinado número de vezes. Como assim? A execução do Para é executada através da verificação e incremento de uma variável inteira, ou seja, determinamos a quantidade de vezes que a repetição será executada através de um limite para o valor da variável. Meio confuso? Calma, nos exemplos abaixo vai ficar mais claro. Primeiro na forma algoritmica…

para i = 1 até 50 faça
   comandos...
fim-para

… e em Pascal…

for i := 1 to 50 do
begin
   comandos...
end;

No exemplo acima, temos a variável I funcionando como o contador de execução do looping, sendo incrementado automaticamente a cada execução do bloco de código. Dessa forma, o nosso looping será executado 50 vezes, ou seja, enquanto a variável I for menor ou igual o valor 50. Podemos, em vez do valor 50 colocar outra variável ou uma constante pra controlar a quantidade de vezes que o Para será executado.

O Para também possui uma particularidade interessante, podendo realizar decrementos a cada execução do looping. Dessa forma, podemos, por exemplo, criar contadores decrescentes, ou ainda exibir vetores invertidos (para mais informações sobre vetores, consulte o próximo post). A seguir temos a sintaxe do Para com decremento tanto em algoritmo quanto em Pascal:

para i = 50 até 1 passo -1
   Comandos...
fim-para
for i := 50 downto 1 do
begin
   Comandos...
end;

Bem, pessoal, acho que é isso. Com o tempo vocês verão que a utilização das estruturas de repetição está sempre presente no dia-a-dia de um programador e tem papel fundamental na construção de um código otimizado. Fiquem atentos, pois no próximo post iremos falar de vetores e matrizes. Até lá e bons estudos!

Estruturas de Seleção

Padrão

Olá pessoal! Não, eu não morri, só sumi por um tempo maior do que eu planejava. E não peguei a gripe suína também. Pelo menos por enquanto. A minha faculdade é uma das únicas da região que não suspendeu as aulas. Enfim, melhor assim. Sinceramente acho que essas medidas de suspender a volta às aulas por uma, duas semanas não adianta em nada.

Bem, mas vamos ao que interessa. No último post, eu deixei alguns exercícios pra vocês treinarem a elaboração de algoritmos e a implementação na linguagem Pascal. Então aqui você encontra as soluções para os exercícios (tanto a resolução algorítmica quanto as implementações em Pascal). As implementações foram feitas no PascalZim. Portanto, se alguém for compilar em algum outro ambiente, como o Turbo Pascal, não se esqueçam de adicionar depois da seção Program a seção Uses.

Vamos então ao nosso assunto de hoje. Até agora, todos os programas que fizemos eram totalmente sequenciais, ou seja, começavam da primeira linha e iam executando uma em seguida da outra, até a última linha. Dessa forma o nosso programa executa sempre as mesmas ações, o que não acontece com os programas em geral. Os programas geralmente, baseados em algum dado, selecionam porções do código a serem executadas, realizando operações diferenciadas a cada execução. Para representar essa seleção em nosso programa utilizamos as estruturas de seleção.

A primeira estrutura de seleção que iremos conhecer é o Se. O Se é utilizado quando esperamos por uma condição. O bloco de código referente só sera executado se a condição for verdadeira. A seguir temos um exemplo algoritmico da utilização dessa estrutura e sua implementação em Pascal.

leia(numero)
se (numero MOD 2 = 0) então
   início
      escreva("O número é par.")
   fim

 

readln(numero);
if (numero MOD 2 = 0) then
   begin
      write('O número é par.');
   end;

Neste exemplo, lemos do usuário a variável numero. Se o resto da divisão do conteúdo da variável for igual a zero, será mostrada na tela a frase O número é par. A partir disso, podemos criar condições para o nosso programa, executando determinadas tarefas somente se algumas condições forem satisfeitas. Mas e se, por exemplo o número não fosse par? Ele seria ímpar… mas como faríamos isso no programa? A estrutura do Se é composta do Senão, que é executado caso a condição do Se não seja satisfeita. A seguir temos o exemplo da utilização do Se com o Senão na forma algoritmica e na linguagem Pascal:

leia(numero)
se(numero MOD 2 = 0)
   então
      escreva("O número é par.")
   senão
      escreva("O número é impar.")
fim-se

 

readln(numero);
if (numero MOD 2 = 0) then
   begin
      write('O número é par.');
   end
else
   begin
      write('O número é ímpar.');
   end;

Dessa forma, se o número digitado fosse par, a mensagem O número é par. Caso contrário, a mensagem O número é ímpar. seria exibida. Além disso, podemos encadear vários ‘Ses’, criando uma série de condições. Podemos também, colocar mais de uma condição ou negar uma condição, com a utilização dos operadores lógicos e, ou e não.

leia(idade)
se (idade < 13)
   então
      Escreva("Criança")
senão se (idade >= 13 e < 21)
      escreva("Adolescente")
   senão
      escreva("Adulto")
fim-Se

 

readln(idade);
if (idade < 13) then
   begin
      write('Criança');
   end
else if (idade >= 13 and idade < 21) then
   begin
      write('Adolescente');
   end
else
   begin
      write('Adulto');
   end;

Ah, um detalhe. Percebam que em Pascal, somente o último End de uma sequência de Ifs tem ponto-e-vírgula! Observando o exemplo acima, podemos realizar diversas tarefas, encadeando diversos Ifs / Elses. Apesar de simples, esta estrutura é vastamente utilizada na programação. Porém, quando precisamos conferir o valor de uma variável frente a muitos valores, o uso de Ifs encadeados torna-se trabalhoso, principalmente ao programador, além de deixar o código repleto de Begins e Ends. Para resolver problemas do tipo, utilizamos uma estrutura chamada Escolha ou Case. Com ela, podemos comparar o conteúdo de uma variável frente a diversas constantes, de uma forma bem mais organizada que o simples encadeamento de Ifs. A seguir, temos um exemplo de um programa de apuração de votos, com a utilização do Escolha.

...
escolha (votos)
   caso (votos = 1) : candidato1 ← candidato1 + 1
   caso (votos = 2) : candidato2 ← candidato2 + 1
senão
   escreva("Voto inválido")
fim-escolha

 

case votos of
   1 : candidato1 := candidato1 + 1;
   2 : candidato2 := candidato2 + 2;
else
   write('Voto inválido');
end;

Neste caso, a variável votos foi comparada com os valores 1 e 2. Dependendo do seu conteúdo,  incrementa-se a variável correspondente ao candidato. Caso o voto não confira com nenhuma das opções do case, ele irá executar o comando para escrever Voto inválido na tela. Nesta estrutura, assim como no se, o uso do senão é facultativo, ou seja, ele não precisa necessariamente estar presente, sendo utilizado de acordo com a necessidade. Outro ponto é que os valores constantes comparados não precisam ser necessariamente inteiros. Podem ser, por exemplo, um tipo caracter.

Bom, pessoal, é isso. No começo pode ser que vocês estranhem um pouco estas estruturas, mas com o tempo elas virão a estar presente em praticamente todas as aplicações que vocês fizerem. E lembrem-se que, em caso de dúvida, podem me mandar um e-mail (rafaelmgn@yahoo.com.br ou rafaeldtoledo@gmail.com) ou simplesmente deixar um comentário aqui no blog.

Obrigado pela visita e até o próximo post com as Estruturas de Repetição. Abraço a todos! 🙂