Sistemas Operacionais e Introdução a Programação (diário 2009-2)

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

Índice

Sistemas Operacionais

27/07: NÃO HOUVE

29/07: NÃO HOUVE

03/08: NÃO HOUVE

05/08: NÃO HOUVE

10/08: História dos Sistemas Operacionais e das Linguagens de Programação

12/08: Usuários e Grupos

Atividades

  • Quem pode executar o comando /usr/bin/firefox? O usuário aluno pode modificar tais permissões?
  • Para o comando acima mencionado, informe quais as permissões e propriedades necessárias para permitir que apenas usuários do grupo "internet" possam executá-lo.
  • Se um dado arquivo tiver as permissões 000 (formato octal de representação), mesmo assim o usuário root tem permissão de leitura. Por quê?
  • A partir do diretório raiz (/), quais são todos os diretórios em que o usuário aluno pode criar arquivos e subdiretórios? Para cada caso, informe que propriedades e permissões garantem tal direito.
  • Um certo programa, chamado firefox-3.6, foi descarregado da Internet pelo usuário aluno, mas ele não pode ser executado. Que permissões garantem que ele possa ser executado por tal usuário?
  • Qual a lista completa de usuário e grupos do sistema operacional Linux? A resposta está em dois arquivos que utilizamos como exemplo... eles estão no diretório /etc.

17/08: Processos

  • Tópicos: árvore de processos, escalonamento de processos e seus estados.
  • Referência externa: Cap. 2 do livro Sistemas Operacionais Modernos - particularmente os tópicos vistos em aula.

19/08: Sistemas de Arquivos

  • Tópicos: arquivos e diretórios, hard link e soft link, sistema de arquivos corrente e pontos de montagem.
  • Referência externa:

Atividades

  • Quantos processos chamados bash estão rodando no sistema?
  • Se todos os processos bash forem encerrados, todas as sessões de usuário (logins) também o serão? Por quê?
  • O comando top pode mostrar todos os processos de um certo usuário, como por exemplo aluno? O comando ps também? Por que um processo precisa ter um usuário associado a ele?
  • É possível identificar, através do comando top, quais processos estão em estado bloqueado?
  • É possível criar um soft link de outro soft link? Eles conterão o mesmo conteúdo?
  • Se o conteúdo de um certo arquivo é acessado pelo seu soft link, por que este arquivo (original) tem a data de modificação alterada?
  • Um arquivo pode ter, como conteúdo, um sistema de arquivos? Explique como isso é possível, e qual o processo de ler este conteúdo.
  • Crie um soft link com o nome /tmp/diretorioNovo/arquivoNovo para o arquivo /etc/passwd. Em caso de dúvida, consulte a lista de comandos.
  • É possível bloquear o uso do comando ls - armazenado no diretório /bin - apenas retirando as permissões de execução? Por quê? Exemplifique com o usuário aluno.

24/08: Linguagens de Programação

  • Tópicos: linguagens de programação, execução de programas.

26/08: Revisão de Sistemas Operacionais

Assumindo como cenário o sistema operacional GNU/Linux para auxiliar nas respostas.

  • Por que as permissões em arquivos são tão importantes?

Pelo fato de o sistema prover um ambiente multi-usuário, é preciso que haja limites para cada um dos usuários desse sistema, não só para controlar o acesso a arquivos pessoais mas também a informação referente ao próprio S.O. - acesso ao arquivo de senhas, por exemplo.

Comandos relacionados: ls.

  • Como se dá o controle de acesso a arquivos abertos por processos?

As permissões a arquivos, sejam eles executáveis ou não, se dão por uma lista de controle de acesso: usuário (dono do arquivo), grupo e outros. Se o usuário possui a permissão por ele desejada (leitura, escrita ou execução), terá o respectivo acesso ao arquivo. Isso acontecerá para um executável (permissão de execução), por exemplo. E como um processo está sempre associado a um usuário (ou por padrão outorgados ao root), ainda assim tem-se a relação usuário-permissões, as quais serão utilizadas para abrir arquivos. Exemplo: o comando cat que lerá o arquivo /etc/passwd

Comandos relacionados: lsof, fuser.

  • Todo processo tem um dono? Por quê?

Todo arquivo tem um dono, e todo processo também. Parte desta resposta já fora apresentada na questão anterior, mas cabe complementar: é preciso estabelecer limites para os usuários e seus processos e arquivos, a fim de que não haja abuso ou mesmo a inoperância do sistema como um todo por causa de falhas humanas e/ou má fé.

Comandos relacionados: ps, top.

  • Quantos e quais são os estados de um processo? O que um processo precisa fazer para entrar para o estado bloqueado?

<graphviz> digraph Estados { Criação Apto Executando [shape=record] Bloqueado Destruição

Criação -> Apto [label="Admissão"] Apto -> Executando [label="Seleção"] Executando -> Apto [label="Int. por tempo/voluntária"] Executando -> Bloqueado [label="Sincr. ou req. de E/S"] Bloqueado -> Apto [label="Ocorrência de evento"] Executando -> Destruição [label="Término"] } </graphviz>

São cinco os estados, conforme o material recomendado pelo Prof. Semprebom. No caso, processos que estão no estado executando podem fazer uso de I/O bound, o que significa que haverá mais E/S e menos processamento. Por este motivo, é interessante que este processo fique aguardando o seu recurso (via E/S), enquanto que outros processos, que fazem mais CPU bound, aproveitem melhor o recurso de processamento.

Comandos relacionados: ps, top.

  • Qual a relação entre escalonamento preemptivo e prioridade de processos?

Associado ao escalonamento preemptivo, faz-se o uso de prioridades para garantir que os processos sejam atendidos conforme a sua demanda: processos referentes a aplicações de tempo real, por exemplo, requerem mais processamento, portanto uma maior prioridade. Contudo, é preciso, ao longo do tempo, manipular tais prioridades, garantindo que os todos os processos rodem - caso contrário aqueles com maior prioridade sempre voltarão ao começo da fila. O envelhecimento de prioridades, portanto, é uma forma de equilibrar o uso do processador de forma mais eficiente e justa. Lembrando que no escalonamento preemptivo o processador pode parar um processo quando finda seu tempo (timeslice) - a figura da questão anterior ilustra o caso (interrupção por tempo).

Comandos relacionados: top.

  • É possível haver vários sistemas de arquivos montados ao mesmo tempo, de forma hierárquica? Por quê?

Nada impede que haja vários sistemas de arquivos montados, desde que eles estejam em formato compreensível pelo kernel - para depois serem abstraídos às aplicações (veja o Virtual File System do Linux). Como o sistema de arquivos corrente, global, já é hierárquico (árvore de diretórios), a montagem sequencial dos vários sistemas se dará de forma natural.

Comandos relacionados: mount, umount.

  • O que é swap e qual a sua função?

Swap é um tipo de partição da memória secundária, formatada para servir como extensão da memória principal. Juntas, elas formam a memória virtual - que será utilizada pelas aplicações. Para manipular as partições de troca, ou swap, veja o Guia Foca Linux. Cabe destacar que este tipo de memória é mais barata e lenta.

Comandos relacionados: free.

  • O que é um interpretador de comandos? Cite um exemplo e um cenário onde ele apareça.

O interpretador de comandos, ou shell, é uma das aplicações voltadas para o usuário. Esta, em particular, é um terminal em formato texto para interpretador ordens do usuários: os comandos. Há vários interpretadores, onde cada um deles implementa uma linguagem para facilitar a entrada de dados ao computador. O bash é talvez o interpretador mais utilizado nos Linux "de usuário": Ubuntu, Mandriva, etc.

Comandos relacionados: bash.

  • O que é kernel? Por que ele é tão importante? Cite pelo menos 3 funções dele.

O kernel é nada mais que o núcleo do sistema operacional, sendo responsável pela maioria das funções ligadas ao S.O. Dentre as suas funções, podemos destacar: gerência de memória, gerência de processos, controle do sistema de arquivos (E/S).

  • Qual a diferença entre arquivo executável e processo? Processos consomem recursos como processamento, memória e barramento: comente e mostre em uma captura de tela.

O arquivo executável é um arquivo regular, cujo conteúdo é um programa - em código de máquina ou passível de interpretação por outro programa (Python, PHP, etc.). Já o processo é um programa carregado em memória, consumindo recursos da máquina (processamento, memória, E/S) para realizar as suas instruções. Portanto, um arquivo executável será carregado em memória para virar um processo (cuidado com código interpretado: o processo será, na verdade, o seu interpretador, e associado a ele o código a interpretar).

Comandos relacionados: ps, top, vmstat.

31/08: Preparativos para a prova

  • Atividades: o que este faz este script? Consegue "executá-lo" mentalmente?
# Etapa 1 - Entrar no diretório "/home/aluno":
cd /
cd home
cd aluno
echo "Estou no diretório /home/aluno."
sleep 1
# Etapa 2 - Criar o diretório "q2":
mkdir q2
echo "Criei o subdiretório q2."
sleep 1
# Etapa 3 - Criar os subdiretórios "a", "b", "c" e "d" sob "q2":
cd q2	
mkdir a
mkdir b
mkdir c
mkdir d
echo "Criei os subdiretório a, b, c, e d."
# Etapa 4 - Criar o arquivo "h" sob "q2" com o conteúdo "xyz":
touch h
echo "xyz" > h
# Etapa 5 - Criar os diretórios "e" e "f" sob "a":
cd a
mkdir e
mkdir f
# Etapa 6 - Criar o arquivo "g" sob "d" com o conteúdo "123":
cd ../
cd d
echo "123" > g
  • Dada a seguinte estrutura de diretórios e arquivos:

<graphviz> digraph Dirs { antialias = true splines = true

e [shape=record] i [shape=record] k [shape=record] f [shape=record,label="<0>f|<1>123456"] j [shape=record,label="<0>j|<1>654321"]

a -> b a -> c a -> d a -> e e -> i [label="link simbólico",arrowhead=onormal] a -> f d -> g g -> h h -> i i -> f [label="link simbólico",arrowhead=onormal] c -> h [label="link simbólico",arrowhead=onormal] g -> j a -> k k -> j [label="link simbólico",arrowhead=onormal] b -> l l -> d [label="link simbólico",arrowhead=onormal] } </graphviz>

Nomenclatura:

  1. Círculo ou elipse: diretório
  2. Quadrado ou retângulo: arquivo
  3. Flecha com ponta escura: hard link
  4. Flecha com ponta vazada (clara): soft link

Perguntas referentes ao grafo anterior:

  1. Os arquivos "e" e "i" contêm o mesmo conteúdo?
  2. Os diretórios b e d contêm o mesmo conteúdo?
  3. Como criar esta estrutura de diretórios e arquivos? Apresente a sequência de passos ou operações; em seguida, a sequência de comandos de shell (interpretador de comandos).
  • Perguntas que "podem" cair na prova:
    • No artigo do UNIX, fala-se no diretório "..". O que ele significa?
    • A linguagem de programação C ajudou na disseminação do sistema UNIX. Comente.
    • Qual a relação entre usuário, uid e /gid?
    • Qualquer usuário pode listar os processos do sistema usando o comando ps? Por quê? Em que parte do sistema esta permissão está dita? Como garantir que apenas o usuário joao possa fazê-lo? Assuma que o usuário joao já exista no sistema; além disso, ignore o caso do usuário root, que ignora tais permissões.
    • Salve o seu histórico de comandos em um arquivo texto.
    • Apresente, na tela, apenas os nomes dos usuários cadastrados no sistema EXCETO o root. Dicas: veja o arquivo /etc/passwd; além disso, os comandos grep e cut, que podem auxiliar na tarefa.
    • Um estado bloqueado pode voltar a ser executado? O que o impede e como liberá-lo?
    • Um pid está, de alguma forma, associado a um uid. Comente.
    • Um arquivo texto pode ser interpretado? Como?
    • O que é E/S? Exemplifique-o com um comando.

02/09: Prova

1a. Aula: "Cola"

  • Usuários
    • Cadastro de usuários e grupos
    • Grupos primário e secundários
    • uid e gid
  • Processos
    • pid
    • Estados
  • Sistemas de arquivos
    • Diretórios
      • Diretórios importantes
      • Comandos de navegação
    • Arquivos
      • Criação e modificação
      • Permissões e propriedades
      • Comandos de manipulação
    • Links simbólicos
      • Criação de links

2a. e 3a. Aulas: Prova teórica

Lógica de Programação

09/09: Introdução

  • Problema 1: quais os arquivos, do sistema de arquivos corrente, com tamanho zero?
  1. Sequência de passos:
    1. Listar todos os arquivos.
    2. Verificar o tamanho de cada arquivo acima listado.
    3. Selecionar apenas os arquivos com tamanho zero.
  2. Proposta de solução em shell script - em 3 etapas conforme a sequência acima descrita:
# find / | xargs du | grep ^0

<graphviz> digraph Passos { rankdir=LR find [shape=record,label="<0>1º: Listar todos|<1>find /"] xargs [shape=record,label="<0>2º: Verificar o tamanho de cada|<1>xargs du"] grep [shape=record,label="<0>3º: Selecionar baseado no padrão|<1>grep ^0"] find:1 -> xargs:1 -> grep:1 [label="|"] } </graphviz>

  • Problema 2: reconhecimento de firma. Proposta de solução (em formato textual):
  1. Distribuir senhas sequencialmente.
  2. Atendimento por ordem sequencial das senhas. Se for preferencial, anteder; caso contrário, atender o próximo número.
  3. Recebimento dos documentos.
  4. Verifica se o assinante está presente. Se estiver, tudo bem; caso contrário, verifica se o documento pode ser reconhecido por semelhança.
  5. Verifica a existência de uma ficha relacionada ao nome do documento.
  6. Se existir,pegar a ficha; caso contrário, e se a pessoa estiver presente, cria uma ficha.
  7. Assinatura de um termo de comparecimento com data.
  8. Comparação das assinaturas dos documentos e do termo.
  9. Se forem iguais, reconhece, aplica o selo e leva ao tabelião assinar; caso contrário negar o reconhecimento.
  10. Recebimento do pagamento.
  11. Devolução dos documentos.
  12. Armazenamento da ficha - de volta ao fichário.
  13. Fim do reconhecimento.
  • Material de apoio: Lógica de Programação, de Paulo Sérgio de Moraes, páginas 4 a 7 - consulte as referências bibliográficas da disciplina.

14/09: Desenvolvendo algoritmos

  • Problema 1: desenhar um triângulo equilátero, um isósceles e outro escaleno.
  • Problema 2: desenhar um pentagrama (estrela de cinco pontas)
  1. Leitura do enunciado
  2. Abstração do problema: o pentagrama é um processo repetitivo (5x):
    1. Desenhar uma linha representando um lado da figura
    2. Definir o ângulo interno para traçar a próxima linha
  3. Definição da sequência de passos:
    1. Desenhar uma linha de tamanho X
    2. Alterar para 144 graus para um lado, no caso direito
    3. Desenhar uma linha de tamanho X
    4. Alterar para 144 graus para um lado, no caso direito
    5. Desenhar uma linha de tamanho X
    6. Alterar para 144 graus para um lado, no caso direito
    7. Desenhar uma linha de tamanho X
  4. A proposta de solução utiliza a linguagem Logo para apresentação do resultado. Foi utilizado o programa Kturtle para esboçar a figura final.
reset
turnleft 54
forward 100
turnright 144
forward 100
turnright 144
forward 100
turnright 144
forward 100
turnright 144
forward 100
  • Material de apoio: páginas 8 a 11 da apostila de Lógica de Programação.

16/09: Pseudocódigo e diagrama de blocos

  • Tópicos: pseudocódigo, regras de construção, etapas de um programa, diagramas de bloco.
  • Problema 1: o clássico problema da troca de uma lâmpada.
  1. Abstração do problema: considere a lâmpada do tipo incandescente instalada no teto da sala.
  2. Pseudocódigo: ações imperativas em linguagem natural (Português).
    1. Pegue uma escada.
    2. Abra a escada embaixo da lâmpada velha.
    3. Suba na escada.
    4. Retire a lâmpada velha.
    5. Desça da escada.
    6. Descarte a lâmpada velha.
    7. Pegue uma lâmpada nova.
    8. Suba na escada.
    9. Inslate a lâmpada nova no bocal.
    10. Desça da escada.
    11. Feche a escada.
    12. Guarde a escada.

O diagrama de bloco do pseudocódigo anterior será bastante simples: uma sequência única de passos a serem executados. <graphviz> digraph Lampada { Início "Pegue uma escada." [shape=record] "Abra a escada embaixo da lâmpada velha." [shape=record] "Suba na escada." [shape=record] "Retire a lâmpada velha." [shape=record] "Desça da escada." [shape=record] "Descarte a lâmpada velha." [shape=record] "Pegue uma lâmpada nova." [shape=record] "Suba novamente na escada." [shape=record] "Instale a lâmpada nova no bocal." [shape=record] "Desça novamente da escada." [shape=record] "Feche a escada." [shape=record] "Guarde a escada." [shape=record] Fim

Início -> "Pegue uma escada."-> "Abra a escada embaixo da lâmpada velha." -> "Suba na escada." -> "Retire a lâmpada velha." -> "Desça da escada." -> "Descarte a lâmpada velha." -> "Pegue uma lâmpada nova." -> "Suba novamente na escada." -> "Instale a lâmpada nova no bocal." -> "Desça novamente da escada." -> "Feche a escada." -> "Guarde a escada." -> Fim } </graphviz>

Contudo, nem mesmo o processo de trocar uma lâmpada é tão linear. Em um cenário mais próximo da vida real, haverá dúvidas e decisões a serem tomadas durante o processo: <graphviz> digraph LampadaComDecisao { Início "Há uma escada disponível?" [shape=diamond] "Pegue uma escada." [shape=record] "Abra a escada embaixo da lâmpada velha." [shape=record] "Suba na escada." [shape=record] "Retire a lâmpada velha." [shape=record] "Desça da escada." [shape=record] "Descarte a lâmpada velha." [shape=record] "Há uma lâmpada nova?" [shape=diamond] "Compre uma." [shape=record] "Pegue uma lâmpada nova." [shape=record] "Suba novamente na escada." [shape=record] "Instale a lâmpada nova no bocal." [shape=record] "Desça novamente da escada." [shape=record] "Feche a escada." [shape=record] "Guarde a escada." [shape=record] Fim

Início -> "Há uma escada disponível?"

 "Há uma escada disponível?" -> "Pegue uma escada." [label=Sim]
 "Há uma escada disponível?" -> Fim [label=Não]

"Pegue uma escada."-> "Abra a escada embaixo da lâmpada velha." -> "Suba na escada." -> "Retire a lâmpada velha." -> "Desça da escada." -> "Descarte a lâmpada velha." -> "Há uma lâmpada nova?"

 "Há uma lâmpada nova?" -> "Pegue uma lâmpada nova." [label=Sim]
 "Há uma lâmpada nova?" -> "Compre uma." [label=Não]

"Compre uma." -> "Há uma lâmpada nova?" "Pegue uma lâmpada nova." -> "Suba novamente na escada." -> "Instale a lâmpada nova no bocal." -> "Desça novamente da escada." -> "Feche a escada." -> "Guarde a escada." -> Fim } </graphviz>


