Neste capítulo discutimos como se podem criar em C tipos de
dados mais avançados e estruturados.
As estruturas em C são muito semelhantes aos registos em Pascal. Agrupam num único tipo vários valores (campos), que podem ser de tipos diferentes. Exemplo:
struct people {
char name[50];
int age;
float salary;
};
struct people funcionarios;
As declarações anteriores definem uma estrutura chamada people e uma variável (funcionarios) desse tipo. Note-se que a estrutura definida tem um nome (tag) que é opcional. Quando as estruturas são definas com tag podemos declarar posteriormente variáveis, argumentos de funções, e também tipos de retorno, usando esse tag, como se mostrou no exemplo anterior. É também possível definir estruturas sem tag (anónimas). Neste último caso as variáveis terão de ser nomeadas entre o último } e ; da forma habitual. Por exemplo:
struct {
char name[50];
int age;
float salary;
} funcionarios;
Podemos ainda inicializar as variáveis do tipo estrutura quando da sua declaração, colocando os valores pela ordem dos campos definidos na estrutura entre chaves:
struct people funcionarios = { "John Smith", 26, 124.5 };
Para acessar um campo, ou membro, de uma estrutura utiliza-se, à semelhança do Pascal, o operador . . Para aumentar o salário do Sr. John Smith deveria escrever-se:
funcionarios.salary = 150.0;
A definição de novos tipos com typedef pode também ser efetuada com estruturas. A declaração seguinte define um novo tipo person, podendo posteriormente declarar-se e inicializar-se variáveis desse tipo:
typedef struct people {
char name[50];
int age;
float salary;
} person;person John = { "John Smith", 26, 124.5 };
Neste exemplo o nome people também funciona como tag da estrutura e também é opcional. Nesta situação a sua utilidade é reduzida.
Arrays e estruturas podem ser misturados (aliás como quaisquer outros tipos):
typedef struct people {
char name[50];
int age;
float salary;
} person;person team[1000];
Aqui a variável team é composta por 1000 person's.
Um acesso poderia ser, por exemplo:
team[41].salary = 150.0;
ou,
total_salaries += team[501].salary;
Uma variável do tipo união pode conter (em instantes diferentes) valores de diferentes tipos e tamanhos. Na linguagem C a declaração de uniões é em tudo semelhante à declaração de estruturas, empregando-se a palavra union em vez de struct. Por exemplo, podemos ter:
union number {
short sort_number;
long long_number;
double real_number;
} a_number;
Aqui declara-se uma união chamada number e uma variável desse tipo - a_number. Esta variável poderá conter, em instantes diferentes, um short, um long ou um double. A distinção faz-se pelo nome do campo respectivo. Quando se preenche um determinado campo, o que estiver noutro qualquer é destruído. Os vários campos têm todos o mesmo endereço na memória.
Os campos ou membros são acessados da mesma forma do que nas estruturas:
printf("%ld\n", a_number.long_number);
Quando o compilador reserva memória para armazenar uma união apenas reserva o espaço suficiente para o membro maior (no exemplo de cima serão 8 bytes para o double). Os outros membros ficam sobrepostos, com o mesmo endereço inicial.
Uma forma comum de num programa se tomar nota do membro ativo em cada instante é embeber a união dentro de uma estrutura, onde se acrescenta um outro membro com essa função. Examinar atentamente o exemplo que se segue:
typedef struct {
int max_passengers;
} jet;typedef struct {
int lift_capacity;
} helicopter;typedef struct {
int max_payload;
} cargo_plane;typedef union {
jet jet_unit;
helicopter heli_unit;
cargo_plane cargo_unit;
} aircraft;typedef struct {
aircraft_type kind;
int speed;
aircraft description;
} an_aircraft;
No tipo an_aircraft
inclui-se um membro description
que é do tipo aircraft que
é, por sua vez uma união de 3 estruturas diferentes (jet, helicopter
e cargo_plane). Inclui-se
ainda no tipo an_aircraft
um membro (kind) que indica
qual é o membro válido no momento para a união description.
O C é uma das poucas linguagens que permite a mudança de tipo das suas variáveis. Para isso usa-se o operador (cast), onde cast é o novo tipo pretendido. Assim é possível escrever:
int k;
float r = 9.87;k = (int) r;
A variável k ficará com o valor 9, sendo a parte fraccionária de r descartada.
O casting pode ser usado com qualquer um dos tipos simples, e muitas vezes quando é omitido o compilador executa a conversão por default. Outro exemplo:
int k;
char ch = 'A';k = (int) ch;
Aqui o valor de k será 65 (o código ascii do carácter 'A').
Outro uso frequente do casting é assegurar que a divisão entre inteiros não seja uma divisão inteira. Basta para isso converter para real o numerador ou denominador. O outro operando é assim também automaticamente convertido, antes de se efetuar a operação:
int j = 2, k = 5;
float r;r = (float) j / k; /* r = 0.4 em vez de 0, se não se fizesse o cast de j */
Os tipos enumerados não são mais do que uma lista de identificadores que funcionam como constantes inteiras. São muito semelhantes aos tipos enumerados do Pascal. Por exemplo:
enum days { sun, mon, tue, wed, thu, fri, sat };
enum days week1, week2;week1 = wed;
week2 = sun;
Na declaração de cima o identificador sun fica associado ao valor 0, o identificador mon ao valor 1, e assim sucessivamente. É possível fazer cast (implícito ou explícito) para int, nos dois sentidos.
No entando é possível, numa enumeração, associar os identicadores a outros valores, que não a sucessão que começa em 0. Por exemplo:
enum escapes {bell='\a', backspace='\b', tab='\t', newline='\n', vertical_tab='\v', return='\r'};
É mesmo possível iniciar a sucessão de valores inteiros associados aos identificadores num valor diferente de 0, como na declaração seguinte:
enum months {jan=1, feb, mar, apr, may, jun, jul, ago, sep, oct, nov, dec};
No exemplo apresentado para as uniões, atrás, aparecia um tipo aircraft_type. Esse tipo pode ser definido como uma enumeração:
typedef enum {a_jet, a_helicopter, a_cargo_plane} aircraft_type;
É possível, quando da declaração de variáveis, locais ou não, prefixá-las com o qualificador static. As variáveis locais estáticas são inicializadas uma só vez e não desaparecem quando a função a que pertencem termina, podendo no entanto ser acessadas apenas dentro da função. As variáveis globais estáticas apenas podem ser acessadas no arquivo onde são declaradas, não sendo exportadas para o exterior.
As variáveis locais estáticas também mantêm o seu valor de umas chamadas para as outras, como se vê no exemplo seguinte:
#include <stdio.h>
void stat(void); /* Protótipo de stat() */
void main(void)
{
int k;for (k=0; k<5; k++)
stat();
}void stat(void)
{
int var = 0;
satic int auto_var = 0;printf("var = %d, auto_var = %d\n", var++, auto_var++);
}
A saída deste programa será:
var = 0, auto_var = 0
var = 0, auto_var = 1
var = 0, auto_var = 2
var = 0, auto_var = 3
var = 0, auto_var = 4
A variável var é
criada e inicializada de cada vez que a função é chamada. A
variável auto_var só é
inicializada da primeira vez e lembra-se do valor que tinha na
última vez que a função terminou.
[REV 03/1999]