C++ Structs

Até aqui, cada informação morava numa variável separada: nome, idade, nota. Isso funciona para uma aluna. Para uma turma inteira, vira uma bagunça de variáveis soltas. Uma struct junta várias informações que andam juntas num tipo novo, criado por você: o tipo Aluno, que tem nome, idade e nota. Nesta aula você vai criar a sua primeira struct, preencher, guardar várias num std::vector e passar para funções.

Você vai aprender
  • O que é uma struct e como declarar uma: struct Aluno { … };.
  • Como criar uma variável do tipo Aluno e acessar os campos com o ponto: a.nome.
  • Por que dar valores padrão aos campos, e o que acontece quando você esquece.
  • Como preencher tudo de uma vez com chaves {}, inclusive pelo nome do campo (.nome = …, C++20).
  • Como guardar uma turma inteira num std::vector<Aluno>.
  • Como passar uma struct para uma função sem copiar (const Aluno&).
  • O que é a seta -> e qual a diferença entre struct e class.

Todos os programas desta aula foram compilados com o g++ 15 na máquina fictícia da Ana (usuária ana, computador notebook), com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa (veja Compilar com g++). Você pode repetir cada um no editor do guia.


A ideia: uma ficha de cadastro

Pense na secretaria de uma escola. Para cada aluno existe uma ficha de papel, sempre com os mesmos espaços: Nome, Idade, Nota. A ficha em branco é o modelo; cada ficha preenchida é um aluno de verdade.

A struct é o modelo da ficha. Ela diz quais espaços existem e o tipo de cada um. Depois você cria quantas fichas preenchidas quiser, todas do mesmo formato.

Na secretariaEm C++Exemplo
O modelo da fichaa struct (um tipo novo)struct Aluno { … };
Cada espaço da fichaum campo (ou membro)std::string nome;
Uma ficha preenchidauma variável do tipo AlunoAluno a;
Ler o espaço "Nome" de uma fichao operador pontoa.nome

O nome vem do inglês structure, "estrutura": uma estrutura de dados montada com peças menores.


1. Declarar a struct e criar uma variável

#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade;
    double nota;
};

int main() {
    Aluno a;
    a.nome = "Ana";
    a.idade = 20;
    a.nota = 8.5;

    std::cout << a.nome << " tem " << a.idade << " anos e tirou " << a.nota << '\n';

    a.nota = 9.0;
    std::cout << "Nota corrigida: " << a.nota << '\n';
}
struct Aluno
Começa a declaração de um tipo novo chamado Aluno. Por costume, nomes de tipos próprios começam com letra maiúscula (Aluno, Produto), e variáveis com minúscula.
{ … };
Entre as chaves ficam os campos. Repare no ponto e vírgula depois da chave: ele é obrigatório na struct (e esquecê-lo é um erro clássico, que você vai ver daqui a pouco).
std::string nome;
Um campo: tipo + nome, igual a declarar uma variável. Os campos podem ter tipos diferentes entre si.
Aluno a;
Cria uma variável chamada a do tipo Aluno. Agora Aluno funciona como int ou std::string: é um tipo como outro qualquer.
a.nome = "Ana";
O ponto entra na ficha a e pega o campo nome. Cada campo se comporta como uma variável comum: dá para guardar, ler e mudar.
$ g++ -std=c++23 -Wall -Wextra aluno.cpp -o aluno
$ ./aluno
Ana tem 20 anos e tirou 8.5
Nota corrigida: 9

O 9.0 aparece como 9 porque o cout não mostra zeros desnecessários (veja float e double).

Onde declarar a struct

Declare a struct fora do main, lá em cima, depois dos #include. Assim todas as funções do arquivo conhecem o tipo Aluno. Em projetos maiores, ela vai para um arquivo .h (veja Arquivos .h e .cpp).

Cada variável tem a sua própria ficha

Duas variáveis do tipo Aluno são duas fichas separadas. Copiar uma struct com = copia todos os campos, e a cópia é independente:

#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

