1. Introdução à Programação

Uma palavra-chave para entendimento do que seja programação é o conceito de algoritmo, discutido na próxima seção. Um exemplo é algoritmo da multiplicação, sobre o qual existem registros de seu uso há mais de 5.000 anos. Outro exemplo é o algoritmo da divisão utilizado por babilônios (2.500 AC) e por egipcios (1.500 AC).

Algoritmos, linguagens de alto ou baixo nível e programas

De modo simplificado, um algoritmoé uma sequência finita de passos bem definidos para a realização de uma tarefa. Um exemplo simples, seria a seguinte descrição para se preparar café:

1. Ferver 1/2 litro de água;
2. colocar 2 colheres (de sopa) de pó de café no coador;
3. derramar (sem deixar vazar) a água fervente sobre o pó no coador.
Quando um algoritmo está codificado em um formato que o computador "entende" (i.é., consiga executá-lo), dizemos tratar-se de um programa. Nesse caso, o programa está codificado em linguagem de máquina ( linguagem de baixo-nível). Por outro lado, se o algoritmo estiver codificado em uma linguagem mais próxima da linguagem humana, dizemos tratar-se de uma linguagem de alto-nível para programação. Alguns exemplos de linguagens de programação são as linguagens CPythonJava e JavaScript.

A tabela 1 ilustra um trecho de código fonte em uma pseudo-linguagem de programação que usaremos em alguns dos textos e do lado direito, seu código executável correspondente (na verdade dois trechos, as linhas 48, 49 e 108).

Tab. 1. Ilustração de códigos em alto e baixo nível.

Código fonte em PortugolTrechos do código executável em Hexadecimal
se (a>b) imprima(a);0000280 4e47 0055 77cb ec51 b2cd b8c3 85e7 4b43
0000290 6846 2199 5e93 2e9f 0001 0000 0001 0000
00006b0 4855 e589 03be 0000 4800 3d8d 00a4 0000

Um programa codificado em uma linguagem alto-nível é denominado programa fonte, enquanto o programa em baixo nível é programa executável. O primeiro recebe o nome de fonte por ser a origem do segundo, que é aquele que pode ser executado por um computador, daí a origem do segundo nome.

Sintaxe de linguagem

Uma característica importante de qualquer linguagem de programação é a sua sintaxe muito bem definida. Por sintaxe entenda o conjunto de regras a partir das quais podemos reconhecer que um texto nessa linguagem está sintaticamente correto ou não. Para ilustrar, podemos pensar na sintaxe de uma expressão aritmética (EA), por exemplo, se EXPA1 e EXPA2 forem expressões aritméticas sintaticamente corretas, então certamente a nova expressão definida como EXPA1+EXPA2 também será uma EA correta.

Entretanto, devemos destacar que enquanto a língua falada permite ambiguidades, uma linguagem de programação não pode admitir isso, pois não seria razoável esperar que um programa (com os mesmos dados de entrada) em determinado momento produza um resultado e noutro produza resultado distinto. Pense no algoritmo da multiplicação, não é admissível que 33 vezes 45, que deveria sempre resultar 1.485, eventualmente obtenha como resposta 4.185.

Um exemplo de frase ambígua na língua Portuguesa seria "essa fruta está verde", que pode significar que a fruta não está madura ou referir-se à cor da fruta (e.g. caiu tinta sobre ela).

Neste texto ilustramos a última etapa do processo de programação usando uma linguagem semelhante ao Português, adotando uma sintaxe derivada da linguagem Algol, por isso denominada Portugol.

Da mesma forma que a sintaxe é importante para existir comunicação entre falantes de uma mesma lingua, toda linguagem de programação deve ter uma sintaxe associada para permitir sua automatização via computador.

Compilador e interpretador

Uma vez um programa codificado em linguagem alto nível é compreensível por humanos, para produzir resultados práticos, é essencial que exista um programa executável

Mas como pode-se imaginar, que deve existir um programa (em linguagem de máquina) que consiga traduzir um código em uma linguagem de alto-nível para seu correspondente em baixo nível. Esses programas tradutores podem ser um compilador ou um interpretador.

