Artigo no estilo: Curso

Por que eu devo ler este artigo:O artigo é útil para desenvolvedores que desejam se aprofundar em Estruturas de Dados, gerenciamento de memória, ponteiros e alocação dinâmica, que em geral, são temas de muita importância e muito utilizados no cotidiano nas mais variadas situações.

Com conhecimentos avançados nestes temas conseguimos desenvolver formas de armazenamento robustas e que podem ser reaproveitadas em vários projetos de software.

Nesta série de artigos o leitor saberá escolher qual a estrutura de dados mais adequada para determinadas situações, além de aprofundar vários tópicos importantes da linguagem Delphi.

Um dos segredos de termos softwares rápidos com certeza passa pela correta implementação e utilização de estruturas de dados. A forma como os dados estão organizados durante o processamento fazem com que o sistema tenha uma melhor ou pior performance.

O estudo das mais variadas estruturas de dados costuma ser uma das maiores dificuldades para os desenvolvedores. Observa-se principalmente nas universidades os problemas de muitos alunos de compreenderem esta área da Ciência da Computação que é tão importante no desenvolvimento de softwares.

Nos dias atuais, a maioria das pessoas utiliza a agenda de um telefone celular para fazer o gerenciamento de seus contatos, sejam números de telefones ou e-mails. Este simples recurso de armazenar telefones e e-mails envolve duas tarefas importantes.

A primeira é definir como o dado será armazenado na memória, pois é necessário de alguma forma persistir a informação. A segunda tarefa é disponibilizar operações de criação, recuperação, ordenação, alteração e remoção de dados desta agenda.

A primeira tarefa influi muito na performance de qualquer aplicativo, pois quando informações são armazenadas de maneira desorganizada ou incorreta, fica muito mais difícil fazer a manipulação destas informações.

Apesar da importância deste armazenamento de informações, o modo como o sistema persiste os dados deve ficar oculto para o usuário final, pois o que realmente interessa para o usuário são maneiras de fazer operações com a agenda e não conhecer a maneira como estes dados são armazenados e recuperados.

Gerenciamento de Memória

Há três áreas de memória onde as aplicações armazenam dados: global memory, heap e stack. Variáveis globais são armazenadas na memória global (global memory). Esta memória é reservada pela aplicação quando o programa inicia e permanece alocada até que o programa termine sua execução. A memória global é também chamada de data segment.

Quando declaramos uma variável dentro de um método, a memória requerida para armazenar tal variável é alocada numa área denominada Stack. Quando o programa sai do escopo do método, as alocações da Stack são liberadas automaticamente.

A memória Stack é alocada dinamicamente utilizando o conceito de pilha (LIFO – Last In First Out). Em Delphi, a memória Stack é utilizada para:

· Variáveis de rotinas locais (métodos, procedimentos e funções);

· Parâmetros de rotinas e tipos de retorno;

· Chamadas a funções da API do Windows;

· Records;

Não é necessário alocar nem liberar a memória Stack, pois este processo é feito automaticamente. O tamanho da memória Stack é, por padrão, grande o suficiente para os programas em Delphi, sendo assim, muito raramente este tamanho terá que ser alterado.

Nota: Para visualizar o tamanho da Stack, clique no menu Project – Options – Linking e note os campos Maximum Stack Size e Minimum Stack Size. Não recomenda-se que se mude estas configurações, pois isso pode acarretar problemas nas aplicações.

Variáveis locais não são inicializadas quando declaradas, portanto, uma boa prática é antes de utilizar sua variável, inicializá-la para que você não tenha que se deparar com um valor absurdo.

Devido à estrutura de pilha utilizada pela Stack, o acesso a esta memória é muito rápido, pois apenas algumas poucas operações são realizadas para armazenar e ler o valor de uma variável.

Temos ainda a memória Heap, ela é utilizada para:

· Criar uma instância de uma classe;

