Mudanças entre as edições de "PRG029003 - Programação II - 2023-2"

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar
Linha 680: Linha 680:
 
{{collapse bottom}}
 
{{collapse bottom}}
  
<!--{{collapse top | Ponteiros}}-->
+
{{collapse top | 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.  
 
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.  
Linha 1 023: Linha 1 023:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
<!--{{collapse bottom}}-->
+
{{collapse bottom}}
  
 
{{collapse top | Usando ponteiros como parâmetros de entrada saída de funções}}
 
{{collapse top | Usando ponteiros como parâmetros de entrada saída de funções}}

Edição das 13h19min de 15 de agosto de 2023

Dados importantes

Professor

Turma virtual

  • Acesse o sigaa bucando a disciplina PRG029003 - PROGRAMAÇÃO II (2023 .2 - T01)

Aulas síncronas presenciais

  • Horários
    • terças-feiras das 13:30 às 15:20
    • sextas-feiras das 13:30 às 15:20
    • Mais informações ver SIGAA
  • Local
    • Laboratório de Programação (LabProg)

Organização curricular

Plano de ensino

A unidade curricular se compõe de conhecimentos relacionados às estruturas de dados, com ênfase em sua utilização na escrita de programas.

  • Usar as estruturas de dados fila, pilha, lista, tabela de dispersão e árvore binária na escrita de programas;
  • Identificar as situações e necessidades em que cada estrutura de dados é apropriada;
  • Conhecer o custo computacional das operações elementares das estruturas de dados, e de algoritmos de busca e ordenamento, para que se possam utilizá-los de forma eficiente;
  • Conhecer o custo computacional das operações elementares das estruturas de dados, e de algoritmos de busca e ordenamento, para que se possam utilizá-los de forma eficiente;

Ementa

Ementa da disciplina na wiki

Metodologia

Os estudos serão guiados por leituras, exercícios, e projetos. O conteúdo da unidade curricular será apresentado por meio de aulas expositivas e aulas práticas de maneira articulada com aplicações do conhecimento.

Recursos auxiliares

  • Utilização do sistema acadêmico SIGAA para avisos e registro de frequência
  • Utilização do moodle para atividades complementares e registros de participação em aula.

Referências bibliográficas

Básica

  • CORMEN, Thomas H. et al. Algoritmos: teoria e prática. LTC, 2012. (link para minha biblioteca - necessário logar via SIGAA primeiro)
  • LORENZI, Fabiana; MATTOS, Patrícia de; CARVALHO, Tanisi de. Estruturas de dados. Cengage Learning, 2006. ISBN 978-8522105564.

Complementar

  • BACKERS, André. Linguagem C: completa e descomplicada. Grupo GEN, 2023. (link para minha biblioteca - necessário logar via SIGAA primeiro)
  • KERNIGHAN, Brian W.; RITCHIE, Dennis M. C. A Linguagem de Programação Padrão Ansi. Elsevier, 1989. ISBN 978-8570015860.

Material de apoio

Tópicos de Aula

Ferramentas e revisão da linguagem C

Introdução ao C e funções de saída e entrada de dados
  • Introdução
    • C e C++ estão entre as 5 linguagens de programação mais populares
    • 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(int argc, char∗∗ argv)
      {
        /*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@computador:~$ </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@computador:~/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);
    }
    
Observe que o C permite também que uma variável seja inicializada com determinado valor no momento de sua criação. Ex.:
#include <stdio.h>
main()
{
    int x = 5; /* declaração de uma variável inteira atribuindo o valor 5*/
    printf ("O valor de x é %d\n",x);
}
  1. 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”);
  • 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.
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 - C (série 0)
  • Exercícios - C
    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 - C
    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. 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
    4. 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.
    5. 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
    6. 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 (id:1.06)
    7. 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"
    8. 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 (id.:1.08)

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>

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 - C (série 5)
  1. Implemente um programa onde no laço principal o usuário é questionado se deseja calcular a área de um triângulo ou de um retângulo, em seguida obtém a base e altura da forma geométrica desejada. De acordo com a seleção invoca uma função específica (ambas recebendo os argumentos base e altura do tipo float). De acordo com a seleção do usuário, utilize structs TRetangulo ou TTriangulo para armazenar a base, altura e área calculada (retornada da função). Imprima o resultado em tela. Lembre-se que a área do retângulo é dada pela base vezes a altura e a do triângulo pela base vezes altura sobre 2.
  2. Crie uma estrutura TUsuario com os campos UserID, senha e quantidade de tentativas. 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.
  3. Implementar uma funcionalidade do administrador para desbloquear o usuário bloqueado. Inclua na estrutura uma variavel _Bool para dizer se o usuário é administrador. Se for, este usuário pode ter acesso a um menu (que você deve desenvolver) de desbloqueio de usuário ao digitar um UserID de certo usuário bloqueado.
  4. 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! Implemente 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 lugar). 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

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