PRG29002 - Programação I - Eng.Telecom 2016-2

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

Professor da Disciplina: Cleber Jorge Amaral
e-mail: cleber.amaral@ifsc.edu.br
Monitoria: Programa_de_monitoria_dos_cursos_superiores_de_Telecomunicações

Ementa de PRG29002

Critérios e instrumentos de avaliação

  • Conceitos numéricos entre 0 e 10. 0 é reservado para alunos com frequência insuficiente
    • N1 = Prova teórica sobre pseudocodigo e fluxograma (sem apoio de computador)
    • N2 = Prova prática sobre C
    • N3 = Apresentação de projeto de desenvolvimento em C (Avaliação do projeto = 30% e da performance do aluno na apresentação = 70%)
    • Recuperação realizada após cada prova e do trabalho uma reapresentação que valerá no máximo conceito 9.
    • Média = (N1+2*N2+N3)/4
  • Ter 75% de frequência.

Datas importantes

  • 19/09 - Prova 1: Lógica de algoritmos em fluxograma e pseudocódigo
  • 03/10 - Recuperação da prova 1
  • 21/11 - Prova 2: Prática em linguagem C
  • 05/12 - Recuperação da prova 2
  • 12/12 e 16/12 - Apresentação do projeto final
  • 19/12 - Possivel recuperação do projeto final

Eventos da área de desenvolvimento

  • Outubro de 2016 - QCon Rio de Janeiro QConRio
  • Outubro de 2016 - The Developers Conference Porto Alegre TDC
  • Março de 2017 (previsto) - Arduino Day São Paulo ArduinoDay
  • Abril de 2017 - Qcon São Paulo QConSP
  • Maio de 2017 (previsto) - The Developers Conference Florianópolis TDC
  • Junho de 2017 (previsto) - JavaOne São Paulo JavaOne
  • Julho de 2017 (previsto)- The Developers Conference São Paulo TDC

Material de aula

Diário de aula

Introdução aos algoritmos utilizando fluxograma

Aula inaugural e introdução aos algoritmos
  • Apresentada ementa através dos slides disponibilizados.
  • Introduzido conceito de algoritmo, discutido definição de problema bem definido
  • Trabalhamos um problema na forma de brainstorming que não foi bem especificado mas serviu para introduzir o conceito de abstração e deixar o pensamento livre para criar soluções de um controle eletrônico dos status das chaves do almoxarifado
    • No desenvolvimento definimos a necessidade de uma estrutura de dados (tabela) para ter o cadastro das chaves que devem ser controladas. Estas chaves foram identificadas por um conjunto de letras único (R1, R2, CAD2 - para representar as chaves do Lab. Redes 1, Lab. Redes 2, e Lab CAD2 respectivamente como exemplo). Neste caso esta tabela ficou com apenas um campo que serve tanto de descrição como de identificação já que este conjunto de letras deve ser único.
    • Vimos que seria necessário uma estrutura na forma de um quadro (tabela) que armazenaria o estado da chave. Serve como representação virtual do estado físico do objeto. No exercício fizemos que a chave que está fora (emprestada) poderia ser expressa com estado 1, a chave presente no quadro físico com código 0. Acabamos não definindo bem como fazer mas sabemos que de alguma forma teríamos que relacionar as chaves que estão fora com uma identificação da pessoa que pegou.
    • Nesta linha vimos que também seria necessário uma estrutura para conter as pessoas que podem pegar chaves. Sem se preocupar com detalhes de permissão a quais chaves, consideramos que uma pessoa cadastrada poderia pegar qualquer chave disponível. Neste cadastro teriamos um campo de identificação, como nos limitamos a um sistema com teclado e monitor ficamos com a matricula e um outro campo contendo o nome da pessoa já que o programa teria que mostrar em tela o nome já que não é comum as pessoas memorizarem as matriculas dos colegas.
  • Ficou como exercício para casa:
    • Gerar um descritivo mais detalhado deste problema, fazendo com que fique bem definido. Neste caso, imagine-se como sendo o cliente, os detalhes que não foram especificados você mesmo deve especificar afim de que fique bem definido
    • Agora coloque-se como executor deste projeto, como você resolve o problema que você mesmo ajudou a definir?
    • Faça na forma de tabelas, diagramas ou texto mesmo, representações de como estes dados estão se relacionando
    • Em sala não tivemos tempo para definir como funcionaria a gravação do histórico das chaves, pense numa forma de controlar este processo. Já usou excel, consegue imaginar uma maneira?
    • Consegue imaginar o fluxo de operação? Digo, o programa mostrará um menu, o usuário entrará com uma informação, o programa então mostra certa mensagem... como são os ciclos de empréstimo e devolução? Como funcionaria a consulta de histórico? E o cadastro de chaves e pessoas?
Algoritmos - fluxogramas
  • Concluindo a análise do problema das chaves, uma descrição que proponho é: Desenvolver um programa em C para registrar os empréstimos e devoluções de chaves do almoxarifado de tele. Deve ser possível cadastrar as chaves (através de um apelido: R1, R2, CAD2...), cadastrar as pessoas autorizadas a pegar as chaves (matricula e nome) e mostrar um quadro em tela que exibe o estado atual dos empréstimos. Deve enfim possuir uma função que mostra o histórico de emprestimos e devoluções. Este problema agora pode ser perfeitamente descrito em um algoritmo, não? Vejamos como poderia ficar a interface:
    • Exemplo de Menu principal do programa
- - - - - - - - - - - - - - - - - - - - - - - 
Estado atual das chaves:
R1 = Emprestada (João)
R2 = Disponível
CAD2 = Emprestada (Pedro)
CAD3 = Disponível
- - - - - - - - - - - - - - - - - - - - - - - 
1: pegar/devolver uma chave
2: cadastro de chaves
3: cadastro de pessoas
4: ver histórico de empréstimos
5: sair do programa
Digite a opção ______
  • Exemplo da opção 1 (pegar/devolver)
Digite sua identificação: ___
Digite o nome da chave a pegar ou devolver: ____
  • Exemplo da opção 2 (cadastro de chaves)
As chaves atualmente cadastradas são: R1, R2, CAD2, CAD3
Digite um nome existente para deletar ou um novo nome para criar uma nova: ____
  • Agora que temos um exemplo de um problema bem definido.
  • Vimos antes que uma consulta como "Qual a melhor maneira de vir do IFSC do centro até o IFSC de São José" não seria bem definida. Há vários detalhes que não estão declarados.
  • Outro exemplo, imagine como poderia ser desenvolvido um algoritmo que tem a missão de realizar a distribuição de móveis em uma casa. Esta é uma tarefa complexa, muitos até contratam arquiteto e decorador para esta tarefa pois envolve preferencias, requisitos de circulação, utilização de pontos de hidráulica, elétrica, uso de eletrodomésticos, enfim, são muitos detalhes técnicos e de gosto do usuário.
  • Problemas desta natureza precisam de técnicas de inteligencia artificial como "Machine Learning" que dá a habilidade a máquina de aprender sem ter sido explicitamente programada para aquela tarefa. Eles vão aprendendo com a massa de dados que vai sendo gerada e pelo comportamento do usuário que está operando o software. Com o tempo ele "aprende" que o usuário costuma se deslocar de carro, por exemplo, que pelo seu histórico costuma se deslocar em determinados horários, por sua agenda do Google planeja ir a determinado local, enfim.
  • Introdução ao fluxograma
    • Ver o vídeos e desenvolver em sala os algoritmos na forma de fluxogramas
    • Desenvolver um algoritmo na forma de fluxograma que lê uma nota que varia de 0 a 10, e imprime em tela "Aluno aprovado" ou "aluno reprovado" sabendo que são aprovados alunos com nota >= 6.0
    • Desenvolver um algoritmo na forma de fluxograma que lê três temperaturas e exibe em tela a menor delas. PDF com o fluxograma pronto
  • Exercícios:
    1. Desenvolve um algoritmo na forma de fluxograma para cálculo da média de dois números fracionários exibindo o resultado
    2. Desenvolva um algoritmo na forma de fluxograma que calcula a média de consumo de um veículo. Deve obter a distância que um veículo percorreu e a quantidade de litros consumida, exibindo em tela o consumo médio em km/l.
    3. Desenvolva um algoritmo na forma de fluxograma que lê dois valores inteiros e apresenta qual é o valor maior
    4. Desenvolva um algoritmo na forma de fluxograma que lê dois valores inteiros e apresenta a diferença do maior para o menor
    5. Desenvolve um algoritmo na forma de fluxograma que obtém a nota do aluno e mostra em tela o conceito sabendo que A se for >= 9.0, B >= 7.5 e < 9.0, C >= 6.0 e < 7.5 e D < 6.0
Fluxogramas
  • Resolver um ou dois dos exercícios da aula passada
  • Desenvolver o fluxograma para o algoritmo de controle das chaves do almoxarifado

Pseudo-código

Pseudo-código utilizando Portugol - Introdução e condicionais

Ver exemplos de códigos Portugol dentro do software portugol (menu Arquivo->Abrir exemplo)

Exercícios:

  1. Desenvolva um algoritmo na forma de pseudocódigo que obtenha três números inteiros distintos e escreva na tela os valores do menor para o maior. (id:0.10)
  2. Implemente um pseudocódigo que converte “celsius para fahrenheit” ou “fahrenheit para celsius” conforme opção que deve ser apresentada pelo algoritmo. (id:0.09)
  3. Implemente por fluxograma e pseudocódigo um controlador de acesso que verifica se a senha digitada é igual a senha padrão “1234” abrindo a porta. O sistema deve sinalizar tanto no caso de acesso liberado ou negado. (id:0.07)
  4. Desenvolva um algoritmo na forma de pseudocódigo que obtenha duas datas fornecidas pelo usuário (três números inteiros cada: dia, mês e por último ano com 4 dígitos) e mostra como resultado qual das duas datas é cronologicamente maior. (id:0.13)
  5. Implementar em pseudocódigo um algoritmo que através do valor ôhmico dado diz qual é a sequencia de cores de um resistor. Considere apenas resistores de 4 bandas de cores incluindo a tolerância, porém neste exercício ignore a tolerância. Desconsidere resistores de precisão, aqueles com multiplicadores ouro e prata. Considere que o multiplicador máximo é de 10MOhm (violeta). (id:0.12)
  6. Implementar em pseudocódigo um algoritmo que através de uma sequencia de cores de um resistor dado dá o valor ôhmico do resistor. Considere apenas resistores de 4 bandas de cores incluindo a tolerância, porém neste exercício ignore a tolerância. Desconsidere resistores de precisão, aqueles com multiplicadores ouro e prata. Considere que o multiplicador máximo é de 10MOhm (violeta). (id. 0.17)
  7. Implemente em pseudocódigo o algoritmo descrito no fluxograma abaixo (clique na imagem para ampliar) (id:0.08):
    Bhaskara.png
  8. Implementar em pseudocódigo algoritmo que imprime em tela o ranking de países através das medalhas obtidas nas olimpíadas. Sabendo que fica melhor colocado no ranking o país que soma mais medalhas de ouro, caso haja empate aquele que soma mais prata entre os empetados e caso haja empate na quantidade de prata também será melhor colocado no ranking o que tiver mais bronzes. O algoritmo deve ser capaz de armazenar e ranquear três países. Dica: crie as variáveis paisA_ouro, paisA_prata e paisA_bronze como numéricas para armazenar as medalhas obtidas pelo país A, da mesma forma para o país B e país C. Você pode limitar a quantidade de medalhas total e determinar pesos para obter um valor absoluto de pontuação. (id:0.18)
  9. Implemente um algoritmo em pseudocódigo que informa ao usuário se ele é obrigado ou não a fazer sua declaração de imposto de renda. O algoritmo deve solicitar ao usuário que responda sim ou não para as seguintes questões, sendo que o contribuinte deve declarar se se enquadrar em uma delas ou conforme descreve as condições (o texto está resumido, caso de dúvidas ver fonte):
    1. Recebeu RENDIMENTOS TRIBUTÁVEIS superior a R$ 28.123,91
    2. Recebeu RENDIMENTOS ISENTOS superior a R$ 40.000,00
    3. OBTEVE GANHO DE CAPITAL NA ALIENAÇÃO DE BENS ou BOLSAS DE VALORES
    4. Caso relacionado a ATIVIDADE RURAL: obteve receita superior a R$ 140.619,55 ou pretenda compensar, PREJUÍZOS de anos anteriores
    5. Tem POSSE total superior a R$ 300.000,00, estando dispensado se seus bens estiverem lançados na declaração do cônjuge
    6. Passou à CONDIÇÃO DE RESIDENTE no Brasil em qualquer mês
    7. Quem optou pela ISENÇÃO DO IMPOSTO SOBRE A RENDA INCIDENTE SOBRE O GANHO DE CAPITAL auferido na VENDA DE IMÓVEIS RESIDENCIAIS
  10. Implemente um algoritmo que solicita ao usuário entrar com 10 temperaturas, o algoritmo deve usar um laço de repetição para isso. No final deve mostrar a maxima, minima e média das temperaturas (id:0.16)
  11. Desenvolva em pseucodigo um algortimo que testa o vencedor de uma rodada de truco. A sequencia das cartas esta descrita na imagem abaixo bem como dos naipes franceses. Além da sequencia ali descrita, uma carta será sorteada e fará com que se torne a mais forte (manilha) aquela de força imediatamente superior sorteada (no caso da carta sorteada ser um 3 a manilha é o 4). A rodada é vencida numa disputa de melhor de três. As manilhas nunca empatam, neste caso prevalesce o naipe conforme sequencia. No caso de empate no último turno vence a rodada quem venceu o primeiro turno. Na ocorrência de empate nos três turnos vence a rodada quem foi o primeiro a jogar.
    Truco.jpg

Proposta de solução para o exercício 1. Por: Guilherme dos Santos Januário: Observar que este algoritmo falha em algumas condições de entrada

algoritmo declare n1, n2, n3, m numerico

escreva "Digite três números:" leia n1, n2, n3 se n1 > n2 e n1 > n3 e n3 > n2 entao

 escreva n1, ", ", n3, ", ", n2 

se n2 > n1 e n2 > n3 e n3 > n1 entao

 escreva n2, ", ", n3, ", ", n1

se n3 > n2 e n3 > n1 e n1 > n2 entao

 escreva n3, ", ", n1, ", ", n2

se n1 > n2 e n1 > n3 e n2 > n3 entao

 escreva n1, ", ", n2, ", ", n3 

se n2 > n1 e n2 > n3 e n1 > n3 entao

 escreva n2, ", ", n1, ", ", n3

se n3 > n2 e n3 > n1 e n2 > n1 entao

 escreva n3, ", ", n2, ", ", n1

fim_algoritmo. </syntaxhighlight>

Uma outra proposta de código para o problema 1: algoritmo

 declare n1, n2, n3 numerico
 escreva "Digite três números:"
 leia n1, n2, n3
 se n1>=n2 e n1>=n3 entao
   se n2>=n3 entao
     escreva n3, "-", n2, "-", n1
   senao 
     escreva n2, "-", n3, "-", n1
 senao se n2>=n1 e n2>=n3 entao
   se n1>=n3 entao
     escreva n3, "-", n1, "-", n2
   senao 
     escreva n1, "-", n3, "-", n2
 senao
   se n1>=n2 entao
     escreva n2, "-", n1, "-", n3
   senao 
     escreva n1, "-", n2, "-", n3

fim_algoritmo. </syntaxhighlight>

