Struct

Introdução

  • Até o momento, vimos agrupamentos de dados de um mesmo tipo: vetores e matrizes.

  • Frequentemente, no entanto, precisamos agrupar um conjunto de dados relacionados, de tipos não necessariamente iguais.

  • Por exemplo:

    • Nome, idade e sexo de uma pessoa.
    • Coordenadas de um ponto.
    • Status do personagem de um jogo
    • Atributos de um produto
  • Para isso, utilizamos registros (também conhecido como structs).

  • Um registro é novo tipo, definido pelo programador, formado por um ou mais campos, que podem ter tipos diferentes de dados.

  • Por esse motivo, registros também são chamados de variáveis compostas heterogêneas.

Registros

Declarando registros

  • Vamos supor que vamos criar um registro para um determinado monstro, criando a tebela dos dados, será maios ou menos assim:

  • Podemos ver que a variável Nome é do tipo vetor de char (string); que Código, Level, Vida, Ataque e Defesa são do tipo int; que Taxa de Captura é do tipo float; e que Paralisado é do tipo booleano.

  • Todas essas variáveis são relacionadas, e estão agrupadas em um novo domínio.

  • Dizemos então que, da mesma maneira que existem os tipos inteiro, real, literal, etc, agora existe um novo tipo, Dados do Monstro, e também podemos criar variáveis desse novo tipo.

  • struct DadosMonstro {
        int cod;
        char nome[30];
        float txcap;
        int level, vida, atq, def;
        int paralis;
    };
    
  • Com a struct criada, basta declarar um variável com um nome qualquer, do tipo struct DadosMonstro como por exemplo:

  • struct DadosMonstro pikachu;
    struct DadosMonstro chikorita;
  • Também é possível declarar um vetor deste tipo de dados.

  • struct DadosMonstro monstros[100];
  • Se soubermos o número de variáveis que queremos criar é possível criar diretamente essas variáveis.
  • int main(){
        struct {
            int cod;
            char nome[30];
            float txcap;
            int level, vida, atq, def;
            int paralis;
        } pikachu, chikorita;
        // cria as variáveis pikachu e chikorita conforme definição.
    }
                                                
  • Também é possível associar a struct a um identificador de tipo, evitando que tenhamos que adicionar a palavra struct sempre que formos instanciar uma variável do novo tipo. Isto é feito com o uso da palavra reservada typedef
  • typedef struct {
        int cod;
        char nome[30];
        float txcap;
        int level, vida, atq, def;
        int paralis;
    } DadosMonstro; // associando "DadosMonstro" à definição da struct
    
    int main() {
        DadosMonstro pikachu, chikorita; // não precisa de struct antes
    }
                                                

Acessando registros

  • Para acessar uma das variáveis internas do registro, ou seja, um de seus campos, fazemos da seguinte maneira :

  • DadosMonstro monstros[3];
    
    for(i = 0; i < 3; i++){
        printf("Digite o nome do monstro %d : ",i+1);
        scanf("%s", monstros[i].nome);
        printf("Digite o level do monstro %s : ",monstros[i].nome);
        scanf("%d", &monstros[i].level);
    }
    
    for(i = 0; i < 3; i++)
        printf("O monstro %s tem level : %d\n", monstros[i].nome, monstros[i].level);
    

Ponteiros de Registros

Um ponteiro de registro é declarado da mesma maneira que um ponteiro para qualquer outra variável, usando o operador asterisco.

Também podemos acessar os conteúdos da struct desrreferenciando seu ponteiro.

DadosMonstro* pt = &pikachu; // ponteiro para pikachu
int codPikachu = (*pt).code; // código do pikachu
float txPikachu = (*pt).txcap;
                                    

Mas existe um operador especial para acessar variáveis internas de um ponteiro de struct, o operador seta (->).

DadosMonstro* pt = &pikachu; // ponteiro para pikachu
int codPikachu = pt->code; // código do pikachu
float txPikachu = pt->txcap;
                                    

Ponteiros para registros são bastante úteis para alterar um registro em uma função diferente. Existem também estruturas de dados que envolvem structs que precisam de um ponteiro para a próxima struct da sequência, como listas, árvores, etc. Essas structs serão vistas em detalhes na matéria de Estruturas de Dados.

Para mais exemplos de declaração, acesso à elementos e uso de ponteiros veja este site.

Registros e Funções