void dar_bonus(Aluno a) {
    a.nota += 1.0;
    std::cout << "Dentro da função: " << a.nota << '\n';
}

int main() {
    Aluno ana{"Ana", 20, 6.5};
    dar_bonus(ana);
    std::cout << "No main: " << ana.nota << '\n';

    Aluno outra = ana;
    outra.nome = "Bia";
    std::cout << ana.nome << " e " << outra.nome << '\n';
}
$ g++ -std=c++23 -Wall -Wextra copia.cpp -o copia
$ ./copia
Dentro da função: 7.5
No main: 6.5          « a função mudou a cópia dela, não a Ana do main
Ana e Bia             « mudar a outra não muda a ana

Guarde esse comportamento da função dar_bonus: na seção 5 você vai ver como mudar a struct de verdade.


2. Campos sem valor: lixo da memória

O que tem dentro de a.idade se você nunca guardou nada lá? Em C++, um int ou double sem valor inicial não vale zero: vale o que estava naquele pedaço da memória antes, um "lixo" (veja Variáveis). Com campos de struct é igual. Só o std::string começa vazio sozinho.

#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade;
    double nota;
};

int main() {
    Aluno a;
    a.nome = "Ana";
    std::cout << a.nome << ", " << a.idade << " anos, nota " << a.nota << '\n';
}
$ g++ -std=c++23 -Wall -Wextra lixo.cpp -o lixo
$ ./lixo
Ana, 658842208 anos, nota 6.31316e-310
$ ./lixo
Ana, 1597478448 anos, nota 6.31785e-310      « outro número a cada execução

Repare: o g++ não deu nenhum aviso, nem com -Wall -Wextra. O programa compilou e mostrou números sem sentido, diferentes a cada vez. Ler um valor que nunca foi guardado é comportamento indefinido: o C++ não promete nada.

A solução: valores padrão nos campos

Desde o C++11, você pode dar um valor inicial a cada campo, direto na struct. Toda ficha nova já nasce com esses valores:

#include <iostream>
#include <string>

struct Aluno {
    std::string nome = "sem nome";
    int idade = 0;
    double nota = 0.0;
};

int main() {
    Aluno a;
    std::cout << a.nome << ", " << a.idade << " anos, nota " << a.nota << '\n';
    a.nome = "Ana";
    std::cout << a.nome << ", " << a.idade << " anos, nota " << a.nota << '\n';
}
$ g++ -std=c++23 -Wall -Wextra padrao.cpp -o padrao
$ ./padrao
sem nome, 0 anos, nota 0
Ana, 0 anos, nota 0

Agora não existe mais lixo: o que você não preencher fica com o valor padrão. Dê sempre um valor padrão aos campos numéricos. Daqui em diante, todos os exemplos usam int idade = 0; e double nota = 0.0;. O nome fica sem valor padrão porque um std::string já começa vazio.

Missão: a variável a já existe. Preencha os três campos com o ponto, para a tela mostrar Bia tem 19 anos e nota 7.5. Rode primeiro sem mudar nada e veja os valores padrão.

#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    Aluno a;
    // preencha os três campos de a aqui

    std::cout << a.nome << " tem " << a.idade << " anos e nota " << a.nota << '\n';
}
#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    Aluno a;
    a.nome = "Bia";
    a.idade = 19;
    a.nota = 7.5;

    std::cout << a.nome << " tem " << a.idade << " anos e nota " << a.nota << '\n';
}

3. Preencher tudo de uma vez com chaves

Preencher campo por campo dá trabalho. Você pode criar a variável já preenchida, colocando os valores entre chaves, na ordem em que os campos foram declarados: primeiro o nome, depois a idade, depois a nota.

