Café y Código
⚡ C++ POO· Lección 9.2

9.2 Colecciones de Objetos: Inventario con std::vector (1 a N) 🎒

Evoluciona tu arquitectura: haz que un objeto contenga una lista dinámica de otros objetos. Aprende a crear un inventario de pociones que alteran atributos (subir vida, veneno y mejoras de poder).

En la lección anterior vimos cómo un Jugador se vincula con un único objeto (su Espada). Sin embargo, en sistemas reales casi siempre necesitamos manejar colecciones dinámicas de objetos: un carrito de compras con múltiples productos, una biblioteca con cientos de libros, o un Jugador con un inventario de múltiples ítems consumibles.

1. Composición de Uno a Muchos (1 a N)

Para almacenar un número variable de objetos en C++ moderno, utilizamos el contenedor canónico de la STL: std::vector<Tipo>.

📦 Relación de Pertenencia

El Jugador es el dueño del vector. Cuando el jugador recoge una poción, se añade al vector con push_back(). Al consumirla, sus efectos se aplican a los atributos del jugador (subiendo o bajando estadísticas) y el ítem se destruye de la lista con erase().

2. Paso 1: La Clase Ítem (Pocion)

Definimos la clase de los objetos individuales que poblarán el inventario. Cada poción tendrá un nombre, un tipo de efecto y un valor numérico de impacto:

Pocion.h - Clase del elemento individual
CPP
1class Pocion {
2private:
3 string nombre;
4 string tipo; // "CURACION", "VENENO", "FUERZA"
5 int valorEfecto;
6
7public:
8 Pocion(string nombre, string tipo, int valorEfecto)
9 : nombre(nombre), tipo(tipo), valorEfecto(valorEfecto) {}
10
11 // Getters para consultar propiedades desde el Jugador
12 string getNombre() const { return nombre; }
13 string getTipo() const { return tipo; }
14 int getValorEfecto() const { return valorEfecto; }
15
16 void mostrarInfo() const {
17 cout << "[" << tipo << "] " << nombre
18 << " (" << (valorEfecto >= 0 ? "+" : "") << valorEfecto << " pts)\n";
19 }
20};

3. Paso 2: La Clase Jugador con std::vector<Pocion>

Incluimos la librería #include <vector> y declaramos el vector como atributo privado dentro de Jugador:

Incorporación de vector<Pocion> como atributo interno
CPP
1#include <vector>
2
3class Jugador {
4private:
5 string nombre;
6 int salud;
7 int saludMaxima;
8 int fuerza;
9 vector<Pocion> inventario; // <- Colección 1 a N de objetos
10
11public:
12 Jugador(string nombre, int saludMax, int fuerzaBase)
13 : nombre(nombre), salud(saludMax), saludMaxima(saludMax), fuerza(fuerzaBase) {}
14
15 // 1. Añadir un objeto a la colección
16 void recogerPocion(const Pocion &pocion) {
17 inventario.push_back(pocion);
18 cout << "🎒 " << nombre << " guardó en su mochila: " << pocion.getNombre() << "\n";
19 }
20
21 // 2. Iterar sobre todos los objetos del vector
22 void mostrarInventario() const {
23 cout << "\n--- 🎒 Mochila de " << nombre << " (" << inventario.size() << " ítems) ---\n";
24 if (inventario.empty()) {
25 cout << " (Tu mochila está vacía)\n";
26 return;
27 }
28
29 for (size_t i = 0; i < inventario.size(); i++) {
30 cout << " " << i + 1 << ". ";
31 inventario[i].mostrarInfo();
32 }
33 }
34};

4. Consumir Ítems: Modificando Atributos y Eliminando del Vector

Al usar una poción ocurren tres eventos fundamentales de POO:

  1. Validación de límites: Comprobamos que el índice solicitado por el usuario exista realmente en el vector (indice >= 0 && indice < inventario.size()).
  2. Alteración del estado interno del Jugador: Según el tipo de poción, se modifican los atributos salud o fuerza:
    • CURACIÓN (+Vida): Aumenta la salud sin exceder saludMaxima.
    • VENENO (-Vida): Disminuye la salud; si llega a 0, el jugador queda derrotado.
    • FUERZA (+Ataque): Aumenta permanentemente el atributo de fuerza física.
  3. Eliminación del vector: Usamos inventario.erase(inventario.begin() + indice) para remover el ítem consumido y reorganizar el contenedor.