Vamos observar alguns exemplos mais elaborados do uso de structs.

  1. Temos um empresa que começou a ser informatizada, queremos cadastrar no máximo 100 funcionários com as seguintes informações de cada funcionário: Nome; Sexo; Salário; Matrícula; Endereço e Cargo.
    typedef struct {
        char nome[50];
        char sexo;
        float salario;
        int matricula;
        char endereco[100];
        char cargo[50];
    } Funcionario;
    
    void cadastrarFuncionario(Funcionario f[], int idx){
        getchar();
        printf("Qual o nome do funcionario?\n");
        scanf(" %[^\n]", f[idx].nome);
        getchar();
        printf("Qual o sexo do funcionario?\n");
        scanf(" %c", &f[idx].sexo);
        printf("Qual o salario do funcionario?\n");
        scanf("%f", &f[idx].salario);
        printf("Qual a matricula do funcionario?\n");
        scanf("%d", &f[idx].matricula);
        printf("Qual o endereco do funcionario?\n");
        scanf(" %[^\n]", f[idx].endereco);
        printf("Qual o cargo do funcionario?\n");
        scanf("%[^\n]", f[idx].cargo);
    }
    
    int main(){
        Funcionario array[100]; // max de 100 funcionarios
        int cadastrados = 0, done = 0;
        while((!done) && (cadastrados < 100)){
            printf("Funcionarios cadastrados: %d\n", cadastrados);
            printf("(1) - Cadastrar novo funcionario\n(2) - Finalizar\n");
            int op;
            scanf("%d", &op);
            while((op != 1) && (op != 2)){
                printf("Opcao invalida\n");
                scanf("%d", &op);
            }
            if(op == 1){
                cadastrarFuncionario(array, cadastrados);
                cadastrados++;
            }
            else
                done = 1;
        }
        int i;
        for(i = 0; i < cadastrados; i++){
            printf("Funcionario %d\n", i);
            printf("Nome: %s (%c)\n", array[i].nome, array[i].sexo);
            printf("Endereco: %s\n", array[i].endereco);
            printf("Matricula: %d\n", array[i].matricula);
            printf("Cargo: %s\n", array[i].cargo);
            printf("Salario: %f\n", array[i].salario);
            printf("============================================\n");
        }
    }
                                        
    Download Code
  2. Faça um simples programa para transformar um ponto polar em um ponto cartesiano.
    #define PI 3.1415
    
    typedef struct {
        double x,y;
    } Pcart;
    
    typedef struct {
        double r, angle;
    } Ppolar;
    
    Pcart polar2cart(Ppolar p){
        Pcart r;
        float rad = (p.angle*PI)/180.0;
        r.x = p.r * cos(rad);
        r.y = p.r * sin(rad);
        return r;
    }
    
    Ppolar cart2polar(Pcart p){
        Ppolar asw;
        asw.r = sqrt(pow(p.x,2) + pow(p.y,2));
        float rad = atan(p.y/p.x);
        asw.angle = (rad*180)/PI;
        return asw;
    }
    
    int main(){
        Ppolar p = {13, 22.6};
        Pcart q = polar2cart(p);
        Ppolar o = cart2polar(q);
        printf("Ponto cartesiano: (%.2lf, %.2lf)\n", q.x, q.y);
        printf("Ponto polar: (%.2lf, %.2lf)\n", o.r, o.angle);
    }
    
    // TERMINAL OUTPUT
    // [aula10]$ ./ponto
    // Ponto cartesiano: (12.00, 5.00)
    // Ponto polar: (13.00, 22.60)
                                        
    Download Code
  3. Veras é um aluno sempre preocupado com suas notas, e que busca subir seu IRA sempre. Sendo de computação, Veras criou um programa simples para ver em qual das matérias que está fazendo ele está com o melhor rendimento, e qual precisa se dedicar mais. Para isso ele usa registros para suas notas de cada matéria, ordenando-os de maneira crescente. Claro que o programa de verdade do Veras é bem mais complicado do que este, mas quando estava no primeiro semestre ele fez algo mais ou menos assim:
                            
    #include <stdlib.h>
    
    typedef struct {
        int mat;
        int pNum;
        float nota;
        int peso;
    } Nota;
    
    void matNome(int idx){
        switch (idx) {
            case 0: printf("APC\n");
                    break;
            case 1: printf("ISC\n");
                    break;
            case 2: printf("C1\n");
                    break;
            case 3: printf("FTC\n");
                    break;
            case 4: printf("InfoSoc\n");
                    break;
            default: printf("Lembro dessa materia nao..\n");
        }
    }
    // funcao de comparacao entre Notas
    int ehMenor(Nota* a, Nota* b){
        float n_a = a->nota;
        float n_b = b->nota;
        if(n_a <= n_b)
            return 1;
        else
            return 0;
    }
    
    void showNota(Nota* n){
        printf("Materia: ");
        matNome(n->mat);
        printf("Prova numero %d\n", n->pNum);
        printf("Nota: %.2f, peso: %d\n", n->nota, n->peso);
    }
    
    void stats(Nota* arr, int n){
        // funcao para ordenar vetores. Faz parte da stdlib.h
        Nota pior = arr[0];
        Nota melhor = arr[0];
        int i;
        for(i = 1; i < n; i++){
            if(ehMenor(&arr[i], &pior))
                pior = arr[i];
            else if(ehMenor(&melhor, &arr[i]))
                melhor = arr[i];
        }
        printf("MELHOR NOTA\n");
        showNota(&melhor);
        printf("=============================\n");
        printf("PIOR NOTA\n");
        showNota(&pior);
    }
    
    int main(){
        Nota vetor[10];
        int sub[] = {0,0,1,1,2,2,3,3,4,4};
        float nota[] = {10,10,6.5,8.2,4.5,7.9,4.2,6.5,7.8,8.5};
        int i;
        for(i = 0; i < 10; i++){
            Nota a = {sub[i], (i%2)+1, nota[i], (i%3)+2};
            vetor[i] = a;
        }
    
        stats(vetor, 10);
    }
                                        
    Code Execution Download Code

Materiais Extras

Registros na Memória

Sabemos usar structs, mas como elas estão armazenadas na mamória? De maneira interessante, elas são semelhantes aos vetores neste quesito também: os atributos da struct estão em endereços adjacentes da memória, conforme mostra a imagem.

  • O primeiro endereço da struct é também o primeiro endereço do primeiro atributo.
  • Cada atributo ocupa a quantidade de bytes necessários para o seu tipo (e.g. int - 4 bytes, float - 4 bytes, char - 1 byte, etc)
  • Os atributos começam em endereços alinhados (i.e. múltiplos de 4), por isso os endereços 105-107 não estão sendo utilizados na figura.
Imagem representando um registro na memória