#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    Aluno a{"Ana", 20, 8.5};
    Aluno b{"Bruno", 19};
    Aluno c{};
    Aluno d{.nome = "Carla", .idade = 22, .nota = 9.5};
    Aluno e{.nome = "Davi", .nota = 6.0};

    std::cout << a.nome << " | " << a.idade << " | " << a.nota << '\n';
    std::cout << b.nome << " | " << b.idade << " | " << b.nota << '\n';
    std::cout << "[" << c.nome << "] | " << c.idade << " | " << c.nota << '\n';
    std::cout << d.nome << " | " << d.idade << " | " << d.nota << '\n';
    std::cout << e.nome << " | " << e.idade << " | " << e.nota << '\n';
}
$ g++ -std=c++23 -Wall -Wextra chaves.cpp -o chaves
$ ./chaves
Ana | 20 | 8.5
Bruno | 19 | 0          « faltou a nota: ficou o valor padrão
[] | 0 | 0              « {} vazio: tudo no valor padrão (o nome, texto vazio)
Carla | 22 | 9.5
Davi | 0 | 6            « pulou a idade: ficou o valor padrão
LinhaO que faz
Aluno a{"Ana", 20, 8.5};Preenche os campos pela ordem: nome, idade, nota.
Aluno b{"Bruno", 19};Valores a menos: os campos do fim ficam com o valor padrão.
Aluno c{};Chaves vazias: todos os campos com o valor padrão (ou zero, se o campo não tiver padrão). É a forma segura de criar uma ficha "em branco".
Aluno d{.nome = "Carla", .idade = 22, .nota = 9.5};Inicialização designada C++20: cada valor vem com o nome do campo, com um ponto na frente. Mais fácil de ler.
Aluno e{.nome = "Davi", .nota = 6.0};Na designada, dá para pular campos. Os pulados ficam com o valor padrão.

A forma designada é ótima quando a struct tem vários campos do mesmo tipo: em {20, 22} você precisa lembrar qual número é qual; em {.idade = 20, .sala = 22}, não. Antes do C++20, ela não existia em C++ (só na linguagem C): use a forma pela ordem.

As regras das chaves

As chaves conferem o que você escreve. Três erros comuns, com as mensagens reais do g++:

Na designada, a ordem tem de ser a da declaração. Você pode pular campos, mas não pode trocar a ordem:

#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    Aluno a{.idade = 20, .nome = "Ana"};
}
$ g++ -std=c++23 -Wall -Wextra ordem.cpp -o ordem
ordem.cpp: In function ‘int main()’:
ordem.cpp:10:39: warning: missing initializer for member ‘Aluno::nome’ [-Wmissing-field-initializers]
   10 |     Aluno a{.idade = 20, .nome = "Ana"};
      |                                       ^
ordem.cpp:10:39: error: designator order for field ‘Aluno::nome’ does not match declaration order in ‘Aluno’

designator order … does not match declaration order = "a ordem dos nomes não bate com a ordem da declaração". O aviso de cima é consequência: depois de .idade, o g++ esperava que o nome já tivesse ficado para trás. Conserto: Aluno a{.nome = "Ana", .idade = 20};.

No editor do guia, isso é só um aviso

O compilador do editor é o Clang, que aceita a ordem trocada e só avisa: warning: ISO C++ requires field designators to be specified in declaration order (o padrão do C++ exige a ordem da declaração). O g++ recusa. Escreva sempre na ordem e o código funciona nos dois.

Valores a mais não cabem:

$ g++ -std=c++23 -Wall -Wextra demais.cpp -o demais
demais.cpp: In function ‘int main()’:
demais.cpp:10:30: error: too many initializers for ‘Aluno’
   10 |     Aluno a{"Ana", 20, 8.5, 3};
      |                              ^

too many initializers = "valores iniciais demais": o Aluno tem três campos e você passou quatro valores.

As chaves não deixam perder parte de um número:

$ g++ -std=c++23 -Wall -Wextra estreita.cpp -o estreita
estreita.cpp: In function ‘int main()’:
estreita.cpp:10:20: error: narrowing conversion of ‘2.05e+1’ from ‘double’ to ‘int’ [-Wnarrowing]
   10 |     Aluno a{"Ana", 20.5, 8.5};
      |                    ^~~~

A idade é int, e 20.5 perderia o .5. Isso se chama conversão que estreita (narrowing), e com chaves ela vira erro (veja Conversão de Tipos). O 2.05e+1 é o jeito do g++ escrever 20.5 (2,05 × 10¹). É uma proteção: com a.idade = 20.5; o número seria cortado em silêncio.


4. Uma turma inteira: vetor de structs

Aqui a struct mostra a sua força. Um std::vector<Aluno> é uma lista de fichas (veja std::vector). Cada elemento é um Aluno completo:

#include <iostream>
#include <string>
#include <vector>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    std::vector<Aluno> turma = {
        {"Ana", 20, 8.5},
        {"Bruno", 19, 6.0},
        {"Carla", 22, 9.5},
    };
    turma.push_back({"Davi", 21, 7.0});

    double soma = 0.0;
    for (const auto& aluno : turma) {
        std::cout << aluno.nome << ": " << aluno.nota << '\n';
        soma += aluno.nota;
    }
    std::cout << "Alunos: " << turma.size() << '\n';
    std::cout << "Média da turma: " << soma / turma.size() << '\n';
    std::cout << "Primeiro: " << turma[0].nome << '\n';
}
std::vector<Aluno> turma = { … };
Um vetor de alunos. Cada {"Ana", 20, 8.5} lá dentro vira um Aluno, pelas mesmas regras da seção 3. A vírgula depois do último é permitida e facilita acrescentar linhas.
turma.push_back({"Davi", 21, 7.0});
Acrescenta mais um aluno no fim. As chaves criam o Aluno ali mesmo.
for (const auto& aluno : turma)
Percorre a turma (veja for de Intervalo). O const auto& olha cada ficha sem copiar e sem poder mudá-la.
aluno.nota
Dentro do laço, aluno é uma ficha; o ponto pega o campo.
turma[0].nome
Primeiro o [0] pega a ficha da posição 0; depois o .nome pega o campo dela.
$ g++ -std=c++23 -Wall -Wextra turma.cpp -o turma
$ ./turma
Ana: 8.5
Bruno: 6
Carla: 9.5
Davi: 7
Alunos: 4
Média da turma: 7.75
Primeiro: Ana

Sem struct, você precisaria de três vetores separados (nomes, idades, notas) e de cuidado para que a posição 2 de cada um fosse sempre a mesma pessoa. Com a struct, cada pessoa é uma peça só, e ordenar ou apagar um aluno nunca separa o nome da nota.

Missão: acrescente a Carla (22 anos, nota 9.5) à turma com push_back, para ela aparecer na lista. Se quiser, use a forma designada dentro do push_back.

#include <iostream>
#include <string>
#include <vector>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    std::vector<Aluno> turma = {
        {"Ana", 20, 8.5},
        {"Bruno", 19, 6.0},
    };
    // acrescente a Carla (22 anos, nota 9.5) aqui

    for (const auto& aluno : turma) {
        std::cout << aluno.nome << ": " << aluno.nota << '\n';
    }
}
#include <iostream>
#include <string>
#include <vector>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    std::vector<Aluno> turma = {
        {"Ana", 20, 8.5},
        {"Bruno", 19, 6.0},
    };
    turma.push_back({.nome = "Carla", .idade = 22, .nota = 9.5});

    for (const auto& aluno : turma) {
        std::cout << aluno.nome << ": " << aluno.nota << '\n';
    }
}

5. Struct e funções

Uma struct pode ser parâmetro e retorno de função, como qualquer tipo. A questão é como passar. Você já viu as três formas em Por Valor × Por Referência; com structs, a escolha pesa mais, porque uma struct pode ser grande (um Aluno carrega um std::string inteiro).

#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

void mostrar(const Aluno& a) {
    std::cout << a.nome << " (" << a.idade << " anos): " << a.nota << '\n';
}

bool aprovado(const Aluno& a) {
    return a.nota >= 7.0;
}

void dar_bonus(Aluno& a, double pontos) {
    a.nota += pontos;
}

Aluno criar(std::string nome) {
    return {nome, 18, 0.0};
}

