O capítulo descreve estruturas em C, incluindo sua declaração, inicialização, acesso aos membros e uso com funções. Estruturas permitem agrupar variáveis relacionadas sob um único nome e são usadas comumente para representar registros armazenados em arquivos.
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
0 notas0% acharam este documento útil (0 voto)
14 visualizações49 páginas
Capi - Tulo 10 - Estruturas em C
O capítulo descreve estruturas em C, incluindo sua declaração, inicialização, acesso aos membros e uso com funções. Estruturas permitem agrupar variáveis relacionadas sob um único nome e são usadas comumente para representar registros armazenados em arquivos.
• A criar e usar estruturas, uniões e enumerações. • A passar estruturas para funções por valor e por referência. • A manipular dados com os operadores sobre bits. • A criar campos de bit para armazenar dados de modo compacto.
10.1 Introdução 10.2 Declarações de estruturas 10.3 Inicialização de estruturas 10.4 Acesso aos membros da estrutura 10.5 Uso de estruturas com funções 10.6 typedef 10.7 Exemplo: uma simulação de alto desempenho de embaralhamento e distribuição de cartas 10.8 Uniões 10.9 Operadores sobre bits 10.10 Campos de bit 10.11 Constantes de enumeração
Estruturas — também chamadas de agregações — são coleções de variáveis relacionadas agrupadas sob um único nome. As estruturas podem conter variáveis de muitos tipos de dados diferentes — diferentemente dos arrays, que contêm apenas elementos do mesmo tipo de dado. As estruturas normalmente são usadas para declarar registros a serem armazenados em arquivos (ver Capítulo 11). Ponteiros e estruturas facilitam a formação de estruturas de dados mais complexas, por exemplo, listas interligadas, filas, pilhas e árvores (ver Capítulo 12).
Estruturas são tipos de dados derivados; elas são construídas a partir de objetos de outros tipos. Considere a declaração de estrutura a seguir: struct card { char *face; char *suit; }; A palavra-chave struct introduz a declaração de estrutura. O identificador card é a tag de estrutura, que dá nome à declaração de estrutura e é usado com a palavra-chave struct para declarar variáveis do tipo de estrutura.
Nesse exemplo, o tipo de estrutura é struct card. As variáveis declaradas dentro das chaves de declaração da estrutura são os membros da estrutura. Os membros que têm o mesmo tipo de estrutura precisam ter nomes exclusivos, mas dois tipos de estrutura diferentes podem ter membros com o mesmo nome, sem conflito (logo veremos por quê). Todas as declarações de estrutura precisam terminar com pontos e vírgulas.
A declaração de struct card contém os membros face e suit do tipo char *. Os membros da estrutura podem ser variáveis dos tipos de dados primitivos (por exemplo, int, float etc.), ou agregações, como arrays e outras estruturas. Os membros da estrutura podem ser de muitos tipos.
Por exemplo, a struct a seguir contém membros de array de caracteres para o nome e para o sobrenome de um funcionário, um membro int para sua idade, um membro char que conteria'M' ou 'F' para indicar a que sexo ele pertence e um membro double para seu salário: struct funcionario { char nome[ 20 ]; char sobrenome[ 20 ]; int idade; char sexo; double salario; };
Uma estrutura não pode conter uma instância de si mesma. Por exemplo, uma variável do tipo struct funcionario não pode ser declarada na definição para struct funcionario. Porém, um ponteiro para struct funcionario pode ser incluído. Por exemplo, struct funcionario2 { char nome[ 20 ]; char sobrenome[ 20 ]; int idade; char sexo; double salario; struct funcionario2 pessoa; /* ERRO */ struct funcionario2 *ePtr; /* ponteiro */ }; struct funcionario2 contém uma instância de si mesma (pessoa), o que é um erro.
Como ePtr é um ponteiro (para o tipo struct funcionario2), ele é permitido na declaração. Uma estrutura que contém um membro que é um ponteiro para o mesmo tipo de estrutura é chamada de estrutura autorreferenciada. As estruturas autorreferenciadas serão usadas no Capítulo 12 para criar estruturas de dados interligadas.
As declarações da estrutura não reservam nenhum espaço na memória; em vez disso, cada declaração cria um novo tipo de dado, que é usado para declarar variáveis. As variáveis da estrutura são declaradas como variáveis de outros tipos. A declaração struct card aCard, deck[ 52 ], *cardPtr; declara aCard como uma variável do tipo struct card, declara deck como um array com 52 elementos do tipo struct card e declara cardPtr como um ponteiro para struct card.
As variáveis de determinado tipo de estrutura também podem ser declaradas colocando-se uma lista separada por vírgulas com os nomes de variável entre a chave que fecha a declaração de estrutura e o ponto e vírgula que encerra a declaração de estrutura. Por exemplo, a declaração anterior poderia ter sido incorporada na declaração de estrutura struct card da seguinte forma: struct card { char *face; char *suit; } aCard, deck[ 52 ], *cardPtr;
O nome para a tag de estrutura é opcional. Se uma declaração de estrutura não tiver um nome para a tag da estrutura, as variáveis do tipo da estrutura só poderão ser declaradas na declaração de estrutura, e não em uma declaração separada..
Boa prática de programação 10.1 Sempre forneça um nome para a tag de estrutura ao criar um tipo de estrutura. O nome para a tag de estrutura será conveniente na declaração de novas variáveis do tipo de estrutura adiante no programa.
Boa prática de programação 10.2
Escolher um nome significativo para a tag de estrutura ajuda a tornar o programa documentado.
As únicas operações válidas que podem ser realizadas nas estruturas são as seguintes: ◦ atribuição de variáveis da estrutura a variáveis da estrutura de mesmo tipo, ◦ coleta de endereço (&) de uma variável de estrutura, ◦ acesso aos membros de uma variável de estrutura (ver Seção 10.4) e ◦ uso do operador sizeof para determinar o tamanho de uma variável de estrutura.
As estruturas não podem ser comparadas usando-se os operadores == e !=, pois os membros da estrutura não são necessariamente armazenados em bytes consecutivos de memória. Às vezes, existem ‘buracos’ em uma estrutura, pois os computadores podem armazenar tipos de dados específicos apenas em certos limites de memória, como os limites de meia palavra, palavra ou palavra dupla. Uma palavra é uma unidade de memória padrão usada para armazenar dados em um computador — normalmente, 2 bytes ou 4 bytes.
Considere uma declaração de estrutura em que sample1 e sample2 do tipo struct exemplo são declaradas: struct exemplo { char c; int i; } ex1, ex2; Um computador com palavras de 2 bytes pode exigir que cada membro de struct exemplo seja alinhado em um limite de palavra (isso depende de cada máquina).
A Figura 10.1 mostra um exemplo de alinhamento de armazenagem para uma variável do tipo struct exemplo que recebeu o caractere 'a' e o inteiro 97 (as representaçãos de bit dos valores são mostradas). Se os membros forem armazenados começando nos limites de palavra, existe um intervalo de 1 byte (byte 1 na figura) no armazenamento para as variáveis do tipo struct exemplo. O valor no intervalo de 1 byte é indefinido. Mesmo que os valores dos membros de ex1 e ex2 sejam realmente iguais, as estruturas não serão necessariamente iguais, pois os intervalos indefinidos de 1 byte provavelmente não conterão valores idênticos.
As estruturas podem ser inicializadas a partir de listas de inicializadores, assim como acontece com os arrays. Para inicializar uma estrutura, siga o nome da variável na declaração com um sinal de igual e uma lista, delimitada por chaves, de inicializadores separados por vírgulas. Por exemplo, a declaração struct card aCard = { “Três”, “Copas” }; cria a variável aCard para ser do tipo struct card (conforme definido na Seção 10.2) e inicializa o membro face como “Três” e o membro suit como “Copas”.
Se o número de inicializadores na lista for menor que os membros na estrutura, os membros restantes serão automaticamente inicializados em 0 (ou NULL se o membro for um ponteiro). As variáveis de estrutura que forem declaradas fora de uma declaração de função (ou seja, que sejam externos a ela) serão inicializadas em 0 ou NULL, se não forem inicializadas explicitamente na declaração externa. As variáveis da estrutura também podem ser inicializadas nas instruções de atribuição, atribuindo uma variável de estrutura do mesmo tipo, ou valores aos membros individuais da estrutura.
Dois operadores são usados para acessar membros das estruturas: o operador de membro de estrutura (.) — também chamado de operador de ponto — e o operador de ponteiro de estrutura (->) — também chamado operador de seta. O operador de membro da estrutura acessa um membro da estrutura por meio do nome da variável da estrutura. Por exemplo, para imprimir o membro suit da variável da estrutura aCard declarada na Seção 10.3, use a instrução printf( "%s", aCard.suit ); /* mostra Copas */
O operador de ponteiro da estrutura — que consiste em um sinal de subtração (-) seguido de um sinal de maior (>) — acessa um membro da estrutura por meio de um ponteiro para a estrutura. Suponha que o ponteiro cardPtr tenha sido declarado para apontar para struct card, e que o endereço da estrutura aCard tenha sido atribuído a cardPtr. Para imprimir o membro suit da estrutura aCard com o ponteiro cardPtr, use a instrução printf( "%s", cardPtr->suit ); /* mostra Copas */
A expressão cardPtr->suit é equivalente a (*cardPtr).suit, que desreferencia o ponteiro e acessa o membro suit usando o operador de membro da estrutura. Aqui, os parâmetros são necessários porque o operador de membro da estrutura (.) tem uma precedência maior que o operador de desreferência de ponteiro (*). O operador de ponteiro da estrutura e o operador de membro da estrutura, com os parênteses (para chamar funções) e colchetes ([]) usados para subscrito de array, possuem a precedência de operador mais alta, e são associados da esquerda para a direita.
Boa prática de programação 10.3 Não coloque espaços em torno dos operadores ‘–>’ e ‘.’ — a omissão dos espaços ajuda a enfatizar o fato de que as expressões em que os operadores estão contidos são basicamente nomes de variável únicos.
Erro comum de programação 10.3
A inserção de espaço entre os componentes – e > do operador de ponteiro da estrutura (ou entre os componentes de qualquer outro operador de múltiplos caracteres, com exceção de ?:) provoca um erro de sintaxe.
Erro comum de programação 10.4 Tentar referenciar a um membro de uma estrutura usando apenas o nome do membro provoca um erro de sintaxe.
Erro comum de programação 10.5
Não usar parênteses ao referenciar um membro da estrutura que usa um ponteiro e o operador de membro da estrutura (por exemplo, *cardPtr.suit) provoca um erro de sintaxe.
O programa da Figura 10.2 demonstra o uso dos operadores de membro da estrutura e de ponteiro da estrutura. Usando o operador de membro da estrutura, os membros da estrutura aCard recebem os valores "Ace" e "Spades", respectivamente (linhas 18 e 19). O ponteiro cardPtr recebe o endereço da estrutura aCard (linha 21). A função printf imprime os membros da variável da estrutura aCard usando o operador de membro da estrutura com nome de variável aCard, o operador de ponteiro da estrutura com o ponteiro cardPtr e o operador de membro da estrutura com o ponteiro desreferenciado cardPtr (linhas 23 a 25).
Os arrays das estruturas — assim como todos os outros arrays — são automaticamente passados por referência. Para passar um array por valor, crie uma estrutura que tenha o array como membro. As estruturas são passadas por valor, de modo que o array também seja passado por valor.
Erro comum de programação 10.6 Supor que estruturas como arrays sejam passadas automaticamente por referência e tentar modificar os valores da estrutura passados por valor na função utilizada consiste em um erro lógico.
Dica de desempenho 10.1
Passar estruturas por referência é mais eficiente do que passar estruturas por valor (o que requer que a estrutura inteira seja copiada).
A palavra-chave typedef oferece um mecanismo de criação de sinônimos (ou aliases) para tipos de dados previamente definidos. Os nomes dos tipos de estrutura normalmente são definidos a partir de typedef que se criem nomes de tipo mais curtos. Por exemplo, a instrução typedef struct card Card; define o novo nome de tipo Card como um sinônimo para o tipo struct card. Programadores de C normalmente usam typedef para definir um tipo da estrutura, de modo que a tag da estrutura não é necessária.
Por exemplo, a declaração a seguir typedef struct { char *face; char *suit; } Card; cria o tipo de estrutura Card sem a necessidade de uma instrução typedef separada.
Boa prática de programação 10.4 Coloque a primeira letra dos nomes de typedef em maiúscula para enfatizar o fato de que eles são sinônimos de outros nomes para tipos.
Card agora pode ser usado para declarar variáveis do tipo struct card. A declaração Card deck[ 52 ]; declara um array de 52 estruturas Card (ou seja, variáveis do tipo struct card). Criar um novo nome com typedef não cria um novo tipo; typedef simplesmente cria um novo nome de tipo, que pode ser usado como um alias para um nome de tipo existente.
Um nome significativo ajuda a tornar o programa autoexplicativos. Por exemplo, quando lemos a declaração anterior, sabemos que “deck é um array de 52 Cards.” Frequentemente, typedef é usado para criar sinônimos para os tipos de dados básicos. Por exemplo, um programa que exige inteiros de 4 bytes pode usar o tipo int em um sistema e o tipo long em outro. Os programas projetados para portabilidade normalmente usam typedef para criar um alias para inteiros de 4 bytes, como Integer. O alias Integer ser alterado uma vez no programa para fazê-lo funcionar nos dois sistemas..
O programa da Figura 10.3 baseia-se na simulação de embaralhamento e distribuição de cartas discutida no Capítulo 7. O programa representa o baralho de cartas como um array de estruturas. O programa usa algoritmos de alto desempenho para embaralhar e distribuir as cartas. A saída do programa de embaralhamento e distribuição de cartas aparece na Figura 10.4.
Para cada carta, um número entre 0 e 51 é escolhido aleatoriamente. Em seguida, a estrutura Card atual e a estrutura Card selecionada aleatoriamente são trocados no array (linhas 64 a 66). Um total de 52 trocas é feito em uma única passada do array inteiro, e o array das estruturas Card é embaralhado! Esse algoritmo não pode sofrer adiamento indefinido, como o algoritmo de embaralhamento apresentado no Capítulo 7. Como as estruturas Card foram trocadas no espaço do array, o algoritmo de alto desempenho implementado na função deal (linhas 71-80) exige apenas uma passada do array para distribuir as cartas embaralhadas.
Erro comum de programação 10.7 Esquecer de incluir o subscrito do array ao se referir a estruturas individuais em um array de estruturas consiste em um erro de sintaxe.