Escolar Documentos
Profissional Documentos
Cultura Documentos
Foguete Ariane 5
E tudo isso não aconteceu por uma falha mecânica, mas por um erro de programação.
O programa que convertia um valor em float (ponto flutuante) para um inteiro de 16 bits (long
int) recebeu como entrada um valor fora da faixa que suportava (“um estouro de inteiros”).
Com esse bug, os computadores principais, inclusive o de backup, desligaram ao mesmo
tempo, alegando um erro de execução (run time error). Por isso, é importante destacar que a
declaração de uma variável de forma incorreta ou com tipos incompatíveis pode trazer erros
muitas vezes catastróficos. Esse desastre mostra a importância da declaração de variáveis
corretamente.
Leia a matéria da BBC News Brasil a respeito do foguete Ariane 5 e outros problemas
relacionados a falhas numéricas em programas e reflita acerca dos cuidados que poderiam ter
sido tomados para que não ocorresse tal problema.
BARANIUK, C. As falhas numéricas que podem causar desastres. BBC News Brasil, 14 maio
2015.
Você sabia que no fim do século XX houve uma grande mobilização no mundo e em uma
comunidade programadores, devido ao chamado "bug do milênio”?
Esse foi o nome dado para um provável acontecimento – que não se concretizou – na virada
do ano de 1999 para 2000: havia o receio de que os sistemas da época não reconhecessem as
datas do ano 2000 e retornassem para 1900.
No caso, o ano de 1999 seria interpretado apenas como “99” (dois últimos dígitos). Com isso, o
receio era que, em vez de os sistemas reconhecerem o ano de 2000, o identificassem como
ano de 1900, zerando os dois últimos dígitos na data.
De fato, o problema não causou tantos impactos, pois muitos sistemas desenvolvidos na época
já consideravam o ano com quatro dígitos. Porém, problemas pontuais ocorreram; alguns sites
da época não reconheciam a data e mostravam a data “01/01/19100”, e houve falhas em
terminais de ônibus e equipamentos de medição de radiação. Houve, também, investimentos
de bilhões de dólares em medidas preventivas.
Você pode ler mais a respeito no artigo da revista Super Interessante, “O que foi o Bug do
Milênio?”.
CORDEIRO, T. O que foi o Bug do Milênio? Super Interessante, São Paulo, 4 jul. 2018.
VAMOS PRATICAR!
Para exercitar um pouco, observe o código com o erro que se assemelha ao ocorrido no caso
do foguete Ariane 5. Mostra que os erros de execução (run time error) são comuns quando se
declara um vetor ou array com menor capacidade que o necessário para o problema. Nesse
caso, o código é compilado sem erros, porém ocorrem problemas na execução.
Análise o código e faça o teste utilizando a ferramenta Online GDB (um compilador online em
C), clique no botão Run:
#include <stdio.h>
int main(){
char nome[4];
int ano;
scanf("%s", nome);
scanf("%d", &ano);
return 0;
Operações e expressões
Sistemas computacionais
Desde o momento em que você liga um computador (ou tablet, ou smartphone), centenas de
processos são inicializados e passam a competir pelo processador para que possam ser
executados e fazer a “mágica” do mundo digital acontecer.
Todos os resultados desses sistemas são obtidos através do processamento de dados e nesta
webaula começaremos a estudar os recursos que lhe permitirão implementar soluções com
processamento.
Os sistemas computacionais são construídos para resolver os mais diversos problemas. Todos
esses sistemas, independentemente da sua aplicação, são construídos em três partes: entrada,
processamento e saída.
Na entrada os valores que serão utilizados pelo processamento são lidos basicamente a partir
de três fontes:
Leitura de arquivos
Nos três casos, a leitura dos dados é feita para um único fim: processamento e geração de
informações e essa etapa é construída a partir da combinação de operações aritméticas,
relacionais, lógicas e outras técnicas de programação.
Operadores aritméticos
++ Pós-incremento x ++ x+1
++ Pré-incremento ++ x x+1
-- Pós-decremento y -- y-1
-- Pré-decremento -- y y-1
Unários
Binários
1° Parênteses.
2° Potenciação e radiciação.
4° Soma e subtração.
Operadores relacionais
Faz parte do processamento fazer comparações entre valores e, a partir do resultado, realizar
novas ações. Em programação, para compararmos valores usamos operadores relacionais. Os
operadores relacionais são utilizados para construir expressões booleanas, ou seja, expressões
que terão como resultado verdadeiro ou falso.
== igual a x == y
!= diferente de X != y
A instrução (n1 == n2) && (n1 == n3), mostrará se n1 é igual a n2 E(&&) n1 é igual a n3. No
caso será impresso o valor 0 (falso), pois n1 e n2 são diferentes.
A instrução (n1 == n2) || (n1 == n3), mostrará se n1 é igual a n2 OU(||) n1 é igual a n3. No
caso será impresso o valor 1 (verdadeiro), pois, os valores de n1 e n3 são iguais.
A instrução (n1 < n3) || (n1 > n2), mostrará se n1é menor que n3 OU (||) n1 é maior
que n2. No caso será impresso o valor 0 (falso), pois, os valores de n1 e n3 são iguais e n1 é
maior que n2.
#include<stdio.h>
int main(){
int n1 = 5, n2 = 10, n3 = 5;
printf("\n (n1 == n2) && (n1 == n3) = %d", ((n1 == n2) && (n1 == n3)));
printf("\n (n1 == n2) || (n1 == n3) = %d", ((n1 == n2) || (n1 == n3)));
printf("\n (n1 < n3) || (n1 > n2) = %d", ((n1 < n3) || (n1 > n2)));
Operadores lógicos
Os operadores lógicos são utilizados juntamente com os relacionais criando operações mais
complexas.
Operadores lógicos
Operador Descrição
O operador “==” compara se as strings de entradas em login é igual a “aluno01” e senha é igual
a “teste01”.
#include <stdio.h>
#include <string.h>
int main()
char login[10];
char senha[10];
scanf("%s", senha );
printf("Bem-vindo aluno\n" );
else {
printf("Login falhou!\n");
return 0;
Funções predefinidas
Nessa webaula, vamos ver o conceito de algoritmo, como funcionam e quais suas aplicações
dentro da programação.
Algoritmo
Algoritmo é uma sequência ordenada de passos que deve ser seguida para a realização de uma
tarefa (BERG e FIGUEIRÓ, 1998).
Acender o fogo;
Refogar os temperos;
Colocar a água;
Cozinhar o arroz;
Abaixar o fogo;
Esperar o ponto;
Desligar o fogo;
Servir o arroz.
Comprar o arroz;
Analisar a qualidade;
Preparar o tempero;
Pegar a panela;
Acender o fogo;
Adicionar o arroz;
Baixar o fogo;
Aguardo o ponto;
Desligar o fogo;
Servir o arroz.
Assim, percebemos que não existe somente uma forma de realizar um algoritmo, podem ser
criada outras formas e sequências para obter o mesmo resultado, ou seja, eles são
independentes, porém, com a mesma finalidade de execução.
Entrada
Dados
Processamento
Execução
(cozimento do arroz)
Saída
Solução/Objetivo atingido
Problema:
Solução
1. Início;
2. Entrar com o primeiro valor (nota do primeiro bimestre);
3. Entrar com o segundo valor (nota do segundo bimestre);
4. Realizar a soma do primeiro valor com o segundo;
5. Realizar a divisão do total dos valores por dois (média das
notas dos bimestres);
6. Armazenar o valor encontrado;
7. Mostrar na tela o resultado da média;
8. Se a média do aluno for maior ou igual a seis;
9. O aluno será considerado aprovado;
10. Senão está reprovado;
11. Fim.
Variáveis e Atribuições
Valor ←← 8
Valor1 ←← marcio
Significa que o número “8” está sendo atribuído para
variável “valor1” e que o texto “marcio” está atribuído para
variável “ nome”.
Terminal
Representa o início ou o fim de um fluxo lógico. Em alguns casos
define as sub-rotinas.
Entrada manual
Determina a entrada manual dos dados, geralmente através de um
teclado.
Processamento
Representa a execução de ações de processamento.
Exibição
Mostra o resultado de uma ação, geralmente através da tela de um
computador.
Decisão
Representa os desvios condicionais nas operações de tomada de
decisão e laços condicionais para repetição de alguns trechos do
programa.
Preparação
Representa a execução de um laço incondicional que permite a
modificação de instruções do laço.
Processo definido
Mostra o resultado de uma ação, geralmente através da tela de um
computador.
Conector
Representa pontos de conexões entre trechos de programas, que
podem ser apontados para outras partes do diagrama de bloco.
Linha
Representa os vínculos existentes entre os símbolos de um
diagrama de blocos.
1. calculo_media;
2. var
4. Início
6. leia valor1;
7. escreva (“Digite valor 2”);
8. leia valor2;
16. Fim.
Comentários
var
inicio
Leia (valor1)
Leia (valor2)
soma ←← (soma / 2 )
soma ←← (soma / 2 )
senao
fimse
Finalgoritmo
Paradigmas de programação
Após os estudos de algoritmos e as suas formas de construções,
Manzano (2015) coloca em destaque os paradigmas de
programação, que são caracterizados pelos paradigmas da:
Programação estruturada
if <(condição)>
{
<conjunto de comandos>;
}
Fluxograma – função “if”
#include <stdio.h>
int main() {
int idade;
scanf("%d", &idade);
printf("\n Você já pode tirar sua carteira de Habilitação, você é maior de 18");
}
return 0;
if <(condição)>
{
<primeiro conjunto de comandos>;
}
else
{
<segundo conjunto de comandos>;
}
Fluxograma – função “if/else”
Fonte:
elaborada pelo autor.
#include <stdio.h>
int main() {
float orcamento;
scanf("%f", &orcamento);
} else {
printf("\n João e Maria irão optar por uma viagem nacional");
return 0;
Fonte:
elaborada pelo autor.
Sintaxe:
switch (variável)
{
case constante1:
<comandos>
break;
case constante2:
break;
default: <comandos>
}
#include <stdio.h>
int main() {
char x;
scanf("%f", &valor);
printf("\n Digite a letra que representa o seu desconto de acordo com a cor: ");
printf("\n a. azul");
printf("\n v. vermelho");
printf("\n b. branco");
switch(x) {
case 'a':
printf("\n Você escolheu a cor azul, seu desconto será de 30 por cento");
desc = 30;
break;
case 'v':
printf("\n Você escolheu a cor vermelha, seu desconto será de 20 por cento");
desc = 20;
break;
case 'b':
printf("\n Você escolheu a cor branca, seu desconto será de 10 por cento");
desc = 10;
break;
default:
desc = 0;
return 0;
Sintaxe:
if (condição) {
<comandos>;
} else {
if (condição) {
<comandos>;
} else (condição) {
<comandos>;
}
}
#include <stdio.h>
#include <stdlib.h>
int main() {
int a, b, c;
if (a == b && a ==c)
else
if (a == b || a == c || b == c)
else
else
printf("\n\n Dadas as medidas: %d, %d, %d, não formam um triângulo", a, b, c);
return 0;
while (<condição>)
{
Comando 1;
Comando 2;
Comando n;
}
Fonte:
elaborada pelo autor.
Quando utilizamos teste no início, pode ocorrer o
famoso loop (laço) infinito, que é quando um processo é
executado repetidamente. Clique para ver os recursos que podem
ser utilizados para que isso não aconteça.
Contador
É utilizado para controlar as repetições, quando esta é
determinada.
Incremente e decremento
Trabalha o número do contador, seja ele, aumentado ou
diminuído.
Acumulador
Irá somar as entradas de dados de cada iteração da repetição,
gerando um somatório a ser utilizado quando da saída da
repetição. (Soffner, 2013)
Condição de parada
Utilizada para determinar o momento de parar quando não se tem
um valor exato desta repetição.
int main() {
int cont=0;
printf("\n PROGRAMA");
return 0;
Do
{
comandos;
}
while (condição);
Fluxograma – comando “do/while”
Fonte:
elaborada pelo autor.
#include <stdio.h>
int main() {
int resp;
metragem1 = 0;
metragem2 = 0;
resultado = 0;
printf("\nC Á L C U L O D E M E T R O S Q U A D R A D O S");
do {
scanf("%f", &metragem1);
scanf("%f", &metragem2);
scanf("%d", &resp);
return 0;
int main() {
float soma=0;
float valor;
int opcao;
do {
printf("\n M E N U D E O P Ç Õ E S");
printf("\n 1. Depósito");
printf("\n 2. Saque");
printf("\n 3. Saldo");
printf("\n 4. Sair");
scanf("%d", &opcao);
switch(opcao) {
case 1:
scanf("%f", &valor);
soma += valor;
break;
case 2:
scanf("%f", &valor);
soma -= valor;
break;
case 3:
break;
case 4:
printf("\n Saindo...");
break;
default:
return 0;
}
Estruturas de repetição determinísticas
Exemplos
A seguir, veremos alguns exemplos de utilização do comando for.
Como primeiro exemplo, iremos criar uma contagem regressiva de
um número qualquer, digitado pelo usuário (Exemplo estrutura de
repetição com variável de controle).
#include <stdio.h>
int main(){
if (i == parar_em) {
break;
int main(){
if (i == parar_em) {
break;
int main()
{
int x,y;
return 0;
#include <stdio.h>
#define VET_TAM 5
int main () {
int num[VET_TAM];
scanf("%d", &num[i]);
return 0;
Instrução continue
#include <stdio.h>
int main() {
if (i % 2 == 0) {
continue;
#include <stdio.h>
int main() {
int matriz[3][3];
printf("\nDigitar os valores da matriz para [linha %d, coluna %d]: ", linha + 1, coluna +
1);
scanf("%d", &matriz[linha][coluna]);
for (coluna=0;coluna<3;coluna++) {
printf("%d\t", matriz[linha][coluna]);
printf("\n");
return 0;
Funções da linguagem C
int main(){
printf("Hello World!");
return 0;
Dividir
Combinar
Função
<tipo de retorno>
Obrigatório. Esse parâmetro indica qual o tipo de valor a função irá
retornar. Pode ser um valor inteiro (int), decimal
(float ou double), caractere (char), etc. Quando a subrotina faz um
processamento e não retorna nenhum valor, usa-se o parâmetro
void e, nesse caso, é chamado de procedimento (MANZANO,
2015).
<nome>
Obrigatório. Parâmetro que especifica o nome que identificará a
função. É como o nome de uma pessoa, para você convidá-la para
sair você precisa “chamá-la pelo nome”. O nome não pode ter
acento, nem caractere especial e nem ser nome composto
(mesmas regras para nomes de variáveis).
<parâmetros>
Opcional. Estudaremos mais a adiante.
<comandos da função>
Obrigatório. Só faz sentido criar uma função se ela tiver um
conjunto de comandos para realizar.
<retorno>
Quando o tipo de retorno for void esse parâmetro não precisa ser
usado, porém, quando não for void é obrigatório. O valor a ser
retornado tem que ser compatível com o tipo de retorno, senão o
problema dará um erro de compilação em algumas linguagens, em
outras retornará um valor errôneo. Na linguagem C, irá ser
retornado um valor de acordo com o tipo.
Local da função
int somar() {
return 2 + 3;
int main() {
int resultado = 0;
resultado = somar();
return 0;
O resultado da função é = 5
Explicação
×
<nome> - somar.
#include<stdio.h>
float calcular() {
float num;
scanf("%f", &num);
int main(){
float resultado = 0;
resultado = calcular();
return 0;
Digite um número:
O quadrado do número digitado é 4.00
Explicação
×
int* gerarRandomico() {
int a;
return r;
int main(){
int *p;
int i;
p = gerarRandomico();
return 0;
r[0] = 83
r[1] = 86
r[2] = 77
r[3] = 15
r[4] = 93
r[5] = 35
r[6] = 86
r[7] = 92
r[8] = 49
r[9] = 21
p[0] = 83
p[1] = 86
p[2] = 77
p[3] = 15
p[4] = 93
p[5] = 35
p[6] = 86
p[7] = 92
p[8] = 49
p[9] = 21
Explicação
×
Escopo de variáveis
int testar(){
int x = 10;
return x;
int main(){
int x = 20;
return 0;
Variáveis locais
int x = 10;
void testar(){
x = 2 * x;
int main(){
testar();
return 0;
}
Valor de x global = 10
Valor de x global alterado em testar() = 20
float calcularMedia(){
int main() {
return 0;
}
Digite as duas temperaturas:
A temperatura média é 15.80 ºC
#include<stdio.h>
int x = 10;
int main(){
int x = -1;
int b;
extern int x;
b = x;
return 0;
Valor de x = -1
Valor de b (x global) = 10
#include<stdio.h>
n1 = -1;
n2 = -2;
int main(){
int n1 = 10;
int n2 = 20;
return 0;
Explicação
×
•Na passagem por referência não será criada uma cópia dos
argumentos passados, na verdade, será passado o endereço
da variável e função irá trabalhar diretamente com os valores
ali armazenados (SOFFNER, 2013).
Como a função utiliza o endereço (ponteiros), na sintaxe serão
usados os operadores * e & (MANZANO, 2015).
resultado = testar(&n1,&n2)
#include<stdio.h>
int main(){
int n1 = 10;
int n2 = 20;
printf("\nValores antes de chamar a função testar(): ");
testar(&n1, &n2);
return 0;
Explicação
×
Passagem de vetor
Esse recurso pode ser utilizado para criar funções que preenchem
e imprimem o conteúdo armazenado em um vetor, evitando a
repetição de trechos de código. Na linguagem C, a passagem de
um vetor é feita implicitamente por referência. Isso significa que
mesmo não utilizando os operadores “*” e “&”, quando uma função
que recebe um vetor é invocada, o que é realmente passado é o
endereço da primeira posição do vetor.
resultado = testar(n1,n2)
No exemplo a seguir o programa por meio de uma função
preenche um vetor de três posições e em outra função percorre o
vetor imprimindo o dobro de cada valor do vetor.
#include<stdio.h>
int i = 0;
for(i = 0; i < 3; i++){
}
}
int i = 0;
for(i = 0; i < 3; i++){
}
int main(){
int numeros[3];
printf("\nPreenchendo o vetor... \n ");
inserir(numeros);
return 0;
}
Preenchendo o vetor...
Digite o valor 0:
Digite o valor 1:
Digite o valor 2:
Explicação
×
Recursividade
Vimos como criar uma função, qual sua importância dentro de uma
implementação, estudamos a saída de dados de uma função, bem
como a entrada, feita por meio dos parâmetros. E nesta webaula,
vamos ver uma categoria especial de função, chamada de funções
recursivas.
Recursividade
Fonte:
elaborada pela autora.
Ponto de parada
A função recursiva chama a si própria até que um ponto de parada
seja estabelecido. O ponto de parada poderá ser alcançado através
de uma estrutura condicional ou através de um valor informado
pelo usuário.
Instâncias
Uma função possui em seu corpo variáveis e comandos, os quais
são alocados na memória de trabalho. No uso de uma função
recursiva, os recursos (variáveis e comandos) são alocados
(instâncias) em outro local da memória, ou seja, para cada
chamada da função novos espaços são destinados a execução do
programa. E é justamente por esse ponto que o ponto de parada é
crucial.
Variáveis
As variáveis criadas em cada instância da função na memória são
independentes, ou seja, mesmo as variáveis tendo nomes iguais,
cada uma tem seu próprio endereço de memória e a alteração do
valor em uma não afetará na outra.
Caso base
Toda função recursiva, obrigatoriamente, tem que ter uma
instância com um caso que interromperá a chamada a novas
instâncias. Essa instância é chamada de caso base, pois representa
o caso mais simples, que resultará na interrupção.
#include<stdio.h>
if(valor == 0) {
//critério de parada
return valor;
} else {
//chamada recursiva
int main() {
int n, resultado;
scanf("%d", &n);
printf("\nResultado = %d",resultado);
return 0;
Explicação
×
#include<stdio.h>
if(valor != 1) {
//chamada recursiva
} else {
//critério de parada
return valor;
int main() {
int n, resultado;
printf("\nDigite um número inteiro positivo: ");
scanf("%d", &n);
resultado = fatorial(n);
return 0;
Explicação
×
Recursividade em cauda
#include<stdio.h>
int fatorialCauda(int n) {
if (n != 1) {
} else {
return parcial;
int main() {
int n, resultado;
scanf("%d", &n);
resultado = fatorialCauda(n);
return 0;
Estruturas de dados
Exemplo de vetor
Listas Ligadas
onde n > 0
Quando uma lista está sem nós, definimos como lista vazia ou lista
nula, assim, o valor do ponteiro para o próximo nó da lista, é
considerado como ponteiro nulo.
struct lista {
int info;
struct lista * prox;
};
typedef struct lista Lista;
Exemplo de declaração para criar uma lista em C
/*Cria a estrutura da lista*/
struct alunos
char nome[25];
struct alunos*prox;
};
Inicialização da lista
Precisamos inicializar a lista para utilizarmos após sua criação.
char *pnt;
pnt = malloc (2); /* Aloca 2 bytes na memória */
scanf ("%c", pnt);
O endereço retornado é o da posição inicial onde se localiza os
bytes alocados.
int main () {
int *p;
p=(int *) malloc(sizeof(int));
if (!p) {
printf("Erro de memoria insuficiente");
}else{
printf("Memoria alocada com sucesso");
}
return 0;
}
Podemos classificar as listas de duas formas.
celula *start;
start = malloc (sizeof (celula));
start -> prox = NULL;
Dados *novo;
return novo;
}
Desenvolvimento de um sistema para o site de uma empresa de casamentos
Os usuários criam a lista de convidados e a lista de presentes, os
noivos podem adicionar convidados ou presentes e remover ou
alterar da lista quando necessário.
if (aux1 == NULL)
fim = novo;
return;
}
Pilhas
Pilhas
Uma pilha tem como definição básica ser um conjunto de
elementos ordenados que permite a inserção e a remoção de mais
elementos em apenas uma das extremidades da estrutura
denominada topo da pilha. (TENENBAUM, LANGSAM e
AUGENSTEIN, 2007). Assim, um novo elemento que é inserido
passa a ser o topo da pilha, e o único elemento que pode ser
removido da pilha é o elemento que está no topo.
Operações de pilhas
Empilhar e desempilhar
A operação de empilhar um novo elemento, segundo Lorenzi,
Mattos, Carvalho (2015), tem a função de inserir um novo elemento
na pilha, e definida na programação em C++ como push(). Já a
operação de desempilhar , tem a função de remover um elemento
do topo da Pilha e utilizada na programação em C++ como pop().
Criar a pilha
void cria_pilha(struct Pilha *p, int c ){
p -> proxElem = (float*) malloc (c * sizeof(float));
p -> topo = -1;
p -> capacidade = c;
}
Inserir elemento na pilha
void push_pilha(struct Pilha *p, float v){
p -> topo++;
p -> proxElem [p -> topo] = v;
}
Problema do Labirinto
Os labirintos são desafios criados como problematização de
estrutura de dados.
Backtracking
As pilhas podem ser aplicadas também no uso de algoritmos
de Backtracking, que consiste em criar marcações para onde o
algoritmo pode retornar. Podem ser aplicados também como
operação de desfazer, existente em diversas aplicações de
usuários, como a utilização deste algoritmo em sistema de GPS.
Quando o motorista utiliza uma rota não indicada pelo programa,
o algoritmo de Backtracking é aplicado para redefinir a nova rota.
Filas
Inicializar a fila
Fila* inicia_fila (void){
Fila* f = (Fila*) malloc(sizeof(Fila));
f -> n = 0;
f -> ini = 0;
return f;
}
Filas Circulares
Segundo Silva (2007), as filas não apresentam uma solução
completa, sendo que, mesmo chegando ao final do vetor
poderemos ter a fila cheia mesmo não estando cheia, uma vez que
elementos podem ter sido removidos e para isso, podemos utilizar
as Filas Circulares como solução para esta situação. A Fila Circular
possui a seguinte definição, em sua implementação:
Definição da constante
/* Vamos definir a constante N com valor de 10 */
#define N 10
struct filacirc { /* Criação da estrutura da Fila Circular */
int tam, ini, fim;
char vet[N];
};
typedef struct filacirc FilaCirc;
Remover da fila
char remove_fila (FilaCirc* f){
if (fila_vazia(f)){ /* Verifica se a Fila está vazia */
printf("Fila vazia\n");
} else { /* Caso a Fila contenha elemento, é removido o
primeiro */
f -> ini = (f -> ini % (N-1)) + 1;
f -> tam--;
}
}
• Utilização de vetor