Programação Orientada à Procedimentos II Programação Orientada à Procedimentos I Processamento Paralelo Lógica de Programação Introdução à Computação Informática Básica

 

 

Informática Básica
Introdução à Computação
Lógica de Programação
Programação Orientada à Procedimentos I
Programação Orientada à Procedimentos II

Processamento Paralelo

Aula do dia 28 de Abril

Aula do dia 08 de Maio de 2003

Aula do dia 12 de Maio


Estruturas


Você provavelmente já deve ter deparado com um problema de programação, onde você deseja agrupar um conjunto de tipos de dados não similares sob um único nome. Seu primeiro impulso seria talvez usar uma matriz.
Como matrizes requerem que todos os seus elementos sejam do mesmo tipo você provavelmente forçará a resolução do problema selecionando uma matriz para cada tipo de dado. O resultado tornaria o programa ineficiente na maneira de manejar os dados. O problema de agrupar dados desiguais em C é resolvido pelo uso de estruturas.
Uma estrutura é uma coleção de uma ou mais variáveis, possivelmente de tipos diferentes, colocadas juntas sob um único nome. (Estruturas são chamadas "registros" em algumas linguagens).
O exemplo tradicional de uma estrutura é o registro de uma folha de pagamento: um funcionário é descrito por um conjunto de atributos tais como nome (uma "string"), o número do seu departamento (um inteiro), salário (um float), e assim por diante.
Provavelmente, haverão outros funcionários, e você vai querer que o seu programa os guarde formando uma matriz de estruturas.
O uso de uma matriz de várias dimensões não resolverá o problema, pois todos os elementos de uma matriz devem ser de um tipo único; você deveria usar várias diferentes matrizes: uma de caracteres para os nomes, uma float para os salários, uma de inteiros para número do departamento, e assim por diante. Esta não seria uma forma prática de manejar um grupo de características que você gostaria que tivessem um único nome: funcionário.
Uma estrutura consiste de um certo número de itens de dados, chamados membros da estrutura, que não necessitam ser de mesmo tipo, agrupados juntos.


Uma Estrutura Simples

O programa seguinte usa uma simples estrutura contendo dois itens de dados: uma variável inteira num e uma variável caractere ch.

#include "stdio.h"
#include "conio.h"

main()
{
	struct facil  // define tipo de dado
	{
		int num;  // inteiro na estrutura
		char ch;  // caractere na estrutura
	};

	clrscr();
	struct facil xx1;    // declara xxl como tipo estrutur
	xx1.num=2;
	xx1.ch='Z';
	printf("xx1.num= %d, xx1.ch= %c\n",xx1.num,xx1.ch);
	getch();
}

A saída deste programa será:

xx1.num=2, xx1.ch=Z

Este programa mostra os três principais aspectos de estruturas: definição do tipo struct, declaração de variáveis estrutura e acesso a seus membros.

Uma estrutura pode conter qualquer número de membros de diferentes tipos; o programa deve avisar o compilador de como é formada uma estrutura particular antes de seu uso. Isto não acontece com, por exemplo, o tipo int que é predefinido, pelo compilador, como sendo sempre de 2 bytes. No exemplo anterior as seguintes instruções definem o tipo da estrutura:

struct facil
{
  int num;
  char ch;
};

Estas instruções definem um novo tipo de dado chamado struct facil.
Cada variável deste tipo será composta por dois elementos: uma variável inteira chamada num, e uma variável caractere chamada ch.
Note que esta instrução não declara qualquer variável, e então não é reservado nenhum espaço de memória. Somente é mostrado ao compilador como é formado o tipo struct facil. A palavra struct informa ao compilador que um tipo de dado está sendo declarado e o nome facil é chamado "etiqueta" e nomeia a estrutura particular que está sendo definida.
Note que a "etiqueta" não é o nome de uma variável, isto é, nós não declaramos uma variável; é um nome de um tipo.
Os membros da estrutura devem estar entre chaves, e a instrução completa termina por ponto e vírgula.


Declarando as Variáveis da Estrutura

Em primeiro lugar nós definimos nosso novo tipo de dado. Podemos, então, declarar uma ou mais variáveis deste tipo. Em nosso programa declaramos a variável xx1 como sendo do tipo struct facil.

Esta instrução executa uma função similar às declarações de variáveis que já conhecemos como:

float x;
int num;

ela solicita ao compilador a alocação de espaço de memória suficiente para armazenar a variável xx1 que é do tipo struct facil, neste caso 3 bytes (dois para o inteiro e um para o caractere).


Acessando Membros da Estrutura

Agora que temos criado uma variável do tipo estrutura, precisamos de um meio para referenciar os seus membros.
Quando usamos uma matriz, podemos acessar um elemento individual através do subscrito: matriz[7].
Estruturas usam uma maneira de acesso diferente: o "operador ponto" (.), que é também chamado "operador de associação".
A sintaxe apropriada para referenciar num que é parte da estrutura xx1, é:

xx1.num

O nome da variável que precede o ponto é o nome da estrutura e o nome que o segue é o de um membro específico da estrutura. As instruções:

xx1.num = 2;
xx1.ch = 'Z'

atribui 2 ao membro num da estrutura xx1 e 'Z' ao membro ch.
O operador ponto é uma poderosa e esclarecedora maneira de especificar membros de uma estrutura. Por exemplo, a expressão funcionario.salario é mais compreensível que funcionário[7].


Múltiplas Estruturas de Mesmo Tipo

Do mesmo modo como podemos ter várias variáveis do tipo int em um programa, podemos também ter qualquer número de variáveis do tipo de uma estrutura predefinida.
Como exemplo, vamos declarar duas variáveis, xx1 e xx2, do tipo struct facil.

#include "stdio.h"
#include "conio.h"
// declara 2 variáveis tipo estrutura facil
main()
{
  struct facil  //define tipo de dado
  { 
    int num;  // inteiro na estrutura
    char ch;  //caractere na estrutura
  };
  struct facil xxl;
  struct facil xx2;
  clrscr();
  xxl.num=2;
  xxl.ch='Z';
  xx2.num=3;
  xx2.ch = 'Y';
  printf("xxl.num=%d, xxl.ch= %c \n",xxl.num,xxl.ch);
  printf("xx2.num=%d, xx2.ch= %c \n",xx2.num,xx2.ch);
  getch();
}
 

Combinando Declarações

Você pode combinar em uma instrução a definição do tipo estrutura e a declaração das variáveis tipo estrutura.
Como exemplo, o programa anterior será reescrito:

#include "stdio.h"
#include "conio.h"
// combina definicao de tipo estrutura com declaracao de variavel
main()
{
  struct facil 		// define tipo de dado
  {
    int num; 		// inteiro na estrutura
	 char ch;		// caractere na estrutura
  }xxl,xx2;

  clrscr();
  xxl.num=2;
  xxl.ch='Z';
  xx2.num=3;
  xx2.ch='Y';
  printf("xxl.num=%d, xxl.ch= %c \n",xxl.num,xxl.ch);
  printf("xx2.num=%d, xx2.ch= %c \n",xx2.num,xx2.ch);
  getch();
}

O efeito é o mesmo, mas o programa torna-se mais compacto, entretanto menos legível.


Definição de Estruturas sem "Etiqueta"

A convenção normal é a de usar a etiqueta da estrutura quando a expectativa é criar várias variáveis do mesmo tipo estrutura. Se você espera usar uma única declaração de variável do tipo estrutura, você pode combinar a declaração com a definição da estrutura e omitir a etiqueta:

struct  
{
    int num; 
    char ch;
}xxl,xx2;


Programa Exemplo: Criando Uma Lista de Livros

A partir deste ponto vamos examinar um exemplo um pouco mais realista que envolve a elaboração de uma lista de livros. A lista de livros compreende uma variedade de informações como: seu título, autor, editora, número de páginas, número do registro de biblioteca, seu preço etc.
Nosso objetivo é o de desenvolver um programa que controla um arquivo simples e mostra um dos mais úteis caminhos de organização de dados em C: uma matriz de estruturas.
Para simplificar o problema, a nossa lista será formada, a princípio, somente pelo título do livro, representado por uma matriz de caracteres e o número do registro de biblioteca representado por um inteiro. Estas informações são fornecidas pelo usuário através da entrada padrão (teclado) e depois impressas na saída padrão (vídeo). Na primeira versão a nossa lista será limitada em apenas dois livros.

#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
// guarda e imprime dados de 2 livros
main()
{
  struct livro 
  {
    char titulo[30];
    int regnum;
  };
  struct livro livrol; 
  struct livro livro2;
  char numstr[81];
  printf("\nLivro 1.\nDigite titulo: ");
  gets(livrol.titulo);
  printf("Digite o numero do registro (3 digitos): ");
  gets(numstr);
  livrol.regnum=atoi (numstr);
  printf("\nLivro 2.\nDigite titulo: ");
  gets(livro2.titulo);
  printf("Digite o numero do registro (3 digitos): ");
  gets(numstr);
  livro2.regnum=atoi (numstr);
  printf("\nLista de livros:\n");
  printf("   Titulo: %s\n",livrol.titulo);
  printf("   Numero do registro: %03d\n",livrol.regnum);
  printf("   Titulo: %s\n",livro2.titulo);
  printf("   Numero do registro: %03d\n",livro2.regnum);
  getch();
}

Uma simples execução do programa terá a seguinte saída:
Livro 1.
Digite titulo: Helena
Digite o numero do registro: 102
Livro 2.
Digite titulo: Iracema
Digite o numero do registro: 321
Lista de livros:
Titulo: Helena
Numero do registro: 102
Titulo: Iracema
Numero do registro: 321

A estrutura criada tem duas partes: uma para armazenar o título e uma para armazenar o número do registro.
As variáveis livro1 e livro2 são declaradas como sendo do tipo struct livro.
Os dados são colocados nos membros apropriados da estrutura usando o operador (.) e trabalhando com eles como se fossem variáveis simples.
Poderíamos ter usado a função scanf() para ler o número do registro dos livros, como na instrução:

scanf("%d",&livrol.regnum);

o que surtiria o mesmo efeito do uso de gets() e atoi().
Preferimos usar gets() e atoi(), pois são bem menores que scanf() e não provocam problemas com o buffer do teclado, se o usuário digitar espaços antes de digitar o número.
Conforme já vimos, a função atoi(), de biblioteca C, converte uma "string" ASCII num inteiro correspondente.


Inicializando Estruturas

Nós já aprendemos como inicializar variáveis simples e matrizes:

int num = 5;
int mat[ ] = { 1, 2, 3, 4, 5 };

Uma estrutura só pode ser inicializada se for de classe extern ou static. O ponto crítico a saber é que a classe extern ou static para estruturas depende do lugar onde ela foi declarada e não do lugar onde foi feita a sua definição.
O exemplo seguinte é uma versão modificada do programa anterior em que os dados dos 2 livros estão contidos na instrução de inicialização dentro do programa, em vez de serem solicitados ao usuário.

#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
// mostra inicializa‡Æo das estruturas
struct livro
{
	 char titulo[30];
	 int regnum;
};
struct livro livrol = {"Helena", 102};
struct livro livro2 = {"Iracema", 321};

main()
{
  clrscr();
  printf("\nLista de livros:\n");
  printf("   Titulo: %s\n",livrol.titulo);
  printf("   Numero do registro: %03d\n",livrol.regnum);
  printf("   Titulo: %s\n",livro2.titulo);
  printf("   Numero do registro: %03d\n",livro2.regnum);
  getch();
}

Aqui, depois da declaração usual do tipo estrutura, as duas variáveis estrutura são declaradas e inicializadas. Da mesma forma como inicializamos matrizes, o sinal de igual usado e em seguida a abertura da chave que irá conter a lista de valores. Os valores são separados por vírgulas.


Atribuições Entre Estruturas

Nas versões mais modernas de C, é possível atribuir o valor de uma variável estrutura a outra do mesmo tipo, usando uma simples expressão de atribuição. Se livro1 e livro2 são variáveis estrutura do mesmo tipo, a seguinte expressão pode ser usada;

livro2 = livro1;

Nesta modificação do nosso programa de lista de livros, tomamos as informações do usuário, sobre um livro, e as atribuímos à segunda variável estrutura usando uma simples expressão de atribuição:

#include "stdio.h"
#include "conio.h"
// mostra atribuicoes entre estruturas
main()
{
  struct livro
  {
	 char titulo[30];
	 int regnum;
  };
  struct livro livrol;
  struct livro livro2;
  printf("\nLivro 1.\nDigite titulo: ");
  gets(livrol.titulo);
  printf("Digite o numero do registro (3 digitos): ");
  scanf("%d",&livrol.regnum);
  livro2=livrol;
  printf("\nLista de livros:\n");
  printf("   Titulo: %s\n",livrol.titulo);
  printf("   Numero do registro: %03d\n",livrol.regnum);
  printf("   Titulo: %s\n",livro2.titulo);
  printf("   Numero do registro: %03d\n",livro2.regnum);
  getch();
}

Quando executamos este programa, os dados de dois livros serão impressos como anteriormente, mas eles serão exatamente os mesmos dados para os 2 livros.
Certamente verificamos que esta é uma estupenda capacidade quando pensamos a respeito: quando você atribui uma estrutura a outra, todos os valores na estrutura estão realmente sendo atribuídos de uma vez para os correspondentes elementos da outra estrutura.
Uma expressão de atribuição tão simples não pode ser usada para matrizes, que devem ser atribuídas elemento a elemento.


Estruturas Aninhadas

Exatamente como podemos ter matrizes de matrizes, podemos ter estruturas que contêm outras estruturas. O que pode ser um poderoso caminho para a criação de tipos de dados complexos.
Como exemplo, imagine que os nossos livros são divididos grupos, consistindo de um "dicionário" e um livro de "literatura".
O programa seguinte cria uma estrutura de etiqueta grupo. Esta estrutura consiste de duas outras do tipo livro.

#include "stdio.h"
#include "conio.h"
// mostra o uso de estruturas aninhadas
struct livro
{
  char titulo[30];
  int regnum;
};
struct grupo
{
  struct livro dicionario;
  struct livro literatura;
};
struct grupo grupol =
{
  {"Aurelio",134 },
  {"Iracema",321 }
};

main()
{
  clrscr();
  printf("\nDicionario:\n");
  printf("   Titulo: %s\n" ,grupol.dicionario.titulo);
  printf("   No. do registro: %03d\n",grupol.dicionario.regnum);
  printf("\nLiteratura:\n");
  printf("   Titulo: %s\n",grupol.literatura.titulo);
  printf("  No.do registro: %03d\n",grupol.literatura.regnum);
  getch();
}

Vamos analisar alguns detalhes deste programa:
Primeiro, declaramos uma variável estrutura grupo1, do tipo grupo, e inicializamos esta estrutura com os valores mostrados.
Quando uma matriz de várias dimensões é inicializada, usamos chaves dentro de chaves, do mesmo modo inicializamos estruturas dentro de estruturas.
Segundo, note como acessamos um elemento da estrutura que é parte de outra estrutura. O operador ponto é usado duas vezes:

grupo1.dicionario.titulo

Isto referencia o elemento titulo da estrutura dicionario da estrutura grupo 1.
Logicamente este processo não pára neste nível, podemos ter uma estrutura dentro de outra dentro de outra. Tais construções aumentam o nome da variável, podendo descrever surpreendentemente o seu conteúdo.


Passando Estruturas para Funções

Suponhamos que você queira usar uma chamada a uma função a obter os dados sobre os livros. A nova versão de C ANSI permite que a função passe ou retorne uma estrutura completa para outra função (compiladores mais antigos não permitem esta facilidade).
Como exemplo, vamos reescrever o programa dos livros para que use uma função para obter os dados sobre os livros pelo usuário e outra para imprimi-los.

#include "stdio.h"
#include "conio.h"
#include "stdlib.h"

// armazena dois livros e mostra a passagem de estruturas p/ funcoes
struct livro
{
  char titulo[30];
  int regnum;
};

// list - funcao que imprime os dados dos livros
list(liv)
	struct livro liv;
	{
	  printf("\nLivro: \n");
	  printf("   Titulo: %s\n",liv.titulo);
	  printf("   Numero do registro: %03d\n",liv.regnum);
	}