Observe que os dois algoritmos apresentados acima possuem falhas em algumas condições caso as entradas possuam números iguais

Pseudo-código utilizando Portugol - repetições

Exercícios - série 1

  1. Implemente um algoritmo que solicita ao usuario entrar com um numero negativo qualquer. O algoritmo deve mostrar todos os números impares e pares de 0 até este negativo indicado. Utilize a estrutura para, verifique qual o passo adequado para resolver esta questão. (id:1.07)
  2. Desenvolva uma algoritmo para marcar o placar de um jogo de futebol, deve solicitar ao usuário digitar A ou B, ao digitar A é somado um gol a equipe A e o mesmo para a B. Se digitado F deve encerrar e mostrar o placar final. (id:1.11)
  3. Implemente um algoritmo que obtém um número do usuário e utilizando laço para verifica se um número primo. Valide seu algoritmo comparando com a lista de primos Lista de números primos (id:1.14)
  4. Implemente um algoritmo que obtém dois números inteiros positivos do usuário e utilizando laço de repetição realização a multiplicação destes dois números através de operações de soma. (id:1.09)
  5. Implemente um algoritmo que obtém dois números inteiros positivos do usuário e utilizando laço de repetição realização a divisão destes dois números através de operações de subtração. (id:1.10)
  6. Faça um algoritmo que apresente a sequencia de Fibonacci dado um valor “n” que representa a quantidade de números em série que se deseja exibir
  7. Implemente um algoritmo que utiliza uma estrutura "PARA" afim de desenhar uma moldura com 10 colunas e "n" linhas. A quantidade de linhas "n" deve ser obtida do usuário. Se o usuário digitar 6 ficaria conforme mostrado a seguir:

XXXXXXXXXX X X X X X X X X XXXXXXXXXX </syntaxhighlight>

Exercícios - série 2

  1. Rode e teste o exemplo do "Quadro 42 - Exemplo de uso de vetores em Portugol" do TCC de Antônio Medeiros, compare com a implementação de teste de números primos feito na lista anterior. Com o uso de vetores o que mudou?
  2. Implemente um algoritmo que solicita do usuário 10 números, após a leitura mostra ao usuários os números na ordem inversa em que foram digitados
  3. Escreva um algoritmo que solicita ao usuário digitar 6 números para uma aposta na megasena. O algoritmo deve utilizar a estrutura repita, gravar em variaveis distintas cada número que deve estar entre 1 e 60. Deve garantir que os 6 números são diferentes entre si e no final mostrar os números digitados
  4. Implemente o pseudocódigo de uma calculadora que realiza operações de soma ou subtração de dois números. A calculadora deve operar em um laço infinito encerrando sua operação se o usuário digitar "q"
  5. Implemente o pseudocódigo de um programa que solicita o nome e idade de 5 pessoas gravando em dois vetores distintos (um literal e outro numérico). No final imprime em tela o indice de memória de cada usuário e seus dados como no exemplo a seguir "O nome do usuário '1' é 'Joao' e tem 40 ano(s)" (Neste exemplo o usuário digitou nome: Joao, Idade: 40). Utilize estrutura de repetição REPITA.
  6. Implemente o pseudocódigo de um programa que utiliza uma matriz para armazenar informações NUMERICAS de um estoque de produtos. Cada linha desta matriz representa um produto, usaremos o limite de 5 produtos para este exercicio. A matrriz deve ter 3 colunas, a primeira representará a quantidade em estoque, a segunda o valor de custo e a terceira o preço de venda. O programa deve apresentar uma entrada de dados para receber do usuário parâmetros destes 5 produtos. No final um relatório deve ser apresentado mostrando a quantidade em estoque e o lucro bruto em % que está sendo aplicado para cada produto. Utilize estrutura de repetição ENQUANTO.
  7. Implemente em pseudocódigo um programa que obtém o nome de 10 alunos e suas 4 notas para cada aluno. No final deve imprimir um relatório mostrando cada aluno por nome, a média de suas notas e sua situação final (aprovado se a média for >= 6). Utilize vetores, matrizes e estruturas de repetição PARA.

Exercícios complementares:

  • Sessão 12.5 (página 294) de Araújo, Everton Coimbra de. Algoritmos: fundamento e prática; 3ª ed. [S.l]:Visual Books, 2007. 414p. ISBN 9788575022092.
Pseudo-código utilizando Portugol - sub-rotinas e registros

Exercícios

  1. Execute o código do TCC do Antonio Medeiros, "Quadro 49 - Exemplo de passagem de parâmetros por referência em Portugol"
  2. Execute o código do TCC do Antonio Medeiros, "Quadro 50 - Exemplo de uso de sub-rotina que retorna valor em Portugol"
  3. Implemente um algoritmo que realiza análises combinatórias pelo uso de uma sub-rotina de cálculo de fatorial. Neste caso o usuário deve entrar com um inteiro e o algoritmo deve retornar o número de possíveis combinações para aquele dado.
  4. Implemente um algoritmo que utilizando as sub-rotinas pré-definidas do portugol (seno e cosseno) criando outras sub-rotinas para cálculos da tangente, arco-seno, arco-coseno e arco-tangente. Neste caso utilizando angulos em radianos
  5. Implemente um algoritmo anterior agora aceitando angulos em graus
  6. Execute o código do TCC do Antonio Medeiros, "Quadro 56 - Exemplo de uso de vetor de registros em Portugol"
  7. Desenvolva uma algoritmo para marcar o placar de um jogo de futebol, deve solicitar ao usuário digitar o nome das equipes A e B, guardando esta informação em campos de registros bem como deve possuir campos para armazenar o placar de cada equipe. Depois que o usuário digitar os nomes deve aguardar o anúncio de gols. Ao digitar A é somado um gol a equipe A e o mesmo para a B. Se digitado F deve encerrar e mostrar o placar final mostrando os nomes das equipes.
  8. Implemente o pseudocódigo de um programa que solicita o nome e idade de 5 pessoas gravando em uma estrutura de dados tipo registro com dois campos (um literal e outro numérico). No final imprime em tela o indice de memória de cada usuário e seus dados como no exemplo a seguir "O nome do usuário '1' é 'Joao' e tem 40 ano(s)" (Neste exemplo o usuário digitou nome: Joao, Idade: 40).

Parte da implementação do problema das funções trigonométricas algoritmo

 declare angulo,s,c,t numerico
 escreva "Digite um angulo: "
 leia angulo
 s <- arredonda(seno(angulo))
 escreva "O seno do angulo ",angulo," é: ",s
 c <- arredonda(cosseno(angulo))
 escreva "O cosseno do angulo ",angulo," é: ",c
 t <- arredonda(tangente(angulo))
 escreva "O tangente do angulo ",angulo," é: ",t

fim_algoritmo

sub-rotina tangente (x numerico)

 declare aux numerico
 aux <- seno(x) / cosseno(x)
 retorne aux

fim_sub_rotina tangente </syntaxhighlight>

Introdução ao C

Introdução ao C e funções de saída e entrada de dados
  • Introdução
    • C e Java lideram os principais rankings de popularidade, sendo o C a linguagem mais popular no desenvolvimento de sistemas embarcados
    • Um programa em C é composto por um conjunto de Funções.
    • A função pela qual o programa começa a ser executado chama-se “main()”.
    • Após cada comando em C deve-se colocar um ; (ponto-e-vírgula).
    • É uma linguagem “tipada”, ou seja, os dados precisam ter tipos definidos
    • Possui estruturas diversas de fluxo e controle como “if… else”, “Switch case”, etc.
  • Código básico de um programa C
    • Observar que sempre deve existir uma função main()
    • O retorno desta função por padrão C90 (gcc) será um int (mesmo se for omitido)
    • As chaves "{" e "}" representam o início e fim de um bloco de instruções
    • Os caracteres "/*" e "*/" representam o início e fim de um bloco de comentários
      int main()
      {
        /*instruções*/
      }
      
  • Preparando o ambiente
    • Como precisaremos digitar alguns comandos, vamos utilizar o terminal do linux, abra portanto o terminal
    • Por padrão o linux inicia na pasta do usuário, ficará algo assim:

aluno@sj-redes1-d1:~$ </syntaxhighlight>

    • Observe a composição é usuário@computador:pasta
    • Neste caso “~$” representa que o usuário está em sua pasta home
  • Criando uma pasta para seus projetos
    • Você pode criar uma pasta via explorador de arquivos no modo gráfico
    • Via terminal conforme segue executando os seguintes comandos para criar e entrar na pasta do usuário:

~$ mkdir ExerciciosC ~$ cd ExerciciosC </syntaxhighlight>

    • Observe que agora o terminal exibe algo como:

aluno@sj-redes1-d1:~/ExerciciosC$ </syntaxhighlight>

  • Editando um arquivo .c
    • O arquivo “.c” é o código-fonte de nosso projeto, é onde digitaremos o código na linguagem C. Trata-se de um arquivo texto simples, porém respeitando a sintaxe do C.
    • Para editar o arquivo “.c” podemos utilizar qualquer editor como o “gedit” do linux que é bem parecido com o “bloco de notas” do windows.
    • Depois de criar o arquivo precisaremos compilar este código para transformá-lo em executável e finalmente poder rodá-lo, para compilar utilizaremos o compilador gcc do linux.
  • Passo-a-passo criando o OlaMundo.c
    1. Abra o gedit com um texto em branco e salve em sua pasta de projetos com o nome “OlaMundo.c”
    2. Digite dentro do arquivo em branco criado o seguinte código:
      #include <stdio.h>
      main()
      {
        printf("Olá Mundo!\n");
      }
      
    3. Certifique-se de salvar o arquivo “OlaMundo.c” após as alterações
    4. No terminal dê um comando ls para listar os arquivos do diretorio, como resultado deve ser exebido o arquivo OlaMundo.c que você criou no gedit

~/ExerciciosC$ ls OlaMundo.c </syntaxhighlight>

    1. No terminal compile o código através do gcc. Neste exemplo a pasta “ExerciciosC” foi criada para gravar o projeto, do gedit foi salvo nesta pasta o programa “OlaMundo.c”. Como resultado nenhuma mensagem deve ser exibida, o terminal simplesmente irá ficar pronto para um novo comando, isso significa que compilou com sucesso (sem erros)

~/ExerciciosC$ gcc OlaMundo.c -o OlaMundo </syntaxhighlight>

    1. Dê um novo comando ls para listar os arquivos do diretorio, como resultado deve ser exibido dois arquivos, o OlaMundo.c e agora o OlaMundo, um arquivo executável criado pelo compilador

~/ExerciciosC$ ls OlaMundo OlaMundo.c </syntaxhighlight>

    1. Execute OlaMundo através do comando a seguir. Observe a mensagem “Olá Mundo!” exibida no terminal.

~/ExerciciosC$ ./OlaMundo Olá Mundo! </syntaxhighlight>

  • Analisando o programa OlaMundo
    • Observe que foi realizada uma declaração antes da função main. Isto é necessário para utilização do comando de impressão em tela, o printf utilizado abaixo.
  1. include <stdio.h> </syntaxhighlight>
    • Observe que não foi definido um retorno para main, o compilador deverá tratar esta função com retorno int
    • A instrução realizada em código é de impressão em tela, neste caso a tela (terminal visualizado pelo monitor) é a saída padrão (standard)

printf("Olá Mundo!\n");</syntaxhighlight>

    • Enfim, observe que não foi especificado um retorno, do tipo “return”. Isso faz com que o retorno deste programa seja indefinido
  • Identificadores
    • São os nomes que o programador dá a suas variáveis, constantes e funções
    • Deve sempre iniciar com uma letra ou “_” (underscore )
    • A partir do segundo caracter pode também conter números
    • A linguagem não suporta caracteres especiais como letras acentuadas
    • Identificadores não podem ser escritos com espaço, exemplo “buscarCodigo()”, não pode ser escrito como “buscar codigo()”
    • A linguagem C é case-sensitive. Por exemplo, as variaveis “numero”, “Numero” e “NUMERO” são endereços diferentes
    • Deve ter no máximo 31 caracteres (compatível com TurboC)
  • Boas práticas quanto a identificadores
    • O uso de nomes auto-explicativos facilita a compreensão e manutencão futura
    • É comum variar maiúsculas e minúsculas para facilitar a leitura como “QtMedidas”, “ValorMedio”
  • Variáveis
    • O que é uma variável? - Khan Academy
    • Por uma questão de eficiência de uso de memória e processamento o C possui diversos tipos de variáveis, vamos agora trabalhar com alguns deles que servirão para praticamente todas as nossas necessidades
    • char: ocupa 1 byte na memória e varia de -127 a +127
    • int: ocupa 4 bytes e varia de -2.147.483.648 a +2.147.483.647
    • double: ocupa 8 bytes e possui dez dígitos de precisão
    • char[]: esta é o mesmo char descrito acima mas aqui simbolizando uma cadeia/vetor de caracteres (string)
    • Apenas para conhecimento neste momento, há outros tipos como short, float e long double e os tipos que não são de precisão podem ainda ser signed ou unsigned
    • Como funciona a memória de computadores? - Khan Academy
  1. Crie o arquivo através do pico “nome do arquivo.c”

