PRG1-2012-2-Engenharia

De MediaWiki do Campus São José
Ir para: navegação, pesquisa

PRG1 - PROGRAMAÇÃO I

Índice

CARGA HORÁRIA

TOTAL: 72 HORAS (4 HORAS/SEMANA)

TEÓRICA: 36 HORAS

LABORATÓRIO: 36 HORAS

DIAS COM AULA: 36 (18 semanas)

PRÉ REQUISITOS: LÓGICA

EMENTA

Introdução a lógica de programação e algoritmos. Constantes, variáveis e tipos de dados. Operadores aritméticos, relacionais e lógicos. Concepção de fluxograma e pseudocódigo. Estruturas de decisão e estruturas de repetição. Introdução a linguagem de programação c. Vetores de caracteres e multidimensionais. Ponteiros e aritmética de ponteiros. Funções: chamada por valor e por referência. Chamada recursiva de funções. Tipos de dados compostos. Operação com arquivos textos e binários.

Bibliografia Básica

  • SCHILDT, Herbert. C Completo e Total - 3.ed. [S.l.]: Makron, 1997. 830p. ISBN 978-8534605953

Referências Complementares

AULAS

AULA 1 DIA 5/10/2012

AULA 1 DIA 5/10/2012

Como fazer um churrasco

Vamos observar atentamente este vídeo para iniciarmos o nosso curso de programação:

EmbedVideo received the bad id "U0xSYIXE9vo#!" for the service "youtube".
O que tem o churrasco com a nossa aula??
Trata-se de uma sequência de passos para execução  
de um objetivo.
EXERCÍCIO: Na forma textual, descrever as etapas
para fazer um bom churrasco.

O que é um algoritmo

Um algoritmo pode ser visto como uma sequência de instruções ou operações que resolvem um dado problema.

A receita de um bom churrasco corresponde 
a um algoritmo.

Como representar um algoritmo ?

Uma forma é representar na forma textual ordenada:

1.Comprar a carne
2.Colocar carvão na churrasqueira
3.Acender o carvão
4.Cortar a carne (picanha)
5.Espetar a carne
6.Salgar a carne
7.Colocar a carne na churrasqueira
8.Aguardar a carne ficar no ponto desejado
9.Bater a carne
10.Servir a carne

Outras formas são mais apropriadas para o uso no meio computacional:

  • pseudo-código
  • fluxogramas
A PENSAR: É possível mudar a ordem das instruções?
É possível paralelizar algumas instruções?

O problema da raposa, do milho e da galinha

EmbedVideo received the bad id "yifW9XueSaI#!" for the service "youtube".


EXERCÍCIO 1: Descrever na forma de etapas um 
solução para o problema da raposa, do milho e da galinha.
Note que somente é possível escrever o algoritmo se tivermos uma solução para o problema.
EXERCÍCIO 2: Descrever na forma de etapas uma 
solução para o problema dos canibais/padres.

Torres de Hanoi

Veja este jogo:

EmbedVideo received the bad id "hLnuMXO95f8#!" for the service "youtube".
EXERCÍCIO 3: Escrever na forma de etapas numeradas a solução para o problema 
das torres de Hanói usando 3 discos.
EXERCÍCIO 4: Escrever na forma de etapas numeradas a solução para o problema 
das torres de Hanói usando 4 discos.

E para quem são os algoritmos?

Uma receita de bolo é apropriada para ser executada 
por um ser humano. 
Um procedimento de como trocar um pneu também. 
Mas muitas vezes  queremos que o algoritmo seja executado 
por uma máquina! O computador é perfeito para isto!
Neste curso vamos nos concentrar no desenvolvimento de
algoritmos simples,  desde a sua concepção até  a sua  
implementação através de uma LINGUAGEM DE
PROGRAMAÇÃO, em um computador.
Um PROGRAMA implementa um algoritmo. É o algoritmo 
materializado na forma de uma sequência de instruções.

Neste sentido, vamos entender minimamente o funcionamento de um computador.

Como funciona um computador? Como ele executa programas ("receitas")?

  • Partes de um computador:
    • UCP (CPU) (Unidade Central de Processamento)
    • Barramentos
    • Memórias: Primária (semicondutora -> RAM e ROM) e Secundárias (disco, pendrive)
    • Dispositivos de Entrada e Saída de Dados (Teclado, Monitor etc)
  • O computador é digital:
    • Dados e Instruções são sequências de bits
    • Representação de dados em sistemas computacionais: uso de códigos (ex:código ASCII);
 A -> 01000001
 B -> 01000010
    • As instruções também são palavras binárias interpretadas pela CPU;
De forma simplificada podemos dizer que as instruções ficam em uma 
memória de programa enquanto os dados a serem processados pelo programa
ficam em uma memória de DADOS;
O programa que está na memória de programa está escrito
em LINGUAGEM DE MÁQUINA
  • Funcionamento Simplificado de um Computador
Suponha que um programa a ser executado se encontra em uma memória de programa.
Ao ligar o sistema, a CPU busca na memória de programa uma instrução a ser executada
(ciclo de busca) e, então, executa a instrução (ciclo de execução). Na SEQUÊNCIA,
a CPU busca a PRÓXIMA instrução na memória de programa, e assim sucessivamente...
O fluxo de execução do programa é, a princípio SEQUENCIAL no sentido 
que a execução de uma instrução é realizada somente após a execução da instrução 
antecedente.

(apresentação do Prof.Semprebom sobre computadores)

Possíveis linguagens de programação

Na prática, é inviável desenvolver programas complexos em
LINGUAGEM DE MÁQUINA.
Em geral, utilizamos linguagens de ALTO NÍVEL que podem, de
alguma forma, serem traduzidas (compiladas) para a linguagem
de baixo nível ou interpretadas em tempo de execução.

Exemplo:

  • Linguagem C
  • Fortran
  • Basic
  • C++
  • Pascal
  • Java
  • Python
 Neste curso utilizaremos a linguagem C. Por que? 
 É uma linguagem muito usada na implementação de produtos 
 eletrônicos, incluindo àqueles voltados as Telecomunicações.

Um exemplo completo

PROBLEMA: Computar a média de dois números reais fornecidos pelo teclado. Mostrar o resultado no monitor.

SOLUÇÃO:

ALGORITMO
DADOS DE ENTRADA: NUM1 e NUM2
DADOS DE SAÍDA: MEDIA
INÍCIO
 1.Ler NUM1
 2.Ler NUM2
 3.MEDIA <- (NUM1+NUM2)/2
 4.Mostrar MEDIA
FIM
/* Calculador de media de dois números reais */
#include <stdio.h>

main()
{
  float num1,num2;

  scanf(&num1);
  scanf(&num2);
  media = (num1+num2)/2;
  printf ("media =\n", media);
}

EXERCÌCIO 5: Implementar o algoritmo com o Scratch.

Plano de Ensino

Agora que temos uma ideia do que será tratado neste curso, vamos apresentar o plano de ensino.

AULA 2 DIA 9/10/2012

AULA 2 DIA 9/10/2012

Objetivos

Recordando o significado de algoritmo

Um algoritmo é um "um conjunto de instruções para resolver um determinado problema".

O nome advém de al-Khwārizmī

Segundo Knuth um algoritmo deve:

  • Sempre terminar após um determinado número de passos. Caso contrário, é chamado (por Knuth) de método computacional;
  • Cada passo do algoritmo deve ser precisamente definido (sem ambiguidades);
  • Um certo número (ou nenhum) de dados de entrada deve ser fornecido no início ou dinamicamente na execução do algoritmo;
  • Ter dados de saída, resultantes do processamento dos dados de entrada;
  • Ser efetivo, no sentido que suas operações sejam simples e se executem em tempo finito de tempo;

Não existe algoritmo para fazer um algoritmo

É sempre bom lembrar que não existe um algoritmo para fazer um algoritmo. Podemos considerar que fazer um algoritmo é uma forma de arte.

No entanto, existem algumas diretrizes que podem ser consideradas. Vamos ver estas:

  • Comprender bem o problema é fundamental para a solução;
“If I had an hour to solve a problem I'd spend 55 minutes thinking about the problem and 5 
minutes thinking about solutions.”
― Albert Einstein
  • Tentar enumerar estratégias possíves;
  • Tentar dividir o problema e resolver os subproblemas.

A Descrição de Algoritmos usando Fluxogramas

Um fluxograma é uma linguagem semi-gráfica que pode ser utilizada 
para descrição de algoritmos.

Exemplo: O algoritmo de cálculo da média de dois números da aula anterior:

FluxogramaMediaDoisNumeros.jpg

Pontos fortes:

  • permite fácil entendimento do algoritmo, mesmo para pessoas leigas;

Ponto fraco:

  • a descrição das estrutura dos dados inexiste. O usuário deve descrevê-los a parte;
Observe no exemplo anterior que nada é dito sobre as variáveis NUM1, NUM2 e MEDIA.

Símbolos de um Fluxograma

TabelaSimbolosFluxograma.jpg

Teste de Mesa

TesteMesaMediaDoisNumeros.jpg

Constantes, Variáveis

Algoritmos operam sobre dados. O que podem ser estes dados?

Variáveis e Constantes

No exemplo anterior podemos identificar três variáveis NUM1, NUM2 e MEDIA Também podemos identificar uma CONSTANTE. O número 2.

  • Tipo de Variáveis:
    • Numéricas: reais e inteiras
Ex: NUM1 = 5.5 /* NUM1 é uma variável real */
    • Booleanas: true ou false
Ex: RES = TRUE /* RES é uma variável booleana */ 
    • caracter:
Ex: LETRA = 'A'
    • alfanumérica
Ex: FRASE = "ALO MUNDO"
E como estas variáveis armazenam os dados?? Depende da linguagem usada. Vamos passar
uma primeira noção do C

Expressões

Expressões sentenças que relacionam variáveis e constantes através de operadores matemáticos
e que RESULTAM em um valor. A instrução do algoritmo:
MEDIA = (NUM1 + NUM2) / 2 

será considerada como uma expressão, que usa os operadores '+', '/' e '='

O operador '=' é um OPERADOR DE ATRIBUIÇÃO e indica que a expressão do lado direito
do '=' será atribuída a variável do lado esquerdo.
Neste curso, para mantermos coerência com a Linguagem C, consideraremos que a expressão
como um todo resulta no valor que é atribuído a variável.

Operadores Aritméticos

Os operadores aritméticos que usaremos neste curso serão os disponíveis no C:

Operador Significado
+ adição
- subtração
* multiplicação
/ divisão
% resto
O único operador desconhecido aqui é o resto, cujo significado é o resto entre dois números
inteiros. Exemplo, se B possui o valor 9, então o resultado da atribuição na expressão:
A = B%2 
será 1.

Operadores relacionais

Os operadores relacionais permitem realizar comparações entre dois operandos. Os operadores são os seguintes:

Operador Significado
> maior que
>= maior ou igual que
< menor que
<= menor ou igual que
== igual a (se o operando a esquerda é maior que o da direita)
!= diferente de (se o operando a esquerda é diferente do da direita)

LINK PARA O EDITOR YED

Se você quiser um editor de fluxogramas pode utilizar a ferramenta abaixo:

Se baixar o zip com o java, descompactar, entrar no diretório e executar com:

java -jar yed.jar


Sheldon e o fluxograma da amizade

Vamos observar o fluxograma da amizade do Sheldom da série de TV "Big Bang Theory"

EmbedVideo received the bad id "VAX4jLlNo-Q#!" for the service "youtube".

Observer que a caixa no formato de LOSANGO permite testar uma condição: é uma caixa de decisão.

Controle do Fluxo de Execução: Caixas de Decisão

Em algumas situações é necessário realizar algum teste sobre uma expressão. Seja o problema:

Problema do Controle de Acesso

PROBLEMA: Controlar o acesso a uma porta usando uma senha pré-configurada no sistema. DADO DE ENTRADA: SENHA (variável alfanumérica) DADO DE SAÌDA: porta aberta (simulado com msg "PORTA ABERTA") ou mensagem de "SENHA NAO CONFERE"


FluxogramaControleAcessoI.jpg

EXERCÍCIO: Elaborar um fluxograma para o o problema de controle de acesso
prevendo um procedimento para modificar a senha de acesso. Para tanto,
assuma a existência de uma senha de administrador fixa (por exemplo,
"xY12"). Se a senha do administrador for fornecida, mostrar um menu de
modificação de senha. A nova senha deve ser repetida.

Exercícios

OBS: sempre anote antes do fluxograma os tipos  e significado das variáveis usadas,
caracterizando os dados de entrada e os de saída.
  1. Apresentar um fluxograma para ler 3 números e fazer a média;
  2. Fazer um teste de mesa usando como valores de entrada 2.0, 5.0 e 3.0;
  3. Construir um fluxograma para ler 3 números e calcular a média dos dois maiores números lidos;
  4. Refazer o exercício anterior, para calcular a média do maior e do menor número entre os 3 lidos.
  5. Considere um algoritmo para calcular o comprimento de uma circunferência. Quem são os dados de entrada e os dados de saída. Fazer um fluxograma para resolver o problema;
  6. Modifique o algoritmo acima para calcular, além do comprimento, a área da circunferência;
  7. Considere que a função de uma reta é dada por: . Elabore um fluxograma para calcular o valor de y dado o valor de x.
  8. Um estudo sobre sensibilidade de pessoas a temperaturas da água identificou que a maioria das pessoas considera fria a água com temperaturas abaixo de 25 graus, morna entre 25 e 30 graus, e quente acima de 30 graus. Escreva um algoritmo na forma de fluxograma que mostre as palavras "fria", "morna" ou "quente" dependendo da temperatura da água que for informada;
AULA 3 DIA 11/10/2012

AULA 3 DIA 11/10/2012

Objetivos

O aluno deverá ser capaz de:

  • construir fluxogramas usando estruturas de repetição;
  • compreender o aninhamento de estruturas de controle de fluxo
  • dividir problemas em subproblemas e utilizar subrotinas para organizar a solução.

Quebrando um problema em subproblemas: SUBPROGRAMAS

PROBLEMA: Aprimorar o exemplo do controle de acesso para que, no caso de 3 tentativas de acesso seguidas, com senha errada, o sistema seja bloqueado.

  • DADOS DE ENTRADA: SENHA /* variável que armazena a senha entrada pelo usuário ou administrador */
  • DADOS DE SAÍDA: mensagem de abertura da porta / usuário bloqueado*/
  • VARIÁVEIS INTERMEDIÁRIAS: CONT_ACESSO /* valor inicial zero - incrementada a cada senha inválida */

FluxogramaControleAcessoComContador.jpg

Note que a variável CONT_ACESSO é iniciada com zero e
incrementada a cada erro no fornecimento da senha. A 
atribuição CONT_ACESSO = CONT_+ACESSO + 1 deve ser 
interpretada da forma: acesse o valor de CONT_ACESSO e some 1
a este valor. Coloque o resultado novamente em CONT_ACESSO
(o conteúdo anterior é sobrescrito!)

Neste procedimento pode ser observado que:

  • começa a aparecer uma certa complexidade no fluxograma;
  • Além disto, não ficou uma abordagem muito interessante, pois se for o administrador que erra a senha, ele pode bloquear o usuário;
  • não existe forma de desbloquear a senha (zerar o contador), a não ser mudando a senha.

Em síntese, existem subproblemas adicionais a serem resolvidos. Como podemos resolvê-los sem deixar um fluxograma complexo?

Usaremos a caixa de processos pré-definidos que usaremos para invocar funções que resolvem determinado subproblema.

Inicialmente, vamos construir três subprogramas:

  • Iniciar_Sistema(): inicia variáveis do sistema, colocando o sistema em um estado inicial conhecido;
  • Tratar_Admin(): é o código que trata a funcionalidade associada ao administrador;
  • Tratar_User(): é a funcionalidade que trata o usuário (procedimento de abrir porta, bloquear etc).


FluxogramaControleAcessoComSubprograma.jpg

OBS: Note que foi usada uma variável auxiliar AUX que permite ajustar o valor
de número de acessos a ser mostrado no display. Note também que na caixa DISPLAY foi usado
uma string a ser impressa e a variável AUX cujo conteúdo deve ser impresso. Ambos separados
por vírgula.
  • ANINHAMENTO DE DECISÔES

A função Tratar_User() se utiliza de aninhamento de decisões. Em um primeiro momento é testado se a senha confere com a senha do usuário. SE ela conferir ENTÂO é testado se o contador de bloqueios está dentro do aceitável.

Estruturas de repetição

No exemplo de controle de acesso da aula passada já construímos uma estrutura de repetição (um loop infinito). Vamos elaborar um pouco mais estas estruturas de repetição.

Uma das grandes vantagens de um sistema computacional é a capacidade de repetir um conjunto de instruções
possivelmente sobre dados diferentes, a uma velocidade muito grande.

