AULA 19 - Programação 1 - Engenharia

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

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.

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.

Fig1Aula24PrgITele.jpg


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.

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

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

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.

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

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

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