21/09: Atividades com pseudocódigo e diagrama de blocos

  • Realize as atividades oferecidas na apostila: páginas 8 a 18.
  • Atividades complementares - responda em pseudocódigo e em diagrama de blocos (veja a nomenclatura na apostila):
    1. Leia 3 números inteiros. Apresente-os de volta nas ordens crescente e decrescente.
    2. Leia dois números inteiros, aplique as 4 funções básicas (soma, subtração, multiplicação e divisão) e apresente os resultados.
    3. Leia um número e informe se é divisível por 3.
    4. Dada uma equação de 2º grau, leia os três números e aplique a fórmula de Bhaskara - para obter a(s) resposta(s).
    5. Baseado nas atividades do programa Kturtle - utilizado no desenho do pentagrama - leia dois números: a quantidade de lados (entre 5 e 8) e o comprimento do segmento de linha para, em seguida, desenhar a estrela.

Obs.: este trabalho servirá como complemento de conceito na próxima avaliação.

23/09: Constantes e variáveis

  • Tópicos: constantes, variáveis. Exemplo: desenho do pentagrama.
  • Realize as atividades oferecidas na apostila: páginas 19 a 25.
  • Problema 1: conversão de moedas.
  • Problema 2: conforme a tabela progressiva para o cálculo mensal do IRPF, construa um programa que calcula o imposto retido na fonte. Apresente pseudocódigo e diagrama de blocos.
    • Informe, em seguida, quantos dias por ano o funcionário trabalha para pagar apenas os impostos. Considere como parte do problema anos bissextos (dica: 2000 foi um ano bissexto...).
  • Problema 3: Construa um programa que informa se um número inteiro é divisível por 2 e/ou por 3. Apresente pseudocódigo e diagrama de blocos.
  • Problema 4: apresente o diagrama de blocos para o problema do reconhecimento de firma, do dia 09/09. Atenção: a solução deste problema é, provavelmente, mais refinada que aquela apresentada no dia...

28/09: Operadores

  • Tópicos: operadores lógicos e aritméticos, expressões.

30/09: Estruturas

  • Tópicos: estruturas de decisão e repetição.

Problema 1

  • Enunciado: calcular a potência de 3 para um dado número inteiro.

Proposta de Solução 1

  • Pseudocódigo:
    • Entrada de dados
      • Leia o número.
    • Processamento
      • Multiplique o número por ele mesmo e armazene o resultado em PotênciaDe2.
      • Multiplique o número por PotênciaDe2 e armazene o resultado em PotênciaDe3.
    • Saída de dados
      • Escreva o número armazenado em PotênciaDe3.
  • Codificação em shell script:
#!/bin/bash

# ENTRADA DE DADOS
# Leia o número.
echo "Digite um número inteiro:"
read numero

# PROCESSAMENTO
# Multiplique o número por ele mesmo e armazene o resultado em PotênciaDe2.
potenciaDe2=`expr $numero \* $numero`
#
# Multiplique o número por PotênciaDe2 e armazene o resultado em PotênciaDe3.
potenciaDe3=`expr $numero \* $potenciaDe2`

# SAÍDA DE DADOS
# Escreva o número armazenado em PotênciaDe3.
echo "O resultado é:"
echo $potenciaDe3

Proposta de Solução 2

  • Pseudocódigo:
    • Entrada de dados
      • Leia o número.
      • Armazene o número na variável Resultado.
    • Processamento
      • Armazene o número 1 na variável Potência.
      • Enquanto Potência < 3 faça
        • Multiplique o número e Resultado.
        • Adicione 1 a Potência
      • FimEnquanto
    • Saída de dados
      • Escreva o número armazenado em Resultado.
  • Codificação em shell script:
#!/bin/bash

# ENTRADA DE DADOS
#Leia o número.
echo "Digite um número inteiro"
read numero
#
#Armazene o número na variável Resultado.
resultado=$numero

# PROCESSAMENTO
#Armazene o número 1 na variável Potência.
potencia=1
#
#Enquanto Potência < 3 faça
#    Multiplique o número e Resultado.
#    Adicione 1 a Potência
#FimEnquanto
while [ $potencia -lt '3' ]; do
  resultado=`expr $numero \* $resultado`
  potencia=`expr $potencia + 1`
done

# SAíDA DE DADOS
#Escreva o número armazenado em Resultado.
echo "O resultado é:"
echo $resultado

Proposta de Solução 3

  • Pseudocódigo:
    • Entrada de dados
      • Leia o número.
      • Armazene o número na variável Potência.
    • Processamento
      • Para "2 3" faça
        • Multiplique número e Potência.
      • FimPara
    • Saída de dados
      • Escreva o número da variável Potência.
  • Codificação em shell script:
#!/bin/bash

# ENTRADA
#Leia o número.
echo "Digite um número inteiro:"
read numero
#Armazene o número na variável Potência.
potencia=$numero

# PROCESSAMENTO
#Para "2 3" faça
#  Multiplique número e Potência.
#FimPara
for lista in 2 3; do
  echo "Multiplicando pela $lista vez..."
  potencia=`expr $numero \* $potencia`
done

# SAíDA
#Escreva o número da variável Potência.
echo "A potência de 3 de $numero é:"
echo $potencia

Problema 2

  • Enunciado: ler números indefinidamente (da entrada de dados) até encontrar um número par.

Proposta de Solução 1

  • Pseudocódigo:
    • Entrada de dados
      • Leia número.
    • Processamento
      • Enquanto "número não for par" faça
        • leia número.
      • FimEnquanto
    • Saída de dados
      • Mostre número par.

Problema 3

  • Enunciado: identificar se um dado número inteiro é potência de outro. Ex.: ler o número 225 (entrada) e verificar que o mesmo é potência do número 15 (saída).

Problema 4

  • Enunciado: identificar se um número é primo.

Problema 5

  • Enunciado: identificar se um número é perfeito.

Problema 6

  • Enunciado: construa a tabuada - de 1 a 9.

05/10: Atividades pré-prova

  • Tópicos: revisão dos conceitos vistos em aula, exercícios.

Problema 1

  • Enunciado: Leia 5 números e ordene-os em ordem crescente e decrescente.

Problema 2

  • Enunciado: Apresente a sequência de Fibonacci até o número 1000.

Problema 3

  • Enunciado: Escolha um número aleatoriamente. Peça ao usuário para descobri-lo e só pare quando o número for encontrado.

Problema 4

  • Enunciado: Apresente as potências de um número cujo resultado é par e inferior a 1000. Informe o graus de potência e o resultado em cada operação.

07/10: Prova de lógica

12/10: Feriado

Linguagem de Programação C

14/10: Ambiente de desenvolvimento

  • Tópicos: editor de código, compilador, depurador, primeiros programas.
<graphviz>

digraph Etapas { "Código-fonte em C" [shape=circle] "Código Assembly" "Código de máquina" [shape=Mrecord] "Código executável" [shape=record]

"Código-fonte em C" -> "Código Assembly" [label="Compilação (compiling)"] "Código Assembly" -> "Código de máquina" [label="Montagem (assembling)"] "Código de máquina" -> "Código executável" [label="Vinculação (linking)"] }

</graphviz>
  • Exemplo de código em C:
#include <stdio.h>

int main(){

  // Declaração de variáveis
  int x;
  int y;
  int soma;

  // Entrada de dados
  scanf("%d",&x);
  scanf("%d",&y);

  // Processamento
  soma = x + y;

  // Saída de dados
  printf("x = %d\n",x);
  printf("y = %d\n",y);
  printf("x + y = %d\n",soma);
}
  • Exercício de programação em C: para resolver uma equação de 2º grau, leia os três números e aplique a fórmula de Bhaskara - para obter a(s) resposta(s).

19/10: Projeto final

Até o dia 11/12 será desenvolvido o projeto final da disciplina utilizando a linguagem C. O projeto está dividido em 4 etapas, a serem entregues conforme calendário estipulado no projeto.

Início da Etapa 1

Para realizar a primeira etapa, será necessário adquirir os conhecimentos básicos da linguagem, em especial tipos e variáveis. Para tanto, foi proposto em sala, hoje, um exercício envolvendo tipos, variáveis, estruturas de decisão e de repetição.

  • Proposta: apresente a sequência de Fibonacci até um valor limite estipulado pelo usuário. Além disso, para cada número da sequência adicione a letra p caso o mesmo seja primo.
  • Primeira parte: sequência de Fibonacci com número de parada. Arquivo fibonacci.c:
#include <stdio.h>

int main()
{
	int a;
	int b;
	int b_original;
	int limite;
	
	a = 0;
	b = 1;
	
	printf("Informe o valor limite para a sequência de Fibonacci (número inteiro): ");
	scanf("%d", &limite);
	
	printf("A sequência de Fibonacci é:");
	printf(" %d", a);
	while ( b <= limite )
	{
		printf(" %d",b);
		b_original = b; // Como Fibonacci utiliza *a* e *b* para somar o
		b = a + b;      // próximo, e *b* tem o seu valor modificado, é
		a = b_original; // preciso guardar o seu "original" para *a*.
	}
	printf(".\n");
}

Compilando e, em seguida, executando o programa:

$ gcc fibonacci.c -o Fibonacci
$ ./Fibonacci
Informe o valor limite para a sequência de Fibonacci (número inteiro): 144
A sequência de Fibonacci é: 0 1 1 2 3 5 8 13 21 34 55 89 144.
  • Segunda parte: teste de primalidade para cada número da sequência. É interessante, em casos assim, criar uma nova função, a ser usada devidamente na função principal (main). Essa função retornará um valor lógico: 0 para não e 1 para sim.
int primo(int numero)
{
	int divisor;
	int resto;
	int ehPrimo = 1;
	
	for(divisor = numero - 1; divisor > 1; divisor--)
	{
		resto = numero % divisor;
		if(resto == 0)
		{
			ehPrimo = 0;
		}
	}
	return ehPrimo;
}

Ao final, o programa será modificado para chamar a função que testa a primalidade de cada um dos números:

#include <stdio.h>

int primo(int numero)
{
	int divisor;
	int resto;
	int ehPrimo = 1;
	
	for(divisor = numero - 1; divisor > 1; divisor--)
	{
		resto = numero % divisor;
		if(resto == 0)
		{
			ehPrimo = 0;
		}
	}
	return ehPrimo;
}