~/ExerciciosC$ pico UsaVariavel.c </syntaxhighlight>

  1. Digite dentro do arquivo em brando criado o seguinte código:
    #include <stdio.h>
    main()
    {
        int x; /* declaração de uma variável inteira */
        x=5;/* atribuindo o valor 5 (constante) a variável x */
        printf ("O valor de x é %d\n",x);
    }
    
  2. Compile e execute
  • Continuação da teoria
    • C é uma linguagem “compilada”, ou seja, de um código fonte (escrito em C) são gerados códigos de máquina formando um ou mais arquivos executáveis e inteligíveis apenas para o computador
    • Há diversos compiladores e estes podem ter algumas diferenças de comportamento e aceitarem diferentes parametrizações
    • Um código é compilado para um sistema operacional específico e uma arquitetura de processador, portanto, um código compilado para um S.O. não tem qualquer garantia de funcionamento em outros sistemas. Da mesma forma um código que roda em um PC, não tem qualquer garantia de rodar em outras arquiteturas diversas
    • Em oposição ao código compilado temos o código interpretado
    • Sempre que um código fonte é modificado se faz necessário nova compilação para que as modificações façam efeito na execução
    • As variáveis que serão utilizadas pelo programa devem ser listadas antecipadamente
    • A linguagem C tem um conjunto de palavras reservadas, que não podem ser utilizadas para outro propósito se não o que está definido na estrutura da linguagem
      • Exemplos: break, case, if, for, while, return,...
    • O C permite que trabalhemos com bibliotecas (lib) que são conjuntos de funções que realizam certas tarefas
    • Além de podermos criar nossas próprias bibliotecas com funções úteis que podemos reutilizar em vários programas, também podemos nos apropriar de diversas libs já desenvolvidas, sejam padrão ANSI (libc) ou não, desta forma não precisamos “reinventar a roda” e já sair de largada com várias funcionalidades
      • Exemplos: <stdio.h>, <math.h>, <complex.h>, <float.h>, <string.h>, etc. (são 24 padrão ANSI no total)
  • Entendendo a compilação
    • Edição: atividade feita pelo programador
    • Preprocessamento: compilador processa o código e ignorando comentários, fazendo associações de constantes e controle de código através de diretivas especiais de compilação
    • Compilação: criação do código-objeto, é a tradução da linguagem C em linguagem de máquina
    • Linkagem: associação de diferentes código-objeto e bibliotecas
    • Carregamento: carrega o programa em memória
    • Execução: cpu realiza a execução das instruções passo a passo, armazenando os resultados em memórias definidas pelo programa e pilhas de dados para controle
  • Comentários
    • Como vimos podemos incluir no programa fonte textos livres que ajudam na compreensão do código
    • Os comentários são ignorados pelo compilador, não se tornam código de máquina
    • Para incluir comentários inicie com /* digitando então o comentário aqui e terminando com */
      • Este formato permite que digitemos varias linhas de comentários, normalmente é utilizado para textos mais extensos
    • A maioria dos compiladores também aceita o formado //comentário, que serve para incluir um comentário de apenas uma linha, apenas os caracteres depois do // serão ignorados e neste caso o terminador é o sinal de nova linha que normalmente está oculto
  • Operadores aritméticos
    • “+” adição
    • “-” subtração
    • “*” multiplicação
    • “/” divisão
    • “%” resto da divisão
  • Por padrão, multiplicações e divisões são operadas antes de somas e subtrações
  • Devemos utilizar parênteses para agrupar operações e definir a sequencia mais adequada. O compilador vai sempre resolver o que está dentro dos parênteses primeiro, de “dentro para fora” quando houver mais de um nível
    • Exemplos
      • 1+2*3 = 7 é o mesmo que 1+(2*3)
      • (1+2)*3 = 9
      • 1+2*3+4*5 = 27 é o mesmo que 1+(2*3)+(4*5)
      • (((1+2)*3)+4)*5 = 65
  • Escrevendo mensagens na tela (saída de dados)
    • A função printf da lib stdio é bastante completa para esta tarefa, permite escrever mensagens com múltiplos argumentos.
    • Formato printf (“string de controle”, lista de argumentos);
    • Exemplo:
      • printf(“Olá Mundo!\n”);
      • printf(“Digite sua idade:\n”);
      • printf(“Sua idade é: %d”,idade);
    • Para saber mais sobre o printf e seus identificadores ver c_function_printf
  • Lendo o teclado do usuário (entrada de dados)
    • A função scanf da lib stdio é bastante útil para esta tarefa, ela aguarda que o usuário entre com uma informação e tecle [ENTER] no final.
    • Esta função é blocante, ou seja, o programa fica parado esperando a entrada de dados para então dar continuidade a execução
    • Formato scanf (“string de controle”, lista de argumentos);
    • Exemplo:
      • scanf(“%d”,&idade);
  • Operadores relacionais e lógicos
    • Relacionais
      • > maior que, ex.: Se (i > j) printf(“i é maior que j”);
      • >= maior ou igual que, ex.: Se (i >= j) printf(“i é maior ou igual a j”);
      • < menor que, ex.: Se (i < j) printf(“i é menor que j”);
      • <= menor ou igual que, ex.: Se (i <= j) printf(“i é menor ou igual a j”);
    • Igualdade
      • == igual a, ex.: Se (i == j) printf(“i é igual a j”);
      • != diferente de, ex.: Se (i != j) printf(“i é diferente de j”);
    • Lógicos
      • && Lógica E (AND), ex.: Se (i > j) && (i > 0) printf(“i é maior que j e positivo”);
      • || lógica OU (OR), ex.: Se (i > j) || (i == 0) printf(“i é maior que j ou é igual a zero”);
      • ! Lógia negação (NOT), ex.: Se !(i > j) printf(“i não é maior que j”);
  • Atividade
    • Ler apostila de C da UFMG
  • Exercícios
    1. Implemente um programa em C que calcula a média de dois números reais digitados pelo usuário e imprime em tela a resposta deste cálculo
    2. Implemente um programa em C que recebe “a”, “b” e “c”, calcula e exibe o delta (delta = b*b-4ac).
    3. Implemente um programa em C que calcule a Potência dissipada por uma carga dados V e I.
    4. Implemente um programa em C que calcula a resistência R dados P e I.
    5. Implementar um programa C para converter um ângulo em radianos para graus.
    6. Implementar um programa C para converter um ângulo em graus para radianos.

Controle de fluxo em C

Condicionais em C
  • A declaração “if (expressão) corpo”
    • Permite o programa escolher por duas alternativas, executando o procedimento presente no corpo ou não
      • O parênteses é obrigatório
      • A expressão pode conter múltiplos testes
      • “if” se escreve com letras minúsculas
    • O corpo com múltiplos comandos deve ficar dentro de {chaves}
    • Exemplos:
        if (i > 0) printf(i é maior que zero);
      
        if ((i > 0) && (j == -1)) {
        j = i;
        printf(o novo valor de j é %d, j);
      }
      
  • A cláusula “else”
    • Permite o programa escolher por duas alternativas, executando apenas o conteúdo do corpo do if ou o conteúdo do do eles

if ( expressão ) corpo_if else corpo_else</syntaxhighlight>

    • Mesmas regras citadas para o if, observe também que os comandos sempre terminam com ;
    • Exemplos:
      if (i > j)
      max = i;
      else
      max = j;
      
      if (i > j)
      if (i > k) max = i; else max = k;
      else
      if (j > k) max = j; eles max = k;
      
  • If em cascata
    • É possível realizar séries de testes parando assim que uma for verdadeira.
    • Exemplo:
      if ((i >= 0) && (i < 6))
      printf(Conceito insuficiente);
      else if ((i >= 6) && (i < 9))
      printf(Conceito suficiente/proficiente);
      else if ((i >= 9) && (i <= 10))
      printf(Conceito excelente);
      else
      printf(Conceito inválido);
      
  • Vídeo
    • A estrutura mais elementar de decisão? Saloni em Code.org
    • O que é uma estrutura de decisão? Bill Gates em Code.org
  • Exercícios
    1. Implementar um programa que lê um número inteiro e imprime se o número é par ou ímpar. SUGESTÃO: Usar o operador de resto.
    2. 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 implemente em C um algoritmo que mostre as palavras "fria", "morna" ou "quente" dependendo da temperatura da água que for informada.
    3. Implementar um programa em C para ler dois números inteiros e imprime uma mensagem indicando se os números lidos são iguais ou diferentes. Caso sejam diferentes, computar a média dos mesmos.
    4. Implementar um programa para ler 4 números inteiros e imprime uma mensagem se a soma dos dois primeiros for igual ou menor que a soma dos dois últimos.
    5. Implemente um programa em C que recebe duas datas fornecidas pelo usuário (três números inteiros cada: dia, mês e ano com 4 dígitos). Deve ser calculada qual a maior data e exibi-la em tela (pesquise sobre if...else para resolver este problema)
    6. 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.
    7. Melhore o programa de cálculo de delta, e calcule as raízes de uma equação de segundo grau. Faça testes para saber se há duas raízes reais (delta > 0), apenas uma (delta = 0) ou não há raízes reais (delta < 0). Usar a função sqrtf ou sqrt de <math.h> (utilizando funções de math.h talvez seja necessário adicionar a flag "-lm" na compilação).
    8. Uma empresa irá ajustar o salário de seus funcionários de acordo com a categoria de trabalho dos funcionários: CAT A (10% de aumento), CAT B (15% de aumento) e CAT C (20% de aumento). Faça um programa que leia o plano de trabalho e o salário atual de um funcionário e calcula e imprime o seu novo salário. Use o comando switch.
    9. Faça um programa que leia um número entre 0 e 10, e escreva este número por extenso. Use o comando switch.
Estruturas de repetição em C
  • Vídeos
  • O que é uma estrutura de repetição? Chris Bosh em Code.org
  • O que é um laço de repetição? - Mark Zuckerberg Code.org
  • Como funciona a estrutura de repetição tipo contagem (for no C)? - Code.org
  • Exemplo de menu de programa
    #include <stdio.h>
     
    main()
    {
      int opcao;
      while (opcao != 2)
      {
        system("clear");
        printf("MENU\n");
        printf("0: Faz isso\n");
        printf("1: faz aquilo\n");
        printf("2: Sair\n");
    
        scanf("%d",&opcao);
        switch (opcao)
        {
          case 0://Faz isso 
            break;
          case 1://faz aquilo
            break;
          case 2: 
            printf("Saindo...\n");
            break;
          default:
            printf("Opção inválida\n");
        }
      }
    }
    
  • Exercícios
    1. Assistir os vídeos da sessão "Condicionais em C" e "Estruturas de repetição em C"
    2. Dado um número inteiro positivo, calcular a soma de todos os números inteiros compreendidos entre 0 e o número dado. Fazer uma versão com while e outra com for.
    3. Implemente um algoritmo em C que solicita do usuário 10 números, após a leitura mostra ao usuários os números na ordem inversa em que foram digitados
    4. Faça um algoritmo que apresente a sequencia de Fibonacci dado um valor “n” que representa a quantidade de números em série que se deseja exibir
    5. Desenvolva uma algoritmo em C para marcar o placar de um jogo de futebol, deve solicitar ao usuário digitar A ou B, ao digitar A é somado um gol a equipe A e o mesmo para a B. Se digitado F deve encerrar e mostrar o placar final.
    6. Implemente um algoritmo em C que obtém um número do usuário e utilizando laço para verifica se um número primo. Valide seu algoritmo comparando com a lista de primos Lista de números primos
    7. Escreva um algoritmo em C que solicita ao usuário digitar 6 números para uma aposta na megasena. O algoritmo deve utilizar a estrutura do...while, gravar em variaveis distintas cada número que deve estar entre 1 e 60. Deve garantir que os 6 números são diferentes entre si e no final mostrar os números digitados
    8. Implemente em C uma calculadora que realiza operações de soma ou subtração de dois números. A calculadora deve operar em um laço infinito encerrando sua operação se o usuário digitar "q"
    9. Usando o comando for aninhado, construa um programa que implemente a figura abaixo. A margem esquerda (margem de espaços), o caracter do desenho, o número de linhas vazadas e o tamanho horizontal da figura devem ser lidos pelo teclado. Na figura abaixo representa uma saída quando a margem esquerda é 0, o caractere do desenho é 'a', o número de linhas vazadas é 1 e o tamanho horizontal é 10

aaaaaaaaaa a a aaaaaaaaaa</syntaxhighlight>

    1. Construa um programa para desenhar a seguinte figura de forma parametrizável (dado caracter, margem, e número de linhas):

AAAAAAAAAA AAAAAAAA AAAAAA AAAA AA BB BBBBB BBBBBBBB BBBBBBBBBBB</syntaxhighlight>

Funções

Funções
  • O que é uma função? Khan Academy
  • O que é uma função - noção intuitiva - MeSalva!
  • Exercícios
    1. Faça um programa que calcula a média de 2 números float utilizando uma função “calculaMedia” que recebe estes parâmetros e retorna a resposta. Na função main deve ser declarado o comando de printf da resposta.
    2. Implemente um programa em C que calcule a Potência dissipada em um resistor e a corrente dados valores de V e R. O mesmo programa deve ser capaz de calcular a R dados P e V e R dados V e I. Faça então um menu inicial para que o usuário possa selecionar a opção de cálculo desejada. Crie diferentes funções para opção.
    3. Crie um loop infinito na função main. Implemente uma função que recebe o valor de salário e percentual de reajuste como um parâmetros de entrada para resolver o exercício: Uma empresa irá ajustar o salário de seus funcionários de acordo com a categoria de trabalho dos funcionários: CAT A (10% de aumento), CAT B (15% de aumento) e CAT C (20% de aumento). Faça um programa que leia o plano de trabalho e o salário atual de um funcionário e calcula e imprime o seu novo salário. Use o comando switch.
A função main
O programa inicia pela primeira instrução contida na função main() e também se encerra na última instrução. O retorno padrão da função main é um int que representa um código de erros reconhecidos por muitos sistemas operacionais. Se o programa terminou sua execução corretamente o retorno deverá ser 0 (zero).
 int main(void)
 {
  //Programa
  return 0;
 }
  • A omissão do retorno da função main (por padrão int) ou utilização de outro tipo de retorno pode ser feita no C99 porém desta forma o código não fica portável a alguns padrões de C e não estaria adequado ao retorno de um programa que convencionalmente é esperado por sistemas operacionais.
  • Quando o programador não especifica este retorno o compilador provavelmente estará atribuindo o retorno como int.
  • Programas que devem ficar rodando indefinidamente normalmente definem um laço infinito na função main para que esta não alcance o final do código salvo se for dado um comando de terminação, por exemplo.
A função exit