· Criar e redimensionar arrays dinâmicos;

· Alocar memória explicitamente, usando GetMem, FreeMem, New e Dispose;

· Utilizar Ansi/Wide/Unicode strings, variants e interfaces (gerenciados automaticamente pelo Delphi);

Os blocos de memória alocados na memória Heap não têm uma ordem lógica de acesso, ou seja, seu acesso é randômico. Por isso que operações realizadas na memória Heap são um pouco mais lentas se comparadas às da Stack.

Estruturas de Dados

Em Ciência da Computação as estruturas de dados são definidas como uma forma de armazenamento e organização dos dados, de modo que os mesmos possam ser usados eficientemente.

Existe uma diversidade muito grande de estruturas de dados, cada uma com um leque de aplicações e algumas ainda bem específicas para atender um único domínio de problema.

Quando criamos uma estrutura de dados definimos algumas operações básicas para a mesma:

· Criação da Estrutura;

· Inclusão de Elementos;

· Remoção de Elementos;

· Acesso ao Elemento;

· Etc.

Quando se trabalha com estruturas de dados, geralmente um assunto tratado de forma conjunta é o de análise de algoritmos.

Análise de Algoritmos e Complexidade Computacional

Quando fazemos a análise de algoritmos, o fazemos para provar que o algoritmo desenvolvido está correto e também para determinarmos os recursos computacionais que foram exigidos pelo mesmo, além do tempo de execução, espaço alocado e utilizado, etc.

Comparamos os recursos exigidos por diferentes algoritmos que resolvem o mesmo problema (um algoritmo mais eficiente exige menos recursos para resolver o mesmo problema) e ainda conseguimos prever o crescimento dos recursos exigidos por um algoritmo à medida que o tamanho dos dados de entrada cresce.

A Complexidade Computacional é um ramo da Matemática Computacional (BOX 1) que estuda a eficiência dos algoritmos. Para medir a eficiência de um algoritmo frequentemente usamos um tempo teórico que o aplicativo leva para encontrar uma resposta em função dos dados de entrada.

BOX 1. Matemática Computacional

A matemática computacional é uma área da matemática e da computação que colabora na solução de problemas de todas as áreas tanto das ciências exatas, quanto de outras áreas quaisquer, através do desenvolvimento de modelos matemáticos, para o tratamento de problemas complexos, e desenvolvimento de métodos numéricos de obtenção de soluções.

Possui também forte integração com a Modelagem Computacional, uma área de conhecimento que trata da aplicação de modelos matemáticos à análise, compreensão e estudo da fenomenologia de problemas complexos em áreas tão abrangentes quanto as Engenharias, Ciências exatas, Computação, e Ciências humanas.

Atua na interface com outras ciências como a física, a engenharia, a biologia, a ciência da computação, a tecnologia da informação, dentre outras.

O Brasil colabora fortemente com a computação científica com vários centros de estudos avançados.

Tipos Primitivos

O Delphi possui uma série de tipos primitivos, como inteiros, números reais para partes fracionárias, etc.

Os tipos de dados integer são usados para representar números inteiros. Existem vários tipos diferentes de dados integer. Os tipos de dados real são projetados para conter um número com uma parte fracionária.

Na maioria das linguagens, você tem que usar um tipo de dado real para representar um valor monetário. O Delphi fornece o tipo Currency especialmente para este uso.

Trata-se de um tipo ponto flutuante que é compatível em relação à atribuição com todos os outros tipos de ponto flutuante. O tipo Currency tem uma precisão de quatro casas decimais e é armazenado como um inteiro de 64 bits (onde os quatro dígitos menos significativos representam os 4 números à direita do ponto decimal).

Você pode estar se perguntando por que deveria usar o tipo Currency em vez do tipo de dado real. O tipo Currency oferece duas vantagens importantes:

· Tem uma precisão maior para conter números grandes;