int main()
{
	int a;
	int b;
	int b_original;
	int limite;
	
	a = 0;
	b = 1;
	
	printf("Informe o valor limite para a sequência de Fibonacci (número inteiro): ");
	scanf("%d", &limite);
	
	printf("A sequência de Fibonacci é:");
	printf(" %dp", a);
	while ( b <= limite )
	{
		if(primo(b) == 1)
		{
			printf(" %dp",b);
		} else
		{
			printf(" %d",b);
		}
		b_original = b; // Como Fibonacci utiliza *a* e *b* para somar o
		b = a + b;      // próximo, e *b* tem o seu valor modificado, é
		a = b_original; // preciso guardar o seu "original" para *a*.
	}
	printf(".\n");
}

Compilando pela segunda vez, chegamos ao resultado final do exercício:

$ gcc fibonacci.c -o Fibonacci
$ ./Fibonacci
Informe o valor limite para a sequência de Fibonacci (número inteiro): 144
A sequência de Fibonacci é: 0p 1p 1p 2p 3p 5p 8 13p 21 34 55 89p 144.

21/10: Estrutura de um Programa em C e Números

Atividades: construa um programa que apresenta na tela...

  • Os números inteiros em sequência ascendente:
#include <stdio.h>

int main()
{
	int inteiro;
	
	inteiro = 0;

	printf("Números inteiros em ordem ascendente:");
	while ( 1 == 1 ) // Condição sempre satisfeita
	{
		printf(" %d",inteiro);
		inteiro = inteiro + 1;
	}
}
  • Os números inteiros pares em sequência ascendente: modifica-se apenas a operação na linha 13.
#include <stdio.h>

int main()
{
	int inteiro;
	
	inteiro = 0;

	printf("Números inteiros pares em ordem ascendente:");
	while ( 1 == 1 ) // Condição sempre satisfeita
	{
		printf(" %d",inteiro);
		inteiro = inteiro + 2;
	}
}
  • Alternativamente, pode-se utilizar uma função dedicada para somar 2 a cada número, criando a sequência:
#include <stdio.h>

int adicioneDois(int numero)
{
	numero = numero + 2;
	return numero;
}

int main()
{
	int inteiro;
	
	inteiro = 0;

	printf("Números inteiros pares em ordem ascendente:");
	while ( 1 == 1 ) // Condição sempre satisfeita
	{
		printf(" %d",inteiro);
		inteiro = adicioneDois(inteiro);
	}
}

É a função das linhas 3 a 7 que, em tempo de execução, é chamada na linha 19. O mesmo raciocínio também se aplica ao exercício anterior (para somar 1).

  • Os números inteiros ímpares em sequência ascendente: a diferença entre pares e ímpares está apenas no primeiro número. Portanto, modifica-se apenas a linha 13:
#include <stdio.h>

int adicioneDois(int numero)
{
	numero = numero + 2;
	return numero;
}

int main()
{
	int inteiro;
	
	inteiro = 1;

	printf("Números inteiros ímpares em ordem ascendente:");
	while ( 1 == 1 ) // Condição sempre satisfeita
	{
		printf(" %d",inteiro);
		inteiro = adicioneDois(inteiro);
	}
}
  • Os números inteiros pares ou ímpares: o usuário escolhe. Neste exemplo, é utilizado um caracter como item de escolha:
    • Linha 12: declaração da nova variável do tipo caracter.
    • Linhas 14 e 15: leitura da variável pela entrada de dados do usuário.
    • Linhas 16 a 31: decisão entre P (16), I (22) ou o encerramento do programa pela digitação de um valor inválido (28-29).
#include <stdio.h>

int adicioneDois(int numero)
{
	numero = numero + 2;
	return numero;
}

int main()
{
	int inteiro;
	char escolha;
	
	printf("Digite P para números pares ou I para números ímpares: ");
	scanf("%c", &escolha);
	if ( escolha == 'P' )
	{
		inteiro = 0;
	}
	else
	{
		if ( escolha == 'I' )
		{
			inteiro = 1;
		}
		else
		{
			printf("Escolha errada!\n");
			return;
		}
	}

	printf("Números inteiros em ordem ascendente:");
	while ( 1 == 1 ) // Condição sempre satisfeita
	{
		printf(" %d",inteiro);
		inteiro = adicioneDois(inteiro);
	}
}
  • Números pares ou ímpares, com valor limite: há um número limite para os valores apresentados na tela.
Coloque aqui o código-fonte em C.
  • Números pares ou ímpares acima de 1 bilhão com valor limite: os números, ao invés de 0 (zero), iniciarão em 1.000.000.000 (1 bilhão). Mantém-se a regra do número limite.

Recuperação da Prova

A recuperação da prova de lógica consiste em um trabalho a ser entregue dia 28/10, quarta-feira, durante a aula.

O problema é o seguinte: construa uma calculadora baseada notação polonesa inversa. Para facilitar o desenvolvimento da aplicação, considere que é possível armazenar até 3 valores na pilha de números, além de utilizar apenas as quatro operações matemáticas: +, -, * e /. A resposta deve ser dada quando houver apenas um número na pilha.

O conceito de pilha é interessante: os primeiros valores adicionados serão os últimos escolhidos, como uma pilha de objetos: <graphviz> digraph Pilha {

subgraph clusterOP { Adiciona Retira } subgraph clusterP { label=Pilha 4 3 2 }

Adiciona -> 4 4 -> Retira 4 -> 3 -> 2 2 -> 3 -> 4 } </graphviz> No exemplo ao lado: o número 2 foi o primeiro adicionado e, portanto, o último a ser escolhido.

Exemplos de operações utilizando notação polonesa inversa:

2 3 +
= 5
2 3 * 4 +
= 10
7 6 3 + -
= -2
2 3 4 * +
= 14

Os números são armazenados para uso posterior. Para cada operação matemática, são "escolhidos" os últimos números inseridos, aplicada a operação e o resultado retorna à pilha de números. Portanto, a ordem dos elementos faz diferença na execução.

Neste último exemplo, seguem os passos da solução:

  1. A pilha está com os valores 2 (base) 3 4 (topo).
  2. Aparece a operação de multiplicação. São utilizados os valores mais próximos do topo: 3 e 4. O resultado é 12.
  3. Os valores 3 e 4 são retirados da pilha e retorna apenas o valor 12. A pilha fica assim: 2 (base) 12 (topo).
  4. Aparece a operação de soma. São utilizados os valores mais próximos do topo: 2 e 12. O resultado é 14.
  5. Os valores 2 e 12 são retirados da pilha e retorna apenas o valor 14.
  6. Como há apenas um número na pilha, o resultado final é apresentado: 14

Outro exemplo:

8 8 6 4 3 + - + *
= 56

A sequência das operações, com a pilha resultante, é:

4 3 +
  Pilha = 8 8 6 7 - + *
6 7 -
  Pilha = 8 8 -1 + *
8 -1 +
  Pilha = 8 7 *
8 7 *
  Pilha = 56
= 56 

Dica: o programa dc utiliza este tipo de notação. A calculadoraHP12C, muito utilizada para Contabilidade e Economia, também se baseia nos mesmos princípios.

O trabalho deve ser entregue em papel ou mídia digital, durante a aula, contendo pseudocódigo e diagrama de blocos da solução do problema. Lembre-se: até 3 números armazenados e as quatro operações básicas.

26/10: Etapa 1 - Tipos e Funções

  • Referência externa: aulas 2, 3, 4, 5 e 11 do curso de C.
  • Tipos simples e compostos
#include <stdio.h>

int main()
{
	char nome[50];
	int idade;
	
	printf("Digite o nome da pessoa: ");
	fgets(nome,sizeof(nome),stdin);
	printf("Digite a idade da pessoa: ");
	scanf("%d", &idade);

	printf("%s tem %d anos.", nome, idade);
}
#include <stdio.h>

int main()
{
	struct pessoa
	{
		char nome[50];
		int idade;
	};
	struct pessoa Aluno;
	
	printf("Digite o nome da pessoa: ");
	fgets(Aluno.nome,sizeof(Aluno.nome),stdin);
	printf("Digite a idade da pessoa: ");
	scanf("%d", &Aluno.idade);

	printf("%s tem %d anos.", Aluno.nome, Aluno.idade);
}

28/10: Etapa 1 - Pseudocódigo

Pseudocódigo da primeira etapa:

  1. Enquanto opção for diferente de "Sair do programa" faça
    1. Apresente as opções para o usuário
      1. Primeira opção: criar um evento
      2. Segunda opção: mostrar um evento já cadastrado
      3. Terceira opção: Sair do programa
    2. Leia a opção do usuário
    3. Se a opção é de criação de um evento então
      1. Leia título
      2. Leia horários de início e fim
      3. Leia data e horário de criação
      4. Leia descrição
      5. Leia local de realização
      6. Leia outros participantes.
      7. Leia estado.
      8. Leia categoria: pessoal, trabalho, etc.
      9. Leia recorrência: com que regularidade ocorre e prazo.
    4. Fim Se
    5. Se a opção é mostrar um evento então
      1. Se já existe um evento cadastrado
        1. Apresente o evento, um item por linha
      2. Fim Se
    6. Sim Se
  2. Fim Enquanto
  • Para pensar: o que faz este código:
#include <stdio.h>
#include <string.h>

int main()
{
	int contador = 0;
	int tamanhoDaString;

	struct pessoa
	{
		char nome[50];
	};
	struct pessoa Aluno;
	
	printf("Inicie a contagem com: ");
	scanf("%d",&contador);
	while(contador < 50)
	{
		printf("Digite o nome da pessoa: ");
		fgets(Aluno.nome,50,stdin);
		if(Aluno.nome[0] == '\n')
		{
			fgets(Aluno.nome,50,stdin);
		}
		tamanhoDaString = strlen(Aluno.nome);
		if( Aluno.nome[tamanhoDaString-1] == '\n' )
			Aluno.nome[tamanhoDaString-1] = 0;

		printf("Nome = %s\n",Aluno.nome);
		contador++;
	}
}

