Sendo um desenvolvedor full-stack eu escrevo, a maior parte do tempo, aplicações web utilizando .Net
e Node.js
em linguagens de alto nível como C#
e JavaScript
. Aqui e acolá, entretanto, eu me envolvo em projetos de automação, IoT ou retro-programação onde o bom e velho C reina absoluto.
Vez por outra, entretanto, eu sinto a falta de algumas facilidades de linguagens de nível mais alto, como templates
, um runtime
um pouco mais rico e, como o título sugere, o suporte a abstrações úteis como os Generics
.
Generics
Os tipos genéricos (
MSDNGenerics
) permitem que você personalize um método, uma classe, uma estrutura ou uma interface para o tipo exato de dados no qual ele atua. Por exemplo, em vez de usar a classeHashtable
, que permite que as chaves e os valores sejam de qualquer tipo, você pode usar a classe genéricaDictionary<TKey,TValue>
e especificar o tipo permitido para a chave e o tipo permitido para o valor.
Então, em C#, por exemplo, eu posso declarar uma lista genérica e especificar o tipo de dados que aquela lista irá manipular, isto permite que a partir da mesma classe (List<T>
) eu possa criar uma lista de números inteiros (List<int>
) uma lista de números reais (List<float>
), uma lista de textos (List<string>
) ou uma lista de um tipo de dados qualquer (List<MyStruct>
).
Listas Encadeadas
Uma lista ligada ou lista encadeada é uma estrutura de dados linear e dinâmica. Ela é composta por células que apontam para o próximo elemento da lista. Para “ter” uma lista ligada/encadeada, basta guardar seu primeiro elemento, e seu último elemento aponta para uma célula (também chamados de nó) nula.
Wikipedia

Não temos classes em C, mas podemos implementar um conceito similar e também muito poderoso: o tipo de dados abstrato (abstract data type – ADT, em inglês) que nos permite definir tanto a estrutura como o comportamento da nossa lista de dados que será criada como uma lista encadeada
. Se o conceito de lista encadeada
é novo para você, recomendo muito a leitura deste artigo do Wikipedia sobre o tópico.
Vamos supor que você precise organizar uma lista de pessoas e que cada pessoa desta lista possuas os seguintes dados:
- Nome
- Lista de tarefas
- Lista de números de telefones
Começando pelas estrutura mais simples, podemos declarar um struct
para as tarefas e um outro para os telefones:
Agora precisamos de uma estrutura para representar uma lista de telefones, outra para representar uma lista de tarefas e uma para representar uma pessoa com todos os atributos que desejamos.
Naturalmente, ainda precisamos definir uma estrutura para representar a lista de pessoas (que foi ocultada no código acima por uma questão de brevidade). Até aqui, definimos somente as estrutura de armazenamento, mas ainda não escrevemos nenhuma função para definir o comportamento destas listas.
Da maneira que foi escrito, precisaríamos de 3 conjuntos de funções praticamente idênticos (um para cada lista) para:
- instanciar uma nova lista
- adicionar itens
- remover itens
- localizar um item específico
- acessar um item pelo seu índice
A matemática não pode ser mais fácil. Temos 3 listas diferentes e precisamos escrever 5 funções para cada uma delas, totalizando 15 funções a serem escritas! Uma tarefa massante, repetitiva, ineficiente e muito suscetível a erros. E a coisa pode ficar muito pior num piscar de olhos…
Imagine se no meio do caminho, percebêssemos que precisamos manter listas de endereços, ou de documentos para cada pessoa. E se precisarmos ordenar as listas? Mais uma função a ser escrita e testada em cada uma delas. O fato é que, esta abordagem, embora muito simples, tende a gerar um código extenso e difícil de manter ao longo do tempo.
A arte de programar, disse Marijn Haverbeke em seu livro “Eloquent JavaScript“, é em grande parte, a arte de controlar a complexidade:
Para muitos de nós, escrever programas de computador é um fascinante jogo. Um programa é uma construção do pensamento. Não tem custos de construção, é leve e cresce facilmente ante nossas digitações.
Marijn Haverbeke
Se não formos cuidadosos, seu tamanho e complexidade vão aumentar fora de controle, confundindo até a pessoa que o criou. Este é o principal problema da programação: manter os programas sobre controle. Quando um programa funciona, ele é lindo. A arte de programar é a habilidade de controlar a complexidade.
Para reduzirmos o crescimento da complexidade do nosso pequeno projeto, precisamos encontrar uma maneira mais genérica de definir uma lista. Uma maneira de abstrair o fato de que estamos tratando com uma lista de números ou uma lista de texto ou seja lá qual for o tipo de dados que temos em mão. Em outras palavras: precisamos abstrair o tipo de dados e nos concentrar no comportamento da lista em si.
Reduzindo a complexidade
Veja um diagrama mais simplificado do funcionamento de uma lista encadeada e perceba como ele é mais objetivo que o diagrama mostrado no início do texto.

Qual a diferença?
Ele nos mostra como a lista funciona, mas não se importa com o que há dentro de cada nó. E é exatamente isto que vamos fazer com nosso código! No lugar de criarmos um NodeTelefones
, um NodeTarefas
e um NodePessoas
, vamos definir um nó genérico apenas: um único Node
. E com uma única maneira de representar um nó, podemos ter uma única maneira de representar uma lista. Finalmente, com uma única maneira de representar uma lista, podemos fazer uma pessoa ter as listas de que quisermos de forma muito mais simples.
Perfeito!
Veja que o segredo aqui foi mudar o tipo de dados do campo data
da estrutura Node
para um ponteiro não tipado o void *
.
Um void *
na linguagem C (lê-se void pointer), é um ponteiro genérico, muitas vezes chamado de ponteiro de propósito geral, que pode apontar para qualquer coisa dentro do seu programa e, em certos casos, para qualquer coisa dentro do seu computador. Tudo o que ele representa é um endereço dentro dos espaços de memória visíveis ao seu programa.
Você pode fazer o um void *
apontar para um int
, um array
, uma string
, uma Tarefa
, uma Pessoa
, um Telefone
, etc. Sabendo disto, podemos declarar o comportamento da nossa lista genérica.
Atribuindo Responsabilidades
Outro passo importante na redução da complexidade é saber escolher bem as responsabilidades de cada elemento de um programa.
Veja. Somos, neste ponto, capazes de representar pessoas, tarefas, telefones e listas. É preciso agora estabelecer uma relação de dependência entre eles para responder a perguntas como:
- quem é responsável por criar e destruir tarefas?
- quem é responsável por atribuir tarefas à pessoa correta?
- uma mesma tarefa pode ser compartilhada entre duas pessoas?
Estas reflexões devem estar explícitas nas assinaturas dos métodos para que você, programador, bata o olho em um conjunto de funções e consiga responder a este tipo de pergunta somente lendo o código-fonte.
Pode parecer óbvio demais para falar, mas se você decidir que uma tarefa pode ser criada e atribuída a mais de uma pessoa, então deve haver uma função para criar uma tarefa e outra para atribuí-a a uma pessoa qualquer. As funções devem ser nomeadas de maneira a refletir sua utilidade e devem, de acordo com o princípio da responsabilidade única, realizar uma única tarefa.
O código a seguir ilustra um exemplo de como as funções podem ser implementadas a partir desta ideia. Repare como é fácil saber o que cada função faz. Repare, também, como a lógica de adição de telefones e tarefas ficou simples com o uso dos void *
.
Escrevendo um programa de testes
Por fim, podemos escrever um programa simples utilizando tudo o que foi desenvolvido até aqui.
Conclusão
Embora o C seja uma linguagem estruturada, muitas vezes, as idéias do mundo da orientação a objeto podem ser utilizadas para facilitar nossa vida e simplificar nosso código.
Cada novo paradigma, técnica ou linguagem que aprendemos é uma ferramenta a mais que, uma vez bem compreendida, pode ser e adaptada para uso em problemas do dia-a-dia. Numa época em que computadores pessoais, poderosos smartphones e pequenos dispositivos microcontrolados convivem lado a lado trocando informações entre si, é importantíssimo manter a mente aberta e permitir-se experimentar buscando intersecções interessantes entre estes mundos.
Muito obrigado pela leitura.
Boa tarde Fabiano. Tudo bem? Estou começando a aprender Delphi e tenho acompanhado seu blog antigo. Lá tem um link para pegar o código fonte mas não tem mais o servidor “delphi-games-blog.googlecode.com/svn”. Vc poderia me informar qual é o novo servidor? Muito obrigado. OrO
LikeLike
Bom dia, tudo bem?
O servidor do site antigo está fora do ar faz algum tempo, mas tenho backups de quase tudo o que está lá.
Qual código você está precisando?
LikeLiked by 1 person
Olá Fabiano. Tudo ótimo. Espero que com você também.
Eu estou interessando nesse artigo: https://comofaazerr.wordpress.com/2015/01/03/ascii-art-convertendo-imagens-para-ascii/
DELPHI ASCII ART – CONVERTENDO IMAGENS PARA ASCII
Seu conversor de imagem em ascii. Você pode me enviar no email? fernandesorozimbo@gmail.com
Ficarei muito grato.
Futuramente vou precisar de uma consultoria para um projeto que estou desenvolvendo.
Posso contar com você?
Abraço!
LikeLike
Bom dia Fabiano. Estou tentando postar uma resposta mas acho que não foi.
O que preciso é sobre este post: DELPHI ASCII ART – CONVERTENDO IMAGENS PARA ASCII
pode me enviar um link para baixar por favor?
Muito obrigado
LikeLike
Bom dia Fabiano. Obrigado por responder.
Eu estou interessado em: DELPHI ASCII ART – CONVERTENDO IMAGENS PARA ASCII
Você poderia enviar ?
Abraço,
OrO
LikeLike