Um array guarda muitos valores de um tipo. Uma struct faz o oposto: guarda alguns valores de tipos diferentes e os trata como uma coisa só. Um ponto é um x e um y. Um funcionário é um nome, um id e um salário. Sem structs você carregaria isso como três variáveis soltas e torceria para nunca confundir de quem era qual salário.
Declarando um tipo struct
Uma declaração de struct nomeia uma tag e lista os membros entre chaves:
struct Point {
int x;
int y;
};
O ponto e vírgula depois da chave de fechamento é obrigatório - esquecê-lo gera uma das mensagens de erro mais confusas que um iniciante em C vai encontrar, porque o compilador culpa a linha seguinte.
Isso declara um tipo, não uma variável. Em C o nome completo do tipo é struct Point, com a palavra-chave incluída:
Os membros são lidos e escritos com o operador ponto, p.x. Uma variável struct é uma variável comum: vive na pilha, pode ser atribuída a outra struct do mesmo tipo com =, e desaparece no fim do seu escopo.
Escrever struct Point em todo lugar cansa, e é por isso que quase todo código C de verdade envolve a declaração num typedef, para que o tipo seja simplesmente Point.
Inicializando uma struct
Você pode preencher uma struct no momento em que a declara. A forma posicional lista os valores na ordem de declaração:
struct Point p = {3, 4}; // x = 3, y = 4
O inicializador designado nomeia cada membro em vez disso, o que é mais claro e mais seguro:
Três coisas para reparar:
- A forma designada pode listar os membros em qualquer ordem, então acrescentar ou reordenar um membro depois não desloca silenciosamente valores para as posições erradas.
- Qualquer membro que você deixe de fora é inicializado com zero -
{0}é a forma idiomática de zerar uma struct inteira. - Um membro
char name[32]é um array de verdade dentro da struct, então a string fica guardada na própria struct, e não em outro lugar. Isso também significa quea.name = "Ada";depois do fato é ilegal; você precisa dostrcpy(veja funções de string).
Uma struct sem inicializador fica não inicializada, e ler seus membros antes de escrevê-los é comportamento indefinido. = {0} não custa nada e elimina a categoria inteira de bug.
Atribuindo e comparando
A atribuição de struct copia todos os membros:
a fica intocada: b ganhou a própria cópia. Mas repare no que o C não lhe dá - não existe == para structs. if (a == b) não compila. Comparar significa comparar membros:
if (a.x == b.x && a.y == b.y) { /* iguais */ }
Não recorra ao memcmp como atalho. Compiladores inserem bytes invisíveis de preenchimento (padding) entre os membros para alinhamento, e esses bytes podem guardar lixo, então duas structs com membros idênticos podem comparar como diferentes byte a byte.
Arrays de structs
Como uma struct é um tipo como qualquer outro, você pode fazer um array delas - a forma padrão de guardar uma tabela de registros.
staff[i].salary se lê como "elemento i, e então o membro salary dele" - a indexação liga mais forte que o ponto, então não são necessários parênteses.
Structs são copiadas para dentro das funções
Essa é a regra que pega as pessoas. Quando você passa uma struct a uma função, o C a copia. A função trabalha sobre a própria cópia privada:
p continua (3, 4). Ler uma struct por valor, como faz distance_from_origin, é perfeitamente adequado e muitas vezes a escolha mais clara para structs pequenas. Mas se a função precisa alterar a struct do chamador - ou se a struct é grande o bastante para que copiá-la seja desperdício - passe um ponteiro. Esse é o assunto de structs e ponteiros.
Devolver uma struct por valor funciona do mesmo jeito e é completamente legal:
struct Point make_point(int x, int y) {
struct Point p = {x, y};
return p;
}
Structs aninhadas
Um membro de struct pode ser ele mesmo uma struct. Encadeie os pontos para alcançar o interior:
b.published.year entra um nível por vez. O aninhamento é como você constrói modelos de dados reais em C - e a struct interna é guardada dentro da externa, e não apontada, então o Book inteiro é um bloco contíguo de memória.
Tamanho, preenchimento e ordem dos membros
O sizeof diz quão grande é uma struct, e a resposta é muitas vezes maior que a soma dos membros:
Numa máquina típica Wasteful tem 12 bytes e Tidy tem 8, mesmo as duas guardando os mesmos três membros. O compilador insere preenchimento para que cada membro fique num endereço que o seu tipo prefere - um int normalmente quer um múltiplo de 4. Agrupar os membros maiores antes dos menores tende a compactar melhor.
Isso raramente importa (um array de um milhão de elementos, o leiaute de um pacote de rede) e nunca importa para a correção do seu próprio código. O que isso explica é por que o sizeof surpreende, e por que memcmp em structs é má ideia.
Perguntas frequentes
Como se declara uma struct em C?
Escreva struct seguido de um nome de tag e uma lista de membros entre chaves: struct Point { int x; int y; };. Isso declara um tipo, não uma variável. Para criar uma variável você escreve depois struct Point p; - a palavra struct faz parte do nome do tipo em C, e é por isso que a maior parte do código a combina com um typedef.
Qual a diferença entre . e -> em structs?
. e -> em structs?Use . quando você tem a struct em si (p.x) e -> quando você tem um ponteiro para ela (ptr->x). A seta é um atalho para (*ptr).x. Veja structs e ponteiros para os detalhes.
Structs são passadas por valor ou por referência em C?
Por valor, sempre. Passar uma struct a uma função copia todos os membros, então mudanças dentro da função não afetam a variável do chamador. Para alterar a original - ou para evitar copiar uma struct grande - passe um ponteiro.
Como inicializo uma struct em C?
Ou por posição, struct Point p = {3, 4};, ou com inicializadores designados, struct Point p = {.y = 4, .x = 3};. A forma designada nomeia cada membro, então sobrevive a alguém reordenar a struct depois e deixa zerados os membros não nomeados.