02/11: Etapa 2 - O uso de funções para organizar código

O interessante, quando o programa adquire complexidade, é organizar o código em funções.

Leitura de uma linha inteira a partir da entrada de dados do usuário:

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


// Estruturas de Dados
struct pessoa
{
	char nome[50];
	int idade;
};


// Funções de entrada de dados
int lerNumero()
{
	int numero;
	int leuNumero = 1;
	do
	{
		if (leuNumero == 0)
		{
			printf("Ops! Problemas ao ler o número. Digite-o novamente: ");
		}
		leuNumero = scanf("%d", &numero);
		if (leuNumero == 0)
		{
			leuNumero = scanf("%*[^\n]");
		}
	} while (leuNumero == 0);
	return numero;
}

void lerLinha(char * linha) {
  scanf(" %[^\n]", linha);
}

struct pessoa lerValores()
{
	int tamanhoDaString;
	struct pessoa qualquerPessoa;
	printf("Nome da pessoa: ");
	// Como a leitura de string é diferente das outras variáveis,
	// isso é feito dentro da própria função.
	fgets(qualquerPessoa.nome,50,stdin);
	if(qualquerPessoa.nome[0] == '\n')
	{
		fgets(qualquerPessoa.nome,50,stdin);
	}
	tamanhoDaString = strlen(qualquerPessoa.nome);
	if(qualquerPessoa.nome[tamanhoDaString-1] == '\n')
	{
		qualquerPessoa.nome[tamanhoDaString-1] = 0;
	}
	printf("Idade da pessoa: ");
	qualquerPessoa.idade = lerNumero();
	return qualquerPessoa;
}

int mostrarValores(struct pessoa qualquerPessoa)
{
	printf("Nome da pessoa: %s\n", qualquerPessoa.nome);
	printf("Idade da pessoa: %d\n", qualquerPessoa.idade);
}


// Função principal: o programa
int main()
{
	int opcao = 0;
	int valoresJaLidos = 0;
	struct pessoa umaPessoa;
	do
	{
		printf("\nOpção 1 - Ler valores do usuário.\n");
		printf("Opção 2 - Mostrar valores já lidos do usuário.\n");
		printf("Opção 3 - Sair do programa.\n");
		printf("Digite a sua opção: ");
		opcao = lerNumero();
		switch(opcao)
		{
			case 1:
				umaPessoa = lerValores();
				valoresJaLidos = 1;
				break;
			case 2:
				if (valoresJaLidos == 1)
				{
					mostrarValores(umaPessoa);
				}
				else
				{
					printf("Ainda não há valores lidos do usuário. ");
					printf("Escolha a opção 1 para lê-los.\n");
				}
				break;
			case 3:
				printf("Fim do programa. Adeus!\n");
				break;
			default:
				printf("Opção inválida! Escolha um número entre 1 e 3.\n");
				break;
		}
	} while (opcao != 3);
}

04/11: Etapa 2 - Continuação da etapa anterior

Estruturando a primeira etapa do programa:

//Inclusão de outros arquivos e bibliotecas
#include <stdio.h>
#include <time.h>

//Estruturas de dados
// Estrutura de um evento:
//	- Título
//	- Data de início e de fim
//	- Horário de início e de fim
//	- Data e horário de criação
//	- descrição
//	- local
//	- estado: confirmado ou não?
//	- categoria: pessoal, trabalho, etc.
//	- recorrência: regularidade.
struct evento
{
	char titulo[50];
	char dataDeInicio[10];
	char dataDeTermino[10];
	char horarioDeInicio[5];
	char horarioDeTermino[5];
	char dataDeCriacao[10];
	char horaDeCriacao[5];
	char descricao[100];
	char local[100];
	char estado[20];
	char categoria[10];
	char recorrencia[20];
};


//Funções auxiliares
char* agora()
{
	time_t hora;
	time(&hora);
	return(ctime(&hora));
}


//Função principal
int main()
{
	int opcao;
	struct evento meuPrimeiroEvento;
	do
	{
		printf("\nOpção 1: criar um evento.\n");
		printf("Opção 2: mostar um evento já criado.\n");
		printf("Opção 3: Sair do programa.\n");
		printf("Digite a sua opção: ");
		scanf("%d", &opcao);
		switch(opcao)
		{
			case 1:
				printf("Digite o título do evento: ");
				scanf("%s", meuPrimeiroEvento.titulo);
				break;
			case 2:
				printf("Apenas um evento cadastrado!\n");
				printf("Título: %s\n", meuPrimeiroEvento.titulo);
				printf("Hora de criação: %s", agora());
				break;
			case 3:
				printf("Fim do programa. Adeus!\n");
				break;
		}
	} while (opcao != 3);
}


Exemplo de uso de vetores para utilizar, em um mesmo programa, várias estruturas de dados:

#include <stdio.h>

struct ponto
{
	int x;
	int y;
	int z;
};

int main()
{
	int indice;
	struct ponto Vertices[3];
	
	for(indice=0; indice <=2; indice++)
	{
		printf("\nPonto %d\n", indice);
		printf("Informe x: ");
		scanf("%d", &Vertices[indice].x);
		printf("Informe y: ");
		scanf("%d", &Vertices[indice].y);
		printf("Informe z: ");
		scanf("%d", &Vertices[indice].z);
	}
	
	printf("\nResultados:\n");
	for(indice=0; indice<=2; indice++)
	{
		printf("Figura %d:", indice);
		printf(" %d", Vertices[indice].x);
		printf(" %d", Vertices[indice].y);
		printf(" %d\n", Vertices[indice].z);
	}
}

09/11: Etapa 2 - Desenvolvimento em etapas

Programação requer dedicação, disciplina e, antes de tudo, perseverança :-)

O núcleo

Um programa simples, como este:

void main()
{
}

é o início de praticamente qualquer programa em C, uma vez que contém a função principal, main(). Neste caso, definida com um conjunto vazio de instruções - limitado pelas chaves nas linhas 2 e 3.

O (clássico) primeiro programa

A partir daí, pode-se construir qualquer programa. Contudo, em C o conhecimento prévio de sua estrutura mínima: bibliotecas, tipos, funções e outros, auxilia bastante a programação. Ao invés de criar todo um código para escrever uma simples linha na tela, pode-se utilizar código pronto para tal: a biblioteca stdio.h, por exemplo, ajuda na entrada e saída de dados (standard input and output). Ali há, dentre outras, a função printf, que mostra uma informação na tela: a velah frase "Olá mundo!".

#include <stdio.h>

void main()
{
	printf("Olá mundo!\n");
}

Reutilizar para não reinventar a roda...

Entrada e saída de dados

Mas interação acontece mesmo quando os dados são transmitidos nas duas direções: ler e escrever dados do usuário. Mais uma vez, a stdio.h possui outra função, a de ler dados: scanf - a mais simples de muitas. No caso abaixo, há a leitura e a repetição dos dados inseridos para garantir que a leitura ocorreu sem problemas:

#include <stdio.h>

void main()
{
	char nome[10];
	
	printf("Olá!\nDigite o seu primeiro nome: ");
	scanf("%s", nome);
	printf("Repetindo: %s\n", nome);
}

Nota-se, na linha 5, a inclusão de uma variável: uma área de memória para armazenar, mesmo que temporariamente, o valor inserido pelo usuário. No caso, um vetor de até 10 caracteres (10 bytes): uma string. Mas cuidado com o último byte...

Mais dados

Isso significa que a interação pode se assemelhar a um "diálogo": escrever e ler sequencialmente vários valores. No exemplo abaixo, o nome de uma pessoa e sua idade.

#include <stdio.h>

void main()
{
	char nome[10];
	int idade;
	
	printf("Olá!\nDigite o seu primeiro nome: ");
	scanf("%s", nome);
	printf("Sua idade: ");
	scanf("%d", &idade);
	printf("Repetindo: %s tem %d anos.\n", nome, idade);
}

As variáveis das linhas 5 e 6 têm seus valores alterados nas linhas 9 e 11, respectivamente - e reapresentados na linha 12.

Organizando os dados

Se as variáveis têm alguma relação entre si, pode-se organizá-las em blocos, facilitando a sua manipulação. Esses blocos são as estruturas de dados:

struct pessoa
{
	char nome[10];
	int idade;
};

mudando um pouco a forma de usar essas áreas de memória:

	#include <stdio.h>

struct pessoa
{
	char nome[10];
	int idade;
};


void main()
{
	struct pessoa umaPessoa;

	printf("Olá!\nDigite o seu primeiro nome: ");
	scanf("%s", umaPessoa.nome);
	printf("Sua idade: ");
	scanf("%d", &umaPessoa.idade);
	printf("Repetindo: %s tem %d anos.\n", umaPessoa.nome, umaPessoa.idade);
}

Ou seja, ao invés de usar diretamente a variável é preciso informar, agora, o seu nome composto: a variável do tipo estrutura e a variável interna dessa estrutura. Nas linhas 3 a 7, foi definida a estrutura. Na linha 12, foi declarada uma variável do tipo composto (struct pessoa) de nome umaPessoa.

Integrando os conceitos

Assim como não existem palavras ou frases em C, mas vetores de caracteres (strings), o mesmo pode se aplicar a outros tipos de variáveis:

int numero;
int numeros[10];
char letra;
char letras[10];

inclusive tipos compostos:

struct pessoa
{
	char nome[10];
	int idade;
};

struct pessoa umaPessoa;
struct pessoa pessoas[10];

Para o programa anterior, pode-se expandir para utilizar não apenas um tipo por vez, mas vários:

#include <stdio.h>

struct pessoa
{
	char nome[10];
	int idade;
};