· É usado em CurrencyField e em outros componentes. Ele é compatível com tipos de banco de dados que representam dinheiro.

O tipo de dados Boolean é um dos mais simples e mais usados. As variáveis desse tipo representam um valor lógico: TRUE ou FALSE. As variáveis do tipo boolean aceitam operadores condicionais.

O tipo char foi projetado para armazenar um caracter. Um caracter tem um byte de comprimento, o que daria 256 caracteres que poderiam ser colocados em uma variável do tipo char.

O Delphi permite também o uso de caracteres do sistema UNICODE (Unicode é um padrão que permite aos computadores representar e manipular, de forma consistente, texto de qualquer sistema de escrita existente) utilizando de 2 Bytes. O tipo de dados String tende a ser um pouco mais útil do que o tipo Char. No Delphi, o tipo de dados String era uma concatenação de até 255 caracteres individuais.

A seguir são listados os principais tipos primitivos do Delphi:

· Byte - (Tipo por valor) - Inteiro de 8 bits sem sinal (0 a 255).

· Short - (Tipo por valor) - Inteiro de16 bits com sinal ( -32768 a 32767).

· Integer - (Tipo por valor) - Inteiro de 32 bits com sinal (- 2147483648 a 2147483647).

· Long - (Tipo por valor) - Inteiro de 64 bits com sinal (-9223372036854775808 a 9223372036854775807).

· Single - (Tipo por valor) - Ponto flutuante 32 bits.

· Double - (Tipo por valor) - Ponto Flutuante 64 bits.

· Decimal - (Tipo por valor) - Ponto Flutuante decimal de 128 bits (1.0 x 10^ -28 a 7.9 x 10^ -28),

· Boolean - (Tipo por valor) - Pode ter os valores True e False.

· Char - (Tipo por valor) - Representa um único caractere unicode de 16 bits.

· Date - (Tipo por valor) - Representa uma data ou hora.

· String - (Tipo por referência) - Representa uma sequência de caracteres unicode.

Recursividade

Muitos problemas têm uma característica comum: cada instância do problema contém uma instância menor do mesmo problema. Dizemos então que esta situação possui uma estrutura recursiva.

A recursividade na prática ocorre quando uma rotina ou método pode invocar a si próprio. Um dos cuidados a ser tomado nesse tipo de implementação é o de cuidarmos para não entrar num loop infinito.

Para que isto não ocorra, é necessário que a cada chamada seja alterado o argumento de passagem do método.

Na Listagem 1 temos um exemplo de emprego do cálculo do fatorial de um número, onde empregamos o cálculo de forma recursiva através da chamada do método Fatorial dentro dele próprio (linha 13).

Listagem 1. Exemplo de Recursividade em Delphi


  01 program Fatorial;
  02 
  03 {$APPTYPE CONSOLE}
  04 
  05 uses
  06   SysUtils;
  07 
  08 function Fatorial(x: Integer): Integer;
  09 begin
  10   if x = 0 then
  11     Result := 1
  12   else
  13     Result := x * Fatorial(x - 1);
  14 end;
  15 
  16 begin
  17   Writeln(IntToStr(Fatorial(10)));
  18   readln;
  19 end.

Alocação Dinâmica e Ponteiros

Os ponteiros são um tipo especial de variável que ao invés de guardar um dado, guarda um endereço de memória que referencia um dado, ou seja, elas apontam para determinado endereço.

Isto às vezes confunde o desenvolvedor, porque além de saber a informação, é necessário também o conhecimento do endereço daquela informação. Se uma variável do tipo ponteiro apontar para um endereço de memória inválido, recebemos um erro de acesso à memória, o famoso Access Violation.

Quando trabalhamos com variáveis estáticas, o próprio compilador faz o gerenciamento da memória necessária para armazenamento dos dados, não há a necessidade da preocupação do programador no sentido de alocar nem liberar espaço para ...

Quer ler esse conteúdo completo? Tenha acesso completo