main()
{
  struct livro livrol;
  struct livro livro2;
  struct livro novonome();

  livrol=novonome();
  livro2=novonome();
  list(livrol);
  list(livro2);
  getch();
}

// novonome() adiciona um novo livro ao arquivo
struct livro novonome()
{
  char numstr[81];
  struct livro livr;

  clrscr();
  printf("\nNovo livro\nDigite titulo: ");
  gets(livr.titulo);
  printf("Digite o numero do registro (3 digitos) ");
  gets(numstr);
  livr.regnum=atoi (numstr);
  return(livr);
}

Visto que as duas funções, como também o programa main() devem conhecer como a estrutura livro é declarada, esta declaração é feita como global colocando-a fora de qualquer função e antes de main().
As funções main(), novonome() e list() declaram internamente suas próprias variáveis estrutura, chamadas livro1 e livro2, livr e liv; para serem deste tipo.
Observe que a função list() recebe uma cópia da estrutura e coloca num endereço conhecido somente por ela; não é a mesma estrutura declarada em main().
A função novonome() é chamada pelo programa principal para obter informações do usuário sobre os 2 livros. Esta função guarda as informações em uma variável interna, livr, e retorna o valor desta variável para o programa principal usando o comando return, exatamente como faria para devolver uma simples variável. A função novonome() deve ser declarada como sendo do tipo struct livro em main(), visto que ela retorna um valor deste tipo.
O programa principal atribui o valor retornado por novonome() variável estrutura livro1 e livro2. Finalmente main() chama a função para imprimir os valores de livro1 e livro2, passando os valores destas duas estruturas para a função como variáveis.
A função list() atribui estes valores à variável estrutura interna liv e acessa os elementos individuais desta estrutura para imprimir seus valores.


Matrizes de Estruturas

Evidentemente, uma lista de livros é composta de muitos livros (provavelmente mais de 2). Cada livro é descrito por uma variável estrutura do tipo livro. Para descrever dois livros nós usamos duas variáveis. Para tratar vários livros é perfeitamente correto pensar numa matriz de estruturas.
O programa seguinte, além de realizar o nosso objetivo principal de criar uma matriz de estruturas onde cada elemento da matriz representa a descrição de um livro, ele irá proporcionar uma simples interface com o usuário constituída da escolha entre duas opções assinaladas pelas letras 'e' e 'l'.
Se o usuário pressionar 'e', o programa permitirá a entrada das informações de um livro. Se o usuário pressionar 'l', o programa listará todos os livros armazenados na memória.
Vamos ainda aumentar as informações de cada livro acrescentando o autor (uma "string") e o preço (um double).
Como queremos trabalhar com uma variável double como membro da estrutura livro, usaremos a função atof(), que converte uma "string" em um número em ponto flutuante de dupla precisão.
A função atof() é uma função do tipo double pois retorna um double, assim é necessário declará-la. Como esta declaração já está feita no arquivo stdlib.h, vamos usá-lo.

#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
//prototipando funcoes
void novonome();
void listatot();

//definindo tipo
struct lista
{
  char titulo[30]; 
  char autor[30]; 
  int regnum; 
  double preco;
};
struct lista livro[50]; 
int n=0;
main()
{
  char ch; 
  do
  {
	 clrscr();
	 printf("\nDigite 'e' para adicionar um livro");
	 printf("\n'l' para listar todos os livros");
	 printf("\nESC para sair");
	 ch=getche();
	 switch(ch)
	 {
		 case 'e': novonome();break;
		 case 'l': listatot();break;
		 case 27: break;
		 default: puts ("\nDigite somente opcoes validas");
	 }
  }while(ch!=27);
}

// novonome() - adiciona um novo livro ao arquivo
void novonome()
{
  char numstr[81];
  printf("\nRegistro %d.\nDigite titulo: ", n+1) ;
  gets(livro[n].titulo);
  printf("\nDigite autor: ");
  gets (livro[n].autor);
  printf("\nDigite o numero do livro (3 digitos): ");
  gets (numstr);
  livro[n].regnum=atoi (numstr);
  printf("\nDigite preco: ");
  gets(numstr);
  livro[n++].preco=atof(numstr);
}