int main() {
    Aluno ana{"Ana", 20, 6.5};
    mostrar(ana);
    std::cout << "Aprovada? " << (aprovado(ana) ? "sim" : "não") << '\n';

    dar_bonus(ana, 1.0);
    mostrar(ana);
    std::cout << "Aprovada? " << (aprovado(ana) ? "sim" : "não") << '\n';

    Aluno novo = criar("Eva");
    mostrar(novo);
}
$ g++ -std=c++23 -Wall -Wextra funcao.cpp -o funcao
$ ./funcao
Ana (20 anos): 6.5
Aprovada? não
Ana (20 anos): 7.5       « o bônus mudou a Ana do main
Aprovada? sim
Eva (18 anos): 0
ParâmetroO que aconteceQuando usar
const Aluno& aA função olha a ficha original, sem copiar, e não pode mudar nada.O padrão para structs: mostrar, aprovado.
Aluno& aA função mexe na ficha original.Quando o objetivo é mudar: dar_bonus.
Aluno aA função recebe uma cópia inteira; mudanças se perdem (você viu em copia.cpp).Raramente; quando a função precisa de uma cópia para mexer à vontade.

E o retorno? Aluno criar(…) devolve uma struct inteira. O return {nome, 18, 0.0}; monta o Aluno com chaves direto no return: o compilador sabe que o tipo é Aluno por causa da declaração da função. Devolver struct por valor é normal e barato no C++ moderno.

O const protege a ficha

Se uma função recebe const Aluno& e tenta mudar um campo, o compilador barra:

#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

void dar_bonus(const Aluno& a) {
    a.nota += 1.0;
}

int main() {
    Aluno ana{"Ana", 20, 6.5};
    dar_bonus(ana);
}
$ g++ -std=c++23 -Wall -Wextra constmuda.cpp -o constmuda
constmuda.cpp: In function ‘void dar_bonus(const Aluno&)’:
constmuda.cpp:10:12: error: assignment of member ‘Aluno::nota’ in read-only object
   10 |     a.nota += 1.0;
      |     ~~~~~~~^~~~~~

assignment of member ‘Aluno::nota’ in read-only object = "atribuição ao campo nota de um objeto somente leitura". Aluno::nota quer dizer "o campo nota que pertence a Aluno". Se a função deve mudar a ficha, tire o const; se não deve, o erro acabou de achar um engano seu.


6. A seta ->: quando você tem um ponteiro

Se em vez da struct você tem um ponteiro para ela (veja Ponteiros), o ponto não serve direto. Você pode escrever (*p).nome, mas existe um atalho: a seta p->nome, que quer dizer exatamente a mesma coisa.

#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade = 0;
    double nota = 0.0;
};

int main() {
    Aluno ana{"Ana", 20, 8.5};
    Aluno* p = &ana;

    std::cout << (*p).nome << '\n';
    std::cout << p->nome << '\n';
    p->nota = 10.0;
    std::cout << ana.nota << '\n';
}
$ g++ -std=c++23 -Wall -Wextra seta.cpp -o seta
$ ./seta
Ana
Ana
10

Regra para lembrar: variável ou referência → ponto; ponteiro → seta. Você vai encontrar a seta com ponteiros inteligentes e em código mais antigo. Para structs comuns, vetores e referências, é sempre o ponto.


7. struct × class

No Nível 3 você vai aprender classes. Spoiler: em C++, struct e class são quase a mesma coisa. As duas podem ter campos, funções dentro e construtores. A única diferença é o padrão de acesso:

  • na struct, tudo é público (public) se você não disser nada: qualquer parte do programa pode ler e mudar os campos;
  • na class, tudo é privado (private) se você não disser nada: só o código de dentro da classe mexe nos campos.
#include <iostream>

struct PontoS {
    int x = 0;
    int y = 0;
};

class PontoC {
    int x = 0;
    int y = 0;
};