void main()
{
	struct pessoa pessoas[2];

	printf("Olá!\nPrimeira pessoa, digite o seu primeiro nome: ");
	scanf("%s", pessoas[0].nome);
	printf("Sua idade: ");
	scanf("%d", &pessoas[0].idade);

	printf("Outra pessoa, digite o seu primeiro nome: ");
	scanf("%s", pessoas[1].nome);
	printf("Sua idade: ");
	scanf("%d", &pessoas[1].idade);
	
	
	printf("Repetindo: a primeira pessoa de chama %s e tem %d anos.\n", pessoas[0].nome, pessoas[0].idade);
	printf("Já a segunda pessoa, de nome %s, tem %d anos.\n", pessoas[1].nome, pessoas[1].idade);
}

Estruturas de repetição

Associado a n variáveis, nada mais adequado que uma estrutura de repetição adequada à quantidade de variáveis utilizadas. Para tanto, faz-se uso de um contador ou índice para controle de quantas variáveis, já declaradas, estão de fato em uso:

#include <stdio.h>

struct pessoa
{
	char nome[10];
	int idade;
};


void main()
{
	struct pessoa pessoas[2];
	int indice = 0;
	
	printf("Olá!\n");
	do
	{
		printf("Pessoa número %d, digite seu primeiro nome: ", indice + 1);
		scanf("%s", pessoas[indice].nome);
		printf("Sua idade: ");
		scanf("%d", &pessoas[indice].idade);
		indice = indice + 1;
	} while (indice < 2);
	
	printf("Repetindo:\n");
	do
	{
		printf("A pessoa número %d, %s, tem %d ano(s).\n", indice, pessoas[indice-1].nome,
			pessoas[indice-1].idade);
		indice = indice - 1;
	} while (indice > 0);
}

Diminuindo complexidade com funções

Até agora, o programa não se preocupou caso o usuário digitasse erroneamente, podendo causar falhas críticas. E como o programa começa a ganhar complexidade, envolvendo vetores, estruturas de repetição e outros, é extremamente interessante organizar o código.

Por isso, as entradas do usuário serão devidamente tratadas e padronizadas em funções auxiliares podendo, com isso, ser reutilizadas quantas vezes forem necessárias. Veja as funções lerNumero() (linhas 9 a 26) e lerLinha() (linha 28 a 30).

#include <stdio.h>

struct pessoa
{
	char nome[30];
	int idade;
};

int lerNumero()
{
	int numero;
	int leuNumero = 1;
	do
	{
		if (leuNumero == 0)
		{
			printf("Ops! Problemas ao ler o número. Digite-o novamente: ");
		}
		leuNumero = scanf("%d", &numero);
		if (leuNumero == 0)
		{
			leuNumero = scanf("%*[^\n]");
		}
	} while (leuNumero == 0);
	return numero;
}

void lerLinha(char * linha) {
  scanf(" %[^\n]", linha);
}

void main()
{
	struct pessoa pessoas[2];
	int indice = 0;
	
	printf("Olá!\n");
	do
	{
		printf("Pessoa número %d, digite seu nome completo: ", indice + 1);
		lerLinha(pessoas[indice].nome);
		printf("Sua idade: ");
		pessoas[indice].idade = lerNumero();
		indice = indice + 1;
	} while (indice < 2);
	
	printf("Repetindo:\n");
	do
	{
		printf("A pessoa número %d, %s, tem %d ano(s).\n", indice, pessoas[indice-1].nome, 
			pessoas[indice-1].idade);
		indice = indice - 1;
	} while (indice > 0);
}

Operando sobre índices

Agora já é possível incrementar o programa: cadastro e apresentação de várias pessoas.

#include <stdio.h>

struct pessoa
{
	char nome[30];
	int idade;
};

int lerNumero()
{
	int numero;
	int leuNumero = 1;
	do
	{
		if (leuNumero == 0)
		{
			printf("Ops! Problemas ao ler o número. Digite-o novamente: ");
		}
		leuNumero = scanf("%d", &numero);
		if (leuNumero == 0)
		{
			leuNumero = scanf("%*[^\n]");
		}
	} while (leuNumero == 0);
	return numero;
}

void lerLinha(char * linha) {
  scanf(" %[^\n]", linha);
}

void main()
{
	struct pessoa pessoas[10];
	int indice = 0;
	int indiceTemporario;
	int opcao;
	
	do
	{
		printf("\n:: Menu interativo ::\n");
		printf("Opção 1: Cadastrar uma pessoa.\n");
		printf("Opção 2: Mostrar as pessoas já cadastradas.\n");
		printf("Opção 3: Sair do programas.\n");
		printf("Digite o número da sua opção: ");
		opcao = lerNumero();

		switch(opcao)
		{
			case 1:
				printf("Nome completo: ");
				lerLinha(pessoas[indice].nome);
				printf("Idade: ");
				pessoas[indice].idade = lerNumero();
				printf("Registro número %d concluído.\n", indice);
				indice = indice + 1;
				break;
			case 2:
				indiceTemporario = indice;
				while(indiceTemporario > 0)
				{
					indiceTemporario = indiceTemporario - 1;
					printf("Registro número %d:\n", indiceTemporario);
					printf("Nome: %s.\n", pessoas[indiceTemporario].nome);
					printf("Idade: %d.\n", pessoas[indiceTemporario].idade);
				}
				break;
			case 3:
				printf("Fim do programa.\n");
				break;
			default:
				printf("Digite um número entre 1 e 3.\n");
		}
	} while (opcao != 3);
}

É comum o uso de índices ou contadores cópias/auxiliares (indiceTemporario) - para não modificar o valor original das variáveis "originais".

Estendendo o programa: relógio do sistema

Mais uma vez: é preciso entender que funções de C já estão disponíveis no sistema. Por que perguntar a hora corrente ao usuário, se o sistema já pode fornecê-lo?

#include <stdio.h>
#include <time.h>

struct pessoa
{
	char nome[30];
	int idade;
	char dataDeCriacao[11];
	char horaDeCriacao[6];
};

char* dia_mes_ano(char * data)
{
	time_t     agora;
	struct tm  ts;

	agora = time(NULL);
	ts = *localtime(&agora);
	strftime(data, 11, "%d/%m/%Y", &ts);
}

char* hora_minuto(char * data)
{
	time_t     agora;
	struct tm  ts;

	agora = time(NULL);
	ts = *localtime(&agora);
	strftime(data, 6, "%H:%M", &ts);
}

int lerNumero()
{
	int numero;
	int leuNumero = 1;
	do
	{
		if (leuNumero == 0)
		{
			printf("Ops! Problemas ao ler o número. Digite-o novamente: ");
		}
		leuNumero = scanf("%d", &numero);
		if (leuNumero == 0)
		{
			leuNumero = scanf("%*[^\n]");
		}
	} while (leuNumero == 0);
	return numero;
}

void lerLinha(char * linha) {
  scanf(" %[^\n]", linha);
}

void main()
{
	struct pessoa pessoas[10];
	int indice = 0;
	int indiceTemporario;
	int opcao;
	
	do
	{
		printf("\n:: Menu interativo ::\n");
		printf("Opção 1: Cadastrar uma pessoa.\n");
		printf("Opção 2: Mostrar as pessoas já cadastradas.\n");
		printf("Opção 3: Sair do programas.\n");
		printf("Digite o número da sua opção: ");
		opcao = lerNumero();

		switch(opcao)
		{
			case 1:
				printf("Nome completo: ");
				lerLinha(pessoas[indice].nome);
				printf("Idade: ");
				pessoas[indice].idade = lerNumero();
				printf("Registro número %d concluído.\n", indice);
				dia_mes_ano(pessoas[indice].dataDeCriacao);	
				hora_minuto(pessoas[indice].horaDeCriacao);
				indice = indice + 1;
				break;
			case 2:
				indiceTemporario = indice;
				while(indiceTemporario > 0)
				{
					indiceTemporario = indiceTemporario - 1;
					printf("Registro número %d: criado em %s às %s.\n", indiceTemporario, 
						pessoas[indiceTemporario].dataDeCriacao,
						pessoas[indiceTemporario].horaDeCriacao);
					printf("Nome: %s.\n", pessoas[indiceTemporario].nome);
					printf("Idade: %d.\n", pessoas[indiceTemporario].idade);
				}
				break;
			case 3:
				printf("Fim do programa.\n");
				break;
			default:
				printf("Digite um número entre 1 e 3.\n");
		}
	} while (opcao != 3);
}

Promessa é dívida :-)

Mas a pergunta é: será que funciona? Talvez haja alguns "probleminhas" a serem resolvidos:

// Bibliotecas de C
#include <stdio.h>	// entrada e saída padrão
#include <time.h>	// relógio do sistema


// Estruturas de dados
struct evento
{
	char titulo[50];		// Título do evento
	char dataDeInicio[10];		// Data de início
	char horarioDeInicio[5];	// Horário de início
	char dataDeTermino[10];		// Data de término
	char horarioDeTermino[5];	// Horário de término
	char dataDeCriacao[11];		// Data de criação do evento (1 byte a mais para \0)
	char horarioDeCriacao[6];	// Hora de criação do evento (1 byte a mais para \0)
	char descricao[100];		// Um texto explicativo do evento
	char local[100];		// Local de realização
	char estado[20];		// Estados: confirmado, a confirmar, etc.
	char categoria[10];		// Categoria: pessoal, trabalho, etc.
	char recorrencia[20];		// Quantas vezes se repete?
};

 
// Funções auxiliares
//
// função dia_mes_ano(): mostra a data atual do relógio do sistema
char* dia_mes_ano(char * data)
{
	time_t     agora;
	struct tm  ts;

	agora = time(NULL);
	ts = *localtime(&agora);
	strftime(data, 11, "%d/%m/%Y", &ts);
}
//
// função hora_minuto(): mostra a hora atual do relógio do sistema
char* hora_minuto(char * data)
{
	time_t     agora;
	struct tm  ts;

	agora = time(NULL);
	ts = *localtime(&agora);
	strftime(data, 6, "%H:%M", &ts);
}
//
// função lerNumero(): lê um número via teclado do usuário
int lerNumero()
{
	int numero;
	int leuNumero = 1;
	do
	{
		if (leuNumero == 0)
		{
			printf("Ops! Problemas ao ler o número. Digite-o novamente: ");
		}
		leuNumero = scanf("%d", &numero);	// Se aparecer algo diferente de número
		if (leuNumero == 0)			// A função retornará zero.
		{
			leuNumero = scanf("%*[^\n]");	// Ou seja, "sujeira" como uma vogal.
		}
	} while (leuNumero == 0);			// E a função não encerrará até que
	return numero;					// se digite um número.
}
//
// função lerLinha(): lê uma linha inteira até o usuário digitar <ENTER>
void lerLinha(char* linha)
{
	scanf(" %[^\n]", linha); // Com isso, lê-se uma linha inteira.
}