//listatot() - lista os dados de todos os livros
void listatot()
{
  int i;
  clrscr();
  if( !n )
	 printf("\nLista vazia.\n");
  else
	 for(i=0;i<n;i++)
	 {
		 printf("\n\nRegistro %d.\n",i+1);
		 printf("Titulo: %s.\n",livro[i].titulo);
		 printf("Autor : %s.\n",livro[i].autor);
		 printf("Numero do registro: %3d.\n",livro[i].regnum);
		 printf("preco: %4.2f.\n",livro[i].preco);
	 }
	 getch();
}

A seguir mostraremos uma simples execução do programa:

Digite 'e' para adicionar um livro
'l' para listar todos os livros: ESC para sair: e
Registro 1.
Digite titulo: Helena
Digite autor : Machado de Assis
Digite o numero do registro (3 dígitos): 102
Digite preco: 70.5
Digite 'e' para adicionar um livro
'l' para listar todos os livros: ESC para sair: e
Registro 2.
Digite titulo: Iracema
Digite autor : Jose de Alencar
Digite o numero do registro (3 digitos): 321
Digite preco: 63.25
Digite 'e' para adicionar um livro
'l' para listar todos os livros: ESC para sair: e
Registro 3.
Digite titulo: Macunaima
Digite autor : Mano de Andrade
Digite o numero do registro (3 digitos): 056
Digite preco: 73.3
Digite 'e' para adicionar um livro
'l' para listar todos os livros: ESC para sair: l
Registro 1.
Titulo: Helena
Autor: Machado de Assis
Numero do registro: 102
Preco: 70.50
Registro 2.
Titulo: Iracema
Autor : Jose de Alencar
Numero do registro: 321
Preco: 63.25 Registro 3.
Titulo: Macunaima
Autor : Mano de Andrade
Numero do registro: 056
Preco: 73.30

Depois desta iteração podemos continuar adicionando mais livros ou listando os livros novamente.
Os dois pontos principais a serem levantados sobre uma matriz estruturas são como declará-las e como acessar membros individuais estrutura.


Declarando Uma Matriz de Estruturas

O processo de declaração de uma matriz de estruturas é perfeitamente análogo à declaração de qualquer outro tipo de matriz.

struct lista livro[50];

Esta instrução declara livro como sendo uma matriz de 50 elementos. Cada elemento da matriz é uma estrutura do tipo lista. Então, livro[0] é uma estrutura do tipo lista, livro[1] é a segunda estrutura lista e assim por diante.
O nome livro não é o nome de uma estrutura e sim o nome de uma matriz em que os elementos são estruturas.
Observe que através desta instrução o compilador providencia espaço de memória para 50 estruturas do tipo lista.
Para simplificar, declaramos a matriz de estruturas como variável global, assim todas as funções do programa poderão acessá-la.


Acessando Membros da Matriz Estruturas

Membros individuais de cada estrutura são acessados aplicando-se o operador ponto seguido ao nome da estrutura, que neste caso é um elemento da matriz, e finalmente o nome do membro desejado.

livro[2].titulo

Observe que o subscrito da matriz é associado a livro e não ao fim do nome.
A razão do uso de livro[2].titulo é que livro[2] é o nome da variável estrutura, livro[1] é um outro nome de variável estrutura. A expressão livro[n].titulo refere-se ao membro titulo da n-ésima estrutura da matriz de estruturas do tipo lista. O que representa a seguinte expressão?

livro[2].titulo[3]

É uma referência ao quarto elemento do título do livro descrito pela terceira estrutura. No nosso exemplo, é o caractere 'u'.
O esquema global deste programa pode ser aplicado a uma grande variedade de situações para armazenar dados sobre quaisquer entidades particulares. Poderá, por exemplo, ser usado para controle de um estoque, onde cada variável estrutura conterá dados sobre um item do estoque, como número do material, preço, tipo, número de peças no estoque etc. Ou poderá ser usado para um programa de orçamento, onde cada estrutura conterá informações sobre a categoria do orçamento, como o nome da categoria, a quantia orçada etc.
A única coisa que falta para tornar-se um banco de dados útil é armazenar os dados num arquivo em disco, o que exploraremos adiante.