Home RecentChanges

Lesson06

Suponhamos que é necessário definir uma representação agregada na linguagem C para manipular inscrições de alunos a disciplinas. Numa inscrição é necessário representar o número de aluno, a sigla da disciplina (máx. 6 caracteres), e a nota obtida. Como representar uma inscrição em C? Será que podemos utilizar um vector? No caso de um vector todos os dados precisam de ser do mesmo tipo... A solução passa por utilizarmos estruturas para representar dados agregados de tipos diferentes.

As estruturas permitem definir estruturas de dados sofisticadas, as quais possibilitam a agregação de diferentes tipos de dados. Exemplos:

struct ponto {
  double x;
  double y;
};

struct data {
  int dia;
  int mes;
  int ano;
};

struct inscricao {
  int numero;
  char disciplina[7];
  int nota;
};

Notar que a definição de uma estrutura introduz um novo tipo de dados, e.g., struct ponto, struct data, e struct inscricao. Uma vez definidas as estruturas, podemos declarar variáveis do tipo de uma estrutura:

struct ponto centro;

Podemos também declarar e inicializar em simultâneo uma variável do tipo estrutura:

struct ponto centro = {12.3, 5.2};

Em geral temos <tipo> <variavel> = { <valores> };

E, é claro, podemos manipular os campos de variáveis do tipo estrutura:

centro.x = 12.3;
centro.y = 5.2;

Notar que uma estrutura pode incluir outras estruturas. Exemplo:

struct rectangulo {
  struct ponto a, b;
};

struct rectangulo rect;
rect.a.x = 3.4;
rect.b.y = 6.1;

Operações válidas sobre estruturas:

A cópia e atribuição incluem passagem de parâmetros para funções e retorno de valores de funções. As estruturas não podem ser comparadas através do operador de comparação de igualdade. Para determinar se duas estruturas são iguais, é necessário comparar cada campo da estrutura.

Estruturas e funções

As funções em C podem receber e retornar estruturas, no entanto é preciso ter em conta que a passagem é por valor, o que significa que as estruturas são copiadas. Por exemplo, na função

struct ponto adicionaPonto(struct ponto p1, struct ponto p2) {
  struct ponto res;
  res.x = p1.x + p2.x;
  res.y = p1.y + p2.y;
  return res;
}

é feita uma cópia de cada um dos argumentos aquando da invocação da função, e é retornada uma cópia da estrutura res. Uma vez que a passagem é por valor, a chamada a esta função com argumentos pa e pb, adicionaPonto(pa, pb), não altera os valores de pa e de pb. Podemos em particular redefinir a função como:

struct ponto adicionaPonto(struct ponto p1, struct ponto p2) {
  p1.x = p1.x + p2.x;
  p1.y = p1.y + p2.y;
  return p1;
}

int main() {
  struct ponto pa = {1, 3}, pb = {5, 2};
  struct ponto pc = adicionaPonto(pa, pb);
  ...
}

Ao invocar a função, adicionaPonto(pa, pb), os valores de pa e pb também não são alterados neste caso.

Vectores de estruturas

A utilização de vectores de estruturas permite-nos representar conjuntos de dados agregados.

struct ponto {
  double x;
  double y;
} figura [100];

struct ponto {
  double x;
  double y;
};
struct ponto figura [100];

Em ambos os casos estamos a declarar a variável figura como um vector de pontos.

Podemos também declarar e inicializar em simultâneo vectores de estruturas:

struct ponto {
  double x;
  double y;
};

struct ponto figura [] = {
  { 1.2, 3.4 },
  { 4.5, 12.6 },
  { 1.2, 10.8 }
};

typedef

Podemos definir novos tipos através do typedef o que permite associar um nome a um tipo de dados já existente, permitindo um grau adicional de abstracção.

typedef int Inteiro;

typedef struct {
  double x;
  double y;
} Ponto;

int main() {
  Inteiro i;
  Ponto x;
  ...
}

A sintaxe é typedef <tipo> <nome>;

Exemplo: Inscrições e notas a disciplinas

Objectivo:

  1. Programa para manipular as inscrições de alunos a disciplinas:
    1. dado um número de aluno, mostra as notas do aluno às disciplinas onde está inscrito;
    2. os dados de entrada são todas as inscrições às disciplinas;
    3. suponha que numa inscrição representamos o número de aluno, a disciplina e a nota do aluno na disciplina;
    4. define-se um limite máximo de inscrições no programa.
  2. Exemplo dos dados de entrada:
    1. 1ª linha com o número total de inscrições ($N$);
    2. $N$ linhas com o formato: número aluno, nota, sigla disciplina
3
36933 16 IAED
12345 14 IAED
23456 8 AC

Implementação:

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

#define MAX_COD_DISC 7
#define MAX_INSCRICOES 10000

typedef struct {
  int numero;                /* numero do aluno           */
  int nota;                  /* nota 'a disciplina        */
  char nome[MAX_COD_DISC];   /* nome/codigo da disciplina */
} inscricao;

int leTodasInscricoes (inscricao v[]);
void mostraNotasAluno(inscricao v[], int n, int aluno);

int main() {
  inscricao insc[MAX_INSCRICOES];
  int numInscricoes = 0, aluno;

  numInscricoes = leTodasInscricoes(insc);
  scanf("%d", &aluno);
  while (aluno > 0) {
    mostraNotasAluno(insc, numInscricoes, aluno);
    scanf("%d", &aluno);
  }

  return 0;
}

int leTodasInscricoes (inscricao v[]) {
  int n, i;

  scanf("%d", &n);
  for (i = 0; i < n; i++)
    scanf("%d%d%s", &(v[i].numero), &(v[i].nota), v[i].nome);

  return n;
}

void mostraNotasAluno (inscricao v[], int n, int aluno) {
  int i;

  for (i = 0; i < n; i++)
    if (aluno == v[i].numero)
      printf("%s %d\n", v[i].nome, v[i].nota);
}

void mostraNotasDisciplina (inscricao v[], int n, char disc[]) {
  int i;

  for (i = 0; i < n; i++)
    if (strcmp(v[i].nome, disc) == 0)
      printf("%d %d\n", v[i].numero, v[i].nota);
} 

Notar que na função leTodasInscricoes utilizámos %s para ler uma sequência de caracteres até encontrar um espaço, tabulação ou fim de linha. Notar também que o scanf coloca ‘\0’ no fim do texto e que, no caso da leitura de strings, não é necessário utilizar & no scanf, veremos mais tarde porque não é necessário. Na função mostraNotasDisciplina utilizámos também pela primeira vez a função strcmp, sendo necessário incluir string.h, para comparar duas strings, em que esta devolve 0 se as strings são iguais.