// Função principal. É aqui em que se define, de fato, o programa.
int main()
{
	// Variáveis do programa
	int opcao; // opção do usuário: criar evento, ler, etc.
	int indice = 0; // quantidade de eventos já cadastrados; ou seja, nenhum (0).
	struct evento eventos[100]; // até 100 eventos possíveis.

	// O programa vai mostrar o menu indefinidamente.
	// O usuário escolherá quando parar.
	do
	{
		// Menu de opções
		printf("\n\nOpção 1: criar um evento.\n");
		printf("Opção 2: mostrar um evento já criado.\n");
		printf("Opção 3: Sair do programa.\n");
		printf("Digite a sua opção: ")
		//
		// O usuário escolhe a opção.
		opcao = lerNumero();
		//
		// Para cada opção, um conjunto de instruções a executar.
		switch{opcao}
		{
			// Opção 1: criar um evento.
			case 1:
				printf("\nSobre o evento, informe:\n");
				printf("Título: ");
				lerLinha(eventos[indice].titulo);
				printf("Data de início (dia/mês/ano): ");
				lerLinha(eventos[indice].dataDeInicio);
				printf("Hora de início (hora:minuto): ");
				lerLinha(eventos[indice].horarioDeInicio);
				printf("Data de término (dia/mês/ano): ");
				lerLinha(eventos[imdice].dataDeTermino);
				printf("Hora de término (hora:minuto): ");
				lerLinha(eventos[indice].horarioDeTermino);
				dia_mes_ano(eventos[indice].dataDeCriacao);	// automático: data do sistema
				hora_minuto(eventos[indice].horarioDeCriacao);	// automático: hora do sistema
				printf("Descrição: ");
				lerLinha(eventos[indice].descricao);
				printf("Local de realização: ");
				lerLinha(eventos[indice].local);
				printf("Estado (confirmado, a confirmar, etc): ");
				lerLinha(eventos[indice].estado);
				printf("Categoria (pessoal, estudos, trabalho, etc): ");
				lerLinha(eventos[indice].categoria);
				printf("Recorrência (taxa de repetição): ");
				lerLinha(eventos[indice].recorrencia);
				indice = indice + 1; // Um evento foi adicionado. Incrementa o indice.
				break;
			// Opção 2: mostrar um evento já cadastrado.
			case 2:
				if(indice == 0)
				{
					printf("Não há eventos cadastrados.\n");
				}
				else
				{
					printf("Há %d eventos cadastrados.\n", indice);
					printf("Escolha um evento entre 0 e %d: ",indice-1);						
					opcao = lerNumero();
					if (opcao < 0 || opcao > indice)
					{
						print("Número fora dos limites.\n");
					}
					else
					{
						printf("\nEvento número: %d\n", opcao);
						printf("Título: %s\n", eventos[opcao].titulo);
						printf("Data de início: %s\n", 
							eventos[opcao].dataDeInicio);
						printf("Hora de início: %s\n", 
							eventos[opcao].horaDeInicio);
						printf("Data de término: %s\n", 
							eventos[opcao].dataDeTermino);
						printf("Hora de término: %s\n", 
							eventos[opcao].horaDeTermino);
						printf("Data de criação: %s\n", 
							eventos[opcao].dataDeCriacao);
						printf("Hora de criação: %s\n", 
							eventos[opcao].horarioDeCriacao);
						printf("Descrição: %s\n", eventos[opcao].descricao);
						printf("Local de realização: %s\n", evento[opcao].local);
						printf("Estado: %s\n", eventos[opcao].estado);
						printf("Categoria: %s\n", eventos[opcao].categoria);
						printf("Recorrência: %s\n", eventos[opcao].recorrencia);
					}
				}
				break;
			// Opção 3: sair do programa.
			case 3:
				printf("Fim do programa. Adeus!\n");
				break;
			// Opção padrão: é interessante criar uma opção padrão para
			// informar o usuário quando ele digita um número fora da lista
			// de opções. Neste caso, 1 > número > 3.
			default:
				printf("Por favor, escolha um número entre 1 e 3.\n");
		}
	// Essa estrutura de repetição (do..while) rodará enquanto a opção
	// for diferente de 3.
	} while (opcao != 3);
}

16-18/11: Leitura e escrita em arquivos

A seguir, um código-exemplo para ler e escrever em arquivos tipo texto:

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

int lerNumero()
{
	int numero;
	int leuNumero = 1;

	do
	{
		if (leuNumero == 0)
		{
			printf("Ops! Problemas ao ler o número. Digite-o novamente: ");
		}
		leuNumero = scanf("%d", &numero);
		if (leuNumero == 0)
		{
			leuNumero = scanf("%*[^\n]");
		}
	} while (leuNumero == 0);
	return numero;
}

void lerLinha(char* linha)
{
	scanf(" %[^\n]", linha);
}

int main()
{
	FILE *arq1;
	char nomeDoArquivo[100];
	char linha[500];
	char *leu;
	int opcao;

	do
	{
		printf("\n----------------------------------------");
		printf("----------------------------------------\n");
		printf("Etapa Dois - Manipulação de Arquivos\n");
		printf("1 - Interação direta com o usuário.\n");
		printf("2 - Leitura e escrita em arquivo.\n");
		printf("3 - Sair do programa.\n");
		printf("Digite a sua opção: ");

		opcao = lerNumero();

		switch(opcao)
		{
			case 1:
				printf("Digite uma frase: ");
				lerLinha(linha);
				printf("A frase é: %s\n", linha);
				break;
			case 2:
				printf("Informe o nome completo do arquivo: ");
				lerLinha(nomeDoArquivo);
				arq1 = fopen(nomeDoArquivo, "r");
				if(arq1)
				{
					printf("\nA seguir, o seu conteúdo original: \n");
					printf("--- Início do arquivo %s ---\n", nomeDoArquivo);
					do
					{
						leu = fgets(linha, sizeof(linha), arq1);
						if (leu)
						{
							printf("%s", linha);
						}
					} while (! feof(arq1));
					printf("--- Fim do arquivo %s ---\n", nomeDoArquivo);
					fclose(arq1);
				}
				else
				{
					printf("ATENÇÃO! Não é possível abrir o arquivo");
					printf(" %s para escrita.\n", nomeDoArquivo);
				}
				printf("\nDeseja mudar o seu conteúdo?\n");
				printf("1 - Sim\n");
				printf("2 - Não\n");
				opcao = lerNumero();
				switch(opcao)
				{
					case 1:
						arq1 = fopen(nomeDoArquivo, "w");
						if(arq1)
						{
							printf("Digite quantas frases quiser.\n");
							printf("Para terminar, digite uma linha");
							printf(" contendo apenas a expressão \"EOF\":\n");
							do
							{
								lerLinha(linha);
								if(strcmp(linha,"EOF") != 0) // as string são diferentes
								{
									fprintf(arq1, "%s\n", linha);
								}
							} while (strcmp(linha,"EOF"));
							fclose(arq1);
						}
						else
						{
							printf("ATENÇÃO! Não é possível abrir o arquivo");
							printf(" %s para escrita.\n", nomeDoArquivo);
						}
						break;
					case 2:
						printf("Preservando o conteúdo original...\n");
						break;
					default:
						printf("Número inválido.\n");
				}
				break;
			case 3:
				printf("Fim do programa.\n");
				break;
			default:
				printf("Digite um número entre 1 e 3, por favor.\n");
		}
	} while (opcao != 3);
}

23/11: Estabelecimento de Metas

A partir de hoje, cada dia de aula será baseado em metas a cumprir.

Para hoje:

  • Menu interativo:
    • Opção de leitura:
      • Ler eventos do usuário: o usuário vai descrever todo o evento de forma interativa.
      • Ler eventos de um arquivo pré-existente.
    • Apresentação/gravação dos eventos:
      • Apresentar qualquer evento, previamente lido e armazenado na memória, na tela.
      • Gravar todos os eventos da memória em um arquivo.
    • Fechar o programa.

25/11: Dicionário de comandos e funções em C

// Aqui, duas bibliotecas:
// - A já conhecida stdio.h
// - A nova string.h, que manipula strings.
#include <stdio.h>
#include <string.h>


// A já conhecida função auxiliar lerLinha, que lê uma linha inteira do usuário
// até aparecer o primeiro ENTER digitado.
void lerLinha(char* linha)
{
	scanf(" %[^\n]", linha);
}