PROBLEMA: Calcular a somatório de N valores a serem fornecidos pelo teclado.

DADOS DE ENTRADA:

  • N /* número de valores */,
  • a_i /* valor de um dos N números a serem inseridos */

DADOS DE SAÌDA = S /* somatório */


FluxogramaSomatoria.jpg

NOTA: Observe que a estrutura de repetição utilizada é caracterizada por um teste (decisão) de uma expressão que deverá resultar em verdadeiro enquanto houver número a ser lido (dentro da quantidade N de números), um contador de itens lidos "i" e um bloco de repeticão terminado em um arco que conduz o fluxo de execução ao início da decisão.

EXERCÍCIOS

EXERCÍCIO 1: Modificar o sistema de controle de acesso para incluir a inserção de nome de usuário (USERID) e senha (SENHA). Prever a existência de dois usuários (USERID_1, USERID_2, SENHA_1, SENHA_2). O admin poderá ser identificado pelo nome ADMIN. Prever bloqueio por usuário (duas variáveis contadores). Sugestão: criar funcões separadas para tratamento de cada usuário. Em breve veremos como podemos contornar esta duplicação através da parametrização de funções/subprogramas.

EXERCÍCIO 2: Implementar o sistema do exercício 1 com o Scratch. Simule as funções usando envio de sinais (isto foi feito no projeto integrador do semestre passado).

EXERCÍCIO 3: Escrever um fluxograma que leia como dados de entrada dois números inteiros positivos: "s" e "q". O programa deve computar os dez primeiros números da PG (progressão geométrica), onde "s" é o número inicial e "q" a razão da progressão.

Obs: Para PG tem-se:

ou

EXERCÍCIO 4: Implementar um fluxograma que permita computar a somatória dos N primeiros termos de uma PG, cujos valores de "s" e "q" são também fornecidos como dados de entrada.

AULA 4 - 15/10/2012

OBJETIVOS

O aluno deverá ser capaz de:

  • Fazer mapeamento de estruturas de controle de fluxo do fluxograma para o Scratch;
  • Compor expressões lógicas utilizando operadores relacionais e lógicos;
  • Parametrizar e retornar valores em subprogramas;
  • distinguir e usar variáveis locais e globais;

Mapeamento de Fluxograma em Pseudocódigo e código SCRATCH

Fluxograma Pseudocódigo Scratch Comentário
ControleFluxograma1.jpg

SE i < 10 ENTÃO

  IMPRIMIR "i menor que 10"

FIMSE </syntaxhighlight>||ControleScratch1.jpg

SE a condição i<10 for verdadeira então será impressa a mensagem caso contrário nada será feito e a próxima instrução depois do comando será execuatda.

ControleFluxograma2.jpg

SE i < 10 ENTÃO

  IMPRIMIR "valor de i menor que 10"

SENÂO

  IMPPRIMIR "valor de i maior ou igual a 10"

FIMSE </syntaxhighlight>

ControleScratch2.jpg
ControleFluxograma3.jpg

SE i <10 ENTÃO

  IMPRIMIR "valor de i menor que 10"

SENAO

  IMPPRIMIR "valor de i maior ou igual a 10"
  SE i == 10 ENTÃO
      IMPRIMIR "Ok valor de i igual a 10

FIMSE </syntaxhighlight>

ControleScratch3.jpg Observar que quando existe mais de uma instrução simples sob o controle do if, deve-se usar as chaves (conceito de bloco).
340px

ENQUANTO verdadeiro FAÇA

  IMPRIMIR "Alo"

FIMENQUANTO </syntaxhighlight>

SEMPRE.jpg Observar que quando existe mais de uma instrução simples sob o controle do if, deve-se usar as chaves (conceito de bloco).
340px

ENQUANTO verdadeiro FAÇA

  SE a == 1 ENTÃO
     IMPRIMIR "Alo"
  FIMSE

FIMENQUANTO </syntaxhighlight>

SEMPRESE.jpg Observar que quando existe mais de uma instrução simples sob o controle do if, deve-se usar as chaves (conceito de bloco).
340px

SE i <10 ENTÃO

  IMPRIMIR "valor de i menor que 10"

SENAO

  IMPPRIMIR "valor de i maior ou igual a 10"
  SE i == 10 ENTÃO
      IMPRIMIR "Ok valor de i igual a 10

FIMSE </syntaxhighlight>

REPITA.jpg Observar que quando existe mais de uma instrução simples sob o controle do if, deve-se usar as chaves (conceito de bloco).
340px

SE i <10 ENTÃO

  IMPRIMIR "valor de i menor que 10"

SENAO

  IMPPRIMIR "valor de i maior ou igual a 10"
  SE i == 10 ENTÃO
      IMPRIMIR "Ok valor de i igual a 10

FIMSE </syntaxhighlight>

REPITAATE.jpg Observar que quando existe mais de uma instrução simples sob o controle do if, deve-se usar as chaves (conceito de bloco).

Expressões Lógicas

Nas aulas anteriores utilizamos expressões usando operadores aritméticos e relacionais. É possível construir expressões ainda mais completas usando os operadores lógicos, apresentados abaixo:

Operador Significado
&& AND
|| OR
! NOT
Uma expressão lógica tem como resultado da sua avaliação um valor VERDADEIRO ou FALSO. Para manter a coerência com a linguagem C
qualquer expressão que resultar em 0 será considerada FALSA e se resultar em algo diferente de 0 será considerada verdadeira.

PROBLEMA: Entrar com um número inteiro pelo teclado. Se o número for maior que 1 e menor que 10, imprimir uma mensagem "Número maior que 1 e menor que 10".

DADO DE ENTRADA: NUM /* Número inteiro a ser comparado */ DADO DE SAÍDA: Mensagem no display PROBLEMA: Entrar com um número inteiro pelo teclado. Se o número for menor ou igual a 1 OU se for maior ou igual a 10 então mostrar a mensagem "Número menor ou igual a 1 ou maior ou igual a 10"

DADO DE ENTRADA: NUM /* Número inteiro a ser comparado */ DADO DE SAÍDA: Mensagem no display

Uma outra solução para este último problema poderia ser:

ARRAYS UNIDIMENSIONAIS (VETORES)

Um vetor pode ser visto como uma variável que pode ser indexada e onde em cada posição existe um elemento do vetor. Os elementos do vetor possuem um tipo único.

PROBLEMA: Ler 10 números inteiros para um vetor de inteiros. Computar um segundo vetor que é o resultado da multiplicação por um escalar inteiro 5.

DADO DE ENTRADA: Os 10 números armazenados em VET1 DADO DE SAÍDA: VET2, o vetor resultado da multiplicação de VET1 por 5.


ExplicacaoVetor1.jpg

Exercício 1: Implementar um algoritmo para ler 10 números inteiros para um vetor e imprimir o número de números acima da média. OBS: Definir um contador, iniciado em zero. Calcular a media e fazer um loop sobre o vetor testando cada item para verificar se é maior que a média.

Armazenamento de cadeias de caracteres em vetores

Em aulas anteriores vimos que um caracter pode ser representado por uma sequência de bits. Utilizando um código é possível definir o significado da sequência. Um código amplamente usado é o ASCII. Com 8 bits (7 no Ascii original) tem-se então a possibilidade de representar qualquer letra, número, ou símbolo (vírgula, ponto-e-vírgula etc). Note que um número representado em ASCII NÂO serve para realizar operações aritméticas. Trata-se de representação textual, por exemplo, um dígito de um número telefone.

Uma cadeia de caracteres ou string nada mais é que uma sequência de caracteres ASCII. Para mantermos coerência com a linguagem C, vamos assumir que uma string bem comportada termina com um zero (0, não o caracter '0' que é o número 48 decimal em ASCII). Em inglês chama-se "string NULL terminated".

Exemplo: A string "IFSC" armazenada em um vetor CADEIA, na memória de um computador, teria a seguinte implementação:

ExplicacaoString1.jpg

Estamos assumindo que cada caracter é armazenado em um byte. Uma string terminada em 0 facilita o seu processamento pois pode-se facilmente detectar o seu final. Note que no exemplo acima, a string está armazenada em um vetor CADEIA cujo tamanho excede ao da string. Os bytes que se seguem ao zero podem ser considerados lixo.

Processando cadeias de caracteres

Sabendo como uma string é armazenada na memória de um computador torna-se fácil processá-la. Por exemplo, vamos ver um algoritmo para contar o número de caracteres de uma string lida pelo teclado para dentro de um vetor CADEIA.

FluxogramaContadorCaracteresCadeia.jpg

EXERCÍCIO 1: Elaborar um fluxograma para computador o número de ocorrências do caracter 'b' em uma string lida pelo teclado.

EXERCÍCIO 2: Estude a tabela ASCII e elabore um fluxograma para capitalizar todos os caracteres minusculos de uma string lida pelo teclado.

AULA 4 - 15/10/2012

OBJETIVOS

PARÂMETROS E RETORNO DE VALORES EM SUBPROGRAMAS

Na aula anterior estudamos o uso de subprogramas como forma de reaproveitar código e estruturar um algoritmo, permitindo a quebra de um problema em subproblemas. Para tornar ainda mais interessante o uso de subprogramas, vamos ver o conceito de passagem de parâmetros e retorno de valores.

Quando chamamos (invocamos) um subprograma podemos passar valores (dados de entrada) para este subprograma.
Estes valores são passados através de variáveis especiais que chamamos de parâmetros.
Parâmetros podem ser passados por valor e por referência. Por valor é quando os dados a serem passados na invocação
do subprograma são copiados para uma variável do subprograma.
Os parâmetros são passados por referência quando o que é passado para o subprograma é simplesmente o endereço da variável
repassada na invocação do subprograma.

Não existe uma forma explícita de definir os parâmetros em um fluxograma. Deve-se realizar um comentário antes ou ao lado do mesmo especificando o tipo e como será tratado o parâmetro.

EXEMPLO: Seja elaborar um fluxograma correspondente a uma subrotina que deve calcular o fatorial de um número inteiro passado como parâmetro. O subprograma deve retornar o valor calculado.

A função fatorial é definida por:


FluxogramaFatorial.jpg

Neste fluxograma, o subprograma denominado CalcFatorial recebe um valor no parâmetro N (implicitamente inteiro) e retorna o valor calculado do fatorial.

O fluxograma principal invoca duas vezes o subrograma. O retorno é armazenado nas variáveis NUM1 e NUm3.

Quando um subprograma retorna um valor, ele é chamado de função. Para manter coerência com o C chamaremos
qualquer subrprograma de função (independente de retornar valor).

EXERCÍCIO 1: Implementar uma função, usando fluxgrama, para computar o valor da PG, dados como parâmetros s e q.

EXERCÍCIO 2: Implementar uma função chamada str_len() que recebe uma string como parâmetro e retorna o tamanho da mesma.

VETORES DE STRINGS

Na aula passada aprendemos vetores de caracteres e como usá-los para armazenamento de strings. Note que é possível construir vetores de strings. Vamos ao exemplo do controle de acesso.

PROBLEMA: Suponha que existe uma Tabela global de usuários chamada TabUserID. Suponha também que esta tabela tem tamanho finito de 20 posições. Posições não usadas estão com string VAZIA (0 na primeira posição). Construir uma função chamada LocalizarUserID, que recebe um UserID (string) como parâmetro (a tabela não precisa ser passada pois é GLOBAL) e retorna o índice onde se encontra o usuário UserID na tabela. Se o usuário não for encontrado, a função deve retornar -1.

EXERCÌCIO: Implementar uma função TratarUsuario() que se utiliza de duas tabelas: a TabUserID e a TabPassword para cada usuário na primeira tabela existe uma senha na segunda tabela. A função deve ler o UserID, usar a função LocalizarUserID para determinar o índice do usuário, ler a senha do usuário e validá-la usando o índice encontrado.

AULA 5

AULA 6 - Dia 20/10/2012

Objetivos

O aluno devera ser capaz de:

  • descrever o processo de compilação;
  • diferenciar código fonte, objeto e executável;
  • compilar, executar pequenos programa em C usando o gcc;
  • declarar e usar variáveis locais inteiras e reais;
  • usar as funções de entrada e saída: scanf() e printf.

Introdução a linguagem C


EmbedVideo received the bad id "rGCbvqz6Kt4#!" for the service "youtube".

Visão geral do processo de compilação com gcc

  • Linguagens compiladas (ex: C) versus linguagens interpretadas (ex: Basic)

ProcessoCompilacao.jpg

Compilando o programa

Neste curso usaremos o compilador da coleção gcc do projeto GNU. O manual completo do gcc pode ser encontrado aqui.

O processo de desenvolvimento do programa envolve:

  • Editar o programa com um editor de texto tal como o vi ou gedit;
  • Salvar o programa com a terminação ".c" (ou ".h" se for um cabeçalho);


NOTA: crie um diretório para trabalhar nos exercícios que se seguem:

mkdir ExerciciosC
cd Exercicios

Exemplo: salve o programa abaixo como teste.c

 

#include <stdio.h>

main()
{
  printf("Alo Mundo\n");
}
  • Compilar/gerar executável do programa usando o gcc:
 gcc teste.c -o teste
  • Testar o programa:
./teste
Nota: o atributo -o permite que se forneça um nome para o executável diferente de a.out

É possível somente compilar (gerar código objeto):

 gcc -c teste.c

Observe os subprodutos listando com detalhes:

 ls -l

Declarando variáveis inteiras e reais locais

No "c" temos que declarar as variáveis que serão utilizadas no programa. Se estas variáveis forem declaradas DENTRO da função elas serão "vistas" somente localmente (escopo local). Este conceito será estendido para blocos de códigos posteriormente.

 
#include <stdio.h>

main()
{
  /* aqui começam as declarações de variáveis */
  int x; /* declaração de uma variável inteira */
  float y;  /* declaração de uma variável real */

  /* aqui começam as instruções do programa principal */ 
  x=5;   /* atribuindo o valor 5 (constante) a variável x */
  y=6.5; 
}

Funções de entrada e saída de dados

No "c" não existe instrução especialmente para leitura ou saída de dados. Este procedimento é realizado através de funções da biblioteca. Na sequência são mostradas duas funções "clássicas" de entrada e saída de dados.


 
#include <stdio.h>

main()
{
 
  int x; /* declaração de uma variável inteira */
  float y;  /* declaração de uma variável real */

  printf ("Entre com o valor de x ");
  scanf("%d",&x);

  printf ("Entre com o valor de y ");
  scanf("%f",&y);

  printf ("O valor de x é %d\n",x);
  printf ("O valor de y é %f\n",y); 
}

Comandos de decisão if() e if() else

 

#include <stdio.h>

main()
{
 
  int x; /* declaração de uma variável inteira */
  int y;  /* declaração de uma variável inteira */

  printf ("Entre com o valor de x ");
  scanf("%d",&x);

  printf ("Entre com o valor de y ");
  scanf("%d",&y);

  if (y>x)
     printf("MSG1:y é maior que x\n");


  if (y>x)
     printf("MSG2:y é maior que x\n");
  else
     printf("MSG3:y é igual ou menor que x\n");
 
}

Outro exemplo, usando blocos:

 
#include <stdio.h>

main()
{
 
  int x,y,z; /* declaração de uma variável inteira */

  printf ("Entre com o valor de x ");
  scanf("%d",&x);

  printf ("Entre com o valor de y ");
  scanf("%d",&y);

  if (y>x) {
     printf("MSG1: y é maior que x\n");
     z = y-x;
     printf("MSG2: Neste caso z =  %d\n", z);
  } else {
     printf("MSG3: y é igual ou menor que x\n");
     z = x-y;
     printf("MSG4: Neste caso z =  %d\n", z);
  }
 
}

Comando de repetição while (): decisão no início do loop

O comando while permite implementar loops com controle no início:

#include <stdio.h>
main() 
{
  int contador;
  
  contador=0;
  while (contador<5) {  
     printf("valor do contador =  %d\n", contador);
     contador=contador+1;
  }
}

Relação de comandos do fluxograma com Programa C

Fluxograma C Comentário
Fluxo1.jpg
main()
{
   printf("Alo Mundo\n");
}

O fluxogram do programa principal corresponde a função main() do C. A primeira instrução que se executa é a primeira instrução da função main() e a última instrução é a última do main()

Fluxo2.jpg
if (y>5)
  x=x+1;