Código fonte traduzido para código executável
Fig. 1. Dada um programa fonte, esse precisa ser traduzido para um executável/interpretável.

A grande diferença entre um compilador e um interpretador, é que o primeiro realiza a tradução uma única vez (compilador), enquanto o segundo realiza a tradução toda que executa o código (interpretador).

Como citado acima, o programa codificado na linguagem alto-nível é denominado programa fonte, enquanto o programa em baixo nível é programa executável.

Existem variantes entre ambos os modelos, como na linguagem Java que existe um passo de compilação, que gera um código binário (byte code) que, ao final deve ser interpretado. Nesse modelo pode-se distribuir para quem está interessado em rodar o seu programa, apenas o código binário, ou seja, não é necessário distribuir o código fonte.

Desse modo, outra grande diferença entre linguagens compiladas e linguagens interpretadas é que, se você é um desenvolvedor de software, utilizando uma linguagem compilada, então você pode distribuir apenas a código executável, não precisando divulgar seu código fonte. Já para desenvolver software em linguagem interpretada (como JavaScript), precisará entregar o código fonte.

Princípio da alavanca: evolução da linguagem baixo nível para alto nível

O modelo de computador atual segue os princípios estabelecidos pelo matemático John von Neumann, no qual tantos dados quanto programa fica na memória, podendo-se alterá-los.

Por ser possível alterar livremente os programas na memória, esse modelo permitiu a construção de linguagens de programação de níveis mais elevados, o que podemos entender como mais uma das aplicações do princípio da alavanca:

  1. desenhar uma primeira linguagem de montagem e implementar um programa de baixo nível que consegue traduzir qualquer código correto nessa linguagem para a linguagem de baixo nível;
  2. desenhar uma segunda linguagem, em nível mais alto (e.g. a linguagem C) e implementar um compilador para essa linguagem usando a linguagem de montagem anteriormente obtida;
  3. e assim por diante até conseguirmos as atuais linguagens de programação.

Princípio da alavanca 1
Princípio da alavanca 2
Fig. 2. Com o princípio da alavanca fica mais fácil "mover o mundo".

Como resultado desse processo de alavancagem, hoje podemos desenvolver software de modo muito mais rápido usando linguagens de alto nível.

A partir da implementação de uma linguagem em computador, pode-se definir uma nova linguagemcodificá-la a partir da primeira e depois gerar uma versão computacional, permitindo programar a partir da segunda linguagem. A primeira linguagem serviu de alavanca para a segunda.

De que se trata aprender a Programar?

Desse modo, um curso introdutório à programação (IP) visa ensinar os princípios da programação em alguma linguagem de alto nível e isso envolve a capacidade de encontrar soluções algorítmicas para problemas práticos ou teóricos, além de ensinar como codificar e testar o programa-solução que implementa tal algoritmo.

Isso significa que a dificuldade em um curso IP não é meramente "aprender" uma nova "língua", esse aprendizado é necessário, mas não é o que demanda maior esforço para aprendizado. É preciso aprender a encontrar um algoritmo que resolva determinado tipo de problema, considerando qualquer das possibilidades de dados para esse tipo. Os dois primeiros desse processo é ilustrado na figura 3: dado o problema, desenhar um modelo para depois construir sua solução algorítmica.

Esquema do que é programar
Fig. 3. Os dois primeiro passos a serem aprendidos em um curso introdutório de programação.

Por fim, sua implementação deveria funcionar para todas as possíveis entradas para o problema considerado. Cada entrada pode ser considerada uma instância do problema. Um exemplo seria multiplicar dois números inteiros, digamos n1 e n2.
Cada par de inteiros n1 e n2 será uma instância de entradas. O algoritmo deveria funcionar para qualquer para de valores inteiros, não apenas para n1=33 e n2=45.