// Todo programa em C precisa de pelo menos uma função, a função principal.
int main()
{
	char frase[50];
	char fraseTratada[50];
	char * auxiliar;
	int indice;
	FILE *arquivo;

	// Pedindo para o usuário digitar um frase. Lendo a frase em seguida:
	printf("Digite uma frase (até 50 caracteres): ");
	lerLinha(frase);

	// Repetindo a frase tal e tal. No jargão do Direito, 'ipsis verbis' :-)
	printf("Repetindo  'ipsis verbis': ");
	printf("%s", frase);
	printf("\n");

	// Agora, uma forma diferente de mostrar a mesma informação.
	// Como uma string é uma sequência de caracteres, a estrutura de repetição
	// abaixo fará exatamente isso: mostrar na tela caractere a caractere, um do 
	// lado do outro enquanto não for encontrado o caractere 0 (tabela ASCII),
	// que significa final da string.
	// Letra por letra, 'ipsis literis' :-)
	// Por uma questão didática, após cada letra impressa na tela, haverá uma
	// pausa de 1/4 de segundo para mostrar que, de fato, a impressão se dá
	// caractere, um a um.
	printf("Repetindo 'ipsis literis': ", frase);
	indice = 0;
	while(frase[indice] != 0)
	{
		printf("%c", frase[indice]);
		indice = indice + 1;
		// Aguarda 250ms
		usleep(250000);
		// Força a impressão de apenas um caractere na tela.
		// Caso contrário o sistema achará melhor aguardar para imprimir um
		// "lote" maior de caracteres - atrapalhando o efeito visual...
		fflush(stdout);
	}
	printf("\n");
	
	// Para facilitar a vida do programador, há diversas bilbiotecas prontas com
	// funções bastantes úteis. A biblioteca string.h, por exemplo, foi incluída
	// na linha 2 deste programa. Com ela, é possível fazer manipulação de
	// strings ou sequências de caracteres. A função strrchr, usada abaixo, vai
	// identificar o índice (posição) onde aparece pela última vez um dado
	// caractere. Para testar, digite uma frase contendo várias palavras.
	// Apenas a última aparecerá. Somente ela estará depois do último espaço.
	printf("\n");
	printf("Repetindo apenas a última palavra: ");
	auxiliar = strrchr(frase,' ');
	auxiliar = auxiliar + 1;
	printf("%s", auxiliar);
	printf("\n");
	
	// Agora, o programa irá gravar, no arquivo chamado
	// /tmp/arquivoTemporario.txt, a frase com o prefixo: "Frase: ":	
	arquivo = fopen("/tmp/arquivoTemporario.txt", "w");
	if(arquivo)
	{
		printf("\n");
		fprintf(arquivo,"Frase: %s", frase);
		fclose(arquivo);
	}

	// O caminho de volta, ler o arquivo, é mais complicado, uma vez que é
	// preciso remover o prefixo antes adicionado, "Frase: ".
	// Uma forma de resolver o problema é utilizar um vetor auxiliar,
	// fraseTratada, cujo conteúdo será parte da primeira variável, frase.
	// O processo parece, mas não é complexo, desde que se entenda que tratar de 
	// strings estamos falando, na verdade, de uma cadeia de caracteres
	// indexadas por uma variável. A variável frase, na verdade, é um apontador
	// para a área de memória cujo conteúdo é:
	// "Frase: <a frase digitada pelo usuário no começo do programa".
	// O comando strchr varre o vetor, procurando pela primeira ocorrência de um
	// caractere em particular, no caso abaixo o espaço (' '). Ao encontrar,
	// associa este endereço de memória a outra variável, chamada auxiliar.
	// Detalhe importante: não há, até este momento, duas frases armazenadas em
	// memória, mas sim dois apontadores para locais diferentes de uma área de
	// memória. Somente depois é que há uma cópia de um vetor para outro,
	// criando duas áreas de memória, na função strcpy. No caso abaixo, uma
	// substring, ou parte da frase original, foi copiada para a nova área.
	// Na prática, significa que o trecho "iniciando" em auxiliar
	// (ponto do vetor) até o final (caractere zero) foi copiado para uma nova
	// área e atribuindo à variável fraseTratada este endereço.
	arquivo = fopen("/tmp/arquivoTemporario.txt", "r");
	if(arquivo)
	{
		fgets(frase, sizeof(frase), arquivo);
		printf("Frase retirada do arquivo: %s", frase);
		printf("\n");
	}
	auxiliar = strchr(frase,' ');
	strcpy(fraseTratada,auxiliar+1);
	printf("Frase retirada do arquivo e 'tratada': %s",
		fraseTratada);
	printf("\n");
}

No programa acima, mais especificamente nas linhas 66 a 69, há duas variáveis apontando para uma mesma área de memória, porém em pontos distintos dessa área. Mais: é possível "movimentar-se" sobre o vetor somando ou subtraindo o valor destas variáveis. Assim funciona porque o conteúdo das variáveis do tipo string são, de fato, endereços de memória: cadeia de caracteres ou bytes. Abaixo, um exemplo de uso com a frase: Aula de SOP é muito... intrigante!: <graphviz> digraph Frase { Frase [shape=Mrecord,label="<0>A|<1>u|<2>l|<3>a|<4> |<5>d|<6>e|<7> |<8>S|<9>O|<10>P|<11> |<12>é|<13> |<14>m|<15>u|<16>i|<17>t|<18>o|<19>.|<20>.|<21>.|<22> |<23>i|<24>n|<25>t|<26>r|<27>i|<28>g|<29>a|<30>n|<31>t|<32>e|<33>!"] frase [shape=record] auxiliar [shape=record] "auxiliar + 1" [shape=record] frase -> Frase:0 auxiliar -> Frase:22 "auxiliar + 1" -> Frase:23 } </graphviz>

30/11: Atividades para revisão dos conceitos de C

Construa um programa que resolva os seguintes problemas abaixo. As respostas estão em página a parte.

Manipulação de String

  • Leia uma palavra e a repita, omitindo a penúltima letra. Exemplo: o programa, ao ler a palavra
Abracadabra

retornará à tela

Abracadaba
  • Faça o mesmo que o programa anterior, desta vez para uma frase completa: omitirá a última palavra. Exemplo: ao ler a frase
Manipulação de palavras ou frases.

o programa deverá mostrar

Manipulação de palavras ou.

Interpretação da Entrada de Dados

  • Leia um número como parâmetro do programa e escreva-o na forma extensa. Exemplo: assumindo que o programa se chama conversor, execute-o com um parâmetro
./conversor 12

o qual deverá retornar

O número é doze.

Para tanto, leia números inteiro de 0 a 29. Para os demais números, informe que o número está fora dos limites estipulados.

Aplicação de Funções

  • Resolva o problema, mas desta vez utilize uma função em separado para cada dezena:
    • Uma função para converter número de 0 a 9.
    • Uma função para converter número de 10 a 19.
    • Uma função para converter número de 20 a 29.

02/12: Guia Básico de C: Funções

  • Continuação da leitura do Guia Básico de C:
    • Passagem por valor
    • Pasagem por referência

07/12: Guia Básico de C: Strings

  • Continuação da leitura do Guia Básico de C:
    • Função e retorno
    • String é um número: endereço de memória
    • Strings e apontadores

09/12: Revisão de C

  • Tópicos: conceitos da linguagem, análise e síntese de código

14/12: Prova de C

16/12: Recuperação

  • Problema: Criar um programa que somará, a cada número da sequência de Fibonacci, o somatório dos UIDs do sistema. O programa deverá rodar, informando as somas na tela, até atingir o número máximo de 1 bilhão.
  • O que recuperar (a próxima substitui a anterior):
    • S.O.: programar em shell script. Mostrar o consumo de recursos do processo (processador e memória).
    • Lógica: descrever uma solução em pseudocódigo e diagrama de blocos.
    • Programação: programar em C.
  • Uma proposta de solução:
#include <stdio.h>
#include <string.h>

int pegaUID(char * linha)
{
	char * token;
	char uid[10];
	int indice;
	
	// Primeiro "corte": token apontará para o primeiro ":" da linha,
	// entre o nome do usuário e a letra "x" (senha oculta) 
	token = strchr(linha, ':');
	if (token != NULL)
	{
		// Segundo "corte": token apontará para o segundo ":" da linha,
		// entre a letra "x" (senha oculta) e o UID do usuário.
		token = strchr(token+1, ':');
		if (token != NULL)
		{
			// O número está, portanto, entre posição token+1 em diante até o próximo ":"
			token = token + 1;
			// É feita uma cópia dos algarismos para uma variável nova: "uid".
			indice = 0;
			while(token[indice] != ':')
			{
				uid[indice] = token[indice];
				indice++;
			}
			// "Termina" a string uid com o zero:
			uid[indice] = '\000';
			// Retorna o número do UID e encerra a função neste ponto.
			// Lembrando que uid é uma string, tornando obrigatória a conversão de tipo:
			return atoi(uid);
		}
		else
		{
			// Se falhar (a linha não se refere a um usuário), retorna zero
			// para não atrapalhar a soma dos UIDs de todos os usuários.
			return 0;
		}
	}
	else
	{
		// Se falhar (a linha não se refere a um usuário), retorna zero
		// para não atrapalhar a soma dos UIDs de todos os usuários.
		return 0;
	}
}	

int main()
{
	float a = 0;
	float b = 1;
	float b_original;
	float limite = 1000000000;
	FILE *etc_passwd;
	char linha[255];
	int somaUIDs = 0;
	
	// Abre o arquivo. Se a operação de abertura retornou sucesso, 
	if(etc_passwd = fopen("/etc/passwd", "r"))
	{
		do
		{
			// Lê do arquivo enquanto houver linhas.
			fgets(linha, sizeof(linha), etc_passwd);
			// A cada linha, soma-se o UID. 
			somaUIDs = somaUIDs + pegaUID(linha);
		} while(!feof(etc_passwd));
		fclose(etc_passwd);
    }
	else
	{
	        printf("Erro: não foi possível abrir o arquivo de usuários para leitura.\n");
		// Encerra a aplicação com valor de retorno negativo
		return -1;
	}
	
	// Aqui há uma preocupação na apresentação dos dados.
	// Como os números são do tipo "float", por causa do limite máximo (1 bi),
	// é preciso ignorar a parte decimal e apresentar apenas a parte inteira,
	// usando neste caso o formato %.0f 
	printf("A soma dos UIDs de todos os usuários mais a sequência de Fibonacci é:\n");
	printf("%d + %.0f = %.0f\n", somaUIDs, a, a + somaUIDs);
	while ( b + somaUIDs <= limite )
	{
		printf("%d + %.0f = %.0f\n", somaUIDs, b, b + somaUIDs);
		b_original = b;
		b = a + b;
		a = b_original;
	}
}



Voltar para página principal da disciplina