Fluxo3.jpg
if (y>5) {
  x=x+1;
  z=z+1;
}
Observar que quando existe mais de uma instrução simples sob o controle do if, deve-se usar as chaves (conceito de bloco).
Fluxo4.jpg
if (y>5) {
  x=x+1;
  z=z+1;
} else {
  x=x-1;
  z=z-1;
}
comando if com a parte else (SENÃO)
Fluxo5.jpg
while(contador<5) {
   printf("Entre com x\n");
   scanf("%f",&x);
   soma=soma+x;
   contador = contador + 1;
}
comando while() aplicado sobre um bloco de instruções. Note que se for uma instrução simples, as chaves podem ser omitidas.
Fluxo6.jpg
if (z>5) {
   while(contador<5) {
        printf("Entre com x\n");
        scanf("%d",&x);
        soma=soma+x;
        if (soma>150) 
           y=z+x;
   }
}
Note que instruções while e if são tratadas como instruções normais que podem ser aninhadas normalmente em outros comandos.

Exercícios

  1. Ler 2 números reais e imprimir a média deles.
     
    #include <stdio.h>
    main()
    {
       float num1, num2, media;
    
       printf("Entre com o primeiro numero ");
       scanf ("%f", &num1);
    
       printf("Entre com o segundo numero ");
       scanf ("%f", &num2);
    
       media = (num1 + num2) / 2;
    
       printf ("\nMedia = %f \n", media);
    }
    
  2. Ler 3 números reais e imprimir o produto dos três.
  3. Ler dois números complexos, em formato retangular, e calcular a soma dos mesmos. Obs: Ler de forma independente a parte real e a parte imaginária de cada um deles.
  4. Ler as aulas 1 e 2 das aulas de C da UFMG (ver referências no início).


AULA 8 Dia 25/10/2012

AULA 8 Dia 25/10/2012

Objetivos da Aula

O aluno deverá ser capaz de:

  • utilizar funções: passar parâmetros por valor (reais, inteiros e caracter) e retornar valores;
  • compor expressões complexas usando constantes, variáveis e operadores;


Funções no C

Chamando funções

Um programa em C basicamente é um conjunto de funções. 
Uma função pode ser vista como um subprograma para o qual podemos repassar 
dados de entrada através de parâmetros e receber os resultados através 
do retorno da função.

Normalmente, um programa bem comportado em C possui pelo menos uma função: a função main(). Esta função é chamada no início da execução do programa. A primeira instrução da função main() é a primeira instrução executada pelo programa (pelo menos do ponto de vista do programador). Da mesma forma, a última instrução desta função é a última instrução a ser chamada.

Um programa normalmente vai apresentar um conjunto de funções. Por exemplo:

#include <stdio.h>

func4()
{
    printf("Esta é a função func4()\n");
}

func3()
{
    printf("Esta é a função func3()\n");
    func4();
}

func2()
{
    printf("Esta é a função func2()\n");
}

func1()
{
    printf("Esta é a função func1()\n");
    func2();
    func3();
}

main()
{
    printf("Esta é a primeira instrução da função main()\n");
    func1();
    printf("Esta é a última instrução da função main()\n");
}

A sequência de chamada de funções pode ser ilustrada da forma:

FuncChamadaFuncoes.png

EXERCÍCIO: Compile e execute o programa acima. Verifique a ordem de impressão das mensagens e compare com as chamadas das funções.

NOTA: Uma função pode ser chamada várias vezes no programa. 
É O REAPROVEITAMENTO DE CÓDIGO...

Passando parâmetros e recebendo valores de retorno

Uma função normalmente resolve um determinado problema para um determinado conjunto de dados e produz uma saída. Estes dados podem ser passados como parâmetros e a saída pode ser retornada pela função.

Exemplo: Uma função media_nums() que retorna a média de 3 números reais passados como parâmetros

#include <stdio.h>

float media_nums(float num1, float num2, float num3)
{
  float media_local;

  media_local = (num1 + num2 + num3)/3;
  return media_local;
}

main()
{
  float media, aux1, aux2, aux3;

  printf("\nEntre com numero 1: ");  
  scanf ("%f",&aux1);

  printf("\nEntre com numero 2: ");  
  scanf ("%f",&aux2);

  printf("\nEntre com numero 3: ");  
  scanf ("%f",&aux3);

  media = media_nums(aux1, aux2, aux3);
  printf ("\nmedia dos 3 numeros é %f\n", media);
}

Deve ser observado que:

  • após o nome da função, entre parênteses, são fornecidos os três parâmetros com os seus respectivos tipos. Os valores (conteúdos das variáveis) aux1, aux2 e aux3 são copiados para as variáveis num1, num2 e num3 da função media_nums().
  • a função media_nums() retorna um valor do tipo float (informado antes do nome da função) que é o valor da variável media_local. Este valor é copiado para a variável media da função main()
  • as variáveis num1, num2 e num3 bem como a variável media_local possuem escopo LOCAL, ou seja, são "vistas" somente pela função media_nums();
  • as variáveis media, aux1, aux2 e aux3 também possuem escopo LOCAL, ou seja são "vistas" somente pela função main();

NOTE que o formato de declaração de uma função é

 tipo_retorno nome_funcao( lista_de_parametros )
 {
   declaracao_variaveis_locais

   instruções
 }

Por enquanto, assumiremos que variáveis devem ser somente declaradas no início da função. Existem situações que poderemos relaxar esta afirmação.

Um pouco mais sobre parâmetros

O termo argumento ou parâmetro real (atual) é usado para referenciar os valores que estão sendo passados na CHAMADA da função. Os parâmetros formais referem-se aos parâmetros listados na função. É comum, no entanto, usar os termos argumentos e parâmetros como sinônimos e identificados pelo contexto em que estão sendo usados.

A passagem de parâmetros POR VALOR diz respeito a copiar o valor do argumento na CHAMADA da função para a variável associada ao parâmetro na função. Mais tarde falaremos na passagem de parâmetro POR REFERÊNCIA. Por ora, usaremos a passagem POR VALOR.

Os parâmetros passados na CHAMADA de uma função não são necessariamente variáveis. Eles podem ser uma expressão qualquer (uma expressão SEMPRE resulta em um VALOR).

Exemplo de chamadas para a função media_nums():

main()
{
  float media, x,y,z;

  x = 5.7;
  y = 9.8;

  /* exemplo de chamada 1 */  
  media = media_nums(4.8,x,y*3);

  /* exemplo de chamada 2 */ 
  media = media_nums (x+y,y*y+5,(x+y)/2);
}

Deve ser observado que:

  • na chamada 1 do exemplo, os parâmetros são uma CONSTANTE, o valor de x, e o valor de y*3. Note que o compilador deve gerar código para resolver estas expressões antes de INVOCAR a função!.
  • na chamada 2 aparecem expressões ainda mais complexas. Todas elas devem ser resolvidas antes da função ser INVOCADA.

Variáveis GLOBAIS e variáveis LOCAIS

Se variáveis são declaradas dentro de uma função, então a visbilidade (ESCOPO) destas variáveis é LOCAL. Nenhuma outra função tem acesso a estas variáveis.

Uma variável pode ser GLOBAL, ou seja, declarada FORA das funções. Neste caso a variável é VISTA por todas as funções. Seja o exemplo anterior modiificado:

float media; /* Variável GLOBAL */

void media_nums(float num1, float num2, float num3)
{
  media = (num1 + num2 + num3)/3;
  return;
}

main()
{
  float aux1, aux2, aux3; /* Variáveis LOCAIS */

  printf("\nEntre com numero 1: ");  
  scanf ("%f",&aux1);

  printf("\nEntre com numero 2: ");  
  scanf ("%f",&aux2);

  printf("\nEntre com numero 3: ");  
  scanf ("%f",&aux3);

  media_nums(aux1, aux2, aux3);
  printf ("\nmedia dos 3 numeros é %f\n", media);
}

Neste exemplo, a variável media é declarada como GLOBAL. Ela é MODIFICADA diretamente pela função media_nums() e impressa pela função main()

NOTE que como a função media_nums() não retorna valor então declaramos 
seu tipo de retorno como void que significa aqui NADA ou VAZIO. 
NOTE também que MESMO que a função retorne um valor, não é obrigatório colocá-la
no lado direito do sinal de atribuição.

Na realidade, uma função pode ser chamada dentro de qualquer expressão. Por exemplo, para o caso em que a função media_nums() retorna um valor, ela poderia ser usada como:

float media_nums(float num1, float num2, float num3)
{
  float media_local;

  media = (num1 + num2 + num3)/3;
  return media;
}

main()
{
  float media, aux1, aux2, aux3;
  printf("\nEntre com numero 1: ");  
  scanf ("%f",&aux1);).

  printf("\nEntre com numero 2: ");  
  scanf ("%f",&aux2);

  printf("\nEntre com numero 3: ");  
  scanf ("%f",&aux3);

  media = media_nums(aux1, aux2, aux3);
  printf ("\nmedia dos 3 numeros multiplicada por 10 é %f\n", 10*media_nums(aux1, aux2, aux3));
}

Nome de variáveis

Um nome de variável pode conter letras, dígitos e o underscore(sublinhado). Ela DEVE iniciar com um underscore ou uma letra. Letras maúsculas e minúsculas podem ser usadas e são distinguidas (o C é CASE SENSITIVE

Variáveis LOCAIS e GLOBAIS podem ter o mesmo nome. A variável LOCAL terá preferência no uso.

Exercício: Execute o programa abaixo e verifique as saídas.

#include <stdio.h>

int i=1;          /* GLOBAL  */

func()
{
    int i=100;     /* LOCAL */
    i=i+1;          /* incrementa LOCAL */
    printf( "Valor de i = %d na função func()\n", i );
}
   
main()
{
    i=i+1;          /* incrementa GLOBAL  */
    func();
    printf( "Valor de i = %d \n", i );
}


 NOTA: não é recomendado o uso de variáveis com o mesmo nome.

Iniciando variáveis na declaração

Tanto as variáveis LOCAIS como as GLOBAIS podem ser inicializadas na declaração.

Exemplo:

int alfa=1;

main()
{
  float beta=1.5;

  printf("Valor de alfa = %d e valor de beta = %f\n", alfa, beta);
}
 NOTA: variáveis LOCAIS não são iniciadas automaticamente: cabe ao programador iniciá-la corretamente.
 NOTA: variáveis GLOBAIS são iniciadas automaticamente com zero. 
       Mas mantenha-se informado sobre o sistema que está trabalhando...
       Em sistemas embarcados pode não ser verdade

AULA 9

Objetivos

O aluno deverá:

  • fixar conhecimentos da aula resolvendo exercícios de chamada de funções com parâmetros;
  • utilizar funções da biblioteca matemática;


Exercícios de fixação de chamada de funções com parâmetros

  1. Implementar um programa com uma função que recebe dois números inteiros como parâmetro e imprime uma mensagem caso o primeiro número seja igual ao segundo. A função não retorna nada. A função main deve mostrar como funciona a função, chamando-a três vezes com diferentes valores.
  2. Refazer o exercício anterior mas agora a função não deve imprimir nada. Ela deve retornar 1 se o primeiro parâmetro é maior que o segundo, 0 se forem iguais e -1 se o segundo for maior que o primeiro.
  3. Implementar uma função chamada imprime_faixa que recebe dois números inteiros como parâmetros e, caso o segundo parâmetro seja maior que o primeiro ela deve imprimir todos os números inteiros entre oos dois parâmetros (incluindo eles). A função retorna -1 se o segundo número for menor ou igual ao primeiro. Exemplo: Seja a chamada
     imprime_faixa(4,6);
    
    A saída deve ser: 4 5 6

USO de DEFINE

O define é um comando do pré-processador do C. Observe abaixo como ele pode ser usado

 
#include <stdio.h>
#define PI 3.1416

main()
{
 
  float x; /* declaração de uma variável real */
  float y;  /* declaração de uma variável real */

  printf ("Entre com o valor de x ");
  scanf("%f",&x);

  y = 2*PI*x;
}

Usando funções da biblioteca matemática

Para usar as funções matemáticas da biblioteca padrão, fazer os seguintes passos:

  • No arquivo-fonte incluir o header math.h da biblioteca matemática:
 
#include <stdio.h>
#include <math.h>

main()
{
 
  double x,y; /* declaração de duas variáveis reais */

  printf ("Entre com o valor de x ");
  scanf("%lf",&x);

  y = sqrt(x);
  printf ("Raiz de x = %lf", y);
}

NOTA: a maior parte de parâmetros e valores de retorno das funções matemáticas são reais de dupla precisão (double).

  • Compilar e linkar o arquivo da forma:
 gcc -lm ex1.c -o ex1

Exercício:

Exercícios

  1. Implementar um programa para calcular a área e o comprimento de uma circunferência, dado como entrada o raio.
  2. Implementar um programa para listar os senos e cosenos de 20 ângulos no intervalo
  3. Implementar um programa para ler um número complexo no formato retangular e convertê-lo para o formato polar. Usar as funções sqrt[1] e atan [2] da biblioteca matemática. Como converter:
    ou
  4. Fazer um fluxograma e implementar um programa C para calcular as raízes de uma equação do segundo grau, dado como entrada os coeficiente a,b e c.
  5. Implementar um programa para calcular o valor de y para um dado valor de x, considerando que:
  6. Implementar um programa para converter radianos em graus;
  7. Modificar o exercício para apresentar um menu que permite escolher se quer transformar graus em radianos ou vice-versa.

AULA 10 - 1/11/2012

Objetivos

  • Conhecer representação binária, hexadecimal, decimal e octal de números;
  • Conhecer Tipos de Dados no C
  • Constantes no C e uso de DEFINE para apoio

Representação binária, hexadecimal, octal e decimal de números

Sistema Decimal

Uma quantidade qualquer pode ser representada de diferentes formas. Uma representação muito conhecida por nós é a decimal:

que pode ser decomposta em:

Observe que a posição do dígito no número define o expoente do

No sistema decimal temos 10 símbolos para representar uma quantidade.

Sistema Binário

No sistema binário temos dois símbolos para representar uma quantidade. Se você ir na feira e pedir em binário:

maçãs

O feirante e especialista em sistema de numeração binário deve colocar no pacote (usando o sistema decimal):

maçãs

Note que a posição do número define o expoente do 2.

Por que representar números em binário é interessante?
É muito simples implementar circuitos somadores com lógica 
digital binária. Todo sistema de processamento de um computador
é digital binário.

Sistema Hexadecimal

O sistema hexadecimal se utiliza de 16 símbolos:

O número:

corresponde a:

ou


NOTA: No C constantes hexadecimais serão precedidas por '0x' ou '0X'

Sistema Octal

Similar ao anteriores mas usa 8 dígitos:

No C um úmero OCTAL começa com ZERO. Exemplo: 0755

Tabela relacionando números nos diversos sistemas

Exercício: Implementar uma tabela que relaciona os 16 primeiros números, a partir do 0, nos 4 sistemas de numeração.


Uma aplicação importante do sistema hexadecimal é permitir 
a rápida conversão para o sistema binário e vice-versa. 
Para o ser humano é mais fácil representar um número em 
hexadecimal e se necessário mapeá-lo em binário.
  • CONVERSÃO BINÁRIO - HEXADECIMAL

Agrupe a palavra binário da direita para a esquerda em grupo de 4 bits. Preencha com zero, se precisar, na esquerda. Substitua cada grupo pelo dígito hexadecimal.

Seja o numero:

PASSO 1: PASSO 2:

  • CONVERSÂO HEXADECIMAL - BINÁRIO

Seja o numero:


Tem-se:

Portanto:

Tipo de Dados no C

Tipo Tamanho formato scanf Explanação Opção Explanação
char 8 bits %c Tipo inteiro. Pode ser sinalizado ou não dependendo da implementação. Usado para armazenar um caracter. signed char garantido sinalizado.
unsigned char garantido não sinalizado.
short
short int
signed short
signed short int
2 bytes >= %hi %hu Tipo inteiro sinalizado short. unsigned short
unsigned short int
mesmo que short int mas não sinalizado.
int
signed int
4 bytes >= %d %ud tipo inteiro sinalizado unsigned
unsigned int
inteiro não sinalizado
long
long int
signed long
signed long int
4 bytes >= %ld %lu Tipo inteiro sinalizado. unsigned long
unsigned long int
mesmo que inteiro long mas não sinalizado.
long long
long long int
signed long long
signed long long int
4 bytes >= %lld %llu Tipo inteiro sinalizado long long. Especificado desde a versão C99. unsigned long long
unsigned long long int
Mesmo que inteiro long long mas não sinalizado. Especificado apenas na versão C99.
float %f (single precision) Tipo floating-point. Formato IEEE 754 single precision floating point.
double %lf Tipo double precision floating-point. Formato IEEE 754 double precision floating point.
long double %LF Tipo extended precision floating-point.
NOTA: Note que o tamanho dos tipos é em geral dependente de máquina. 
O operador sizeof pode ser usado para verificar o tamanho.

Exemplo: Imprimir o número de bytes usado para armazenar uma variável do tipo long.

#include <stdio.h> 

main()
{
  long int x;
  printf("Tamanho do long = %d", sizeof(x)); /* ou sizeof(long) simplesmente */
}

EXERCÍCIO: Verificar o tamanho de um char, short int, float, double e long double.

Constantes no C

Ver constantes

Operadores Aritméticos e de Atribuição no C

Ver Operadores Aritméticos e de Atribuição


AULA 11 - 5/11/2012

Objetivos

Operadores aritméticos

Já temos vistos expressões aritméticas com operadores básicos (+,-,*,/). Além destes operadores temos os seguintes:

Operador Significado
% resto de uma divisão
++ incrementa o operando em uma unidade
-- decrementa o operando em uma unidade

Exemplo 1

#include <stdio.h>
main()
{
   int x,y;
   printf("Entre com x\n");
   scanf("%d", &x);
   y=x%2;
   printf("resto x divido por 2 =%d\n",y);
}

Exemplo 2


#include <stdio.h>

main()
{
   int x,y;
   printf("Entre com x\n");
   scanf("%d", &x);
   y=x++;
   printf("Valor de y =%d e o valor de x = %d\n",y, x);
   x=1;
   y=++x;
   printf("Valor de y =%d\n",y);
}

Exemplo 3

#include <stdio.h>
main()
{
   int x,y;
   printf("Entre com x\n");
   scanf("%d", &x);
   y=x--;
   printf("Valor de y =%d\n",y);
}

Note que existe uma diferença entre

 y = x--;

e

 y = --x;

Exemplo 4

#include <stdio.h>

main()
{
  int x,y;

  x=1;
  y = x+++x++;
  printf("x=%d y=%d\n", x,y);

  x=1;
  y = ++x+x++;
  printf("x=%d y=%d\n", x,y);
}

Operador de Atribuição

#include <stdio.h>

main()
{
  int x,y,w;
  
  x=1;
  w=y=x+1;
  printf("x=%d y=%d w=%d\n", x,y,w);
  
  w=2*(y=x+1);
  printf("x=%d y=%d w=%d\n", x,y,w);

}

NOTE que:

 w=2*y=x+1;

produz um erro de compilação:

erro: lvalue required as left operand of assignment

Ver conceito de lvalue e rvalue aqui.

Operadores Relacionais e Lógicos

Ver Operadores Relacionais e Lógicos

Tipo Char

Uma variável do tipo caracter é tratada como um número inteiro e declarada com o tipo char, que na prática é um número inteiro de byte.

Exemplo

#include <stdio.h>
 
main ()
{
  char x='A',y=65,w=0x41,z;
 
  scanf("%c",&z);
  printf("Caracter lido = %c\n",z);
  printf("Caracter lido = %d\n",z);
  printf("Caracter lido = %x\n",z);
  if (z==x)
      printf("Iguais 1\n");
  if (z==y)
      printf("Iguais 2\n");
  if (z==w)
      printf("Iguais 3\n");
}

ExercícioS

  1. Implementar um programa calculadora em que são fornecidos dois números reais e a operação na forma de um operador +,-,x e /. O programa deve mostrar o resultado da operação.
  2. Implementar uma função em C que recebe 5 caracteres e retorna o número de ocorrência do caracter 'a'.
  3. Implementar uma modificação da função do exercício anterior em que o caracter a ser verificado nas ocorrências, é repassado como sexto parâmetro.

Limpando sujeira do teclado

Indentação

Indentação Estilos

Exercícios

  1. Implementar uma FUNÇÂO que converte temperaturas de graus Fahrenheit (passado como parâmetro) para Celsius (retornado).
  2. Implementar um programa em C para ler dois números inteiros e imprimir uma mensagem indicando se os números lidos são iguais ou diferentes. Caso sejam diferentes, computar a média dos mesmos.
  3. Implementar um programa para ler 4 números inteiros e imprimir uma mensagem se a soma dos dois primeiros for igual ou menor a soma dos dois últimos.
  4. Implementar um programa para ler dois números reais e, na sequência, um número inteiro. Se o número inteiro for 1 os dois números iniciais deverão ser somados, se for 2 eles serão subtraídos, se for 3 eles serão multiplicados e se for 4 serão divididos. Mostrar mensagem de erro se o número inteiro não estiver na faixa de 1 a 4. Mostrar mensagem caso a divisão não seja possível.
  5. Implementar duas FUNÇÕES em C para receber a resistência em ohms de 2 resistores e então calcular a resistência série e paralela dos mesmos.
  6. Dado o código de cores dos resistores, fazer um programa que permite mostrar o valor de resistência em Kilo - ohms.Tabela de Cores
  7. Faça um programa que leia valores de moedas e some as quantidades de tipos de moedas informadas. Para sair do programa o usuário deve digitar um número negativo. Valores inexistentes devem ser ignorados. Por exemplo, se o usuário digitar 25, 50, 25, 5, 10, 5, o programa deve informar: 2 moedas de 5 centavos, 1 moeda de 10 centavos, 2 moedas de 25 centavos, 1 moeda de 50 centavos. São aceitos apenas valores de moedas de 1, 5, 10, 25 e 50 centavos. Seu programa deve ler 10 valores de moedas, e então apresentar o resultado.
  8. Incrementar o programa anterior para calcular o total em reais equivalente as moedas lidas.
  9. Incrementar o programa anterior para que no final do programa uma mensagem seja dada para o usuário que possui mais do que 5 moedas de 50 e 3 moedas de 25 e mais do que 2 reais. A mensagem deve mandar o usuário comprar um X-salada.
  10. Um estudo sobre sensibilidade de pessoas a temperaturas da água identificou que a maioria das pessoas considera fria a água com temperaturas abaixo de 25 graus, morna entre 25 e 30 graus, e quente acima de 30 graus. Escreva um algoritmo na forma de fluxograma que mostre as palavras "fria", "morna" ou "quente" dependendo da temperatura da água que for informada;

Exercícios da Avaliação

1.Considere o fluxograma abaixo. a) Elabore um programa em C equivalente. Considere que TODAS as variáveis são inteiras. b) Coloque todos os valores finais das variáveis do programa.

