Introducción a un lenguaje avanzado
Después de C, un lenguaje con objetos permite pensar en piezas que se arman entre sí. No es una moda: es la manera de que un proyecto de diez mil líneas siga siendo comprensible.
01Por qué no alcanza con funciones
En C un programa crece como una lista de funciones que se pasan estructuras. Funciona hasta que el
proyecto llega a unas miles de líneas y aparecen tres problemas: cualquiera puede tocar cualquier campo,
el mismo dato se valida en veinte lugares, y agregar un caso obliga a buscar todos los
if desperdigados.
- Encapsulamiento. Los datos y las operaciones que los tocan viven juntos, y desde afuera sólo se ve lo que se decidió mostrar. Si el estado sólo se cambia por métodos, siempre es válido.
- Herencia. Una clase parte de otra y agrega o cambia lo que necesita, sin repetir lo común.
- Polimorfismo. Se llama al mismo método sobre objetos distintos y cada uno hace lo suyo. Es lo que permite agregar un instrumento nuevo sin tocar el código que los usa.
No es una moda ni una obligación: es una manera de que un proyecto de diez mil líneas siga siendo comprensible. Para un firmware de doscientas líneas, C y una estructura siguen siendo la respuesta correcta.
02De la estructura a la clase
| En C | En C++ o Python |
|---|---|
struct Canal con sus campos | La clase, con los mismos campos privados |
canal_init(&c, ...) | El constructor |
canal_leer(&c) | Un método: el objeto es el primer parámetro implícito |
canal_liberar(&c) | El destructor, o el bloque with |
| Un puntero a función en la estructura | Un método virtual |
class Canal:
def __init__(self, nombre, escala):
self.nombre = nombre
self._escala = escala # el guion bajo dice «no me toques»
def leer(self):
return self._crudo() * self._escala
Ese self explícito de Python es el mismo this implícito de C++ y el mismo
puntero a la estructura que se pasaba a mano en C. Cambia la sintaxis, no el mecanismo.
El mismo código llama a medir() sobre objetos distintos. Elegí cuál es y mirá qué implementación se ejecuta.
03El despacho dinámico
Cuando se llama a un método sobre un objeto, el lenguaje tiene que decidir cuál de las implementaciones ejecutar. Si esa decisión se toma al compilar, es despacho estático; si se toma al ejecutar, mirando el tipo real del objeto, es dinámico, y es lo que hace posible el polimorfismo.
for equipo in banco: # multímetro, osciloscopio, fuente…
print(equipo.describir()) # cada uno responde lo suyo
El bucle no sabe ni le importa qué hay adentro de la lista. Agregar un instrumento nuevo no obliga a tocarlo: alcanza con que la clase nueva implemente los mismos métodos. Eso es lo que se gana.
En C++, cada clase con métodos virtuales tiene una tabla de funciones virtuales, y cada objeto guarda un puntero a la tabla de su clase. Llamar a un método virtual es leer ese puntero, buscar la entrada y saltar: dos indirecciones más que una llamada común. En Python es todavía más dinámico —se busca el nombre en un diccionario— y por eso es más flexible y más lento.
04Tipos abstractos y excepciones
Un tipo abstracto de datos se define por lo que se puede hacer con él, no por cómo está hecho. Una pila es «apilar, desapilar y ver el tope», sin importar si adentro hay un arreglo o una lista enlazada. La biblioteca estándar es justamente eso: contenedores probados que ya no hay que escribir.
| Necesidad | C++ | Python |
|---|---|---|
| Secuencia que crece | std::vector | list |
| Buscar por clave | std::map, unordered_map | dict |
| Sin repetidos | std::set | set |
| Cola de doble punta | std::deque | collections.deque |
| Memoria que se libera sola | unique_ptr, shared_ptr | Recolector de basura |
Excepciones
En C, una función informa el error devolviendo un código que casi nadie verifica. Las excepciones separan el camino normal del camino de error:
try:
v = instrumento.medir()
except TimeoutError:
registrar("el equipo no respondió")
finally:
instrumento.cerrar() # esto corre pase lo que pase
Tres reglas: atrapar sólo lo que se sabe manejar —un except vacío esconde errores
de programación—, liberar siempre con finally o con with, y no usar
excepciones para el flujo normal, que son caras y confunden.
05Cuál usar en electrónica
| Trabajo | Herramienta razonable |
|---|---|
| Firmware de microcontrolador chico | C, y C++ sin excepciones ni asignación dinámica |
| Firmware grande, con capas | C++ moderno: clases, plantillas, constexpr |
| Herramienta de laboratorio, interfaz, análisis | Python, por lo rápido que se escribe |
| Procesar mediciones y graficar | Python con NumPy y Matplotlib |
| Lo que tiene que volar | C o C++, o la parte crítica en C llamada desde Python |
La combinación más útil para un electrónico es saber los dos: C o C++ para el equipo y Python para todo lo que lo rodea —automatizar el banco, procesar las mediciones, armar el informe—. No compiten: se usan en capas distintas del mismo proyecto.
06En el laboratorio
Implementar una pila en C con estructura y funciones, y en C++ o Python como clase. Comparar cuántas formas hay de usarla mal en cada caso.
Definir una clase base Instrumento con conectar, medir y
describir, y tres derivadas. Recorrer una lista mezclada llamando a los mismos métodos, y
después agregar una cuarta clase sin tocar el bucle.
Escribir una función que lea el puerto serie y lance excepción si el equipo no responde. Verificar
con finally que el puerto queda cerrado incluso cuando falla.
07Errores frecuentes
- Hacer clases para todo. Si no hay estado que cuidar, una función alcanza.
- Herencia profunda. Cuatro niveles son más difíciles de seguir que un poco de repetición.
- Exponer todos los atributos y perder la única garantía que daba el encapsulamiento.
- Atrapar
Exceptiony seguir como si nada, ocultando errores reales. - Usar excepciones para el flujo normal, como salir de un bucle.
- Suponer que Python es lento en todo: lo lento es el bucle escrito a mano; con NumPy el cálculo corre en C.
08Autoevaluación
¿Qué es el encapsulamiento y qué garantiza?
Guardar juntos los datos y las operaciones, y exponer sólo lo necesario. Garantiza que el estado sólo cambia por caminos validados.
¿Qué permite el polimorfismo que sin él costaría mucho?
Agregar un tipo nuevo sin tocar el código que lo usa: el bucle que recorre la lista de instrumentos sigue igual.
¿Cómo resuelve C++ una llamada a un método virtual?
Por la tabla de funciones virtuales: el objeto guarda un puntero a la tabla de su clase y la llamada salta a la entrada que corresponde, ya en ejecución.
¿Por qué está mal except: pass?
Porque atrapa todo, incluidos los errores de programación, y los esconde. Hay que atrapar sólo las excepciones que se saben manejar.
¿Qué hace finally?
Ejecuta ese bloque haya o no excepción: es donde se cierra el puerto, el archivo o la conexión.
¿Conviene usar objetos en un microcontrolador de 2 kB de RAM?
Clases simples sí; excepciones y asignación dinámica, no. La herramienta se elige por el problema, no por costumbre.
09Para ampliar
- Bjarne Stroustrup. A Tour of C++. 3.ª ed., Addison-Wesley, 2022. El C++ moderno en doscientas páginas, escrito por su autor.
- Mark Lutz. Learning Python. 5.ª ed., O'Reilly, 2013. Completo y cuidadoso con el modelo de objetos del lenguaje.
- Erich Gamma y otros. Design Patterns. Addison-Wesley, 1994. Los patrones clásicos. Conviene leerlo después de haber sufrido los problemas que resuelven.
- Robert C. Martin. Clean Code. Prentice Hall, 2008. Discutible en varias cosas, pero instala la pregunta correcta: si esto se entiende dentro de un año.