Assim, programar demandará o aprendizado em três fases: modelar problemas, encontrar algoritmos usando tal modelo e, por fim, implementar esse algoritmo em uma linguagem de programação (e testar se funciona). Cada uma dessas fases tem as suas dificuldades, mas deve-se destacar que uma vez que você aprendeu a programar usando uma linguagem, poderá aprender outra linguagem mais facilmente, poderia focar nas diferenças entre as linguagens, mas essas podem não ser pequenas.

Variáveis para armazenar valores para as instâncias dos problemas

Para ser possível aplicar um algoritmo a uma ampla variedade de distintas entradas é necessário que o algoritmo seja desenhado a partir de valores variáveis, como o já citado algoritmo da multiplicação entre dois números.

No exemplo anterior, os termos n1 e n2 são as variáveis, que poderiam armazenar valores que variariam. Os valores para n1 e n2 definem uma instância para o problema da multiplicação e, desse modo, o algoritmo desenhado para multiplicar é aplicável à quaisquer dois valores.

Todas as linguagens de programação de alto nível permitem o uso de variáveis e são com elas que podemos armazenar os valores para cada instância do problema. Isso será ilustrado no exemplo 1, com variáveis do tipo vetor ou lista.

Ilustração do processo de contrução de solução algoritmíca

Para ilustrar o que você deverá aprender um curso de introdução à programação (IP), podemos ilustrar os dois passos acima em uma situação envolvendo realizar compras semanais.

Exemplo 1. Um "cliente" (quem lhe contratou para desenvolver o programa) precisa ir ao mercado para comprar determinados produtos (lista de compra) e, por meio da Internet, consegue obter o valor unitário de cada produto desejado em (digamos) N mercados. Então seu objetivo seria implementar um programa que determina qual o "melhor mercado" para realizar suas compras.

Assim, resolver esse problema na forma de algoritmo não seria meramente computar qual dos mercados teria "o menor valor para a lista de compra desejada naquele momento", mas encontrar uma solução que possa ser aplicada a outros produtos e mercados. Ou seja, deveria funcionar para qualquer instância do problema (para qualquer lista de compra e para qualquer lista de mercados).

Modelagem e estrutura de dados para o problema

Desse modo, o modelo consideraria a existência de duas listas, uma lista de produtos ( q[0] unidades do produto 0q[1] unidades do produto 1 e assim por diante) e uma lista de preços desses produtos em cada mercado (o mercado i teria o valor unitário para o produto j de v[i][j] unidades monetárias). Assim, por facilidade para implementação marcamos o primeiro produto e primeiro mercado como de índice 0, daí v[1][1] e v[1][3] seriam, respectivamente, os preços unitários do produto 1 e do produto 3 no mercado 1. Portanto, os valores de produtos por mercados poderiam ser armazenados em uma tabela, que em programação receberá o nome de matriz. Mas se imaginarmos que para esse problema usaremos os preços por mercado apenas uma vez, podemos armazenar todos eles em uma única lista, v[0]v[1] e assim por diante até v[N-1].

Encontrar o modelo para o problema está associado com a melhor escolha para a estrutura de dados, a melhor forma para armazenar os dados da instância do problema. No caso, a lista de produtos e as listas de preços. Em um curso de introdução serão examinadas apenas estruturas de dados básicas, como vetores/listas e matrizes.

Até o final de um curso de IP você deve ser capaz de desenhar o modelo acima e de conseguir visualizar a sua solução na forma de um algoritmo. Mas para você ter uma ideia do que se deve aprender em IP, apresentamos o processo de construção desse algoritmo.

Dificuldades e ideiais centrais

O problema envolve a necessidade de tratar a "cesta de compra", a lista do que deve ser comprado (que nomeamos q[.]), conseguir saber o preço da "cesta" em cada mercado e conseguir obter o menor preço.

Conseguir o preço em um mercado implica em determinar um laço de repetição usando a ideia da acumulação: uma variável recebe o que ela guardava (até então) somado ao resultado de alguma expressão aritmética. No exemplo usaremos o seguinte acumulador: valor receber o conteúdo em valor mais "q[i]*v[i]", que codificamos como "valor = valor + q[i]*v[i]".