Ex1prova-11-05-2012.jpg 2.Implementar um programa em C para ler 5 números inteiros e imprimir uma mensagem se a média dos dois primeiros for igual ou menor a soma dos três últimos. 3.Elaborar um fluxograma e um programa para ler 10 números reais para um vetor e, em seguida, computar a média de todos os números menores ou iguais a 11.5 E maiores que 5.5. A média deve ser impressa. 4.Implementar um programa em C para receber a resistência em ohms de 2 resistores e então calcular a resistência série e paralela dos mesmos. Se uma (ou ambas) resistências forem negativas, mostrar uma mensagem e encerrar o programa.

AULA 12 - 07-11-2012

Estruturas de Repetição

Existem 4 estruturas/comandos que permitem implementar loops ou repetições de blocos de código:

  • while()
  • do while()
  • for()
  • goto label
NOTA 1: Observe que repetir o código siginifica voltar a executá-lo, normalmente sobre o controle de uma expressão lógica.

Revisitando o comando while()

Fluxograma C Comentário
Fluxo5.jpg
while(contador<5) {
   printf("Entre com x\n");
   scanf("%f",&x);
   soma=soma+x;
   contador = contador + 1;
}
comando while() aplicado sobre um bloco de instruções. Note que se for uma instrução simples, as chaves podem ser omitidas.
NOTE que no exemplo anterior o contador inicialmente DEVE conter um valor válido.

Comando do while

O comando do while() permite a repetição de uma ou mais instruções, com controle do loop no final. Isto permite que o bloco seja executado pelo menos uma vez.

A estrutura do comando, informalmente, é:

do 
  instrução_simples;
while (expressão);

ou

do {
  lista_de_instruções
} while (expressão); 
 
Fluxograma C Comentário
FluxoDoWhile-Eraldo.jpg
contador = 0;
do {
        scanf("%d",&x);
        soma=soma+x;
        if (soma>150) 
           y=z+x;
        contador++;
} while(contador<5);
Note que instruções while e if são tratadas como instruções normais que podem ser aninhadas normalmente em outros comandos.

Comando for()

O comando for() permite uma forma mais elaborada de loop, com controle no início do bloco de repetição.

A estrutura do comando é:

for(expressão_inicial;expressão_de_controle; expressão_de_final_de _bloco) {
    instrução_simples;

ou

for(expressão_inicial;expressão_de_controle; expressão_de_final_de _bloco) {
    lista_de_instruções
}
Fluxograma C Comentário
FluxogramaComandoFor-Eraldo.jpg
for(i=0; i<10; i++) {
  printf("Laço de número %d\n", i);
  if (i==5)
      printf("Este é o laço 5\n");
}
Observe que a expressão i=0 é executada SEMPRE e uma única VEZ, no início do comando.

A expressão i<10 é o controle do loop. Se FALSA o loop é encerrado. Ela é executada após a expressão de inicialização e, na sequência, no início de cada loop. A expressão i++ é executada no final de cada loop.

Comando goto

O comando goto é um dos mais antigos da programação. A ideia é comandar um salto para um determinado ponto específico do programa marcado por um rótulo (LABEL). Para utilizá-lo deve-se, portanto, marcar o ponto para onde será feito o salto usando um LABEL.

Exemplo:

main()
{
   int i;

   i=0;

PONTO1:
   printf("Laço de número %d\n", i);
   i++;
   if (i<10)
       goto PONTO1;
}
Devido a ser uma instrução "desestruturante", em geral NÂO se recomenda o uso deste comando.

Em alguns casos de tratamento de erro pode ser interessante o uso do goto.

Leia um pouco mais sobre o goto aqui.

Loop Infinito

É possível implementar loops infinitos com qualquer uma das instruções acima.

Exemplo com comando for:

main()
{
   for(;;) {
        /* Este bloco se executará infinitamente */
   }
}

ou com o comando while:

main()
{
   while(1) {
        /* Este bloco se executará infinitamente */
   }
}

EXERCÍCIOS

1.Estude o programa (referência) abaixo:

/* rand example: guess the number */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main ()
{
      int iSecret, iGuess;

      /* initialize random seed: */
        srand ( time(NULL) );

      /* generate secret number: */
       iSecret = rand() % 10 + 1;

      do {
          printf ("Guess the number (1 to 10): ");
          scanf ("%d",&iGuess);
          if (iSecret<iGuess) 
              printf ("The secret number is lower\n");
          else if (iSecret>iGuess) 
              printf ("The secret number is higher\n");
      } while (iSecret!=iGuess);

      printf ("Congratulations!\n");
      return 0;
}

Explique o significado das instruções:

 srand ( time(NULL) );
 iSecret = rand() % 10 + 1;

2.Faça uma versão "politicamente incorreta" deste programa fazendo um loop infinito com o do while() e usando uma instrução goto para sair do loop.

3.Ainda sobre o exercício 1, implemente uma versão usando o comando while().

4.Usando o comando for, implemente uma função da forma:

int plot_retangulo(int lado1, int lado2, int margem_esquerda, char caracter)

Se a função for chamada da forma:

 plot_retangulo(10,3,5,'a');

ela deve produzir:

  aaaaaaaaaa
  a        a
  aaaaaaaaaa

Se houver inconsistência nos parâmetros retornar um código de erro -1 senão retornar 0.

AULA 13 - 19/11/2012

Vetores

Um vetor pode ser facilmente definido no C:

main()
{
  float x[10]; /* vetor com 10 floats */
  int i;
  x[0] = 22.5; /* colocando 22.5 na posição 0 do vetor */
  x[9] = x[0] + 2.5;
  printf("Entrar com o número na posição 5\n");
  scanf("%f",&x[5]);
  /* usando uma expressão como índice */
  i=2;
  x[i*2]=i*1.5;
  /*usando loop para acessar o vetor */
  while (i<8) {
     x[i]=0.0;
     i++;
  }
}
NOTA: vetores sempre começam na posição 0

Exercícios

  1. Implementar um programa em C para ler 10 números reais (float) para um vetor. Usar o comando while.
    #include <stdio.h>
    
    main()
    {
    	float x[10];
    	int i;
    
     	i=0;
     	while (i<10) {
    		printf("Entre com x[%d] -> ", i);
    		scanf("%f",&x[i]);
     		i++;
    	}	
    }
    
  2. Modificar o exercício para computar a média dos 10 números que estão no vetor. Usar um DEFINE para definir o tamanho do vetor.
  3. Modificar o exercício anterior para computar a quantidade de números do vetor que estão acima da média.
    #include <stdio.h>
     
    main()
    {
    	float x[10];
    	float soma = 0, media; 
    	int i, cont;
     
    	/* leitura do vetor */
     	i=0;
     	while (i<10) {
    		printf("Entre com x[%d] -> ", i);
    		scanf("%f",&x[i]);
    		soma = soma + x[i];
     		i++;
    	}	
       
    	/* calculo da media */
    	media = soma /10;
    
    	/* computação de números acima da média */
    	cont = 0;
     	i=0;
     	while (i<10) {
    		if (x[i] > media) 
    			cont++;
    		i++;
    	}	
    	printf ("Número de números acima da média = %d\n", cont);
    }
    
  4. Refazer os exercícios anteriores usando o comando for;

Vetor de Char

É possível definir vetores do tipo char. Tais vetores permitem definir cadeias de caracteres. Para marcar um final de cadeia usa-se o número 0 (NULL),

Exemplo:

Fazer um programa para computar o número de caracteres de uma cadeia (string) lida pelo teclado.Use o comando while.

#include <stdio.h>
void main ()
{
   char alfa[50];

   int i=0;

   printf ("Entre com a cadeia: ");
   scanf("%s",alfa);
   while(alfa[i]!=0)
	i++;
   printf ("\nNumero de caracteres em %s = %d \n", alfa, i);
}

Exercício

  1. Implementar um programa que computa o número de caracteres 'a' de uma string lida pelo teclado.

Iniciando uma cadeia na declaração

#include <stdio.h>
void main ()
{
   char alfa[50]="IFSC-SJ";

   printf ("\nNumero de caracteres em %s\n", alfa);
}

Como passar um vetor de caracteres como parâmetro

int str_len(char x[])
{
  int i=0;
  while (x[i]!=0)
    i++;
  return i;
}

main()
{
  int tam;
  tam = str_len("teste");
}

Exercícios

  1. Elaborar uma função que conta o número de ocorrências da letra 'a' em uma string passada como parâmetro.
    #include <stdio.h>
    
    int num_ocorr_a (char x[])
    {
       int  i,cont;
    
       i = 0;
       cont = 0;
       while (x[i]!=0) {
    	if (x[i]=='a')
    		cont++;
            i++;
       }
       return cont;
    }
    
    void main()
    {
       char string[100]="ababababab";
       int num_oc;
    
       num_oc  = num_ocorr_a(string);
    }
    
  2. Melhorar o exercício anterior passando o caracter a ser verificada as ocorrências também como parâmetro.

AULA 14

Suspensa para a MCC

AULA 15

Objetivos

  • Operações sobre strings
  • Funções de manipulação de matrizes da biblioteca
  • Matrizes
  • Vetor de strings
  • Uso do depurador gdb

Processamento de strings

Na aula anterior vimos vetores. Vimos que é possível armazenar strings em vetores de char. O processamento de strings é de grande interesse em programação. Vamos continuar a ver alguns aspectos deste processamento.

Ainda na aula aula passada vimo como computar o tamanho de uma string usando a função str_len(). V Vamos elaborar mais algumas funções para comp

Copiando strings

Implementar e testar uma função que copia uma cadeia de caracteres de um vetor de strings fonte para um vetor de destino, de forma similar a função strcpy.

#include <stdio.h>

void str_cpy(char auxs1[], char auxs2[])
{
   int i;
   
   for(i=0;auxs2[i]!=0;i++) 
        auxs1[i]=auxs2[i];
   auxs1[i]=0;
}

main()
{
   char str1[100], str2[100];

   printf("Entre com a string => ");
   scanf ("%s", str2);
   str_cpy(str1, str2);
   printf("\nString copiada = %s\n", str1);
}

ou

#include <stdio.h>

void str_cpy(char auxs1[], char auxs2[])
{
   int i=0;
   
   do { 
        auxs1[i]=auxs2[i];
   }while(auxs2[i++]!=0);
}

main()
{
   char str1[100], str2[100];

   printf("Entre com a string => ");
   scanf ("%s", str2);
   str_cpy(str1, str2);
   printf("\nString copiada = %s\n", str1);
}

Concatenado strings

  1. De forma similar ao exercício anterior, implementar uma função similar a função strcat.
    #include <stdio.h>
    
    void str_cat(char auxs1[], char auxs2[])
    {
       int i=0;
       
       /*localizar o final da string de destino*/
      while(auxs1[i]!=0)
                i++;
    
      /* usando a função de cópia já implementada temos */
      str_cpy(&auxs1[i], auxs2);
    }
    
    main()
    {
       char str1[100]="IFSC em ", str2[100]="Sao Jose",  x[]="teste";
    
       str_cat(str1, str2);
       printf("\nString copiada = %s\n", str1);
    }
    

Comparando strings

Exercício: Implementar uma função chamada str_cmp que recebe duas strings como parâmetro e retona 0 se elas são iguais ou 1 se elas forem diferentes.

Funções de manipulação de strings da biblioteca

Ver aula 5 da UFMG.

Como definir e operar com matrizes no C

De forma similar ao vetor, basta definir a matriz usando colchetes para indicar a dimensão da variável.

Exemplo: Definir duas matrizes 2x3 já inicializadas e computar a soma das mesmas:

#include <stdio.h>

void main()
{
  int mA[2][3]={ 11,12,13,
                 21,22,23},
      mB[2][3]={1,2,3,
                1,2,3},
      mC[2][3];
  int i,j;

  for(i=0;i<2;i++){
     for(j=0;j<3;j++) {
        mC[i][j] = mA[i][j] + mB[i][j];
     }
  }
  
}

Exercício

  1. implementar um programa para calcular a média de todos elementos da matriz C do exemplo acima.
    #include <stdio.h>
    
    void main()
    {
      int mA[2][3]={ 11,12,13,
                     21,22,23},
          mB[2][3]={1,2,3,
                    1,2,3},
          mC[2][3];
      int i,j, soma_ac=0;
      float media;
    
      for(i=0;i<2;i++){
         for(j=0;j<3;j++) {
            mC[i][j] = mA[i][j] + mB[i][j];
            soma_ac = soma_ac + mC[i][j];
         }
      }
      media = soma_ac/6.0;
    }
    
  2. Implementar um programa para ler uma matriz quadrada NxN pelo teclado e armazená-la em uma matriz matA. Defina matA com um tamanho máximo matA[N_MAX][N_MAX].
  3. IMplementar um programa para ler duas matrizes (matA e matB) e multiplicá-las, colocando o resultado em uma matriz matC.

Passando matrizes como parâmetro

#include <stdio.h>


void somar_mat(int aA[][3],int aB[][3], int cC[][3])
{
  int i,j;

  for(i=0;i<2;i++){
     for(j=0;j<3;j++) {
        cC[i][j] = aA[i][j] + aB[i][j];
     }
  }
}

void main()
{
  int mA[2][3]={ 11,12,13,
                 21,22,23},
      mB[2][3]={1,2,3,
                1,2,3},
      mC[2][3];

 somar_mat(mA,mB,mC);
  
}
OBSERVE que matrizes são sempre passadas como referência.

Exercício

  1. Fazer uma função que recebe duas matrizes 2x3 como parâmetros e retorna a média da soma destas matrizes.
    #include <stdio.h>
    
    
    float media_soma_mat(int aA[][3],int aB[][3])
    {
      int i,j;
      int soma_ac=0;
      /*int cC[2][3];*/
    
      for(i=0;i<2;i++){
         for(j=0;j<3;j++) {
            /*cC[i][j] = aA[i][j] + aB[i][j];*/
            soma_ac = soma_ac +aA[i][j] + aB[i][j];
         }
      }
      return (soma_ac/6.0);
    }
    
    void main()
    {
      int mA[2][3]={ 
                     11,12,13,
                     21,22,23},
          mB[2][3]={
                    1,2,3,
                    1,2,3};
      float media;
    
      media =  media_soma_mat(mA,mB);
      
    }
    
  2. Modificar este exercício para que a função receba um parâmetro adicional do tipo inteiro. A função deve retornar a média de todos os valores da matriz soma que estão acima do valor passado como parâmetro.

Matrizes de caracteres e vetores de strings

Um vetor de strings pode ser construído usando matrizes de char. Cada string será armazenada em uma linha do vetor. Exemplo

#include <stdio.h>

main()
{
  char TabelaUsuarios[4][10] = {
         "joao",
         "maria",
         "jose",
         "lara",
                                };
  int i;

  for (i=0;i<4;i++)
       printf("%s\n",&TabelaUsuarios[i][0]);
}
Note a forma como é realizada a inicialização da matriz.

Exercício

  1. Implementar um programa para "abrir uma porta" para um usuário que se encontra na tabela acima.
    #include <stdio.h>
    #include <string.h>
    
    main()
    {
      char TabelaUsuarios[4][10] = {
             "joao",
             "maria",
             "jose",
             "lara",
                                    };
      int i;
      char userId[50];
      int userEncontrado=1;
    
      scanf("%s",userId);
    
      for (i=0;i<4 && userEncontrado; i++) {
           if( strcmp(userId, &TabelaUsuarios[i][0])==0)
                 userEncontrado=0;
      }
      if (userEncontrado==0)
           printf("Abrir porta!!!\n");
    }
    

Usando o gdb para depurar programas

È possível controlar a execução de um programa usando um outro programa, chamado depurador (debugger), para controlar a execução do primeiro. A GNU desenvolveu o debugger gdb que é hoje amplamente utilizado por desenvolvedores.

Documentação do GDB

Seja o programa armazenado no arquivo teste.c:

#include <stdio.h>

main()
{
  int x,y;
  
  x = 2;
  y = 0;
  while (y<5)
     x++;
  printf ("Valor de x = %d\n",x);
}

Para que um programa possa ser depurado, devemos compilá-lo da forma:

 gcc -g teste.c -o teste


O flag -g garante que seja incorporada informação simbólica e textual para a depuração.

O gdb pode então ser chamado da forma:

 gdb teste

Um breakpoint pode ser colocado em qualquer linha ou entrada de função do programa. Para colocar um breakpoint na entrada da função pode-se fazer:

 b main

Para executar o programa basta fazer o comando run:

 r

A execução para no breakpoint. A instrução mostrada ainda vai ser executada.

Para acompanhar o valor de variáveis pode-se colocá-las em display:

 display x
 display y

Para execução passo a passo pode-se utilizar o comando next:

 n

Para ver o

Para ver o conteúdo de uma variável pode-se ainda fazer o comando print:

print x

Visualizadores gráficos para o gdb

Existem vários visualizadores de código que se utilizam do gdb para depurar de forma mais amigável um programa. Exemplo: ddd, nemiver.

Demonstração do uso do ddd

AULA 16 - Dia 28/11/2012

Objetivos

  • Revisão aula anterior;
  • mapeamento de matrizes em memória
  • comando switch
  • exercícios

Mapeamento de matrizes em memória

Revisando a aula anterior

Considere o programa para "abrir uma porta" para um dado usuário, conforme aula anterior.

#include <stdio.h>
#include <string.h>

main()
{
  char TabelaUsuarios[4][10] = {
         "joao",
         "maria",
         "jose",
         "lara",
                                };
  int i;
  char userId[50];
  int userEncontrado=1;

  scanf("%s",userId);

  for (i=0;i<4 && userEncontrado; i++) {
       if( strcmp(userId, &TabelaUsuarios[i][0])==0)
             userEncontrado=0;
  }
  if (userEncontrado==0)
       printf("Abrir porta!!!\n");
}
  1. Implementar uma tabela adicional com senhas dos usuários. O acesso deve ser concedido somente se o usuário for validado e a senha. Defina as tabelas como variáveis globais.
    #include <stdio.h>
    #include <string.h>
    
    char TabelaUsuarios[4][10] = {
             "joao",
             "maria",
             "jose",
             "lara",
                                    };
    char TabelaSenhas[4][10] = {
             "aaaa",
             "cccc",
             "jdddd",
             "ldfsaf",
                                    }; 
    
    main()
    {
      int i;
      char userId[10];
      char senha[10];
      int userEncontrado=1;
    
     
     printf("Entre com UserId\n");
      scanf("%s",userId);
     
     /* 
         Loop para encontrar o usuário na tabela. 
         Ao final do loop a variavel i conterá o índice do usuário (se ele estiver
         na tabela
      */
      for (i=0;i<4 && userEncontrado; i++) {
           if( strcmp(userId, &TabelaUsuarios[i][0])==0)
                 userEncontrado=0;
      }
    
      /* se usuário encontrado abre a porta */
      if (userEncontrado==0) {
    	printf("Bom dia %s! Entre com a senha\n", userId);
      	scanf("%s",senha);
    	i--; /* o indice do sujeito é i-1 */
    	if(strcmp(senha,&TabelaSenhas[i][0])==0) 
    	       printf("Abrir porta!!!\n");
    	else
    		printf("Senha Inválida\n");
      }
    }
    


AULA 17 - Dia 03/12/2012

Objetivos

  • entrega de provas
  • comando switch
  • exercício: controle de senha
  • estruturas

Comando switch

O comando switch permite controlar o fluxo de um programa de forma condicional. O comando testa uma expressão que deve resultar em um número inteiro. Uma sequência de cláusulas case permite executar uma sequência de instruções conforme o valor da expressão. Note que esta sequência de instruções pode ser interrompida por um break.

main () {
{
  int opcao;
  printf("Entre com uma opção (número inteiro)\n");
  scanf ("%d",&opcao);
  switch(opcao) {
  case 1:
          printf("opcao 1\n");
          break; /* o break força o encerramento da instrução*/
  case 2:
          printf("opcao 2\n");
          x++;  /* instrução demonstrativa apenas */
          printf("Note que pode existir ums lista de instruções");
          break;
  case 3:
          printf("opcao 3\n"); /* note o efeito de não ter o break */
  case 4:
          printf("opcao 4\n");
          break;
  case 5:
          printf("opcao 5\n");
          break;
  default:
          printf("opcao default\n");
          break;    /* a opção default é opcional */
  }

Um sistema completo de controle de senha

Vamos aplicar os conhecimentos adquiridos para implementar um sistema completo de controle de senha. Vamos seguir uma abordagem de desenvolvimento top down, definindo a estrutura do sistema e progressivamente implemetando as funções. O sistema deve fazer o seguinte. Normalmente mostra uma mensagem de solciitação de UserID para abrir a porta. Se for teclado o UserId "admin", pré-programado, o sistema entra em modo administrador e mostra as seguintes opções possíveis: 1.Editar nova senha do admin 2.Remover usuário das tabela. 3.Inserir usuário e senha na tabela. 4.Modificar a senha de um usuário 5.Limpar tabelas.

Para fims de organização vamos implementar inicialmente a seguinte estrutura:

TrabalhoFinalFluxogramaPrincipal.jpg

Por ora, deixe vazia as funções:

#include <stdio.h>
#include <string.h>

/**********************************************/
/*** PROGRAMA DE CONTROLE DE ACESSO  **/
/** Autor: Turmas A e B da Fase 1 CST - 2012.1 */
/**********************************************/

/** VARIÁVEIS GLOBAIS DESTE MÓDULO ****/

char userID[20];


/** FUNÇÔES DESTE MÓDULO ****/

void mostrar_menu_entrada_usuario()
{
  printf("*******************************\n");
  printf("Entre com o seu USERID para ter acesso\n"); 
  printf("*******************************\n");
}

/** Função que implementa as tarefas do administrador **/

void  administrar()
{
}

/** Função que valida um usuário e abre a porta **/

void tratar_usuario()
{
}

void main()
{
 for(;;) {
     mostrar_menu_entrada_usuario();
     scanf("%s",userID);
     if (strcmp(userID, "admin")==0) {
          administrar();
     } else {
         tratar_usuario();
     }
 }   
}

Execício: Implementar a função tratar_usuario(). Transporte o código e as tabelas desenvolvidas nas aulas anteriores.

#include <stdio.h>
#include <string.h>

/**********************************************/
/*** PROGRAMA DE CONTROLE DE ACESSO  **/
/** Autor: TurmaENG.TELECOM - 2012.2 */
/**********************************************/

/** VARIÁVEIS GLOBAIS DESTE MÓDULO ****/

/* Tabela de Usuários */
char TabelaUsuarios[4][10] = {
         "joao",
         "maria",
         "jose",
         "lara",
};

/* Tabela de Senhas */ 
char TabelaSenhas[4][10] = {
         "aaaa",
         "cccc",
         "jdddd",
         "ldfsaf",
}; 
  
char userID[20];
 
 
/** FUNÇÔES DESTE MÓDULO ****/
 
void mostrar_menu_entrada_usuario()
{
  printf("*******************************\n");
  printf("Entre com o seu USERID para ter acesso\n"); 
  printf("*******************************\n");
}
 
/** Função que implementa as tarefas do administrador **/
 
void  administrar()
{
}
 
/** Função que valida um usuário e abre a porta **/
 
void tratar_usuario()
{
  char senha[10];
  int userEncontrado=1;
  int i;
 

 /* 
     Loop para encontrar o usuário na tabela. 
     Ao final do loop a variavel i conterá o índice do usuário (se ele estiver
     na tabela
  */
  for (i=0;i<4 && userEncontrado; i++) {
       if( strcmp(userID, &TabelaUsuarios[i][0])==0)
             userEncontrado=0;
  }

  /* se usuário encontrado abre a porta */
  if (userEncontrado==0) {
	printf("Bom dia %s! Entre com a senha\n", userID);
  	scanf("%s",senha);
	i--; /* o indice do sujeito é i-1 */
	if(strcmp(senha,&TabelaSenhas[i][0])==0) 
	       printf("Abrir porta!!!\n");
	else
		printf("Senha Inválida\n");
  }
}
 
void main()
{
 for(;;) {
     mostrar_menu_entrada_usuario();
     scanf("%s",userID);
     if (strcmp(userID, "admin")==0) {
          administrar();
     } else {
         tratar_usuario();
     }
 }   
}

Estruturas

No C é possível criar tipos de dados que representam uma estrutura. Veja o exemplo

#include <stdio.h>

struct TUsuario
{
  char userID[20];
  char senha[20];
} Usuario;

struct TUsuario TabelaUsuario[20];

main()
{
  scanf("%s", Usuario.userId);
  scanf("%s", Usuario.senha);
  scanf("%s", TabelaUsuario[10].userID);
  scanf("%s", TabelaUsuario[10].senha);
}

Aula Dia 06/12/2012

Objetivos

  • Consolidar o uso de estruturas

Exercícios de Estruturas

Exercício: Implementar o exercício da aula anterior usando estruturas.

#include <stdio.h>
#include <string.h>
 
/**********************************************/
/*** PROGRAMA DE CONTROLE DE ACESSO  **/
/** Autor: TurmaENG.TELECOM - 2012.2 */
/**********************************************/
 
/** VARIÁVEIS GLOBAIS DESTE MÓDULO ****/

struct TRegistroUsuario {
	char UserId[10];
	char Senha[10];
};

/* Tabela de Usuários */
struct TRegistroUsuario TabelaUsuarios[4] = {
         {"joao","abcd"},
         {"maria","xxxx"},
         {"jose","yyyy"},
         {"lara","zzzz"},
};

 
char userID[20];
 
 
/** FUNÇÔES DESTE MÓDULO ****/
 
void mostrar_menu_entrada_usuario()
{
  printf("*******************************\n");
  printf("Entre com o seu USERID para ter acesso\n"); 
  printf("*******************************\n");
}
 
/** Função que implementa as tarefas do administrador **/
 
void  administrar()
{
}
 
/** Função que valida um usuário e abre a porta **/
 
void tratar_usuario()
{
  char senha[10];
  int userEncontrado=1;
  int i;
 
 
 /* 
     Loop para encontrar o usuário na tabela. 
     Ao final do loop a variavel i conterá o índice do usuário (se ele estiver
     na tabela
  */
  for (i=0;i<4 && userEncontrado; i++) {
       if( strcmp(userID, TabelaUsuarios[i].UserId)==0)
             userEncontrado=0;
  }
 
  /* se usuário encontrado abre a porta */
  if (userEncontrado==0) {
	printf("Bom dia %s! Entre com a senha\n", userID);
  	scanf("%s",senha);
	i--; /* o indice do sujeito é i-1 */
	if(strcmp(senha,TabelaUsuarios[i].Senha)==0) 
	       printf("Abrir porta!!!\n");
	else
		printf("Senha Inválida\n");
  }
}
 
void main()
{
 for(;;) {
     mostrar_menu_entrada_usuario();
     scanf("%s",userID);
     if (strcmp(userID, "admin")==0) {
          administrar();
     } else {
         tratar_usuario();
     }
 }   
}

Exercício:

Implementar um contador de acesso que permita bloquear o usuário após 3 tentativas seguidas. Note que caso o usuário acerte a senha, este contador deverá ser zerado.

AULA 19 - 10/12/2012

Objetivos

  • Revisão aula anterior
  • exercício de fixação
  • separação do programa em múltiplos arquivos: uso de extern
  • continuação do exemplo do controle de acesso

Exercício de Fixação

Exercício: No programa de controle de senha inserir um campo na estrutura do usuário de forma a acomodar uma mensagem de boas vindas particularizada para cada usuário. A mensagem "DEFAULT" é Bom dia!

Separação do programa em múltiplos arquivos

Muitas vezes o programa se torna grande demais e o uso de múltiplos arquivos fonte torna-se necessário. A divisão também permite o desenvolvimento organizado do projeto, onde cada arquivo contém um grupo de instruções e variáveis globais relacionados com uma determinada parte do sistema (subsistema ou módulo).

É necessário, no entanto, criar arquivos cabeçalho (headers ou .h) para declarar protótipos de funções e variáveis globais cuja visibilidade deve ser exportada para outros arquivos. Exemplo:

Seja um projeto com dois arquivos. No arquivo t1.c existe o seguinte conteúdo:

#include <stdio.h>

#include "t2.h"

int y;

void alfa(int x)
{
  printf("x=%d\n",x);
}

main()
{
  y = 20;
  alfa(2);
  beta();
}

Observe que a função main() usa as funções a alfa() e beta(). Mas beta() não está implementada em t1.c. Ela está implementada em um outro arquivo t2.c. Neste caso, para que o compilador possa validar os parâmetros e o retorno da função beta() é necessário incluir um arquivo header t2.h que possui tais informações.

Arquivo t2.h:

extern void beta();
Note o uso da palavra extern para informação do PROTÓTIPO da função.

O arquivo t2.c possui a implementação de beta():

#include <stdio.h>
#include "t1.h"
void beta()
{
  alfa(23);
  printf ("y=%d\n",y);
}

Note que beta() usa a função alfa() que está implementada em t1.c. Neste caso ela inclui o arquivo t1.h que contém o protótipo de alfa():

extern int y;
extern void alfa(int x); /* extern aqui é opcional */

REGRAS GERAIS PARA CONSTRUÇÂO DO HEADER

  1. Se você definir uma variável global em arquivo fonte, digamos no t1.c, e deseja que outros arquivos "vejam" está variável então coloque uma declaração desta variável em um arquivo header (t1.h) com a palavra chave extern.
 NOTA: note que existe uma diferença entre definir e declarar. Se você cria uma variável global, por exemplo, int x; no
 arquivo fonte t1.c, você está definindo a variável. Será alocada uma área de memória para esta veriável. Se você declara
 a variável no t1.h usando o extern, você simplesmente está informando que esta variável existe e qual tipo possui.
  1. Nunca defina a variável no header pois estará abrindo a possibilidade para que cada arquivo que inclua este header crie uma instância desta variável;
  2. Se você quer publicar (informar) outros arquivos fonte sobre funçṍes de um arquivo, por exemplo, a função beta() do exemplo passado, então insira uma declaração da função no header. Neste caso a palavra chave é opcional.

Ver discussão em: [3]

Compilando múltiplos arquivos

É possível compilar e linkar os múltiplos arquivos da forma:

gcc t1.c t2.c -o t1

A desvantagem desta abordagem é que os dois arquivos sempre serão compilados, independente de mudanças. Pode-se compilá-los de forma independente da forma:

gcc -c t1.c
gcc -c t2.c

E então linká-los:

gcc t1.o t2.o -o t1

Nas aulas posteriores veremos que é possível utilizar o utilitário make para controlar a compilação do projeto.

Você deve ter notado o uso de include com aspas e com < e >. Quando um arquivo include é fornecido entre aspas então o arquivo a ser incluído é procurado no próprio diretório onde está o fonte. Caso contrário, ele é procurado em diretórios que o gcc está configurado (consulta em variáveis do sistema) para procurá-lo.

Separando as Funcionalidades do Administrador

  1. Implementar o esqueleto da função administrar conforme o fluxograma abaixo. Use o comando switch. Todas as funções associadas ao administrador devem ser implementadas em um arquivo separado chamado admin.c. As fuinções do usuário e o main() devem ser impleemnatdas em um arquivo usuario.c. Implementa os headers necessários para exportar as variáveis e funções de um arquivo para outro.

Administrar.jpg

  1. implementar a funcionalidade de desbloquear a conta de um usuário bloqueado.
  2. Implementar uma funcionalidade de inserir uma mensagem para um determinado usuário.

AULA 20 - 12/12/2012

Objetivos

  • Guard headers
  • compilação condicional
  • continuação do exemplo de controle de acesso

O problema de múltiplas inclusão de headers [4]

Seja um arquivo avo.h:

struct familia {
};

E um arquivo pai,h:

#include "avo.h"

Finalmente um arquivo filho.h:

#include "avo.h"
#include "pai.h"

Considere o fonte filho.c

#include "filho.h"

Se este arquivo for compilado, teremos um erro porque devido a dupla inclusão do header avo.h a estrutura familia estará sendo duplicada.

Compilação Condicional e Guard Headers

As diretivas de pré-compilação #ifndef e #ifdef são usadas quando queremos compilar "condicionalmente" determinado bloco de código.

Por exemplo, podemos criar guard headers usando a diretiva #ifndef de forma a inserir unicamente um código de um header. No exemplo anterior, o arquivo avo.c poderia ser "guardado" da forma:

#ifndef AVO_H

#define AVO_H

struct familia {
};

#endif

Neste caso, guando o arquivo filho.c for compilado, o arquivo avo.h é duplamente incluído. O compilador, ao encontrar a diretiva #ifndef AVO_H da primeira inclusão, observa que o símbolo AVO_H não foi definido ainda (com um #define). Neste caso, ele compila o código que se segue. No código que se segue o símbolo AVO_H é definido. Ao encontrar a segunda inclusão de de avo.h, o compilador novamente se depara com a diretiva #ifndef AVO_H. Neste momento, o símbolo AVO_H já foi definido e o compilador ignora o código que se segue até encontrar o #endif. É a compilação condicional...

É uma boa prática proteger os arquivos headers com uma guarda de forma a evitar problemas posteriores.

Aula 21

Exercícios de preparação para a prova

  • Questão 3 da prova 1. Depurar com GDB colocar break na entrada do main() e entrar nas funções print_quad().

Visualizar os valores das variáveis. Colocar break na entrada do segundo for.

#include <stdio.h>

/* 
   FUNCAO QUE IMPRIME UM PSEUDO QUADRADO DE n linhas por n caracters e
   com margem configuravel
*/

void print_quad(int margem, char caracter, int lado)
{
	int i,j;
	for (i=0;i<lado;i++) {
		for(j=0;j<margem;j++)
			printf("%c", ' ');
		for(j=0;j<lado;j++)
			printf("%c",caracter);
		printf("\n");
	}
}

void main()
{
	print_quad(10,'A',10);
	print_quad(5,'B',20);
}
  • Multiplicação de Matrizes
void mult_matr(int a[3][3],int b[3][3],int c[3][3])
{
	int i,j,k;
	
	for (i=0;i<3;i++)
		for (j=0;j<3;j++) {
                        c[i][j]=0;
 			for (k=0;k<3;k++)
				c[i][j]=a[i][k]*b[k][j]+c[i][j];
                }                 
}

void main()
{
  int x[3][3]={
					{3,5,6},
					{9,5,6},
					{4,3,8}
				};
  int y[3][3]={
					{2,5,3},
					{4,2,8},
					{1,3,9}					
				};
  int z[3][3];

  mult_matr(x,y,z);

}
NOTA: Os parâmetros matrizes podem ser vistos com:
      graph display *c @ 3
  • Deletando elementos da Tabela de Usuários do Sistema de Controle de Acesso

Para remover um elemento da tabela de usuários vamos convencionar o usu de uma string vazia. Para remover um elemento basta portanto colocor 0 no primeiro elemento do vetor.

Exercício: Implementar o limpar_tabela, limpar_

Uso do comando break para sair de um loop

Trabalho de Final de Disciplina

Itens a serem incluídos no controle de acesso:

  • Interface GUI para o administrador. Ver Tutorial GTK e [5];
  • Interface usuário GUI simulando o display LCD de 2 linhas 40 caracteres por linha e o teclado numérico;
  • Interface usuário via celular/App Inventor com bluetooth;

Programa C acesso bluetooth

  • Arquivo de log de entrada/saída de usuários com horário e data. Prever um mecanismo de aviso de saída;
  • Armazenamento da base de dados de usuários/administrador em arquivo;
  • Prever possibilidade de vários administradores;
  • Prever perguntas adicionais para abrir porta: randomicamente perguntar nome pai,cidade onde nasceu etc;

Compilando para GTK

Instale a biblioteca GTK:
sudo apt-get install libgtk2.0-dev

e compile da forma:

gcc -o hello_gtk -DGTK_ENABLE_BROKEN  hello_gtk.c `pkg-config --libs --cflags gtk+-2.0

Ponteiros

  • referências:

[6] [7] [8] [9]


#include <stdio.h>

main()
{
  int x;
  int *p;

  x=5;
  printf("Valor de x antes = %d\n", x);
  
  p = &x;
  *p=10;

  printf("Valor de x depois = %d\n", x);
  printf("Valor de p = %p\n", p);
}


#include <stdio.h>

main()
{
   char x[10]="ifsc";
   char *p;

   p = &x[2];

   printf("x[2] = %c\n", *p);

   p = x;

   printf("string %s\n", p);

   while (*p!=0) {
       printf("Endereco %p conteúdo %c\n", p,*p);
       p++;
   }	
}
#include <stdio.h>

main()
{
   int x[10]= {0,1,2,3,4,5,6,7,8,9};
   int *p;
   int i;
   
   p = x;

   i=0;
   while (i<10) {
       printf(" endereco %p e conteudo %d\n", p, *p);
       p++;
       i++;       
   }	
}


#include <stdio.h>

void str_cpy(char *pdest, char *pfonte)
{
   while (*pfonte!=0) {
        *pdest++ = *pfonte++;
   }
   *pdest = 0;
}


int str_len (char *p)
{
   int i=0;
   while (*p++!=0)
		i++;
   return i;
}

main()
{
   char fonte[10]="ifsc";
   char destino[10];

   str_cpy(destino, fonte);
   printf("string destino = %s\n", destino);

   printf("tamanho de dest = %d\n", str_len(destino));
}

AULA 22 Dia 4/2/2013

Objetivos

  • Exercícios de preparação para avaliação
  • Revisão Ponteiros

Exercícios

Exercício 1

Elaborar uma subrotina que recebe uma cadeia de caracteres como parâmetro (passada como referência). A subrotina deve excluir todas as vogais da cadeia e retornar o número de vogais eliminadas. Por exemplo: se a cadeia for "casa", deve ficar "cs"

Esqueleto da função:

#include <stdio.h>

int elimina_vogal(char cadeia[])
{
}

void main()
{
  char x[20]="casa";
  int num_vg;

  num_vg = elimina_vogal(x)
  printf("cadeia = %s e numero vogais eliminadas = %d\n", x, num_vg);
}

Saída:

 cadeia = cs e numero vogais eliminadas = 2

Exercício 2

Elaborar uma tabela global (com 4 registros) que permita armazenar em cada registro (linha) os dados de um aluno:

 -nome
 -endereço
 -nota bim1, bim2, bim3, bim4
 -média anual

Implementar um programa que inicia a tabela com o nome, endereço e com as notas bimestrais. Construir uma função que permita calcular a média anual do aluno, a média anual da turma e o número de alunos com nota acima da média.

struct TRegistroAluno{
  char nome[30];
  :
} TabelaTurma[4]; /* colocar inicialização para os 4 alunos */

void calcular_medias()
{
 /* calcular média por aluno e geral*/

 /* computar número de alunos acima da média */
}

main()
{
   calcular_medias();
}

Revisando ponteiros

Entendendo a memória do computador

A memória de um computador pode ser vista como um vetor de bytes. 

Cada byte possui um endereço. O tamanho da memória é definido pelo tamanho do barramento de endereços usado para acessá-la.

Uma variável ocupa uma área da memória. Tipicamente uma variável to tipo char se utiliza de um byte. Já uma variável do tipo int pode (dependendo do sistema) usar 4 bytes contíguos.

 Uma variável possui um endereço e um conteúdo (dados).

Ponteiros

Uma variável ponteiro tem como conteúdo um endereço.

Portanto a variável ponteiro possui um endereço e contém um endereço como conteúdo.

AULA 23 Dia 6/2/2013

Avaliação de Laboratório


Questão 1

Implementar uma função para inverter uma string, duplicando qualquer ocorrência de vogal 'a' ou 'o'. Retornar o número de vogais 'a' encontradas. Se julgar necessário use strlen e/ou strcpy da biblioteca.

int inv_string_filtra(char cadeia[])
{
}

main()
{
  char alfa[100]="florianopolis";
  int num;

  num = inv_string_filtra(alfa);
  printf("string invertida = %s e numero de ocorrencias = %d\n", alfa, num);
}

A saida deve ser:

string invertida = siloopoonaairoolf e numero de ocorrencias = 1 

Questão 2

Implementar uma tabela global de registros de produtos que reflete o estoque de uma loja. Cada registro deve permitir descrever: -código do produto -descrição do produto -quantidade em estoque -setor (1-eletronicos, 2-eletrodomesticos, 3-cozinha, 4-esportes) -preço unitário -código do fornecedor.

Obs: implemente a tabela com 5 registros para fins de teste. Implementar uma função que computa o total em reais relativo a produtos estocados de um determinado setor. O setor é repassado como parâmetro. Se for repassado 0 então será computado para todos os setores.

AULA 24

Objetivos

  • Vetor de ponteiros
  • Argumentos de linha de comando
  • Alocação dinâmica de memória
  • Aplicação de alocação dinâmica no exemplo do controle de acesso

Vetor de ponteiros

Como visto em aulas anteiriores, variáveis ponteiros possuem como conteúdo um endereço. É perfeitamente possível construir vetores e matrizes de ponteiros. Por exemplo:


#include <stdio.h>

int main()
{
  int i;

  char *vp[4];
  char alfa[5]="IFSC";
  char beta[5]="TELE";
  char delta[5]="RAC";
  char gamma[5]="CGER";

  vp[0] = alfa;
  vp[1] = beta;
  vp[2] = delta;
  vp[3] = gamma;  

  for(i=0;i<4;i++)
	printf("%s\n", vp[i]);
}

Fig1Aula24PrgITele.jpg

Observe que vp é um vetor de ponteiros para char e cada elemento aponta para uma cadeia de caracteres.

O Layout de memória de um programa C

http://shivacherukuri.blogspot.com.br/2011/03/memory-layout-in-cdata-segmentbss-code.html

Argumentos de linha de comando

Um bom exemplo de vetor de ponteiros é a passagem de parâmetros na linha de comando. Cada parâmetro é tratado como uma cadeia de caracteres apontada por um elemento do vetor argv. O número de parâmetros é passado em argc. Note que argv[0] aponta para uma string que inidica o nome do programa.

Exemplo: Considere o programa abaixo:

#include <stdio.h>

main(int argc, char *argv[])
{
  int i;

  for (i=0;i<argc;i++) {
       printf("%s\n", argv[i]);
  }
  printf("Numero de parametros passados = %d\n", argc-1); /* o primeiro é o nome do arquivo executavél" */
}

Exercício:

Implementar um programa chamado cmpcadeia que testa se duas strings passadas na linha de comando são iguais. O programa deve imprimir uma mensagem indicando se são iguais ou diferentes. Usar a função strcmp da biblioteca. Caso sejam passados mais ou menos que dois parâmetros o programa deve se encerrar mostrando uma indicão do tipo:

 cmpcadeia: dois parametros devem ser passados

Renomeie o executável e veja seja a mensagem de erro mostra o nome correto do programa.


Aula 25

  • Alocação dinâmica de memória
  • Aplicação de alocação dinâmica no exemplo do controle de acesso

Alocação dinâmica de memória

Ver aula da UFMG

Alocando dinamicamente uma tabela de estruturas

#include <stdio.h>
#include <stdlib.h>

void main()
{
  struct Tteste{
     int x;
  } *teste;

  if ((teste = malloc (100*sizeof(struct Tteste)))==NULL) {
      printf("erro de alocação");
      exit(1);
  }



  teste[10].x= 5;

  if ((teste = realloc(teste, 10000*sizeof(struct Tteste)))==NULL) {
      printf("erro de alocação");
      exit(1);
  }

  teste[9000].x=20;
  free(teste);

}

Exercício: Refazer o exemplo anterior para que a quantidade de memória a ser alocada pela tabela seja passada na linha de comando.

Aplicação de alocação dinâmica no programa de controle de senha

Substitua o vetor estático alocado para a mensagem personalizada a ser mostrada para o usuário, por um ponteiro para char inicialmente iniciado com NULL. Caso exista uma mensagem a ser mostrada, crie uma área de memória dinâmica de 256 caracteres para armazená-la. Na remoção da mensagem ou na remoção do usuário, liberar esta área de memória.

AULA 26

Objetivos

Exercício de lista ligada

#include <stdlib.h>
#include <stdio.h>

/*========================*/
/** OPERAÇÔES COM LISTA LIGADA ******/
/*========================*/

/*
   tipos e variáveis globais
*/

struct TProduto{
   int codigo;
  struct TProduto *next;
} *head, *tail;


/*
   adiciona item a cabeça da lista
   retorna 0 se tudo ok e -1 se erro
*/
int add_nodo_head(int codigo)
{

}

/*
   adiciona item ao final  lista
   retorna 0 se tudo ok e -1 se erro
*/
int add_nodo_tail(int codigo)
{
   struct TProduto *p =  malloc (sizeof(struct TProduto));
   if (!p)
        return -1;

   p->codigo = codigo;
   p->next = NULL;

   if (tail==NULL) {
      /* lista vazia */
      tail = head = p;
   }else {
     /*lista não vazia */
       tail->next = p;
       tail = p;
   }
   return 0;
}

/*
   imprimir lista 
*/

void print_list()
{

}
 
main()
{
  int i;

  head = tail = NULL;
  print_list ();
  for (i=0;i<5;i++)
     add_nodo_tail(i);

  print_list ();
}

Lição para casa

  • Implementar a função add_node_head()
  • Implementar a função print_list
  • Implementar a função delete_node(int codigo)

Aula 27 - 25/02/2013

Objetivos

  • Estruturas com Unions

http://www.ead.cpdee.ufmg.br/cursos/C/c.html

Exercício: Construa uma union para representar um número complexo.
  • Renomeando tipos com typedef

http://www.ead.cpdee.ufmg.br/cursos/C/c.html

  • Criando variáveis com tipos enumerados.

http://www.ead.cpdee.ufmg.br/cursos/C/c.html

  • Discutir o encaminhamento do trabalho
  • Uso do IDE eclipse
  • Refazer a questão 1 da avaliação usando ponteiros.

Observação sobre ponteiros x arrays

struct {
  char alfa[15];
  int code1;
  int code2;
  int code3;
} tabela[10],*p;

int i;

main()
{ 
  p=tabela;
  i=5;

 /* PARTE 1 */
 tabela[i].code1 = 1;
 tabela[i].code2 = 1;
 tabela[i].code3 = 1;
 /* ASSEMBLY CORRESPONDENTE DA PARTE 1
	movl	i, %eax
	leal	0(,%eax,4), %edx
	leal	0(,%edx,8), %eax
	subl	%edx, %eax
	movl	$1, tabela+16(%eax)
	movl	i, %eax
	leal	0(,%eax,4), %edx
	leal	0(,%edx,8), %eax
	subl	%edx, %eax
	movl	$1, tabela+20(%eax)
	movl	i, %eax
	leal	0(,%eax,4), %edx
	leal	0(,%edx,8), %eax
	subl	%edx, %eax
	movl	$1, tabela+24(%eax)
 */

 /* PARTE 2 */
 p=p+5;
 p->code1 = 1;
 p->code2 = 1;
 p->code3 = 1;
 /* ASSEMBLY CORRESPONDENTE DA PARTE 2
	movl	p, %eax
	addl	$140, %eax
	movl	%eax, p
	movl	p, %eax
	movl	$1, 16(%eax)
	movl	p, %eax
	movl	$1, 20(%eax)
	movl	p, %eax
	movl	$1, 24(%eax)
 */ 

 /* PARTE 3 */
 p[i].code1 = 1; 
 p[i].code2 = 1; 
 p[i].code3 = 1; 
 /* ASSEMBLY CORRESPONDENTE DA PARTE 3
	movl	p, %edx
	movl	i, %eax
	sall	$2, %eax
	leal	0(,%eax,8), %ecx
	movl	%ecx, %ebx
	subl	%eax, %ebx
	movl	%ebx, %eax
	leal	(%edx,%eax), %eax
	movl	$1, 16(%eax)
	movl	p, %edx
	movl	i, %eax
	sall	$2, %eax
	leal	0(,%eax,8), %ecx
	movl	%ecx, %ebx
	subl	%eax, %ebx
	movl	%ebx, %eax
	leal	(%edx,%eax), %eax
	movl	$1, 20(%eax)
	movl	p, %edx
	movl	i, %eax
	sall	$2, %eax
	leal	0(,%eax,8), %ecx
	movl	%ecx, %ebx
	subl	%eax, %ebx
	movl	%ebx, %eax
	leal	(%edx,%eax), %eax
	movl	$1, 24(%eax)
 */ 
}

AULA 28 - Dia 28/02/2013

Tese do Gustavo

AULA 29 - Dia 4/3/2013

Objetivos

  • Tratamento de arquivos no C
  • O sistema de arquivos no Linux
  • O acesso com funções de alto nível

O sistema de arquivos no Linux

O Arquivo

Um arquivo é um conjunto de dados que pode ser referenciado por um nome e pode ter outros atributos tais como: permissão para leitura e escrita, data da criação, data da última modificação etc.

Note que um arquivo pode conter dados (um relatório, por exemplo), um programa C, um programa executável, música, fotos etc. Seja qual for a natureza dos dados o armazenamento será na forma de bits.

Normalmente, arquivos são armazenados em memórias secundárias, tais como CD, hard disk etc, mas eles podem se armazenados na memória principal também (RAM, FLASH).

Quanto a forma como os dados são armazenados, podemos dizer que os arrquivos são binários ou texto. Qualquer um deles é binário mas o arquivo texto possui bytes que representam código ASCII.

Sistema de Arquivos

Um sistema tal como o Linux possui milhares de arquivos. Para que arquivos possam ser acessados e armazenados de forma consistente, eles são organizados em um sistema de arquivos.

Tipicamente, um sistema de arquivos ocupa uma área de um disco (ou mídia de armazenamento). Nesta área ficam armazenados blocos de armazenamento dos dados dos arquivos e também as estruturas chamadas de inodes.

Um inode é um estrutura que possui as propriedades do arquivo e ponteiros para os blocos que contém os dados do arquivo. Tipicamente um sistema de arquivos possui uma lista de inodes que permite "indexar" cada um dos arquivos do sistema de arquivos.

Existem vários formatos de sistema de arquivos, dependendo do sistema operacional. No linux os formatos mais conhecidos são: ext2, ext3, ext4 etc.

Um sistema de arquivos normalmente possui uma estrutura de dados inicial chamada de superbloco. O superbloco traz informações sobre o tamanho de blocos do sistema, o início da lista de inodes (/), etc.

+-------------+-----------------+------------------------+
| superbloco  | lista de inodes | blocos dos arquivos    |
+-------------+-----------------+------------------------+

Diretórios

São arquivos especiais que contém basicamente uma lista contendo nome e inode correspondente dos arquivos que o diretório contém.

Em um sistema de arquivos, o diretório / é o diretório raiz do sistema, a partir do qual pode-se encontrar todos os demais arquivos.

Referência a um arquivo

A localização de um arquivo pode ser realizada pela referência absoluta, ou seja, desde o diretório / do sistema:

cat /etc/passwd

O comando cat tem por objetivo mostrar no terminal o conteúdo do arquivo passwd. Para que este arquivo seja encontrado na árvore de diretórios, deve-se fornecer a referência absoluta, desde o diretório /

Uma alternativa de acesso, é o uso da referência relativa ao diretório de trabalho. O conceito de diretório de trabalho é criado pelo interpretador de comandos (shell). Desta forma pode-se por exemplo fazer o comando:

cat passwd

O sistema procurará o arquivo passwd dentro do diretório de trabalho, que é armazenado pelo shell. Neste caso, o diretório de trabalho deveria ser /etc

cd /etc
cat passwd

No Linux/Unix tudo é arquivo

No Linux, qualquer referência/acesso ao hardware/dispositivos é realizada na forma de acesso a arquivo. Ver arquivos no diretório de dispositivos:

 ls -l /dev

Como consequência, a partir de um programa em C, é possível abrir e escrever/ler em um dispositivo (desde que se tenha autorização).

Acessando arquivos a partir de programas C

Acesso a arquivos: funções de baixo e alto nível

Em sistemas do porte do Linux e Windows, por questões de segurança e controle, todo acesso a arquivo é realizado através de código do sistema operacional. Desta forma, um programa que deseja acessar um arquivo deve gerar uma CHAMADA AO SISTEMA. O Linux (assim como outros sistemas) possui uma API (Application Programming Interface) bem definida, na forma de um conjunto de chamadas que permitem realizar uma série de funcionalidades (serviços) para os processos(programas em execução).

Um programa em C realiza uma CHAMADA AO SISTEMA com auxílio de funções da biblioteca C (a glib no caso do Linux). Duas categorias de funções para acesso a arquivo são disponibilizadas (ver detalhes aqui):

  • Funções de baixo nível: acessam diretamente o sistema;
  • Funções de alto nível: as funções intermediam o acesso, criando buffers no espaço de endereçamento do processo. As funções de baixo nível são invocadas para ler e escrever dados no buffer.

Acesso através de funções de alto nível

O acesso em alto nível é realizado usando uma estrutura do tipo FILE definida no stdio.h. Todo acesso passa inicialmente por abrir o arquivo (função fopen), ler/escrever (várias funções, tipo fread(), fwrite()) e fechar o arquivo (fclose()).


Exemplo 1: Escrevendo e lendo um arquivo texto de forma formatada

Nos exemplos que se seguem, serão usadas as funções fprintf() e fscanf() para leitura e escrita. A forma é similar ao printf e scanf, a não ser pelo fato de que a escrita e leitura é realizada no arquivo indicado por p_arq

#include <stdio.h>

void main()
{
  FILE *p_arq;
  int i;
  int res;

  if ((p_arq=fopen("IFSC.txt", "w")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return;
  }

  for (i = 0; i<10;i++) {
  /* A funcao fprintf devolve o número de bytes gravados  ou EOF se houve erro na gravação */
      if((res = fprintf(p_arq,"Linha %d\n",i))==EOF) {  					  	    
	         printf("Erro\n");
			   break;
      }
  }
  fclose(p_arq);
}
Note que se o arquivo IFSC.txt não existir, ele será criado.

Faça:

cat IFSC.txt
#include <stdio.h>

void main()
{
  FILE *p_arq;
  int i,j;
  int res;
  char buff[100];

  if ((p_arq=fopen("IFSC.txt", "r")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return;
  }

  for (i = 0; i<10;i++) {
      if((res = fscanf(p_arq,"%s%d\n",buff,&j))==EOF) {  					  	    
	         printf("Fim de leitura\n");
			   break;
      }
      printf("%s %d\n",buff,i);
  }
  fclose(p_arq);
}

Note que o fscanf se comporta de forma similar ao scanf.

Por default, scanf pula todos espaços embranco (e também tabs/nelines).
  • Exercícios
  1. Implementar uma função que soma duas matrizes fornecidas em dois arquivos texto separados: MatA.dat e MatB.dat. Colocar o resultado em um arquivo chamado MatC.dat.

Exemplo2

Neste exemplo usaremos as funções fgetc e fputc para ler e cescrever caracteres ASCII nos arquivos. Seja um programa que conta o número de ocorrências do caracter 'a' em um arquivo e reescreve o arquivo sem os "as":

#include <stdio.h>
#include <stdlib.h>
main()
{
   FILE *fp_fonte,*fp_destino;
   int x,cont=0;
                   
   if((fp_fonte=fopen("teste.dat","rt")) == NULL) {
		puts("Não conseguiu abrir o arquivo\n");
		exit(1);
   }
   if((fp_destino=fopen("dest.dat","wt")) == NULL){
		puts("Não conseguiu abrir o arquivo\n");
		exit(1);
   }

   /* note que fgetc retorna um inteiro contendo o ASCII lido */
   while ((x=fgetc(fp_fonte)) != EOF){
      if(x=='a')
         cont++;
      else
	 if((x=fputc(x,fp_destino))==EOF) {
		puts("Não conseguiu abrir o arquivo\n");
		exit(1);
	 }	
   }	
   printf("ocorrências de a = %d\n", cont);
   fclose(fp_fonte);
   fclose(fp_destino);
}

Exemplo 3

Neste exemplo vamos explorar o modo de abertura do arquivo para fazer um append (escrever no final do arquivo).

#include <time.h>
#include <stdio.h>
 
int main(void)
{
   time_t ltime;
   FILE *fp;
   int num;

   time(&ltime); 

   if ((fp=fopen("DATA.txt", "a")) == NULL) {
       printf("Problemas na abertura do arquivo\n");
       return;
   }
   if ((num = fputs( ctime(&ltime), fp )) != EOF ) {
       fclose(fp);
   } else {
       printf("Erro na escrita do arquivo!\n");
   }
}

Execute o programa da forma (suponha que se chame LOG_tempo:

 log_tempo
 cat DATA.txt
 log_tempo
 cat DATA.txt
 log_tempo
 cat DATA.txt  
 
  • Exercício: Substitua o modo de append por uma simples escrita (w). Reexecute o programa conforme especificado anteriormente.

Exemplo 4

Neste exemplo vamos escrever e ler uma struct (com dados binários) usando fwrite e fread.


#include <stdio.h>
#include <stdlib.h>

struct TNum {
  float x;
  float y;
} Num = {  1.5,
           2.6, 
         };

main()
{
   FILE *fp;
   int num_itens;

   fp = fopen("data.bin", "wb");
   num_itens = fwrite((char*)&Num, sizeof(struct TNum), 1, fp);
   fclose(fp);

   Num.x =0;
   Num.y = 0;

   fp = fopen("data.bin", "r");
   num_itens = fread((char*)&Num, sizeof(struct TNum), 1, fp);

   printf("Valor de x = %f\n", Num.x);
   fclose(fp);

}

Referências

Material IBM


AULA 30 - Dia 7/3/2013

Verificação dos projetos em andamento.

AULA 31 - Dia 11/3/2013

Objetivos

  • posicionamento de cursor em arquivos
  • Modificador de acesso const
  • Classes de armazenamento

Ainda funções de acesso a arquivos

A localização corrente do acesso a um arquivo pode ser modificada antes de uma leitura ou escrita. Tipicamente, quando se abre uma arquivo para leitura/escrita, o "cursor" de acesso é 0, ou seja início do arquivo. Se o arquivo for aberto em modo append, o "cursor" é posicionado no final. A cada acesso (leitura ou esrita), este cursor é incrementado conforme o número de dados lidos ou escritos.

É possível entretanto modificar a posição deste cursor, tipicamente com as funções fseek() e rewind(). (ver http://www.gnu.org/software/libc/manual/html_node/File-Positioning.html#File-Positioning)

Considere o programa abaixo que escreve um vetor de 100 inteiros em um arquivo.

#include <stdio.h>

int x[100];

void main()
{
  FILE *fp;

  fp = fopen("teste.dat","w");
  x[0]=32;
  x[90]=11;
  fwrite(x, sizeof(int),100,fp);
  fclose(fp);
}

Note que o vetor, sendo uma variável global não inicialiazada, será zerado pelo procedimento de startup do programa. Entretanto, a posição 90 recebe um valor (11).

Agora observe o programa seguinte que lê especificamente a posição 90 e na sequência restabelece o cursor no início. Note que o fread lê somente um inteiro na posição corrente do arquivo. A posição corrente foi determinada por fseek e depois retornada para o início com rewind.


#include <stdio.h>

int y;

void main()
{
  FILE *fp;

  fp = fopen("teste.dat","r");
  fseek(fp, 90*sizeof(int), SEEK_CUR);
  fread(&y, sizeof(int),1,fp);
  printf("y=%d\n", y);
  rewind(fp);
  fread(&y, sizeof(int),1,fp);
  printf("y=%d\n", y);
  fclose(fp);
}

Execício

Considero o programa abaixo. Ele deve acessar uma tabela que se encontra em um arquivo binário. Cada item da tabela se apresenta conforme o registro TRegistro. Implemente a função LerTab e crie um programa para escrever uma tabela iniciada com um tabela de 5 registros a fim de testar a função implementada.

#include <stdio.h>

struct TRegistro {
  char nome[30];
  int idade; 
} Registro, *pAux;

struct Tregistro *LerTab()
{
}

void main()
{
  pAux=LerTab(3);
  if (pAux!=0) {
      printf("Nome lido %s\n", pAux->nome);
  }
}

Modificador de Acesso const

Este modificador aplica-se ao acesso de variáveis. Uma variável declarada com const não pode ser modificada ao longo programa (ou escopo). Muito usada em passagem de parâmetros por referência. Garante que o parâmetro não será modificado.

voi alfa (const float *p)
{
  *p=11.5;  /* acesso não permitido - o compilador gera erro */
}

void main()
{
  float x=2.4;

  alfa(&x)
}

Note que no exemplo anterior, o compilador vai gerar um erro no acesso a x através do ponteiro p.

Classes de Armazenamento

  • auto
  • extern
  • static
  • register

Ver link ufmg.

Recursividade com Funções C

Observe a função somatoria. Ela se utiliza de recursividade para solução do problema da somatória de um número.

#include <stdio.h>

int somatoria(int num)
{
  int sum;
  if(num>1)
     return (num+somatoria(num-1));
  else
     return 1;
}
void main()
{
  printf("somatoria = %d\n", somatoria(5));
}

Exercíco: Implementar de forma recursiva o cálculo do fatorial de um número.


AULA 32 - Dia 14/3/2013

Objetivos

  • Funções de acesso a arquivos em baixo nível
  • Acesso a múltiplos arquivos usando select()

Funções de acesso a arquivos em baixo nível.

Alguns conceitos iniciais

No acesso a baixo nível, as funções da biblioteca basicamente chamam o sistema operacional (via SYSTEM CALLs) sem qualquer intervenção. Qualquer arquivo será referenciado por um número inteiro que reflete o índice em uma TABELA DE ARQUIVOS abertos, mantida pelo sistema operacional para cada processo execução.

Tipicamente, quando um processo é iniciado ele já possui a tabela de arquivos abertos populada nas três primeiros posições. A posição 0 corresponde a entrada padrão (normalmente será o terminal associado), a posição 1 (saída padrão, também o terminal) e a saída de erro padrão (também o terminal).

Exemplo: O comando ls é implementado na forma de um programa que quando se executa se torna um processo no sistema com os arquivos de entrada e saída padrão abertos. Faça:

ls -l

A listagem sai no arquivo de saída padrão. Você poderia redirecionar esta saída:

 ls -l > lista.txt

Se você tentar listar algo que não existe é um erro. A mensagem de erro é remetida para saída de erro padrão.

 ls -l /gdhfgjdkhsjdf

Não adianta redirecionar a saída padrão que a mensagem continua a aparecer.

 ls -l /fsadasdfsadf 1> lista.txt

Note que o 1 no redirecionador 1> pode ser omitido.

Mas é possível redirecionar a saída de erros também:

 ls -l /fdsdfgsdfgfsdg 2> erro.txt

Abra um outro terminal e verifique com o comando ps, o terminal virtual associado. Suponha que seja /dev/pts/1

Redirecione esta saída para p terminal virtual do outro terminal:

 ls -l > /dev/pts/1

Funções de acesso

O acesso é basicamente realizado por funções open(), close(), read() e write() (Ver aqui o manual da glib).


Escrevendo na saída padrão e de erro usando write()

#include <unistd.h>
#include <stdlib.h>
int main()
{
  write(1, "Escrevendo dados na saída padrão\n", 35);
  write(2, "Agora escrevendo na saída de erros\n",37);
  exit(0);
}

Agora execute testando as redireções:

./esc
./esc 1> lixo.dat
./esc 1> lixo.dat 2> erro.dat

Vamos fazer um exemplo de cópia de arquivos passados como parâmetro:

#include  <unistd.h>
#include  <sys/stat.h>
#include  <fcntl.h>
#include  <stdlib.h>
int main()
{
    char c;
    int in, out;
    in = open("file.in", O_RDONLY);
    out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
    while(read(in,&c,1) == 1)
         write(out,&c,1);
    exit(0);
}

Exercício 1: Implementar um programa de cópia similar ao que existe no linux. Capture o nome dos arquivos da linha de comando e inclua testes de erro na abertura de arquivo.

Exercício 2: Modificar o exercício anterior para realizar múltiplas cópias (um arquivo para vários).

Acesso a múltiplos arquivos usando select()

Por vezes é necessário esperar dados de várias fontes diferentes. A função select permite testar a existência de dados em uma dada fonte de dados (arquivo). A deia geral é abrir os arquivos de interesse e colocar o descritor em conjunto do tipo fd_set. O programa ao executar o select sobre este conjunto é BLOQUEADO até que exista dados disponíveis em um dos arquivos. Uma macro FD_ISSET executada sobre o conjunto de descritores de arquivo permite detectar em qual arquivo foi alterado o status.

No exemplo a seguir, você deve fornecer para o programa quais terminais virtuais deseja escutar. O programa pergunta por um usuário em cada terminal. O usuário fornece seu nome e recebe em troca uma mensagem de boas vindas.

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/select.h>

char *MsgLogin="Entre com USERID\n";

int main(int argc, char *argv[])
{
    int fd;
    int fdmax;
    int i,j;
    size_t bytes_read;
    fd_set master_set, myset;
    char buffer[256]; 

    /* Limpa o conjunto de descritores */
    FD_ZERO(&master_set);

    /* abre cada arquivo passado na linha de comando e insere no conjunto master_set */
    for (i=1;i<argc;i++) {
            if ((fd=open(argv[i],O_RDWR))==-1) {
                perror("open");
                exit(1);
            }
            FD_SET(fd,&master_set);
            write (fd,MsgLogin,strlen(MsgLogin));
    }

    /* registra o maior descritor */ 
    fdmax=fd;

    for (;;) {
        myset=master_set; /* deve ser copiado sempre pois o conjunto é alterado */
        if (select(fdmax+1,&myset,NULL,NULL,NULL)==-1) {
            perror("select");
            exit(1);    
        }

        /* rastreia o descritor onde houve entrada de dados */
        for (i=0;i<=fdmax;i++) {
            if (FD_ISSET(i,&myset)) {
                if ((bytes_read=read(i,buffer,sizeof(buffer)))==-1) {
                    perror("read");
                    exit(1);
                }
                  
                if (bytes_read>0) {
                    write (i, "Bom Dia ", 8);
                    for (j=0;j<bytes_read;j++) { 
                        write(i, &buffer[j], 1);
                    }
                    buffer[j-1]=0; /* eliminar o Line feed e colocar NULL no final da string */
                    if (!strcmp("f", buffer))
                        goto final; /* ok um goto para polemizar... */
                    write (i,"\n",1);
                } 
            }
        }
     
    }
final:
    for (i=0;i<=fdmax;i++) {
       close(i);
    }    
    return 0;
}

Para executar o programa faã o seguinte:

  1. abra um terminal, e execute o comando ps no terminal. Verifique qual TTY está associado ao seu terminal, por exemplo pts/0
  2. abra outro terminal e também verifique o terminal com ps. Por exemplo, pts/1
  3. neste último terminal bloqueio o shell fazendo: while true; do sleep 3600; done </dev/null
  4. volte ao terminal original e execute o programa da forma:
 readmult /dev/pts/0 /dev/pts/1
  1. entre com um userid em cada terminal e verifique o resultado
  2. para finalizar tecle f como userid

AULA 20

Objetivos

  • operações com bits

Operações bit a bit disponíveis

Symbol Operator
& bitwise AND
l bitwise inclusive OR
^ bitwise exclusive OR
<< left shift
>> right shift
~ one's complement (unary)

Rotacionando a direita

#include <stdio.h>

main()
{
  unsigned char i;

  i=0x80;
  printf("i=%d ou em hex i=0x%x\n", i, i);
  i=i>>2; /* rotaciona dois bits a direita */
  printf("i=%d ou em hex i=0x%x\n", i, i);
}

Note que na memória o valor 0x80 aparece como:

 10000000

Rotacionando dois bits a esquerda este valor fica:

 00100000

Que corresponde ao valor 0x40


Rotacionando a esquerda

#include <stdio.h>

main()
{
  unsigned char i;

  i=0x14;
  printf("i=%d ou em hex i=0x%x\n", i, i);
  i=i<<3; /* rotaciona três bits a esquerda */
  printf("i=%d ou em hex i=0x%x\n", i, i);
}

Note que na memória o valor 0x80 aparece como:

 00010100

Rotacionando três bits a esquerda este valor fica:

 10100000

Que corresponde ao valor 0xA0

NOTE que uma rotação a direita corresponde em dividir o número por 2 enquanto uma rotação a esquerda corresponde a multiplicar o número por 2.

Operações lógicas bit a bit

Lembrar que :

bit a bit b a & b (a AND b)
0 0 0
0 1 0
1 0 0
1 1 1


bit a bit b a | b (a OR b)
0 0 0
0 1 1
1 0 1
1 1 1



#include <stdio.h>

main()
{
  unsigned char i;

  i=0xF2;
  printf("Operação E com a máscará 0x03 -> %x\n", i&0x03);
  printf("Operação OR com a máscará 0x03 -> %x\n", i|0x03);
  printf("Operação OR EXCLUSIVO com a máscará 0x03 -> %x\n", i^0x03);
  printf("Complementando i -> %x\n", (char)~i);
}

Note que as operações acima são realizadas em nível de bit e não conforme as operações lógicas realizadas sobre expressões.

Exercício:

Implementar uma função que retorna o valor da operação lógica bita a bit sobre os valores de um vetor de bytes como parâmetro. Veja esqueleto abaixo.

unsigned char oplog (unsigned char pVetor[], int tam, char operador)
{
}

main()
{
  unsigned char alfa[3] = { 0xFF,0xAA,0x00};
  unsigned char valor;

  valor = oplog(alfa, 3, '|'); /* calcula o OU bit a bit sobre o vetor */
  valor = oplog(alfa, 3, '&'); /* calcula o E bit a bit sobre o vetor */
  valor = oplog(alfa, 3, '^'); /* calcula o OU EXCLUSIVO bit a bit sobre o vetor */ 
}

AULA 21

AULA 22

AULA 23

AULA 24

AULA 25

AULA 26

AULA 27

AULA 28

AULA 29

AULA 30

AULA 31

AULA 32

AULA 33

AULA 34

AULA 35

#include <string.h>
 
int elimina_vogal(char cadeia[])
{
 int i, j, cont=0;
 char aux_cadeia[100];

 for(i=0,j=0;cadeia[i]!=0;i++) {
    	if (cadeia[i]=='a') {
	      cont++;
    	} else {
           aux_cadeia[j] = cadeia[i];
		 j++;
     }		
 }
 aux_cadeia[j]=0;
 strcpy (cadeia,aux_cadeia);
 return cont;
}
 
void main()
{
  char x[20]="casa";
  int num_vg;
 
  num_vg = elimina_vogal(x);
  printf("cadeia = %s e numero vogais eliminadas = %d\n", x, num_vg);
}


#include <stdio.h>

#include <stdio.h>

void comp_num(int a, int b)
{
   if (a == b)
	printf("parametros iguais\n");
}

main()
{
   int x = 2;
   comp_num(2,4);
   comp_num(2,2);
   comp_num(2,x);
}

int comp_num(int a, int b) {

  int aux;
  /* o codigo abaixo pode ser otimizado com if elseif */
  if (a == b)

aux = 0;

  if(a > b)
      aux = 1;
  if (a < b)
      aux = -1;
  return aux;

}

main() {

  int x;
  x = comp_num(2,4);
  printf ("Retorno igual a %d\n", x);
  x = comp_num(2,2);
  printf ("Retorno igual a %d\n", x);
  x = 2;
  x = comp_num(2,x);
  printf ("Retorno igual a %d\n", x);

}

</syntaxhighlight>outra solução seria

#include <stdio.h>

int comp_num(int a, int b)
{
   int aux;

   /* o codigo abaixo pode ser otimizado com if elseif */
   if (a == b)
	aux = 0;
   else {
        if(a > b)
            aux = 1;
        else
            aux = -1;
   }

   return aux;
}

main()
{
   int x;
   x = comp_num(2,4);
   printf ("Retorno igual a %d\n", x);
   x = comp_num(2,2);
   printf ("Retorno igual a %d\n", x);
   x = 2;
   x = comp_num(2,x);
   printf ("Retorno igual a %d\n", x);
}
#include <stdio.h>

void imprime_faixa(int a, int b)
{
   if (b>a) {
        while (a <=b)
		printf("%d ",a++);
        printf("\n");
   }   
}

main()
{
   imprime_faixa(1,3);
   imprime_faixa(2,7);
}


SOLUÇÔES:

1.Atravessar G
2.Retornar
3.Atravessar M
4.Retornar G
5.Atravessar R
6.Retornar
7.Atravessar G
1.ATR CP
2.RET P
3.ATR CC
4.RET C
5.ATR PP
6.RET PC
8.ATR PP
9.RET C
10.ATR CC
11.RET C
12.ATR CC


PROBLEMA: determinar as raízes da equação do segundo grau (Baskara)
DADOS DE ENTRADA: coeficientes reais A, B e C
DADOS DE SAÍDA: raízes X1-REAL,X1-IMG e X2-REAL, X2-IMAG 
VARÍAVEIS INTERMEDIÁRIAS: DELTA
COMENTÁRIO: DELTA pode ser positivo, 0 ou negativo. O valor de DELTA deve ser testado.
FLUXOGRAMA
  1. Construir um fluxograma para ler 3 números e calcular a média dos dois maiores números lidos;
  2. Construir um fluxograma para ler 3 números e calcular a média do maior e do menor número entre os 3 lidos.
  1. Refazer o exercício para ler 3 números e calcular a média dos dois maiores números lido usando variáveis auxiliares MAIOR e MENOR;
  2. Refazer o exercício 4 para calcular a média do maior e do menor número entre os 3 lidos usando variáveis auxiliareas MAIOR e MENOR;
  3. Construir um fluxograma para ler 6 números e calcular a média dos dois maiores números lidos. Faça um teste de mesa;


  1. Faça um algoritmo para fazer a divisão de dois números reais. Antes de dividí-los deve ser feito um teste de validade. Caso não seja possível dividi-los, deve ser mostrada uma mensagem de erro. Se for possível, deve-se mostrar o resultado da divisão;
  2. Fazer um algoritmo para computar a área e perímetro de um retângulo. Entrar com os dois lados;
  3. Fazer um programa para computar as áreas de um retângulo e de um círculo. O programa deve mostrar uma mensagem indicando qual figura possui maior área;


  1. Implementar uma função que recebe como parâmetros dois vetores de floats cujos tamanhos são passados também como parâmetros. A função deve retornar 1 se a média dos elementos do primeiro vetor for maior que o primeiro e 0 se for menor ou igual. Ver esqueleto da funçao:
    #include <stdio.h>
    
    int compara_media_vetores(float vet1[], int tam1, float vet2[], int tam2)
    {
      /* implementar aqui */
    }
    
    main()
    {
      float x1[5]={2.4,,6.7,8.5,9.6,1.5};
      float x2[4]={1.6,2.5,6.7,3.2};
      int res;
    
      /* exemplo de uso da função */
      res = compara_media_vetores(x1,5,x2,4);
      if (res==1)
         printf("media de x1 maior que média de x2\n");
      
    }
    
  1. Implementar uma função que recebe como parâmetros dois vetores de floats cujos tamanhos são passados também como parâmetros. A função deve retornar 1 se a média dos elementos do primeiro vetor for maior que a do segundo e 0 se for menor ou igual. Ver esqueleto da funçao:
    #include <stdio.h>
    
    int compara_media_vetores(float vet1[], int tam1, float vet2[], int tam2)
    {
      /* implementar aqui */
    }
    
    main()
    {
      float x1[5]={2.4,,6.7,8.5,9.6,1.5};
      float x2[4]={1.6,2.5,6.7,3.2};
      int res;
    
      /* exemplo de uso da função */
      res = compara_media_vetores(x1,5,x2,4);
      if (res==1)
         printf("media de x1 maior que média de x2\n");
      
    }
    
  1. Implementar um programa C (não precisa ser função) que lê valores (usando scanf) de uma matriz alfa de dimensão MxN de inteiros (onde M e N são dimensões que devem ser lidas). O programa deve mostrar (usando printf) quantas linhas desta matriz possuem valores entre 5 e 10 (inclusive). Exemplo:

A matriz: | 12 5 3 | | 1 4 17 | | 13 14 10 | Esta matriz possui 2 linhas com valores que estão entre 5 e 10.

  1. Implementar um programa C (não precisa ser função) que lê valores (usando scanf) de uma matriz alfa de dimensão MxN de inteiros (onde M e N são dimensões que devem ser lidas). O programa deve mostrar (usando printf) os índices (i,j) do maior elemento. Considere que não existirá elementos iguais e que a primeira linha e primeira coluna são representadas por (0,0). Exemplo:

A matriz: | 12 5 3 | | 1 16 45 | | 13 14 11 | O maior elemento está na posição (1,2).

  1. Implementar uma função que recebe um vetor de caracteres como parâmetro. Este vetor conterá uma string terminada em NULL. A função deve retornar o número de vogais 'a' que são precedidos pela letra b. Na função main apresentar um teste de uso da função
    int num_vog(char cadeia[])
    {
      /* implementar aqui */
      
    }
    main()
    {
     /* testar o uso da função aqui */
    }
    

Explicação adicional:Para a cadeia "abacate" a função deveria retornar 1.

  1. Implementar uma tabela global usando structs que permita representar as contas bancários de pessoas físicas. Cada registro deve conter nome, cpf, endereço e o saldo atual da conta. Contas podem estar negativas. Implementar uma função que permita calcular o total de dinheiro do banco em função do saldo das contas e uma função que retorna o saldo de um usuário dado o seu CPF.