Uma alternativa a terminação do programa chegando ao fim da função main é a função exit da biblioteca <stdlib.h>. Para esta função deve-se passar um argumento inteiro que tem o mesmo significado do código de retorno da função main, portanto exit(0) representa uma terminação normal, alternativamente exit(EXIT_SUCCESS). Para representar uma terminação anormal pode-se utilizar exit(EXIT_FAILURE) ou exit(1). Há outros códigos de erro (ver [http://tldp.org/LDP/abs/html/exitcodes.html aqui) porém são pouco usuais, no geral o que se pretende é saber apenas se a operação do programa terminou de forma bem sucedida ou não.

Vetores e matrizes em C

Vetores e matrizes em C
  • Matrizes de qualquer dimensão são caracterizadas por terem todos os elementos pertencentes ao mesmo tipo de dado;
  • Vetores são matrizes unidimensionais;
  • O indexador começa sempre em zero;
  • É importante observar que o C não controla se o programador está ou não acessando uma posição de memória válida da estrutura, cabe ao programador criar proteções;
  • Declaração:
     
    tipo_da_variável nome_do_vetor [tamanho]; 
    tipo_da_variável nome_da_matriz [linhas][colunas];
    
  • Strings são vetores de chars;
  • Têm o seu último elemento como um '\0'
Declaração:
char nome_da_string [tamanho];
  • Inicialização:
    float vect [6] = { 1.3, 4.5, 2.7, 4.1, 0.0, 100.1 };
    int matrx [3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    char str [10] = { 'J', 'o', 'a', 'o', '\0' };
    char str [10] = "Joao";
    char str_vect [3][10] = { "Joao", "Maria", "Jose" };
    
  • Inicialização sem especificação de tamanho
    char mess [] = "Linguagem C: flexibilidade e poder.";
    int matrx [][2] = { 1,2,2,4,3,6,4,8,5,10 };
    
Execícios de vetores em C
  • Exercícios
    1. Implementar um programa em C para ler 10 números reais (float) para um vetor. Usar o comando while.
    2. Modificar o exercício para computar a média dos 10 números que estão no vetor.
    3. Modificar o exercício anterior para computar a quantidade de números do vetor que estão acima da média.
    4. Refazer os exercícios anteriores usando o comando for;
    5. Teste o exemplo dado na apostila da UFMG página 46 e execute a AUTO AVALIAÇÂO sugerida na página seguinte.
    6. Considere um vetor global de floats chamado vetRnd de tamanho 100. Construa uma função que inicia este vetor com 100 números randômicos entre 1 e 6. Imprima em tela os valores.
    7. Implementar uma função que recebe dois vetores de inteiros como parâmetro e o tamanho dos mesmos (suponha vetores de mesmo tamanho). A função deve retornar o número de elementos iguais comparados posição por posição. O esqueleto da função deve ser como:
      int compara_vetores(int ve1[],int vet2[], int tamanho)
      {
        int num_elementos;
      
        return num_elementos;
      }
      
      Exemplo: Para os vetores x[]={1,1,3,4,5} e y[]={1,2,3,3,5} temos três elementos iguais (nas posições 0, 2 e 4).
    8. Implemente um algoritmo em C que conta a ocorrência de cada letra (incluindo números de 0 a 9) dada uma frase. Deve ignorar se a letra foi digitada em maiúscula ou minúscula (portando somar junto). Limite 100 caracteres.
    9. Implemente um algoritmo em C que calcula a média de notas de alunos. Para isso deve solicitar a quantidade de alunos, em seguida obter o nome de cada aluno e depois recebe 5 notas por aluno (num único scanf). Usar uma matriz de float de duas dimensões global. Deve então calcular as médias e apresentar: Nomes dos alunos que ficaram em recuperação (média <6), Nome do aluno que tirou a maior média, Obs.: Criar a função calculaMedia().
    10. Utilize a função rand() e gerador de semente srand() para gerar 6 números aleatórios distintos que variam de 1 a 60 e imprima esta sequencia em tela
  • Exercícios de strings
    1. Implemente um programa em C que solicita ao usuário digitar duas palavras, o programa analisa estas palavras e diz se são ou não anagramas (palavra formada pela alteração da ordem ou transposição de letras) ex.: “America” e “Iracema”, “Roma” e “Amor”, “Semolina” e “Is no meal” são anagramas)
    2. Implemente um algoritmo que dada uma frase, apresente 3 opções: tornar todas as letras em maiúsculas, tornar todas em minúsculas ou a primeira letra de cada palavra em maiúscula e demais minúsculas (limite 100 caracteres). Faça estas operações através de cálculos utilizando como base a tabela ASCII, utilize funções distintas (tudoMaiusculo(), tudoMinusculo(), primeiraMaiuscula()), utilizando o vetor de caracteres como uma variável global.
Gerando números pseudo-aleatórios
  • Pergunta: é possivel que um computador binario utilizando apenas recursos computacionais seja puramente aleatorio?
  • Existem artifícios de se criar números que variam conforme determinadas condições como sequencias variáveis associadas a data e hora atual por exemplo, a isso chamamos de números pseudo aleatórios. Para conseguir este recurso em C podemos utilizar a função rand() da stblib associada a função srand que configura uma semente de aleatoriedade a função rand, o resultado pode ser conferido no código a seguir:
    #include <stdlib.h>
    #include <time.h>
    #include <stdio.h>
    
    int main(void) {
    
        srand( (unsigned)time(NULL) );
        printf("Numero gerado: %d\n", rand() % 10); //Gera números que variam de 0 a 9
    
        return 0;
    }
    
Tabela ASCII
  • Os computadores armazenam todos os tipos de dados na forma numérica, incluindo letras. Para apresentar textos em tela neste caso o programa em C precisa conhecer o tipo de dado que está escrito em memória, sendo um dado do tipo caractere (ou string) ele será então tratado como uma letra. O conjunto de letras, números e símbolos imprimíveis está sintetizado na tabela ASCII.
    • Para ver a tabela, acesse o link Tabela ASCII
    • Observe que as letras maiúsculas variam de 65 ('A') até 90 ('Z')
    • Observe também que as letras minúsculas estão em outra faixa, variam de 97 ('a') até 122 ('z')
    • Os números imprimíveis também tem seus representantes, variam de 48 ('0') a 57 ('9')
    • Caracteres especiais como '$', '%', '*', '+' também estão relacionados na tabela
    • A tabela também apresenta códigos de caracteres não imprimíveis como o 9 (TAB), 13 ('\r' presente no ENTER)
    • Para representações de outros caracteres pode ser necessário acessar a extensão da tabela ASCII, podendo-se obter 'Ç' e caracteres acentuados
    • A tabela que está sendo apresentada esta em acordo com o padrão ISO 8859-1 e Microsoft® Windows Latin-1, outros caracteres podem ainda ser obtidos se alterado o padrão de codificação
Vetor de tamanho variável

O vetor de tamanho variável (variable lenght array) é um recurso do C que permite que o tamanho do vetor seja definido em tempo de execução. Na prática o C irá alocar uma quantidade de memória que não precisa estar definida antes da execução. variable-lenght

Operadores e precedência

Operadores e precedência

Tipos de dados compostos

Estruturas
Estruturas

Assim como o vetor a estrutura é um conjunto de dados, mas traz uma vantagem: a possibilidade de possuir "campos" de diferentes tipos de variáveis. Por exemplo, a struct TPessoa poderia ter os campos nome (char[40]) e idade (int).

A declaração genérica da estrutura é:

struct TNome_do_tipo {
  //variável 1
  //variável 2
  //variável N
} nome_instancia;
  • Convencionalmente damos ao tipo da estrutura um nome "TNome_do_tipo", onde "T" representa Tipo e a letra seguinte também vem em maiúscula
  • nome_instancia representa a instancia de variável (do tipo struct) que será alocada em memória, esta declaração também pode ser um vetor "nome_instancia[10]", por exemplo
  • Em uma declaração é necessário ao menos definir um dos parâmetros "TNome_do_tipo" ou "nome_instancia são opcionais"
#include <stdio.h>

struct TUsuario /* struct TUsuario é o nome do tipo que está sendo criado */
{ 
  char userID[20];
  char senha[20];
} Usuario; /* aqui é definida uma variável do  tipo struct TUsuario */

struct TUsuario TabelaUsuario[20];

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

Neste exemplo, foi definido um tipo (modelo) para o registro (struct TUsuario) e foi criada uma variável chamada Usuario a partir deste tipo. Na sequência foi criada mais uma variável (um vetor de estruturas) chamada TabelaUsuario. Note que basta usar as palavras chave struct Usuario para criar novas variáveis. O tipo completo é definido uma única vez no início.

Exercícios
  1. Criar um programa que define uma struct para armazenamento do nome e das notas bimestrais de um aluno. Atualizar a estrutura usando o scanf.
  2. Alterar o programa para que ele calcule e imprima a média de cada aluno.
#include <stdio.h>

#define NUM_MAX 3

struct TAluno {
  char nome[30];
  char matricula[11];
  float b1,b2,b3,b4;
} Turma[NUM_MAX];

void print_aluno(struct TAluno aux)
{
  printf("Nome -> %s\n", aux.nome);
  printf("Matrícula -> %s\n", aux.matricula);
  printf("Bimestre 1 -> %f\n", aux.b1);
  printf("Bimestre 2 -> %f\n", aux.b2);
  printf("Bimestre 3 -> %f\n", aux.b3);
  printf("Bimestre 4 -> %f\n", aux.b4);          
}

main()
{
  int i;
  
  for(i=0;i<NUM_MAX;i++) {
  	printf("Entre com o nome do aluno\n");
  	scanf("%s", Turma[i].nome);
  	printf("Entre com a matrícula do aluno\n");
  	scanf("%s", Turma[i].matricula);
  	printf("Entre com a nota do bimestre 1\n");
  	scanf("%f", &Turma[i].b1);
  	printf("Entre com a nota do bimestre 2\n");
  	scanf("%f", &Turma[i].b2);
  	printf("Entre com a nota do bimestre 3\n");
  	scanf("%f", &Turma[i].b3);
  	printf("Entre com a nota do bimestre 4\n");
  	scanf("%f", &Turma[i].b4);
  }
  for(i=0;i<NUM_MAX;i++) {
    printf("=========== Aluno %d ============\n", i);  
  	print_aluno(Turma[i]); 
  }      
}
Copiando Estruturas

O exemplo a seguir demonstra como se pode copiar uma variável struct para outra do mesmo tipo.

#include <stdio.h>
  
struct THoras{
   int hora;
   int minuto;
   int segundo;
};

struct THoras Ontem = {2,10,57};

void main()
{
     struct THoras Hoje;
     Hoje = Ontem;

     printf("Hora hoje = %d, Minuto hoje = %d e Segundo hoje %d\n", Hoje.hora, Hoje.minuto, Hoje.segundo);
}
Estruturas dentro de estruturas

Vamos ver um exemplo com estruturas definidas dentro de estruturas:

#include <stdio.h>
  
struct TEndereco{
     char rua[50];
     char numero[10];
};

struct TCidadao{
  char nome[50];
  char cpf[20];
  struct TEndereco endereco;
  int num_filhos;
};

void main()
{
  struct TCidadao Cidadao;

  printf("Entre com o nome\n");
  scanf ("%s",Cidadao.nome);

  printf("Entre com o cpf\n");
  scanf ("%s",Cidadao.cpf);

  printf("Entre a rua\n");
  scanf ("%s",Cidadao.endereco.rua);

  printf("Entre a numero\n");   
  scanf ("%s",Cidadao.endereco.numero);

  printf("Entre com o número de filhos\n");
  scanf ("%d",&Cidadao.num_filhos);
  
}
Iniciando structs na definição

Como toda variável, é possível dar valores para uma variável do tipo struct definida no programa:

#include <stdio.h>
 
struct TEndereco {
     char rua[50];
     int numero;
};
 
struct TCidadao{
  char nome[50];
  char cpf[20];
  struct TEndereco endereco;
};
 
int main(void)
{
  //Inicializando com parâmetros em sequencia (ordem tem que ser respeitada)
  struct TCidadao CidadaoMaria = {"Maria","42342342234",{"Rua AlfaBeta",145}};
  //Inicializando com parâmetros via campo (não é necessário respeitar qualquer ordem)
  struct TCidadao CidadaoJose = {.cpf = "1234567890", .endereco.numero = 541,.nome = "Jose",.endereco.rua = "Rua GamaDelta"};

  printf("Rua do cidadao %s = %s\n", CidadaoMaria.nome, CidadaoMaria.endereco.rua);
  printf("Rua do cidadao %s = %s\n", CidadaoJose.nome, CidadaoJose.endereco.rua);
 
}
Passando estruturas como parâmetro e retornando estruturas

Se não for usado o operador "&" , um parâmetro que é estrutura será passado por cópia. Não apresentaremos agora a passagem por endereço pois necessita do conceita de ponteiro. Observe o exercício abaixo.

#include <stdio.h>
  
struct TEndereco{
     char rua[50];
     char numero[10];
};

struct TCidadao{
  char nome[50];
  char cpf[20];
  struct TEndereco endereco;
  int num_filhos;
};

void print_struct (struct TCidadao aux)
{
  printf("nome=%s cpf=%s\n", aux.nome, aux.cpf);
  printf("endereço inicial do aux %p\n", &aux);
}

void main()
{
  struct TCidadao Cidadao;

  printf("Entre com o nome\n");
  scanf ("%s",Cidadao.nome);

  printf("Entre com o cpf\n");
  scanf ("%s",Cidadao.cpf);

  printf("Entre a rua\n");
  scanf ("%s",Cidadao.endereco.rua);

  printf("Entre a numero\n");   
  scanf ("%s",Cidadao.endereco.numero);

  printf("Entre com o número de filhos\n");
  scanf ("%d",&Cidadao.num_filhos);
  
  print_struct(Cidadao);

  printf("endereço inicial do Cidadao %p\n", &Cidadao);
}
Exercícios
  1. 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.
  2. Implementar uma funcionalidade do administrador para desbloquear o usuário bloqueado.
  3. 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!
  4. Implementar na função administrar a inserção da mensagem no exercício anterior.
  5. Na solução acima criar uma função que procura usuário na tabela (já que este código é utilizado em mais do que um luga). A função deve receber o UserID a ser procurado e deve retornar um inteiro correspondente ao índice do usuário encontrado ou -1 se não for encontrado.
Unions
Union é um recurso do C que permite declarar um conjunto de dados que irá ocupar um mesmo espaço. É bastante empregado quando se deseja economizar espaço ou não se tem certeza sobre qual tipo de dado deve ser armazenado para determinada instancia. No exemplo a seguir é criada uma struct chamada TProduto e dentro destra estrutura há uma área de detalhamento do produto que é de uso genérico, para alguns produtos há campos específicos para preenchimento e outros não se tem ao certo os detalhes, portanto fica um campo de uso geral.
 
#include <stdio.h>
 
struct TRoupeiro{
  char cor[20];
  int volume;
  float peso;
};
 
struct TProduto{
  int id;
  char nome[20];
  union {
    struct TRoupeiro roupeiro;
    char descricao_generica[sizeof(int)+sizeof(float)+20];
  };
};
 
int main(void)
{
  struct TProduto vaso_decorativo = {
      .id = 2,.nome = "Vaso decorativo 1",
      .descricao_generica = "em vidro - peça única"
  };
 
  struct TProduto guarda_roupas_solteiro = {
      .id = 1,.nome = "Roupeiro 3 portas",
      .roupeiro.cor = "CZ", .roupeiro.volume = 304,.roupeiro.peso = 50.0
 
  };
  printf("nome = %s, \ndescrição = %s, \ncor = %s, \nvolume = %d, \npeso = %f\n\n\n", 
        guarda_roupas_solteiro.nome, 
        guarda_roupas_solteiro.descricao_generica,
        guarda_roupas_solteiro.roupeiro.cor,
        guarda_roupas_solteiro.roupeiro.volume,
        guarda_roupas_solteiro.roupeiro.peso
  );
  printf("nome = %s, \ndescrição = %s, \ncor = %s, \nvolume = %d, \npeso = %f\n\n\n", 
        vaso_decorativo.nome, 
        vaso_decorativo.descricao_generica,
        vaso_decorativo.roupeiro.cor,
        vaso_decorativo.roupeiro.volume,
        vaso_decorativo.roupeiro.peso
  );
}
  • Observe que a union é feita de duas entre uma struct e um vetor de caracteres. O autor neste código criou este vetor baseado no tamanho que ocupa a struct aproveitando todo o espaço já alocado, porém o C não exige que os dados ocupem o mesmo espaço, neste caso a alocação ocorrerá em relação a maior estrutura.
  • Observe que ao imprimir os valores da instancia "guarda_roupas" o "descricao_generica" apesar de não ter sido formalmente preenchido, foi indiretamente preenchido quando no caso "roupeiro.cor" recebeu um valor. Como este valor foi "Cinza" o C escreveu um "\0" no final da string "roupeiro.cor" que acabou também servindo como final da string "descricao_generica", por isso neste print ambos os campos apresentam o mesmo valor. Porém observe também que os valores de "volume" e "peso" estão perfeitamente preservados.
  • Agora foi interessante o cado da instancia "vaso_decorativo", ela foi descrita pelo campo "descricao_generica" e apenas para fins didáticos o autor imprimiu o que teria dentro de "roupeiro.cor", "roupeiro.volume" e "roupeiro.peso". Neste caso cairam valores oriundos da constante "em vidro - peça única" que não passam de sujeira neste contexto.

Ponteiros

Ponteiros

A memória de um computador pode ser vista como um vetor de bytes. Neste espaço vimos a utilização de variáveis diversas que podem armazenar valores que podem ser obtidos do usuários, serem resultados de ariméticas e muitas outras operações. O ponteiro nada mais é que um tipo de dado igualmente armazenado em memória, porém este dado se refere a um endereço da memória, ou seja, a um outro objeto.

Este recurso é muito útil para diversos propósitos, basta pensar na própria aplicação do conceito "endereço", imagine como seria localizar uma casa em uma cidade sem haver uma forma de endereçar e armazenar os endereços das casas. Explorando esta analogia, cada lote possui um endereço e pode ter um conteúdo de diferentes tipos como uma casa, um prédio ou um conjunto de lojas, enfim, trazendo para o C seria como os tipos int, char, vetores diversos, etc.

Assim é a memória, 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).
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. Este recurso é largamente utilizado para passar parâmetros como referência (ao invés de copiar uma variável quando se quiser processá-la em alguma função), bem como para algoritmos diversos que buscam formas mais otimizadas de executar operações. Rode o código a seguir e compare com as respostas que foram obtidas:
#include <stdio.h>
int main(void) 
{
  int i = 10;
  int *p;
  long int li;
  p = &i;
  printf("Conteúdo de i:      i = %d\n",i);
  printf("Endereço de i:     &i = %p\n",&i);
  printf("Conteúdo de p:      p = %p\n",p);
  printf("Endereço de p:     &p = %p\n",&p);
  printf("Conteúdo apontado: *p = %d (conteúdo do endereço apontado por p)\n",*p);
  printf("Tamanho do ponteiro   = %li bytes\n",sizeof(p));
  printf("Tamanho do lont int   = %li bytes\n",sizeof(li));
  printf("Tamanho do int        = %li bytes\n",sizeof(i));
  return 0;
}

Resposta obtida através do gcc em uma máquina Linux Ubuntu:

Conteúdo de i:      i = 10
Endereço de i:     &i = 0x7ffeb25859e4
Conteúdo de p:      p = 0x7ffeb25859e4
Endereço de p:     &p = 0x7ffeb25859e8
Conteúdo apontado: *p = 10 (conteúdo do endereço apontado por p)
Tamanho do ponteiro   = 8 bytes
Tamanho do lont int   = 8 bytes
Tamanho do int        = 4 bytes
  • Observações:
    • "&i" e "p" são iguais, isso porque "p" tem como conteúdo um endereço (neste caso o endereço de "i")
    • O conteúdo de "i" (10) é igual "*p", que exatamente está extraindo o conteúdo da variável apontada (é o próprio "i")
    • O endereço de "p" (&p) é um valor próprio o que prova que p é também uma variável alocada na memória armazenando um valor próprio
    • Nesta máquina o ponteiro está representado por uma variável inteira de 8 bytes (uintptr_t).


Resposta obtida através do codechef.com (gcc-4.9.2):

Conteúdo de i:      i = 10
Endereço de i:     &i = 0xbfadc2b8
Conteúdo de p:      p = 0xbfadc2b8
Endereço de p:     &p = 0xbfadc2bc
Conteúdo apontado: *p = 10 (conteúdo do endereço apontado por p)
Tamanho do ponteiro   = 4 bytes
Tamanho do lont int   = 4 bytes
Tamanho do int        = 4 bytes
  • Observações:
    • O endereço de "p" e "i" são completamente diferentes da resposta anterior. Isso ocorre em diferentes máquinas e cada vez que o programa for rodado deverá também gerar novos endereços. O endereço é atribuído pelo sistema operacional que por várias condições naquele instante disponibilizou estes endereços ai listados.
    • Nesta máquina o ponteiro está representado por uma variável inteira de 4 bytes (uintptr_t), o tamanho de uma variável ponteiro varia conforme a plataforma.
Ponteiro para inteiro

Observe o programa abaixo. A variável p é um ponteiro para inteiro. Isto significa que ela pode armazenar um endereço de um inteiro.

#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);
}