Entretanto, precisamos disso para cada mercado e, na medida que computamos o custo em um mercado, devemos atualizar o registro de qual deles apresentou o menor "até o momento". Logo isso resultará em um algoritmo de laço duplo(laço de repetição dentro de outro laço).

Desenhando uma solução completa usando Portugol

O objetivo dessa seção é apresentar uma solução para o problema de encontrar o "melhor" mercado a comprar e talvez seja dificil entender completamente o processo. O mais importante é tentar perceber a ligação do algoritmo com o modelo (o tratamento das variáveis).

Passo 1. Deve-se tentar desenhar o algoritmo de modo construtivo. Podemos pensar em obter o valor da lista para cada mercado, começando com o mercado 0: deve-se digitar os preços para os produtos 0, 1, 2 até N-1, respectivamente, nas variáveis v[0], v[1] até v[N-1]. Com a lista q[.] e os preços v[.], devemos conseguir efetuar o cálculo para obter o preço no mercado 0, assim

valor = q[0]*v[0]+ q[1]*v[1] + ... + q[N-1]*v[N-1].

Como desejamos o menor preço e o algoritmo iniciou-se agora, podemos pensar que o menor preço até aqui é

menor_valor = valor e melhor_mercado = 1.

Uma vez que já sabemos o preço total da lista no mercado 0, podemos usar as mesmas variáveis v[.] para digitar os preços no mercado 1.

Passo 2. Depois poderiamos fazer a mesma coisa para o mercado 2, computando

valor = q[0]*v[0]+ q[1]*v[1] + ... + q[N-1]*v[N-1].

Então podemos comparar o preço do mercado 1 com o menor preço até então (esse argumento está associado ao processo de indução finita).

se (valor<menor_valor) então { menor_valor = valor; melhor_mercado = 2 }

Desse modo, após executar esse comando, teremos na variável menor_valor o menor valor para as compras entre os 2 primeiros mercados e na variável melhor_mercado o índice do mercado com esse menor preço.

Passo k. Usando um princípio de álgebra elementar, a transitividade (a<b e b<c então a<c), podemos estender a ideia acima para um passo genérico k, conseguindo o "menor preço dentre os k primeiros mercados".

Uma vez que o número de mercado é arbitrário, a próxima tarefa é generalizar o esquema acima lançando mão de um conceito presente em qualquer linguagem de programação de alto nível: codificar por meio de um laço. No exemplo usaremos um laço de repetição com tamanho fixo, que em Portugol recebe o nome de repita_para, mas em várias linguagens é apenas o comando for.

Uma solução completa em Portugol poderia ser:

// Leitura dos dados com quantidades de produtos desejados e total de mercados:
leia(N); // usuario devera' digitar quantos produtos deseja
leia(M); // quantos foram os mercados pesquisados
repita_para i de 0 ate N // cesta: digitar quantidades de sejada de cada produto
leia(q[i]); // entrada da qde do produto i

// variavel que devera a cada passo ter menor valor e indice de melhor merc
menor_valor = 999999999; // truque: coloque mairo valor possivel
melhor_mercado = -1; // nao sabemos qual o melhor, deixar indice inexistente

// Bloco para determinar melhor mercado
repita_para i de 0 ate M {
valor = 0; // variavel para permitir calculo dos N produtos no primeiro 
repita_para j de 0 ate N // laco para calcular valor de compra no merc i
  leia(v[j]); // digitar valor no mercado i
  valor = valor + q[j]*v[j]; // acumule custo prod 0 ate' j no merc i

se (valor<menor_valor) { // redefina menor: mercado i ganha ate' aqui
  menor_valor = valor; // armazene menor valor ate' o passo i
  melhor_mercado = i;  // armazene indice do melhor mercado
  } // final do lado "repita_para i"
escreva("Menor preco foi alcancado no mercado", melhor_mercado);
escreva("Menor preco total de compra foi", menor_valor);
Alg. 1. Generalizando os passos acima na forma de um algoritmo usando laços.

Última atualização: segunda-feira, 26 fev. 2024, 18:13