int main() {
    PontoS s;
    s.x = 3;
    PontoC c;
    c.x = 3;
    std::cout << s.x << '\n';
}
$ g++ -std=c++23 -Wall -Wextra classe.cpp -o classe
classe.cpp: In function ‘int main()’:
classe.cpp:17:7: error: ‘int PontoC::x’ is private within this context
   17 |     c.x = 3;
      |       ^
classe.cpp:9:9: note: declared private here
    9 |     int x = 0;
      |         ^

O s.x = 3 da struct passou; o c.x = 3 da classe deu is private within this context ("é privado neste contexto"). O costume da comunidade C++: use struct para um "pacote de dados" simples, sem regras (como o Aluno); use class quando o tipo precisa proteger os seus dados (uma conta bancária que não pode ficar com saldo negativo). Isso é assunto de public e private.


Formas de criar uma struct: qual usar

FormaExemploQuando usar
Campo por campoAluno a; a.nome = "Ana";Quando os valores chegam aos poucos (lidos do teclado, por exemplo). Exige valores padrão nos campos.
Chaves pela ordemAluno a{"Ana", 20, 8.5};Structs pequenas, com tipos diferentes, em listas (vector).
Designada C++20Aluno a{.nome = "Ana", .nota = 8.5};Structs com muitos campos ou campos do mesmo tipo. A mais legível.
Chaves vaziasAluno a{};Uma ficha em branco, com tudo no padrão ou zerado, mesmo sem valores padrão na struct.

Quando dá errado

"expected ‘;’ after struct definition"
#include <iostream>
#include <string>

struct Aluno {
    std::string nome;
    int idade;
    double nota;
}

int main() {
    Aluno a;
    a.nome = "Ana";
    std::cout << a.nome << '\n';
}
$ g++ -std=c++23 -Wall -Wextra semponto.cpp -o semponto
semponto.cpp:8:2: error: expected ‘;’ after struct definition
    8 | }
      |  ^
      |  ;

O que aconteceu: a chave que fecha a struct precisa de ; depois. Funções não precisam (int main() { … }), structs precisam; é a diferença que mais engana.

Como resolver: o g++ até mostra onde: o ^ aponta logo depois da } e a linha de baixo sugere o ;. No editor do guia, o Clang diz expected ';' after struct.

"has no member named"
$ g++ -std=c++23 -Wall -Wextra membro.cpp -o membro
membro.cpp: In function ‘int main()’:
membro.cpp:12:20: error: ‘struct Aluno’ has no member named ‘email’
   12 |     std::cout << a.email << '\n';
      |                    ^~~~~

O que aconteceu: o Aluno não tem o campo email. Acontece por erro de digitação (a.Nome em vez de a.nome) ou porque você esqueceu de declarar o campo.

Como resolver: confira o nome exato na declaração da struct (maiúsculas contam) ou acrescente o campo lá.

"no match for ‘operator<<’": mostrar a struct inteira com cout
$ g++ -std=c++23 -Wall -Wextra imprimir.cpp -o imprimir
imprimir.cpp: In function ‘int main()’:
imprimir.cpp:12:15: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘Aluno’)
   12 |     std::cout << ana << '\n';
      |     ~~~~~~~~~ ^~ ~~~
      |          |       |
      |          |       Aluno
      |          std::ostream {aka std::basic_ostream<char>}
imprimir.cpp:12:15: note: there are 52 candidates
«… mais de 360 linhas listando os 52 "candidatos" que não serviram …»

O que aconteceu: o cout sabe mostrar números e textos, mas não sabe mostrar um Aluno: você criou o tipo, ninguém ensinou o cout a escrevê-lo. O g++ então lista tudo o que ele tentou (378 linhas ao todo). Leia só a primeira linha de error.

Como resolver: mostre campo por campo (ana.nome, ana.nota) ou escreva uma função mostrar(const Aluno&), como na seção 5. Ensinar o << a escrever um Aluno é assunto de Sobrecarga de Operadores.

Números estranhos como 658842208 anos

O que aconteceu: o campo nunca recebeu valor e não tem valor padrão. Você leu lixo da memória (seção 2). O compilador nem sempre avisa.

Como resolver: dê valor padrão aos campos (int idade = 0;) ou crie a variável com chaves (Aluno a{};).

A função mudou a struct, mas no main nada mudou

O que aconteceu: o parâmetro é Aluno a (por valor). A função mexeu numa cópia, como em copia.cpp.

Como resolver: receba por referência: void dar_bonus(Aluno& a).


Referência rápida

EscritaSignificado
struct Aluno { std::string nome; int idade = 0; };Declara um tipo novo com dois campos (não esqueça o ; final).
Aluno a;Cria uma variável; campos com o valor padrão (ou lixo, se não houver padrão).
Aluno a{};Cria com tudo no padrão ou zerado.
Aluno a{"Ana", 20};Preenche pela ordem dos campos.
Aluno a{.nome = "Ana"};Preenche pelo nome do campo C++20, na ordem da declaração.
a.nomeAcessa um campo (ler ou mudar).
p->nomeAcessa um campo por um ponteiro; o mesmo que (*p).nome.
Aluno b = a;Copia todos os campos; b é independente.
std::vector<Aluno> turma;Uma lista de structs.
void f(const Aluno& a)Passa sem copiar e sem deixar mudar: o padrão para structs.
void f(Aluno& a)Passa sem copiar, deixando a função mudar o original.

Exercícios

1. Declarar um campo

Complete a struct Produto com um campo preco do tipo double, valendo 0.0 por padrão:

struct Produto { std::string nome; };

2. Acessar o campo

A variável p é um Produto. Complete para mostrar o nome dele:

std::cout << << '\n';

3. Parâmetro sem cópia

Complete o parâmetro da função, que só mostra um Produto (sem copiar e sem poder mudá-lo):

void mostrar() { … }

4. Com struct Aluno { std::string nome; int idade = 0; double nota = 0.0; };, o que vale b.nota depois de Aluno b{"Bruno", 19};?

5. Qual é a única diferença entre struct e class em C++?

6. p é um ponteiro para um Aluno. Como se lê o nome?

Missão final: o estoque da lojinha

Missão: a lojinha da Ana guarda cada produto numa struct Produto. Escreva a função double valor_total(const std::vector<Produto>& estoque), que soma preco * quantidade de todos os produtos, e mostre o resultado no main: Valor do estoque: R$ 52.5.

#include <iostream>
#include <string>
#include <vector>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
};

// escreva aqui a função valor_total

int main() {
    std::vector<Produto> estoque = {
        {"caderno", 12.5, 2},
        {"caneta", 2.5, 5},
        {"borracha", 1.5, 10},
    };
    // mostre "Valor do estoque: R$ " seguido do resultado de valor_total(estoque)
}
#include <iostream>
#include <string>
#include <vector>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
};

double valor_total(const std::vector<Produto>& estoque) {
    double total = 0.0;
    for (const auto& p : estoque) {
        total += p.preco * p.quantidade;
    }
    return total;
}

int main() {
    std::vector<Produto> estoque = {
        {"caderno", 12.5, 2},
        {"caneta", 2.5, 5},
        {"borracha", 1.5, 10},
    };
    std::cout << "Valor do estoque: R$ " << valor_total(estoque) << '\n';
}

Resumo

  • Uma struct cria um tipo novo que agrupa vários campos: struct Aluno { std::string nome; int idade = 0; double nota = 0.0; };. Não esqueça o ; depois da chave.
  • Acesse cada campo com o ponto: a.nome. Com ponteiro, use a seta: p->nome.
  • Campo numérico sem valor = lixo. Dê valores padrão aos campos ou crie com {}.
  • Preencha de uma vez com chaves: {"Ana", 20, 8.5} (pela ordem) ou {.nome = "Ana", .nota = 8.5} (designada, C++20, na ordem da declaração).
  • std::vector<Aluno> guarda uma turma inteira; percorra com for (const auto& a : turma).
  • Passe structs para funções por const Aluno& (só ler) ou Aluno& (mudar). Por valor, a função recebe uma cópia.
  • struct e class só diferem no padrão: struct é pública; class é privada.