Observe que para se referenciar o conteúdo da posição de memória apontada por p deve-se usar o asterisco: *p


EXERCÍCIO 1
Considere o programa abaixo:
main()
{
  int x=10;
  int y, *p;

}

Complete o código para copiar o conteúdo de x para y, sem que qualquer variável apareçam no lado esquerdo de um sinal de atribuição. Ou seja, sem envolver diretamente x e y.

EXERCÍCIO 2
Tente inferir qual seria o valor da variável y no final do programa abaixo:
main()
{
  int x,y,w,*p1,*p2;
  x = 20;
  w = 30;
  p1 = &x;
  p2 = &w;
  y = *p1 + *p2;
}
EXERCÍCIO 3
Tente inferir qual seria o valor da variável y no final do programa abaixo:
main()
{
  int x,y,w,*p1,*p2, *p3;
  x = 20;
  w = 30;
  p1 = &x;
  p2 = &w;
  y = *p1 + w;
  p3 = &y;
  *p3 = *p3 + 10;
  y = *p1 + *p2 + *p3;
}
EXERCÍCIO 4
Qual seria o valor das variáveis y e x no final do programa abaixo:
#include <stdio.h>
void main()
{
	int x,y;
	int *p;
	y=0;
	p=&y;
	x=*p; 	
	x=4; 	
	(*p)++;	
	x--;	
	(*p) += x; 
	printf("\ny=%d x=%d\n",y,x);
}
Ponteiro para char

Os ponteiro para char são muito utilizados pois permitem apontar para strings. A ideia é que ele aponte para o primeiro caracter (char) da string. Veja o exemplo abaixo.

#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++;
   }	
}

Neste foi usado o incremento de um ponteiro, o que implica em adicionar ao endereço armazenado em p uma quantidade relativa ao tamanho do tipo apontado. No caso é 1 (tamanho de um char é um byte).

EXERCÍCIO
Sem executar o programa abaixo, determine o valor de y no final do programa:
main()
{
   char x[10]="ifsc";
   char *p, y;
   p = x + 2;
   y= *p;
}
Apontando para um vetor de inteiros


Da mesma forma que usamos um ponteiro para char para apontar uma string, podemos fazer um ponteiro para int apontar para para um elemento de um vetor de inteiros.

#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++;       
   }	
}

OBSERVE que p++ incrementa em 4 unidades.

Apontando para estruturas

Ponteiros podem apontar para qualquer "objeto" de qualquer tipo. Vamos verificar como é possível apontar para uma estrutura:

#include <stdio.h>
struct TRegistro {
   char nome[20];
   int idade;
} Tabela[4] = {
          {"joao",18,},
          {"maria",18,},
          {"jose",19,},
          {"lara",17,},
};
struct TRegistro *p;
main()
{
  p = &Tabela[3]; /*p aponta para o registro 3 da tabela */
  printf("O nome na posição 3 é %s e idade = %d\n", p->nome,p->idade);
}

NOTE que o uso de p->nome é uma alternativa ao uso de (*p).nome

No primeiro caso pode-se ler: o campo nome do objeto que é apontado por p.

Escrevendo e lendo de campos de uma estrutura através de ponteiro
#include <stdio.h>
struct TRegistro {
   char nome[20];
   int idade;
} Tabela[4];
struct TRegistro *p;
main()
{
  int i;
  p = &Tabela[0]; /*p aponta para o registro 0 da tabela */
  for (i=0;i<4;i++,p++)
  {
    printf("Digite o nome e idade da pessoa %d\n", i+1);
    scanf("%s %d",p->nome,&(p->idade));
    printf("\n\n>>>> O nome e idade da pessoa %d é: %s, %d \n\n\n", i+1, p->nome, p->idade);
  }
}
Retornando uma estrutura em uma função

No exemplo a abaixo a função RetornarStruct() retorna um ponteiro para uma estrutura. O cuidadado que se deve ter é que a função não deveria apontar para uma estrutura que foi criada localmente na função!

#include <stdio.h>
struct TRegistro {
   char nome[20];
   int idade;
} Tabela[4] = {
          {"joao",18,},
          {"maria",18,},
          {"jose",19,},
          {"lara",17,},
};
struct TRegistro *p;
struct TRegistro * RetornarStruct(int indice)
{
  return &Tabela[indice];
}
main()
{
  p = RetornarStruct(2); /*p aponta para o registro 3 da tabela */
  printf("O nome na posição 2 é %s e idade = %d\n", p->nome,p->idade);
}
Passando uma estrutura como parâmetro
#include <stdio.h>
struct TRegistro {
   char nome[20];
   int idade;
} Tabela[4] = {
          {"joao",18,},
          {"maria",18,},
          {"jose",19,},
          {"lara",17,},
};
struct TRegistro *p;
void MudarStruct(struct TRegistro *p1, int indice)
{
  Tabela[indice] = *p1;
}
main()
{
  struct TRegistro aux = {"luisa",16};
  MudarStruct(&aux,2);
  p = &Tabela[2];
  printf("O nome na posição 2 é %s e idade = %d\n", p->nome,p->idade);
}


Os argumentos argc e argv (o ponteiro de strings argv)

A função main() pode ter parâmetros formais, mas o programador não pode escolhores quais serão eles. A declaração que se pode ter para a função main() é: int main (int argc, char *argv[]); Exemplo: Escreva um programa que faça uso dos parâmentros argv e argc. O programa deverá receber da linha de comando o dia, mês e ano correntes (dd/mm/aaaa), e imprimir a data em formato apropriado. Veja o exemplo, supondo que o executável se chame data: $ data 04 11 2016 O programa deverá imprimir:

$ 04 de novembro de 2016
#include <stdio.h>
#include <stdlib.h>
void main(int argc, char *argv[])
{
  int mes;
  char *nomemes [] = {"janeiro","fevereiro","março","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro"};
  if(argc == 4) /* Testa se o numero de parametros fornecidos esta' (nome do programa, o dia, o mes e os dois ultimos algarismos do ano */
  {
    /* argv contem strings. A string referente ao mes deve ser 
     * transformada em um numero inteiro. A funcao atoi esta sendo 
     * usada para isto: recebe a string e transforma no inteiro equivalente 
     */
    mes = atoi(argv[2]);
    if (mes<1 || mes>12) /* Testa se o mes e' valido */
      printf("Erro!\nUso mes: mm, deve ser de 1 a 12.\n");
    else
      printf("\n%s de %s de %s\n\n", argv[1], nomemes[mes-1],argv[3]);
  }
  else
    printf("Erro!\nUso: dd/mm/aaaa, devem ser inteiros, ou estão faltando.\n");
}
Usando ponteiros como parâmetros de entrada saída de funções
Enviar um ponteiro a uma função tem diversas aplicações, uma delas é a de evitar redundância de dados e realizar a leitura de informações "diretas da fonte". Estas informações quando são de grande volume também poderiam requisitar um grande volume de memória para copiar, então mais um motivo para se passar a referência (ponteiro). Observe como podemos usar ponteiros na passagem de parâmetros:
#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));
}
Este recurso também tem especial utilidade quando se deseja que a função retorne mais de um valor. Como sabemos a função só pode ter um retorno. Utilizando ponteiro, portanto, pode-se passar um endereço de referência para que a função utilize este espaço de memória para copiar um resultado que será posteriormente aproveitado pela função invocadora:
#include <stdio.h>
void alfa(int *p)
{
  *p=10;
}
main()
{
  int x;
  x =5;
  printf("Valor de x antes da chamada de alfa = %d\n", x);
  alfa(&x);
  printf("Valor de x depois da chamada de alfa = %d\n", x);
}
Exercícios
  1. Elabore um programa em C que declara duas estruturas (TRetangulo e TTriangulo) tendo uma instancia para cada na forma global (retangulo e triangulo). Cada estrutura deve conter dois campos (base e altura, ambos unsigned int). Na função main atribua valores quaisquer para as bases e alturas das formas geométricas. Por fim imprima em tela os valores de base, altura e área obtendo dos campos da struct sabendo que a área do retângulo é base x altura e do triangulo é base x altura dividido por 2.
  2. Crie uma funcao calculaAreaRetangulo e outra calculaAreaTriangulo para executar o calculo das áreas sem receber parâmetros e retornando a área calculada na forma unsigned int. A função deve ser chamada de dentro das chamadas de impressão dos valores já feitas no main. Declare os protótipos de ambas as funções no início do arquivo ".c".
  3. Mova as declarações de instancia (retangulo e triangulo) originalmente criadas como globais para dentro do main e modifique as funções de cálculo para que passem a receber o ponteiro da struct como parâmetro.
  4. Declare as variáveis retangulo e triangulo como vetores de structs (com quantidade definida por MAX_R e MAX_T respectivamente). Altere o código para que os valores de base e altura sejam obtidos do usuário (crie loops para receber os valores e calcular as áreas utilizando as funções já existentes)
  5. Construir uma função que imprime a Tabela usando ponteiros. A função deve receber como parâmetro um ponteiro para o início da tabela e o tamanho da tabela.
  6. Implementar a função str_cat que concatena duas strings usando ponteiros.
  7. Ordenar valores de um vetor de inteiros passando por referencia o ponteiro para esse vetor.
  8. Implememtar um programa que recebe 3 parâmetros na linha de comando: dois números reais e um operador (char). Operador pode ser + ou menos. O programa deve mostrar o resultado da operação. Exemplo: calcula 3.5 + 2.6. Dica: usar a função atof para converter string em float.
  9. 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 de alerta ao usuario de que os parametros estao incorretos.
Auto-avaliação institucional
Referências Complementares

Alocação dinâmica de memória

Alocação dinâmica de memória

