Menu

Struct em C: definir, inicializar e usar estruturas

Como agrupar valores relacionados num só tipo com uma struct do C - declarar, acessar membros com o operador ponto, inicializadores designados, arrays de structs, aninhamento e a regra de cópia na passagem que surpreende todo mundo.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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 que a.name = "Ada"; depois do fato é ilegal; você precisa do strcpy (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?

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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR