AULA 19 - Programação 1 - Engenharia

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

1 Objetivos

Ao final da aula o aluno deverá ser capaz de utilizar:

  • vetor de ponteiros e aplicar o conceito de vetor de ponteiros;
  • parâmetros em linha de comando argc e argv;
  • ponteiros para qualquer "objeto" na memória (nada de relacionado com Programação Orientada a Objetos);
  • ponteiros para estruturas
ADENDO: Links Sugeridos Pelo Aluno Jean. Este link possui um material muito interessante sobre ponteiros.

2 Vetor de ponteiros

Como visto em aulas anteriores, variáveis ponteiros possuem como conteúdo um endereço. Neste sentido, é perfeitamente possível construir vetores e/ou matrizes de ponteiros. Tais arranjos, possuem como elementos valores que correspondem a endereços. No exemplo abaixo, vp é um vetor de ponteiros para char e cada elemento de vp apontará para uma cadeia de caracteres (strings) armazenadas em outros vetores.

NOTAR que os endereços possuem 32 bits (8 dígitos hexadecimais).

NOTAR também como é declarado o arranjo vp:

 char *vp[4];

O tipo de cada elemento do vetor vp é char *, ou seja, ponteiro para char. Na prática, cada elemento de vp é um ENDEREÇO onde está armazenado um valor do tipo char (poderia ser também um início de string, já que uma string é uma sequÊncia de char com NUL no final.


#include <stdio.h>

int main()
{
  int i;

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

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

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

O desenho abaixo mostra uma possível representação na memória, das variáveis envolvidas no exemplo. Note que a posição 0 de vp (vp[0]) aponta para a string "IFSC". Apontar significa que vp[0] contém o endereço inicial da string que neste caso é o endereço inicial do vetor alfa.


3 Argumentos de linha de comando

Um bom exemplo de USO de vetor de ponteiros é a passagem de parâmetros na linha de comando de um programa. É muito comum que na execução de um comando se passe parâmetros para "customizar" o funcionamento do mesmo. É comum comandos do Linux em linha de comando serem chamados com parâmetros:

 ls -l
 cp /home/aluno/relatorio.doc /tmp

Nos exemplos acima chamamos comando ls (listar arquivos) customizado para listar detalhadamente. Para tal, fornecemos a opção -l. Note que é dado um espaço entre o nome do programa executável (ls) e a opção. O programa deve ser capaz de acessar a string -l para interpretá-la e então modificar o seu comportamento.

Quando um programa é chamado, o sistema operacional CUIDA para que os parâmetros da linha de comando (strings) sejam copiados para a área de memória do programa, de forma que o código executável possa acessá-los.

3.1 Passagem de parâmetro para um programa C

Do ponto de vista de um programa C, tais parâmetros podem ser acessados a partir de parâmetros formais da função main, que devem ser declarados conforme o exemplo abaixo. Cada parâmetro é tratado como uma cadeia de caracteres apontada por um elemento do vetor argv. O número de parâmetros é passado em argc. Note que argv[0] aponta para uma string que é o nome do programa.

Exemplo: Considere o programa abaixo:

#include <stdio.h>

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

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

EXERCÍCIO 1: 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 indicação do tipo:

cmpcadeia: dois parametros devem ser passados

Solução Ex.1
#include <stdio.h>
#include <string.h>
 
int main(int argc, char *argv[]){

  if (argc==3) {
     if (strcmp(argv[1],argv[2])==0) {
       printf("Strings são iguais\n");
     } else {
       printf("Strings são diferentes\n");
     }
  } else {
     printf("%s: Número inválido de argumentos\n", argv[0]);
  }
 return 0;
}

EXERCÍCIO 2: Renomeie o executável e veja seja a mensagem de erro mostra o nome correto do programa.

EXERCÍCIO 3: Implementar um programa chamado "calculador" que recebe 3 parâmetros na linha de comando: dois números reais e um operador (char). O operador pode ser "+" ou "-". O programa deve mostrar o resultado da operação. Exemplo:

calculador 3.5 + 2.6

OBS: usar a função atof para converter string em double.

solução Ex. 3
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int quant, char *argv[])
{
  float num1, num2, result;
  char oper;

  oper = *argv[2];

  num1 = atof (argv[1]);
  num2 = atof (argv[3]);

  switch (oper) {
    case '+':
        result = num1 + num2;
        printf("Resultado: %0.2f\n", result);
        break;
    case '-':
        result = num1 - num2;
        printf("Resultado: %0.2f\n", result);
        break;
    case '*':
        result = num1 * num2;
        printf("Resultado: %0.2f\n", result);
        break;
    case '/':
        if ( num2 != 0 ){
            result = num1 / num2;
            printf("Resultado: %0.2f\n", result);
        }
        else
            printf("Denominador inválido\n");

        break;
    default:
        printf("Operacao invalida\n");
    }
    return 0;
}

3.2 Como consultar o valor de retorno de um programa no shell

No shell (como bash), o valor de retorno (também chamado de código de saída) do último comando ou programa executado pode ser acessado usando a variável especial:

echo $?

3.2.1 Exemplo prático

Suponha que você tenha um programa em C chamado busca:

./busca

Logo após a execução, para ver o valor de retorno:

echo $?

Esse número é o valor retornado pela função main() do programa em C:

int main() {
    // ...
    return 2;  // Este valor será mostrado com echo $?
}

3.2.2 Observações

  • O valor de retorno é um inteiro entre 0 e 255.
  • Por convenção, o valor 0 indica sucesso e qualquer outro valor indica algum tipo de erro.
  • Em scripts, o valor de retorno pode ser usado para controle de fluxo. Exemplo:
./meu_programa
if [ $? -eq 0 ]; then
    echo "Tudo certo!"
else
    echo "Erro no programa!"
fi

4 Ponteiros para qualquer "coisa"...

Podemos criar ponteiros para apontar para qualquer objeto na memória. Por exemplo, podemos apontar para variáveis do tipo float, double etc.

int main(void)
{
  float a, *p;
  
  p=&a;

  *p= 5.5;
   return 0;
}

Este exemplo mostra como usar um ponteiro para float para alterar o valor de uma variável dentro de uma função.


#include <stdio.h>

// Função que dobra o valor de um float usando ponteiro
void dobrar(float *p) {
    *p = (*p) * 2;
}

int main() {
    float valor = 3.5;

    printf("Antes: %.2f\n", valor);

    // Passa o endereço de valor para a função
    dobrar(&valor);

    printf("Depois: %.2f\n", valor);

    return 0;
}

5 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;
} pessoa ={"Maria", 10}; 


main()
{
  struct TRegistro *p;
  p = &pessoa; /*p aponta para o registro pessoa */
  printf("O nome da pessoa é %s e idade = %d\n", p->nome,p->idade);
}
NOTE a forma como a partir de um ponteiro, pode-se acessar uma campo da estrutura!
O uso de p->nome é uma alternativa ao uso de (*p).nome

Agora vamos ver um exemplo acessando estruturas armazenadas em tabelas (vetor de estruturas):

#include <stdio.h>
#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);
}


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

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

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

Exercício 4:

No programa acima 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.

Solução ex4
//autor: Victor Cesconetto
#include <stdio.h>
struct TRegistro {
   char nome[20];
   int idade;
} Tabela[4] = {
          {"joao",18,},
          {"maria",18,},
          {"jose",19,},
          {"lara",17,},
};
 
struct TRegistro *p;
 
void imprimeStruct(struct TRegistro *p1, int tamanho)
{
  int i;
  for(i=0;i<tamanho;i++){
      printf("nome %s tem idade de %d\n",p1->nome,p1->idade);
      p1++;
  }
}
 

int main()
{
  struct TRegistro aux = {"luisa",16};
  imprimeStruct(&Tabela,2);
 }

Exercício 5

Implementar um programa que implementa um relógio através de uma estrutura. Ao ser chamado, o programa deve mostrar as horas, minutos e segundos, iniciando de 00:00:00. Trata-se de uma variação do programa mostrado em (ver https://www.youtube.com/watch?v=cnqIhFsFSFU). Utilize a função da biblioteca sleep para aguardar 1s. O corpo do programa é dado a seguir:

struct tempo {
 char horas;
 char minutos;
 char segundos;
};

void atualizar_relogio(struct tempo *p)
{
}

void mostrar_relogio(struct tempo *p)
{
}

main ()
{
  struct tempo meu_relogio;

  meu_relogio.horas=0;
  meu_relogio.minutos=0;
  meu_relogio.segundos=0;

  while(1){
     atualizar_relogio(&meu_relogio);
     mostrar_relogio(&meu_relogio);
  }
}


Solução
#include <unistd.h>
#include <stdio.h>

struct tempo {
 char horas;
 char minutos;
 char segundos;
};
 
void atualizar_relogio(struct tempo *p)
{
  sleep(1);
  p->segundos++;
  if(p->segundos==60) {
     p->minutos++;
     p->segundos=0;
     if(p->minutos==60) {
        p->horas++;
        p->minutos=0;
        if(p->horas==24)
           p->horas=0;
     }
  }
}
 
void mostrar_relogio(struct tempo *p)
{
  printf("%02d:%02d:%02d\n", p->horas,p->minutos,p->segundos);
}
 
main ()
{
  struct tempo meu_relogio;
 
  meu_relogio.horas=0;
  meu_relogio.minutos=0;
  meu_relogio.segundos=0;
 
  while(1){
     atualizar_relogio(&meu_relogio);
     mostrar_relogio(&meu_relogio);
  }
}

Exercício 6:

#include <stdio.h>
#define TAM_TABELA 4

struct TRegistro {
   char nome[20];
   int idade;
} Tabela[TAM_TABELA] = {
          {"joao",18,},
          {"maria",18,},
          {"jose",19,},
          {"lara",17,},
};

struct TRegistro *retorna_usuario(struct TRegistro *tabela, int tam_tabela, char *usuario)
{
  
}

int main()
{
  /* TESTAR AQUI */
}

5.3 Múltiplas indireções

Observe que é tecnicamente possível várias indireções. Um ponteiro pode apontar para um ponteiro (ponteiro para ponteiro). Veja o exemplo seguinte.

#include  <stdio.h>

void main()
{
  int a, *b, **c, ***d;

  a =  3;
  b = &a;
  c = &b;
  d = &c;

  printf("Valor de a = %d\n",***d);
}