As estruturas de dados em C são normalmente de tamanho fixo. Mesmo num vetor de tamanho variável, seu tamanho apesar de ser determinado em tempo de execução, seu tamanho se mantém fixo até que seja destruído. Nos exemplos que trabalhamos a maioria tinha número de registros fixo, esta situação é bastante restringente. Em um programa de registros de notas de alunos, por exemplo, se determinado um número de registros pequeno pode-se deparar com a necessidade de aumentar este vetor, havendo necessidade de recompilação. Se por outro lado for alocado um grande espaço de memória, pode-se estar desperdiçando bastante espaço caso fiquem obsoletos. Para resolver este problema há funções de alocação dinâmica de memória, que permitem que seja alocada e desalocada memória conforme crescimento ou redução da necessidade de espaço. As funções mais conhecidas são (http://en.wikipedia.org/wiki/C_dynamic_memory_allocation):

  • malloc: aloca n bytes de memória;
  • free: libera memória;
  • realloc: realoca memória
  • calloc: aloca n bytes de memória e zera.
EXEMPLO
Alocação dinâmica de números inteiros (exercício puramente didático):
#include <stdlib.h>

main()
{
  int *px, *py;
  int resultado;
  px = (int *) malloc(sizeof(int));
  *px = 5;
  py = (int *) malloc(sizeof(int));
  *py = 2;
  resultado = *px + *py;

  free (px);
  px = NULL;
  free (py);
  py = NULL;
}

Observe que há duas chamadas de alocação utilizando "malloc", onde são alocados a quantidade de bytes que uma variável "int" ocupa (normalmente 4 bytes). Conforme consta na documentação lincada acima, o malloc retorna um void*, que é um ponteiro genérico. Para que este ponteiro seja tratado como um ponteiro de int (que é nossa necessidade neste código) é realizado um typedef de (int*). Após as alocações são escritos e lidos valores destes espaços de memória através do apontamento de "px" e "py". Por fim, é chamada a função "free" que recebe como argumento um ponteiro e libera o bloco de memória apontado por e este ponteiro.

A função free deve ser chamada sempre que a memória alocada (por malloc, calloc ou realloc) não for mais necessária. A não desalocação correta de memória pode causar "vazamentos de memória" (ou memory leak). Uma chamada de free em um espaço de memória já desalocado poderá também causar falha, trata-se de um comportamento não previsível, possivelmente uma falha de segmentação.

Após desalocar o bloco de memória apontado por um ponteiro, setar este ponteiro para NULL é uma boa prática. Esta ação evita que acidentalmente será tentado utilizar ou desalocar um espaço de memória já livre o que poderia causar falhas graves. Esta prática de "anular" o ponteiro transforma este em um "ponteiro nulo" (ou NULL pointer), sendo um tipo especial que não aponta para lugar nenhum (nulo não é um endereço de memória válido), isso significa que uma desalocação em um ponteiro nulo não deverá ter nenhum efeito.

As funções malloc e calloc são muito parecidas, o efeito prático é que o calloc além de alocar a memória também "zera" este espaço. O ato de "zerar" a memória faz do calloc uma função menos performática, isso ocorre por duas razões: o simples processo de escrever "zeros" é dispendioso e também o ato de escrever algo na memória obriga o sistema operacional a "tocar" na memória e realizar possíveis ações de swap de outros processos. No caso do malloc estas ações só ocorrerão quando houver real necessidade de escrever naquele determinado espaço de memória.

EXEMPLO
Alocando memória com calloc

Diferente da função malloc, calloc além de inicializar os espaços de memória ainda atribui o valor 0 (zero) para cada um deles.

Observe o calloc sendo realizado no início do loop, garantindo que as variáveis iniciem em zero
#include <stdio.h>
#include <stdlib.h>
 
int main(void){
 
  int *valores, *aux;
  int qtd, i, j, vlr;
 
  printf("\nEntre com a quantidade de números: ");
  scanf("%d", &qtd);
 
  if(qtd == 0) exit(0);
 
  for (j=0;j<2;j++)
  {
    //Aloca um vetor com a quantidade recebida em qtd
    valores = (int *) calloc(qtd, sizeof (int));

    printf("Rodada %d:\n", j);

    //Imprime o endereco e valor (zero) para a quantidade de números informada
    aux = valores;
    for(i = 1; i <= qtd; i++){
        printf("Situação inicial da memória: %p\t%d\n", aux, *aux);
        aux++;
    }

    aux = valores; //Ponteiro para o primeiro espaço de memória
    for(i = 1; i <= qtd; i++){
        printf("Digite o número %d ->: ", i);
        scanf("%d", &vlr);
        *aux = vlr;
        aux++;
    }
    aux = valores; //Retorna à posição inicial no mapa de memória
    for(i = 1; i <= qtd; i++){
        printf("Situação final da memória  : %p\t%d\n", aux, *aux);
        aux++;
    }
    printf("\n");

    free(valores);
  } 

  return 0;
}
EXEMPLO
malloc versus calloc
Observe o malloc sendo realizado no início do loop, não há garantia de qual valor se encontra naquele espaço de memória.
#include <stdio.h>
#include <stdlib.h>
 
int main(void){
 
  int *valores, *aux;
  int qtd, i, j, vlr;
 
  printf("\nEntre com a quantidade de números: ");
  scanf("%d", &qtd);
 
  if(qtd == 0) exit(0);
 
  for (j=0;j<2;j++)
  {
    //Aloca um vetor com a quantidade recebida em qtd
    valores = (int *) malloc(qtd * sizeof (int));

    printf("Rodada %d:\n", j);

    //Imprime o endereco e valor (zero) para a quantidade de números informada
    aux = valores;
    for(i = 1; i <= qtd; i++){
        printf("Situação inicial da memória: %p\t%d\n", aux, *aux);
        aux++;
    }

    aux = valores; //Ponteiro para o primeiro espaço de memória
    for(i = 1; i <= qtd; i++){
        printf("Digite o número %d ->: ", i);
        scanf("%d", &vlr);
        *aux = vlr;
        aux++;
    }
    aux = valores; //Retorna à posição inicial no mapa de memória
    for(i = 1; i <= qtd; i++){
        printf("Situação final da memória  : %p\t%d\n", aux, *aux);
        aux++;
    }
    printf("\n");
    free(valores);
  } 

  return 0;
}
EXEMPLO
Alocando uma estrutura e testando o memory overflow
#include <stdio.h>
#include <stdlib.h>
#define QT_INSTANCIAS 100000
 
void main()
{
  struct TTeste{
    long int x;
    long int y;
    char st[50];
  };

  struct TTeste *teste;
 
  teste = (struct TTeste *) malloc (QT_INSTANCIAS*sizeof(struct TTeste));
  //Boa pratica: se o retorno do malloc é NULL houve erro de alocação
  if (teste!=NULL) {
    printf("%li MBytes de memória alocados com sucesso!\n",QT_INSTANCIAS*sizeof(struct TTeste)/(1024*1024));
    printf("Digite qualquer tecla para prosseguir: ");
    getchar();
  } else {
      printf("Erro ao tentar alocar %li MBytes de memória!\n",QT_INSTANCIAS*sizeof(struct TTeste)/(1024*1024));
      exit(1);
  }
 
  long int i;
  struct TTeste *aux;
  aux = teste;
  for (i=0;i<QT_INSTANCIAS;i++,aux++)
  {
    aux->x=i+1;
    aux->y=aux->x*2;
    sprintf(aux->st,"%li",i*1000000);
    printf("%li: teste.x=%li, teste.y=%li e teste.st=%s\n",i,aux->x,aux->y,aux->st);
  }
  aux=NULL;
  free(teste);
  teste=NULL;
}
EXEMPLO
Alocando dinamicamente uma tabela de estruturas

Observe agora o uso da função "realloc". Como argumentos além do tamanho em bytes (idem ao malloc), também é necessário setar o ponteiro que será redimensionado. Este tamanho poderá ser maior ou menor ao tamanho anteriormente alocado, fazendo com que seja liberada memória ou alocado um bloco ainda maior. O retorno da função é o próprio ponteiro do bloco realocado. Se o retorno for nulo, a operação de alocação não foi bem sucedida.

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

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

  if ((teste = (struct TTeste *) 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ÍCIOS
  1. Refazer o exemplo "Alocação dinâmica de números inteiros" tentando desalocar duas vezes o mesmo ponteiro e também tentando desalocar o ponteiro após se tornar um ponteiro nulo.
  2. Estude atentamente o exemplo "Alocando memória com calloc", por que foi criado um ponteiro aux? Crie linhas de código com printf para exibir o endereço de memória que aux tem em cada iteração do loop, o que se conclui desta observação? aux não está sendo liberado por free, o que isso significa? O que aconteceria se fosse dados free de aux também? aux e valores não estão sendo setados para NULL, que risco isso traz?
  3. Altere o exemplo "Alocando uma estrutura e testando o memory overflow" na quantidade de instancias e/ou tamanho do vetor teste.st para obervar até quanto de memória é possível alocar e quando não há memória disponível suficiente para esta operação.
  4. Rode o exemplo "Alocando uma estrutura e testando o memory overflow" com uma quantidade grande de memória sendo alocada. Enquanto são impressas as linhas mostrando que os espaços de memória estão sendo preenchidos abra um outro terminal e rode o programa chamado "free" que mostra a ocupação de memória ram. Rode o free algumas vezes seguidas para observar o aumento de uso de memória e finalmente após finalizar o programa veja a memória ser desalocada.
  5. Rode o exemplo "Alocando uma estrutura e testando o memory overflow" com uma quantidade grande de memória sendo alocada. Enquanto são impressas as linhas mostrando que os espaços de memória estão sendo preenchidos abra um outro
  6. O que aconteceria se fosse tentado acessar um espaço de memória apontado por um ponteiro nulo ou de memória já desalocada? Utilize um dos exemplos anteriores para testar ambos os casos.

Diretivas de pré-compilação

Definição de diretiva de pré-compilação

Uma diretiva de pré-compilação é um código processado pelo pré-compilador que "prepara", então, o código que será efetivamente compilado. Nesta execução pode haver definições, mudanças de comportamento do compilador e mesmo blocos lógicos que decidirão o que será ou não compilado. As diretivas de pré-compilação são úteis para configurações diversas que o compilador precisa conhecer para poder gerra o código objeto como também são úteis para criar "macros" diversas que podem tornar processos mais simples ou tornar o código mais inteligível.

#include

O pré-processador busca o conteúdo de um outro arquivo e traz este código para que o compilador possa realizar a compilação e posterior link.

#include <stdio.h>
int main(void) {
	int i = 20;
	printf("i = %d\n",i);
	return 0;
}

No exemplo acima a função "printf" está definida na biblioteca "stdio", portanto, é necessário incluir este código para que o compilador passe a "conhecer" o que é a função printf e possa então chamar este código quando esta função é evocada. Quando a biblioteca é incluída com sinais de maior e menor (< e >) envolvando o nome do arquivo significa que o pré-compilador deve buscar esta biblioteca nos diretórios listados como endereços de biblioteca do compilador. Portanto precisa estar definido nos arquivos de configuração do compilador. O diretório onde se encontra a libc (biblioteca padrão do C) já vem por padrão listada nestes endereços. Quando a biblioteca é incuída entre aspas duplas (" ") envolvendo o nome do arquivo significa que o pré-compilador deve procurar em um endereço específico. Se o caminho não está todo definido será o endereço relativo, havendo apenas o nome do arquivo da biblioteca será buscado no diretório em que se encontra o código c que está sendo compilado.

#define

A diretiva "define" cria uma macro que é uma substituição de um valor por um código ou lista de códigos de programa. Há algumas vantagens de se utilizar macros:

  • O programa fica mais fácil de ler e entender pois pode-se substituir códigos complexos por textos auto-explicativos (exemplo: #define PI 3.14159)
  • Fica mais fácil de modificar já que o identificador pode se repetir várias vezes no código, bastando modificar o #define para modificar todo o código.
  • Evita inconsistências e erros de digitação possíveis como por exemplo definindo PI como 3.14159 e utilizando esta definição garante-se que este valor será utilizado em todo o programa igualmente, evita o engano de usar em algum lugar 3.1416, por exemplo.
#include <stdio.h>
#define qtnotas 5
int main(void) 
{
  float notas[qtnotas], soma=0;
  int i;
  for(i = 0;i < qtnotas;i++)
  {
    printf("Digite a nota = %d\n",i+1);
    scanf("%f",&notas[i]);
    soma += notas[i];
  }
  printf("A média é: %.1f\n",soma/qtnotas);
}

Observe acima como o uso da macro qtnotas traz benefícios como os listados anteriormente.

Acessando arquivos em C

Introdução

Arquivos em C são tratados como "streams" que literalmente significa córrego (riacho), em computação significa dados em fluxo. Utilizado para tratar quantidade desconhecida de informações (potencialmente infinita). Por esta característica arquivos são acessados através de ponteiros do tipo FILE * (um tipo de dados declarado na biblioteca <stdio.h>).

Ainda na stdio, há três streams padrão (stdin, stdout e stderror) que não precisam ser declarados e estão prontos para uso. Funcões como scanf e getchar, por exemplo, na prática estão obtendo dados do teclado (stdin) e funções como printf e putchar estão escrevendo da tela (stdout). Eventuais falhas serão também enviadas para a tela (stderr).

A quantidade de streams que um programa mantém aberto pode ser limitado pelo sistema operacional.

A biblioteca stdio.h suporta dois tipos de arquivos: binários ou texto. Os arquivos tipo texto são compostos por caracteres humanamente compreensíveis. Já os arquivos binários são codificados. Arquivos binários podem armazenar mais informações em menor espaço e são normalmente mais fáceis de serem lidos por programas tornando-os também mais performáticos.

Operações de abertura e fechamento de arquivos
Operação abrir arquivo

A primeira ação será a de abertura de uma arquivo. É realizado com a função fopen que deve receber como parâmetros o nome do arquivo a ser aberto e o modo de abertura que especifica se será para leitura ou escrita. A diretiva restrict que aparece em ambos os argumentos não é muito relevante para o momento, basicamente está dizendo que estes espaços de memória não podem ser compartilhados (C99).

FILE *fopen(const char * restrict filename, const char * restrict mode)
Um exemplo de chamada seria
FILE * arquivo;
arquivo = fopen("IFSC.txt", "r");

Onde "r" significa somente leitura

Outro exemplo de chamada seria
arquivo = fopen("c:\\temp\\temp.txt", "w");

Onde "w" significa que o arquivo está sendo aberto para escrita Observe também o uso de "\\" e não apenas "\". Isto de deve pois algo como "C:\temp\temp.txt" teria então um "\t" que significa TAB. O uso de "\\" previne esta má interpretação. Uma outra alternativa é escrever "C:/temp/temp.txt", ou seja, com as barras invertidas. O operação fopen deve retornar um endereço válido se for bem sucedida, se falhar retornará NULL. Razões para falhas estão listadas aqui.

Modos de acesso a arquivos (arquivos tipo texto)
  • "r": Abre para leitura
  • "w": Abre para escrita (o arquivo não necessariamente precisa existir, o conteúdo possivelmente será sobrescrito caso não tratado, ver modo "a")
  • "a": Abre para escrita adicionando dados no final do arquivo (o arquivo não necessariamente precisa existir)
  • "r+": Abre para leitura e escrita
  • "w+": Abre para leitura e escrita (trunca se o arquivo existir, apagando o conteúdo anterior)
  • "a+": Abre para leitura e escrita adicionando dados no final do arquivo (o arquivo não necessariamente precisa existir)

O caractere '+' representa o modo atualização (update mode). Para arquivos binários devem ter a letra "b" associada ("rb", "wb", "ab", "r+b"/"rb+", "w+b"/"wb+", "a+b"/"ab+"). No sistema operacional Linux a especificação de abertura de arquivo como binário não trará mudança pois é utilizado apenas um caractere para representar nova linha (\n). Porém, para manter portabilidade com o Windows que utiliza dois caracteres de nova linha '\r\n' é importante utilizar o modo 'b' para arquivos binários.

Operação fechar arquivo

Permite o programa fechar um arquivo que não está mais sendo utilizado. Deve receber como parâmetro o ponteiro (FILE *) para o arquivo. Se fechar com sucesso retornará zero, caso ocorre erro retornará EOF (uma macro definida na stdio.h.

int fclose(FILE *stream);
Um exemplo de chamada seria
fclose(arquivo);
Operações de escrita de arquivos
Operação escrever texto formatado em arquivo

Escreve uma saída em um stream apontado. A string apontada no segundo argumento se parece as utilizadas na função "printf".

int fprintf(FILE * restrict stream, const char * restrict format, ...);
Outras operações de escrita
int fputc(int c, FILE *stream); //Escreve um caracter
int fputs(const char * restrict s, FILE restrict *stream); //Escreve uma string
Exemplo
Abrindo e escrevendo um arquivo
#include <stdio.h>
 
int main(void)
{
  FILE *p_arq;
  int i;
 
  if ((p_arq=fopen("IFSC.txt", "w")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return 0;
  } else {
    printf("Arquivo aberto com sucesso. Iniciando escrita no arquivo...\n");
  }

  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((fprintf(p_arq,"Linha %d\n",i))==EOF) {
      printf("Erro ao escrever no arquivo!\n");
      return -1;
    }
  }
  printf("Fim da escrita, observe o arquivo 'IFSC.txt' criado na mesma pasta deste executável!\n");
  fclose(p_arq);
  return 0;
}

Note que se o arquivo IFSC.txt não existir, ele será criado.

Exercícios
  1. Após criar um arquivo IFSC.txt com o código acima, modifique o texto deste arquivo manualmente, escreva seu nome, por exemplo, salve e feche. Execute novamente o executável acima. O que ocorreu? O que você observa que foi realizado com o modo 'w' utilizado acima?
  2. Modifique o programa exemplo para que abra o arquivo com modo "r+", Aumente e diminua a quantidade de iterações do loop para escrever mais e menos linhas. Observe o comportamento nos modos "w"/"w+" e "r+" . O que você conclui de diferença no comportamento dos modos 'r+' e 'w'/'w+'?
  3. Crie um programa que recebe como parâmetro de entrada o nome de um arquivo que deve ser criado e num segundo argumento um texto qualquer que deve ser inicializado neste arquivo. Os parâmetros de entrada são argc e argv - veja documentação aqui na wiki.
  4. Utilize as funções fputc e fputs para escrever em arquivos
Operações de leitura de arquivos
Operação escrever texto formatado em arquivo

Lê informações de um stream apontado em um formato definido.

int fscanf(FILE *stream, const char *format, ...);
Outras operações de escrita
int fgetc(FILE *stream); //Lê um caracter e avança o cursor
char *fgets(char *str, int n, FILE *stream); //Lê um conjunto de caracteres limitado a n de tamanho, nova linha ou fim do arquivo
Exemplo
Abrindo e lendo um arquivo
#include <stdio.h>
 
int main()
{
  FILE *p_arq;
  int i,j;
  char buff[100];
 
  if ((p_arq=fopen("IFSC.txt", "r")) == NULL) {
    printf("Problemas na abertura do arquivo, o arquivo existe?\n");
    return 0;
  } else {
    printf("Arquivo aberto com sucesso. Inicio da leitura do arquivo...\n");
  }
 
  while(1) {
    if((fscanf(p_arq,"%s %d",buff,&j))==EOF) {  					  	    
      printf("Fim de leitura\n");
      break;
    }
    printf("%s %d\n",buff,j);
  }
  fclose(p_arq);
  return 0;
}

Note que o fscanf se comporta de forma similar ao scanf. A função retorna o caractere EOF (end-of-file) quando não existe mais dados a serem lidos.

Exercícios
  1. Implemente um programa em C que lê de um arquivo chamado "anagrama.txt" duas palavras (uma na primeira linha e a outra na segunda linha). O programa analisa estas palavras e diz se são ou não anagramas (palavra formada pela alteração da ordem ou transposição de letras) ex.: “America” e “Iracema”, “Roma” e “Amor”, “Semolina” e “Is no meal” são anagramas). Utilize o modo de acesso a arquivo "r".
  2. Implemente um programa em C que dada uma frase obtida através de um scanf, torne todas as letras em maiúsculas (código 1), torne todas em minúsculas (código 2) ou a primeira letra de cada palavra em maiúscula e demais minúsculas (código 3) e escreve em tela o resultado. O limite para a frase digitada é 100 caracteres. Faça estas operações em funções distintas (tudoMaiusculo(), tudoMinusculo(), primeiraMaiuscula()), utilizando o vetor de caracteres como uma variável global. A escolha das opções dependerá de um numero inteiro lido de um arquivo chamado "config.cfg" este número deve estar escrito no início do arquivo e pode ser ser 1, 2 ou 3. O arquivo config.cfg deve ser criado manualmente e preenchido com diferentes opções para testar o algoritmo. Utilize o modo "r".
  3. Implemente um algoritmo em C que conta a ocorrência de cada letra (incluindo números de 0 a 9) dada uma frase. Deve ignorar se a letra foi digitada em maiúscula ou minúscula (portando somar junto). Limite a 100 caracteres. Para cada palavra digitada deve gerar um log em uma arquivo chamado "ocorrencias.log" que contém a data/hora em que foi gerado o registro e quantas letras de cada tipo foram lidas. Utilize o modo "a" ou "a+".
  4. Utilize a função rand() e gerador de semente srand() para gerar 6 números aleatórios distintos que variam de 1 a 60 e grava esta sequencia em um arquivo chamado "aposta.txt". Utilize o modo "w" ou "w+".
  5. Crie uma estrutura chamada TReferencia que contém um campo "livro" de 100 caracteres e um campo "autores" de 100 caracteres. Crie um loop infinito para receber uma quantidade qualquer de livros que compõem as referencias bibliográficas de um trabalho acadêmico. O programa deve então receber duas string (que podem conter espaços em branco), uma sendo o nome do livro e a outra os autores. Recebidos estes dados devem ser instanciados na struct. Se recebido uma string contendo a palavra "sair" significa que deve-se então proceder com a finalização do programa gravando as instancias da struct em um arquivo chamado "referencias.txt". Como resultado este arquivo deve ter em cada linha o nome do livro entre aspas ("nome do livro") depois uma vírgula e o nome dos autores entre aspas também ("S. AUTOR1, S. AUTOR2").Utilize o modo "r+".
Adicionando uma linha no final de um arquivo
#include <stdio.h>
#include <time.h>
 
int main(void)
{
   time_t ltime;
   FILE *fp;
 
   if ((fp=fopen("leituras.log", "a")) == NULL) {
       printf("Problemas na abertura do arquivo\n");
       return;
   }

   time(&ltime); 
   if ((fputs(ctime(&ltime), fp)) != EOF ) {
       fclose(fp);
   } else {
       printf("Erro na escrita do arquivo!\n");
   }
}

Tipicamente, quando se abre uma arquivo para leitura/escrita, o "cursor" de acesso fica na posição 0 (início do arquivo). Se o arquivo for aberto em modo append ('a'), o "cursor" é posicionado no final. A cada acesso (leitura ou escrita), este cursor é incrementado conforme o número de dados lidos ou escritos. Execute este código algumas vezes e vá observando o que ocorre com o arquivo 'leituras.log'.

Exemplo 2
#include <stdio.h>
#include <time.h>
#include <string.h> 
int main(void)
{
  time_t ltime;
  FILE *fp;
  struct tm *info;
  char buffer[100],b2[100];
 
  if ((fp=fopen("tst.log", "a+")) == NULL) {
    printf("Problemas na abertura do arquivo\n");
    return;
  }
 
  time(&ltime); 
  info = localtime( &ltime );
  strftime(buffer,sizeof(buffer),"%x-%I:%M%p", info);
  printf("Formatted date & time : |%s|\n", buffer );
  strcat(buffer," Joao\n");

  if ((fputs(buffer, fp)) == EOF ) {
    printf("Erro na escrita do arquivo!\n");
  }

  fseek(fp,0,SEEK_SET);
  while(1) {
    if((fscanf(fp,"%s %s",buffer,b2))==EOF) {
      printf("Fim de leitura\n");
      break;
    }
    printf("Marcação lida: %s %s\n",buffer,b2);
  }
  fclose(fp);
  return 0;
}
Alterando o cursor com fseek

Observe este exemplo que escreve em um arquivo e depois lê o conteúdo escrito. Neste caso é utilizado fseek para voltar o cursor para o início do arquivo.

#include <stdio.h>
int main(int argc, char *argv[])
{
  FILE *p_arq;
  int i,j;
  char buff[100];

  if ((p_arq=fopen("IFSC.txt", "w+")) == NULL) {
     printf("Problemas na abertura do arquivo\n");
     return 0;
  } else {
    printf("Aberto com sucesso. Iniciando escrita no arquivo...\n");
  }

  for (i = 0; i<10;i++) {
    if((fprintf(p_arq,"LINHA %d\n",i))==EOF) {
      printf("Erro ao escrever no arquivo!\n");
      return -1;
    }
  }
  printf("Escrito! 'IFSC.txt' criado na pasta deste executável!\n");
 
  /*Muda a posição do cursor para o início do arquivo*/
  fseek(p_arq,0,SEEK_SET);
  while(1) {
    if((fscanf(p_arq,"%s %d",buff,&j))==EOF) {
      printf("Fim de leitura\n");
      break;
    }
    printf("%s %d\n",buff,j);
  }
  fclose(p_arq);
  return 0;
}
Exercícios
  1. Utilize no exemplo acima o modo "w" ao invés de "w+". Qual sua conclusão?
  2. Teste agora com o modo append, da mesma maneira qual a diferença de "a" e "a+"?
Tratando erros de abertura de arquivo

Quando não é possível abrir um arquivo a função retorna NULL, porém para se saber o real motivo da falha é necessário consultar o "errno" que fica setado na biblioteca <errno.h>. Há diversos códigos de erro para váriadas funções, no caso da fopen, consulte aqui os erros possíveis.

#include <stdio.h>
#include <errno.h>

int main()
{
  FILE *fp;
 
  printf("Para simular o erro certifique-se de não haver um arquivo 'testeErro.txt' na pasta\n");
  if ((fp = fopen("testeErro.txt","r")) == NULL) {
    if (errno == ENOENT) 
      printf("Código de erro %d, arquivo não existe!\n",errno);
    else
      printf("Erro não previsto (código: %d)!\n",errno);
    return 0;
  }
  fclose(fp);
  return 0;
}

Utilizando typedef

Typedef introdução

O compilador possui uma lista de tipos definidos como 'char', 'int', 'float' e outros. O C permite ainda que outros tipos sejam adicionados por código através da diretiva 'typedef'. A vantagem é principalmente tornar o programa mias inteligível, utilizando-se nomes intuitivos.

Um exemplo, em um programa que processa cálculos financeiros poderia-se criar um tipo chamado para representar valores monetários, o tipo "Real", por exemplo. O comando seria conforme segue:

typedef float Real;

Então, declarar variáveis como salário, comissão e outras sendo do tipo "Real" tornaria o programa mais fácil de compreender.

Real salario, comissao; //Seria equivalente a declarar "float salario, comissao", porem com 'Real' fica mais inteligível

Outra vantagem de uso do typedef é a portabilidade entre plataformas. Imaginando um programa que roda em uma máquina 64 bits e é portado para uma 32 bits, ou 16 bits. Neste exemplo, a tratativa de números 64 bits é bastante diferente entre plataformas. Imaginando um campo 'quantidade' que pode armazenar números inteiros que podem variar até 2 bilhões (positivo ou negativo). Em uma máquina em que o inteiro ocupa 32 bits este variação é possível em um 'int'. Porém em uma plataforma cujo o inteiro ocupa 16 bits (short) já não será possível tal variação. Uma possível solução:

typedef int quantidade; //Plataformas 32 bits ou superior

Ou:

typedef long quantidade; //Plataformas 16 bits
Utilizando typedef em estruturas
Usando o typedef para ajudar na definição e declaração de estruturas
#include <stdio.h>
#include <stdlib.h>
 
void main()
{
  typedef struct {
     int x;
     int y;
  } TTeste;
  
  TTeste *teste;
  
  teste = (TTeste *) malloc (sizeof(TTeste));
  if (teste==NULL) {
      printf("erro de alocação");
      exit(1);
  }
  
  teste->x=10;
  
  free(teste);
  teste=NULL;
}

Recursividade

Introdução a Recursividade

A recursividade ocorre quando uma função chama a si própria. O retorno da função é sempre dado a função que a chamou, então se a função chama a si própria ela mesma recebe o retorno de uma segunda chamada, a segunda chamada receberá o retorno se esta se chamar uma terceira vez e assim por diante. Esta característica é possível pois o C quando inicia a execução de uma função, salva o contexto da anterior em uma pilha, esta função anterior oportunamente receberá o retorno de uma função que chamou.

É importante que a função quando projetada para trabalhar recursivamente possui um teste seguro para que não se chame infinitamente.

Há ainda dois modos de operação, simples ou múltiplo. Na recursividade simples a função se chama apenas uma vezes dentro de uma execução. Na recursividade múltipla em um ciclo de execução a função se chama mais de uma vez (na mesma recursão). O modo simples tende a ser mais eficiente, o tempo e espaço tendem a aumentar linearmente conforme a quantidade de recursões. No modo múltiplo o tempo de execução e o espaço de memória que a função precisa aumentam exponencialmente.

Fontes:

  • KING, K.N. C Programming: A Modern Approach; 2ª ed. [S.l]:W. W. Norton & Company, 2008. 832p. ISBN 9780393979503.
  • wikipedia
Exemplo de recursividade simples
Exemplo 1
Cálculo do fatorial

O fatorial de um número natural n, representado por n!, é o produto de todos os inteiros positivos menores ou iguais a n fonte.

<stdio.h>

int main()
{
  int in, out = 0;
  printf("Entre com o valor de entrada da fatorial: ");
  scanf("%d",&in);
  out = fact(in);
  printf("Resultado: %d\n",out);
  return 0;
}

int fact(int n)
{
  if (n <= 1)
  {
    printf("Finalmente n = 1, a função para se se chamar e retorna o resultado final.\n");
    return 1;
  }
  else
  {
    printf("Nesta chamada n = %d, esta função se chamará recursivamente se for maior que 1.\n",n);
    return n * fact(n-1);
  }
}

No exemplo acima, se entrarmos com o valor 3 (para realizar o fatorial de 3), a seguinte sequencia será processada:

fact(3) Como 3 não é menor ou igual a 1, realiza chamada de si mesma porém com n-1 que será 2
  fact(2) Como 2 não é menor ou igual a 1, realiza chamada de si mesma com n-1 que será 1
    fact(1) Finalmente 1 é menor ou igual a 1, então o retorno será 1, este retorno será entregue a chamada anterior, quando era fact(2)
  fact(2) retornará n * 1 (retorno recebido), resultado 2
fact(3) retornará n * 2 (2 foi o retorno que ela recebeu), resultando 6

Este modo é chamado de recursividade simples pois a função fact() é chamada apenas uma vez em uma recursão.

Exercícios
  1. Qual a limitação de amostragem do resultado do fatorial? Por que existe limitação?
  2. Como se comporta o tempo de processamento a medida que se solicita uma quantidade maior de recursões?
Exemplo de recursividade múltipla
Exemplo 2
Cálculo do número de Fibonacci e da sequência

Na matemática os números de Fibonacci são definidos pela equação F(n) = F(n-1) + F(n-2) onde F(0) = 0 e F(1) = 1 fonte.

#include <stdio.h>

int main()
{
  int in, out = 0, i;
  printf("Entre com o sequencial que deseja saber o fibonacci: ");
  scanf("%d",&in);
  out = fib(in);
  printf("Resultado: %d\n",out);

  printf("A sequencia fica assim:\n");
  for (i=1;i<=in;i++) printf("%d ",fib(i));
  printf("\n");
  return 0;
}

int fib(int n)
{
  if (n-1 <= 0)
    return 0;
  else if (n-1 == 1)
    return 1;
  else {
    return fib(n-1) + fib(n-2);
  }
}
Exercícios
  1. Observe que a função fib() se chama duas vezes em cada recursão, observe como o tempo de processamento é afetado a medida que se aumenta o número de fibonacci requisitado
  2. Qual o número máximo de fibonacci que pode ser obtido neste exemplo, por que existe esta limitação?
  3. Execute este código em um terminal e em outro rode um programa como o 'top' para observar o uso de recursos que cada programa está fazendo. Vá aumentando a sequencia para verificar como o programa se comporta. (para sair do 'top' pressione 'q').

Referências

Referências bibliográficas

  • Araújo, Everton Coimbra de. Algoritmos: fundamento e prática; 3ª ed. [S.l]:Visual Books, 2007. 414p. ISBN 9788575022092.
  • KERNIGHAN, Brian W.; RITCHIE, Dennis M C: a linguagem de programação padrão ANSI; 1ª ed.[S.l]:Campus, 1989. 304p. ISBN 9788570015860.
  • SCHILDT, Herbert C Completo e Total; 3ª ed. [S.l]:Makron Books, 2009. 827p. ISBN 9788534605953.
  • FORBELLONE, Andre L. Lógica de Programação; 3ª ed. [S.l]:Makron Books, 2005. 197p. ISBN 9788576050247.
  • KING, K.N. C Programming: A Modern Approach; 2ª ed. [S.l]:W. W. Norton & Company, 2008. 832p. ISBN 9780393979503.
  • MANZANO, Jose Augusto Navarro Garcia Estudo Dirigido em Linguagem C. ; 16ª ed. [S.l]:Erica, 2012. 216p. ISBN 9788571948877.
  • NEVES, Júlio Cézar Programação Shell Linux; 5a ed. Rio de Janeiro:Brasport, 2005. 408p. ISBN 8574522031.
  • VEIGA, Roberto G. A. Comandos do Linux: guia de consulta rápida; ed. São Paulo:Novatec, 2004. 144p. ISBN 85-7522-060-8.

Referências adicionais

Ferramentas úteis

  • VisualG3: Uma IDE para desenvolvimento de programas em pseudocódigo (freeware), permite editar e compilar programas utilizando uma sintaxe própria de pseudocódigo muito parecida com a que trabalhamos em sala. Muito útil para verificar o funcionamento real dos algoritmos. Ver exemplos de códigos visualG3 em Exemplos VisualG3
  • LibreOffice: O LibreOffice é um programa gratuito (freeware) e de código aberto (opensource). Além de editor de textos, planilhas e apresentações tem a ferramenta Draw que permite a criação de fluxogramas.
  • VirtualBox: O Oracle VirtualBox é um programa gratuito (freeware) que permite criar e instanciar máquinas virtuais. O uso de máquinas virtuais é bastante interessante quando desejamos ter diferentes sistemas operacionais em um computador bem como quando se está realizando ensaios e deseja-se isolar estes experimentos do sistema principal.
  • Debian: O Debian, é umas das distribuições Linux mais estáveis existentes, suportando atualmente 12 arquiteturas de processador. É software livre e de código aberto e mantido por uma ampla comunidade com mais de 18000 desenvolvedores. Sua versão atual é a 8.5 (codinome Jessie, do filme Toy Story).
  • Ubuntu: O Ubuntu é uma distribuição linux (freeware e opensource) bastante estável e com uma comunidade bastante ativa que está sempre atualizando o sistema e presente nos foruns e redes sociais para dirimir dúvidas.
  • LinuxMint: O LinuxMint é uma distribuição linux (freeware e opensource) bastante estável e confortável aos usuários windows, pois traz um gerenciador de janelas configurado de uma forma mais natural para estes usuários e vem com um conjunto de programas pré-instalados que consegue atender a maior parte das demandas inicias.
  • dbDesigner4: O dbDesigner é uma ferramenta gratuita para elaboração de diagramas de bancos de dados relacionais. Não trabalhamos com bancos na disciplina PRG29002, porém trabalhamos com dados, esta ferramenta é útil para organizá-los em diagramas.

Projeto final

O aluno deve propor ao professor um projeto de sua preferência que respeite os requisitos mínimos. Sendo aceito deverá desenvolver o projeto e apresentá-lo.

Requisitos mínimos

  • Realizar acesso a arquivo, lendo e escrevendo informações (escrever em arquivo de log e em arquivo de configuração);
  • Utilizar funções (ao menos duas além do main, sendo que devem receber argumentos e possuem retorno);
  • Apresentar menu utilizando switch case e conter laço infinito;
  • Utilizar Structs ou Unions;
  • Utilizar alguma biblioteca (além da stdio.h);
  • Utilizar diretivas de pré-compilação;
  • Utilizar comentários;
  • Utilizar Ponteiros;
  • Utilizar alocação dinâmica de memória.
  • Aceitar argumento de entrada no programa;

Modelo

  • Trabalho individual

Metodologia

  1. Apresentar a proposta de projeto ao professor
  2. Opcional: Documentar o escopo do projeto utilizando descrição narrativa (descrição simples)
    1. Cenário
    2. Problema
    3. Dados de entrada e saída
    • O planejamento do cronograma não será cobrado porém cabe ao aluno se organizar quanto ao tempo para entrega no prazo
  3. Desenvolver o projeto
  4. Apresentar individualmente ao professor
    • Serão realizados testes diversos, arguido sobre o funcionamento, possibilidades de alterações, etc

Algumas ideias de projetos

  • Sugestão geral: veja em outras disciplinas que processos podem ser automatizados e proponha um projeto que realiza esta tarefa como de cálculos diversos de eletrônica, de rádio transmissão, etc.
  • Implementar o jogo Pedra, papel ou tesoura. Neste jogo dois ou mais jogadores em diferentes computadores devem rodar um aplicativo que fará a leitura de um arquivo compartilhado. O algoritmo deve tratar as etapas do jogo (Setup do aplicativo, entrada na sala, escolha da figura e apresentação do resultado)
  • Implementar o jogo da velha escrevendo em arquivo. Neste jogo dois jogadores em diferentes computadores devem rodar um aplicativo que fará a leitura de um arquivo compartilhado. O algoritmo deve tratar as etapas do jogo (Setup do aplicativo, entrada na sala, seleção das casas e apresentação do resultado)
  • Implementar controle de empréstimo de objetos. Neste software o usuário poderá digitar nomes de objetos que emprestou, a pessoa a quem emprestou e automaticamente o software guarda a data. Deve haver uma opção para gerar relatório dos itens emprestados e opção para marcar a devolução (podendo manter o registro em histórico ou apagando o registro).
  • Implementar software gerador de lista de compras. Neste software o usuário poderá digitar itens de supermercado com nome e quantidade. O software escreve num arquivo que poderá depois ser impresso. O software também pode ter função de numa segunda execução já trazer a antiga listagem digitada e permitir que o usuário apenas selecione novas quantidades ou inclua novos itens.
  • Implementar software para realização de cálculos de eletrônica. Neste software um menu apresenta várias opções de cálculo como de potencia através de tensão e corrente, como obtenção do valor de um resistor, como solução de equivalência de paralelo de vários resistores e outras. Num arquivo texto pode ser armazenado um histórico de operações realizadas.
  • Implementação de software para apostas na mega sena. Neste software são dadas sugestões de números para apostas de acordo com o número do sorteio da mega sena. Com este histórico armazenado é possível então entrar com um número de sorteio e digitar quais foram os números verdadeiramente sorteados na loteria federal checagem os acertos.

Projetos dos alunos

  • Alexandre: Plano de vôos
  • Ameliza: Forca
  • André: Calculos de Eletronica
  • Fabio: Controle de biblioteca
  • João: Controle de estoque de supermercado
  • Luiza: Controle Financeiro Pessoal
  • Marcone: Emprestimo de objetos
  • Gabriel Santos: Jokempô
  • Gabriel Martins: Jogo de corrida
  • Guilherme Vieira: Controle de estoque
  • Guilherme Januário: controle de comanda de eventos
  • Gustavo Prim: Lista de compras
  • Vinicius: Mega sena
  • Yan: Controle de estacionamento

Avaliações

Conceitos
Conceitos
Matrícula A1 R1 M1 A2 R2 M2 A3 R3 M3 Conceito Final
161006340-6 5 7 7 3 3 3 7 5
161005803-8 1 2 2 0 0 0 0 1
161001156-2 8 7 8 3 4 4 0 4
142003344-1 2 7 7 1 1 1 6 4
152000674-8 1 3 3 0 0 0 0 1
142001213-4 0 2 2 0 0 0 0 1
152000584-9 6 0 6 0 5 5 8 6
142001549-4 0 0 0 0 0 0 0 0
152000614-4 6 0 6 0 0 0 0 2
152000195-9 0 0 0 0 0 0 0 0
151006902-0 2 3 3 1 1 1 4 2
152000492-3 6 0 6 1 1 1 0 2
161000791-3 7 0 7 2 3 3 6 5
161006555-7 3 7 7 0 0 0 0 2
151005047-7 0 0 0 0 0 0 0 0
161000400-0 3 8 8 1 2 2 5 4
161000397-7 6 0 6 1 3 3 0 3
161001792-7 9 0 9 10 0 10 10 10
142003391-3 5 7 7 1 3 3 7 5
161001388-3 5 8 8 1 4 4 7 6
132002139-5 0 0 0 0 0 0 0 0
161005026-6 7 0 7 4 1 4 0 4
161000776-0 5 8 8 1 3 3 8 6
142001175-8 1 6 6 1 1 1 0 2
161006541-7 7 0 7 0 0 0 0 2
132005743-8 0 0 0 0 0 0 0 0
161006345-7 8 0 8 8 0 8 8 8
151003419-6 0 5 5 0 0 0 0 1
151001656-2 3 1 3 0 0 0 0 1
161001497-9 0 1 1 0 0 0 0 1
122001586-5 0 0 0 0 0 0 0 0
142003303-4 0 0 0 0 0 0 0 0
152000345-5 0 0 0 0 0 0 0 0 0 0

Plano de aula

Aula Data Horas Conteúdo Recursos
1 12/8 2 Aula inaugural, apresentação do professor e turma, apresentação da disciplina e introdução aos algoritmos
2 15/8 2 Prática: Introdução ao fluxograma
3 19/8 2 Algoritmos continuação (representação por pseudo-codigo, apresentacao o portugol)
4 22/8 2 Prática: Resolução de problemas em pseudocódigo e fluxogramas (declaração de variáveis, leia e escreva, uso de condicionais e biblioteca portugol)
5 26/8 2 Pseudocódigo: Estruturas de repetição e sub-rotinas
6 29/8 2 Prática: Resolução de problemas em pseudocódigo e fluxogramas
7 2/9 2 Pseudocódigo: Uso de vetores e sub-rotinas
8 5/9 2 Prática: Resolução de problemas com vetores e sub-rotinas
9 9/9 2 Introdução ao C, primeiros conceitos de compilação, variáveis, if… else, printf e scanf
10 12/9 2 Prática: Aula de exercicios de C
11 16/9 2 Continuação C, condicionais, operadores relacionais, operadores lógicos
12 19/9 2 Prática: Estruturas de repetição em C
13 23/9 2 Revisão de algoritmos para preparação para prova e Funções no C
14 26/9 2 Prática: Exercicios de funções
15 30/9 2 Avaliação 1 - Algoritmos, resolução de problemas e C básico
16 3/10 2 Prática: Correção da Avaliação
17 7/10 2 Resolução de exercícios de vetores
18 10/10 2 Prática: Resolução de exercícios de fixação de vetores.
19 14/10 2 Estruturas
20 17/10 2 Prática: Exercícios adicionais de preparação para avaliação 1 de Laboratório
21 21/10 2 Exercícios adicionais de preparação para avaliação 1 de Laboratório
22 24/10 2 Prática: Avaliação de Laboratório
23 28/10 2 FERIADO: DIA DO SERVIDOR PUBLICO
24 31/10 2 Prática: Correção da avaliação
25 4/11 2 Ponteiros
26 7/11 2 Prática: Exercicios ponteiros
27 11/11 2 Vetor de Ponteiros e Ponteiro Para Estruturas
28 14/11 2 PROVAVEL RECESSO
29 18/11 2 Desenvolvimento do Projeto
30 21/11 2 Prática: Desenvolvimento do Projeto
31 25/11 2 Avaliação II de Laboratório
32 28/11 2 Prática: Desenvolvimento do Projeto
33 2/12 2 Desenvolvimento do Projeto
34 5/12 2 Prática: Desenvolvimento do Projeto
35 9/12 2 Desenvolvimento do Projeto
36 12/12 2 Prática: Desenvolvimento do Projeto
37 16/12 2 Desenvolvimento do Projeto
38 19/12 2 Prática: Recuperação
TOTAL 76

Tópicos avançados

Calculo de tempo recursividade simples

Na recursividade simples o tempo aumenta linearmente como mostra o código abaixo

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

int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
  return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
           ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}

int fact(int n)
{
  if (n <= 1)
  {
    printf("Finalmente n = 1, a função para se se chamar e retorna o resultado final.\n");
    return 1;
  }
  else
  {
    printf("Nesta chamada n = %d, esta função se chamará recursivamente se for maior que 1.\n",n);
    return n * fact(n-1);
  }
}

int main()
{
  uint64_t tempoFact10 = 0;
  uint64_t tempoFact20 = 0;
  uint64_t tempoFact30 = 0;
  int i;
  int in, out = 0;
  struct timespec start, end;

  for (i=0;i<10;i++)
  {
    clock_gettime(CLOCK_MONOTONIC, &start);

    // Some code I am interested in measuring 
    in = 10;
    out = fact(in);
 
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t timeElapsed = timespecDiff(&end, &start);
    tempoFact10 += timeElapsed / 10;
  }
  printf("Resultado: %d\n",out);

  for (i=0;i<10;i++)
  {
    clock_gettime(CLOCK_MONOTONIC, &start);

    // Some code I am interested in measuring 
    in = 20;
    out = fact(in);
 
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t timeElapsed = timespecDiff(&end, &start);
    tempoFact20 += timeElapsed / 10;
  }
  printf("Resultado: %d\n",out);

  for (i=0;i<10;i++)
  {
    clock_gettime(CLOCK_MONOTONIC, &start);

    // Some code I am interested in measuring 
    in = 30;
    out = fact(in);
 
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t timeElapsed = timespecDiff(&end, &start);
    tempoFact30 += timeElapsed / 10;
  }
  printf("Resultado: %d\n",out);

  printf("\nMédia de tempo Fact(10): %li\n",tempoFact10);
  printf("\nMédia de tempo Fact(20): %li\n",tempoFact20);
  printf("\nMédia de tempo Fact(30): %li\n",tempoFact30);
}
Calculo de tempo recursividade multipla

Na recursividade simples o tempo aumenta linearmente como mostra o código abaixo

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

int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
  return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
           ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}

int main()
{
  uint64_t tempoFib10 = 0;
  uint64_t tempoFib20 = 0;
  uint64_t tempoFib30 = 0;
  uint64_t tempoFib40 = 0;
  int i;
  int in, out = 0;
  struct timespec start, end;

  for (i=0;i<10;i++)
  {
    clock_gettime(CLOCK_MONOTONIC, &start);

    in = 10;
    out = fib(in);
 
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t timeElapsed = timespecDiff(&end, &start);
    tempoFib10 += timeElapsed / 10;
  }
  printf("Resultado: %d\n",out);

  for (i=0;i<10;i++)
  {
    clock_gettime(CLOCK_MONOTONIC, &start);

    in = 20;
    out = fib(in);
 
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t timeElapsed = timespecDiff(&end, &start);
    tempoFib20 += timeElapsed / 10;
  }
  printf("Resultado: %d\n",out);

  for (i=0;i<10;i++)
  {
    clock_gettime(CLOCK_MONOTONIC, &start);

    in = 30;
    out = fib(in);
 
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t timeElapsed = timespecDiff(&end, &start);
    tempoFib30 += timeElapsed / 10;
  }
  printf("Resultado: %d\n",out);

  for (i=0;i<10;i++)
  {
    clock_gettime(CLOCK_MONOTONIC, &start);

    in = 40;
    out = fib(in);
 
    clock_gettime(CLOCK_MONOTONIC, &end);

    uint64_t timeElapsed = timespecDiff(&end, &start);
    tempoFib40 += timeElapsed / 10;
  }
  printf("Resultado: %d\n",out);

  printf("\nMédia de tempo Fib(10): %li\n",tempoFib10);
  printf("\nMédia de tempo Fib(20): %li\n",tempoFib20);
  printf("\nMédia de tempo Fib(30): %li\n",tempoFib30);
  printf("\nMédia de tempo Fib(40): %li\n",tempoFib40);
}

int fib(int n)
{
  if (n-1 <= 0)
    return 0;
  else if (n-1 == 1)
    return 1;
  else {
    return fib(n-1) + fib(n-2);
  }
}

Exemplo de resultado:

Média de tempo Fib(10): 1113
Média de tempo Fib(20): 113658
Média de tempo Fib(30): 9817635
Média de tempo Fib(40): 427706133

Ou seja, há um aumento exponencial no tempo

Verificando o código de retorno de um programa
  • Após encerrar o programa executar:

$ echo $?</syntaxhighlight>