Lógica de consumo, modificación de atributos y eliminación con erase()
CPP
1bool usarPocion(int slotUsuario) {
2 int idx = slotUsuario - 1; // Convertimos de 1-based (usuario) a 0-based (C++)
3
4 // 1. Verificación defensiva de rango
5 if (idx < 0 || idx >= static_cast<int>(inventario.size())) {
6 cout << "❌ No existe ningún ítem en la posición #" << slotUsuario << ".\n";
7 return false;
8 }
9
10 Pocion consumida = inventario[idx];
11 cout << "\n🧪 " << nombre << " consume " << consumida.getNombre() << "...\n";
12
13 // 2. Modificación de atributos según el efecto
14 if (consumida.getTipo() == "CURACION") {
15 salud += consumida.getValorEfecto();
16 if (salud > saludMaxima) salud = saludMaxima;
17 cout << "💚 ¡Salud restaurada! Ahora tienes: " << salud << "/" << saludMaxima << " HP\n";
18 }
19 else if (consumida.getTipo() == "VENENO") {
20 salud -= consumida.getValorEfecto();
21 if (salud < 0) salud = 0;
22 cout << "🤢 ¡El frasco contenía veneno vil! Pierdes " << consumida.getValorEfecto() << " de vida.\n";
23 cout << " Salud actual: " << salud << "/" << saludMaxima << " HP\n";
24 if (salud == 0) cout << "💀 ¡" << nombre << " ha caído derrotado por envenenamiento!\n";
25 }
26 else if (consumida.getTipo() == "FUERZA") {
27 fuerza += consumida.getValorEfecto();
28 cout << "💪 ¡Tus músculos arden de poder! Fuerza incrementada a: " << fuerza << "\n";
29 }
30
31 // 3. Removemos la poción gastada del inventario
32 inventario.erase(inventario.begin() + idx);
33 return true;
34}

5. Ejemplo Completo y Ejecutable

Este programa interactivo muestra la creación del jugador, la recolección de múltiples pociones con distintos efectos, y cómo sus atributos cambian dinámicamente al consumirlas:

Inventario de objetos con std::vector y modificación de atributos
CPP
1#include <iostream>
2#include <string>
3#include <vector>
4
5using namespace std;
6
7// --- CLASE POCION ---
8class Pocion {
9private:
10 string nombre;
11 string tipo; // "CURACION", "VENENO", "FUERZA"
12 int valorEfecto;
13
14public:
15 Pocion(string nombre, string tipo, int valorEfecto)
16 : nombre(nombre), tipo(tipo), valorEfecto(valorEfecto) {}
17
18 string getNombre() const { return nombre; }
19 string getTipo() const { return tipo; }
20 int getValorEfecto() const { return valorEfecto; }
21
22 void mostrarInfo() const {
23 cout << "[" << tipo << "] " << nombre
24 << " (" << (valorEfecto >= 0 ? "+" : "") << valorEfecto << " pts)\n";
25 }
26};
27
28// --- CLASE JUGADOR CON INVENTARIO ---
29class Jugador {
30private:
31 string nombre;
32 int salud;
33 int saludMaxima;
34 int fuerza;
35 vector<Pocion> inventario;
36
37public:
38 Jugador(string nombre, int saludMax, int fuerzaBase)
39 : nombre(nombre), salud(saludMax), saludMaxima(saludMax), fuerza(fuerzaBase) {}
40
41 string getNombre() const { return nombre; }
42 int getSalud() const { return salud; }
43 int getFuerza() const { return fuerza; }
44
45 void recogerPocion(const Pocion &pocion) {
46 inventario.push_back(pocion);
47 cout << "🎒 " << nombre << " recogió: " << pocion.getNombre() << "\n";
48 }
49
50 void mostrarInventario() const {
51 cout << "\n=== 🎒 INVENTARIO DE " << nombre << " (" << inventario.size() << " ítems) ===\n";
52 if (inventario.empty()) {
53 cout << " (Mochila vacía)\n";
54 return;
55 }
56
57 for (size_t i = 0; i < inventario.size(); i++) {
58 cout << " " << i + 1 << ". ";
59 inventario[i].mostrarInfo();
60 }
61 }
62
63 bool usarPocion(int slot) {
64 int idx = slot - 1;
65 if (idx < 0 || idx >= static_cast<int>(inventario.size())) {
66 cout << "❌ Número de ítem inválido: #" << slot << "\n";
67 return false;
68 }
69
70 Pocion p = inventario[idx];
71 cout << "\n🧪 " << nombre << " bebe " << p.getNombre() << "...\n";
72
73 if (p.getTipo() == "CURACION") {
74 salud += p.getValorEfecto();
75 if (salud > saludMaxima) salud = saludMaxima;
76 cout << "💚 ¡Te has curado! HP actual: " << salud << "/" << saludMaxima << "\n";
77 }
78 else if (p.getTipo() == "VENENO") {
79 salud -= p.getValorEfecto();
80 if (salud < 0) salud = 0;
81 cout << "🤢 ¡Cuidado! Era veneno puro. Pierdes " << p.getValorEfecto() << " de vida.\n";
82 cout << " HP actual: " << salud << "/" << saludMaxima << "\n";
83 if (salud == 0) cout << "💀 ¡" << nombre << " se desploma sin vida!\n";
84 }
85 else if (p.getTipo() == "FUERZA") {
86 fuerza += p.getValorEfecto();
87 cout << "💪 ¡Tu fuerza física aumenta en " << p.getValorEfecto() << " pts! Total: " << fuerza << "\n";
88 }
89
90 // Se retira el ítem usado de la lista
91 inventario.erase(inventario.begin() + idx);
92 return true;
93 }
94
95 void mostrarFicha() const {
96 cout << "┌───────────────────────────────────────────────┐\n";
97 cout << " Héroe: " << nombre << " | HP: " << salud << "/" << saludMaxima
98 << " | Fuerza: " << fuerza << " | Pociones: " << inventario.size() << "\n";
99 cout << "└───────────────────────────────────────────────┘\n";
100 }
101};
102
103// --- SIMULACIÓN EN MAIN ---
104int main() {
105 Jugador valiente("Link", 100, 20);
106
107 // Creamos pociones con diversos efectos
108 Pocion elixirVida("Elixir Rojo de Vida", "CURACION", 35);
109 Pocion pocionPoder("Extracto de Fuerza de Dragón", "FUERZA", 15);
110 Pocion frascoVeneno("Brebaje Sospechoso de la Bruja", "VENENO", 40);
111
112 // Llenamos el inventario
113 valiente.recogerPocion(elixirVida);
114 valiente.recogerPocion(pocionPoder);
115 valiente.recogerPocion(frascoVeneno);
116
117 valiente.mostrarFicha();
118 valiente.mostrarInventario();
119
120 // 1. Consumir la poción de Fuerza (slot 2)
121 valiente.usarPocion(2);
122
123 // 2. Beber por error el frasco de veneno (que ahora quedó en el slot 2)
124 valiente.usarPocion(2);
125
126 // 3. Beber la poción de curación para recuperarse del veneno (slot 1)
127 valiente.usarPocion(1);
128
129 // Estado final
130 cout << "\n=== ESTADO FINAL DEL HÉROE ===\n";
131 valiente.mostrarFicha();
132 valiente.mostrarInventario();
133
134 return 0;
135}
136
Continúa tu aprendizaje

10. Herencia y Polimorfismo ⚡

Ahora que dominas la composición ("Tiene-un"), adéntrate en la herencia ("Es-un"): clases base, clases derivadas y métodos virtuales.

Ir a Herencia y Polimorfismo →

Ejercicio de Práctica

1. Agrega a la clase Jugador un límite máximo de capacidad para la mochila (por ejemplo, máximo 4 pociones). Si el inventario ya está lleno, el método recogerPocion() debe mostrar un mensaje advirtiendo que no cabe más peso y no debe agregar la poción.
2. Agrega un nuevo tipo de poción: "MANA" con su correspondiente atributo mana en la clase Jugador.

Autoevaluación Formativa

Comprueba lo que Has Aprendido 🧠

Valida tu comprensión de colecciones dinámicas de objetos y gestión de estado en C++.

Dato curioso: C++ apareció en 1985; Bjarne Stroustrup en Bell Labs. Wikipedia

Ko-fi
Donaciones
Apoyá cafeycodigo con un café en Ko-fi. Colaboradores: insignia, muro y zona exclusiva.