Capítulo 1: Hello World
Tu primer programa en C++ y una inmersión profunda en qué sucede realmente desde que escribes el código hasta que ves el resultado en pantalla.
Objetivos
- Escribir, compilar y ejecutar tu primer programa en C++
- Entender el proceso de compilación completo
- Conocer cómo funciona la función
main() - Comprender la diferencia entre código fuente y código máquina
El código
// Main.cc
#include <iostream>
int main() {
std::cout << "Hello world!" << std::endl;
return 0;
} Explicación línea por línea
#include <iostream>
Esta es una directiva del preprocesador. El preprocesador es la primera fase del proceso de compilación, ejecutándose antes de que el compilador propiamente dicho vea tu código. Su función principal es procesar directivas que comienzan con #.
Esta directiva #include le indica al preprocesador que copie textualmente todo el contenido del archivo especificado (iostream en este caso) en la ubicación donde aparece la directiva. Es literalmente como hacer "copiar y pegar" del archivo completo.
Los corchetes angulares <> le dicen al compilador que busque en los directorios estándar del sistema (donde se instala la biblioteca estándar de C++). Si usaras comillas dobles (#include "iostream"), buscaría primero en tu directorio de proyecto, luego en los directorios del sistema.
int main()
La función main() es el punto de entrada de todo programa en C++. Cuando ejecutas tu programa desde la terminal o haciendo doble clic, el sistema operativo realiza varias operaciones: carga el ejecutable en memoria, configura el entorno de ejecución, y finalmente transfiere el control a la función main(). Es una convención heredada del lenguaje C (creado en 1972 por Dennis Ritchie en Bell Labs) - el sistema operativo busca específicamente un símbolo llamado main en el binario compilado. El int antes de main especifica el tipo de retorno: un entero que representa el código de salida del programa (0 = éxito, cualquier otro valor = error). Este código es crucial para scripts y herramientas de automatización: ./mi_programa && echo "Éxito" || echo "Falló"
Hay otras formas de escribir main Intermedio
Sí, hay dos formas estándar:
// Forma 1: Sin argumentos
int main() { return 0; }
// Forma 2: Con argumentos de línea de comandos
int main(int argc, char* argv[]) { return 0; } argc = número de argumentos
argv = array de argumentos (como strings)
Usarás la forma 2 cuando necesites pasar parámetros al programa desde la terminal,
por ejemplo: ./programa archivo.js --verbose
std::cout << "Hello world!"
Esta línea imprime texto en la consola. Vamos a desglosarla:
std- El namespace de la biblioteca estándar::- Operador de resolución de scope (acceso a miembros del namespace)cout- "Character Output", el objeto que representa la salida estándar<<- Operador de inserción (envía datos a cout)
Puedo evitar escribir std cada vez Intermedio
Sí, puedes usar using namespace std; al inicio de tu archivo:
#include <iostream>
using namespace std; // Ahora puedes omitir std::
int main() {
cout << "Hello!" << endl; // Sin std::
return 0;
}
Usar using namespace std; en archivos header (.h)
es considerado mala práctica porque contamina el namespace de todos los archivos
que incluyan ese header. Puede causar conflictos de nombres difíciles de depurar.
Regla: Está bien en archivos .cc pequeños para
aprender, pero en código profesional es mejor ser explícito con std::.
std::endl
std::endl hace dos cosas:
- Inserta un salto de línea (
\n) - Flush del buffer de salida (fuerza la escritura inmediata)
Si solo necesitas un salto de línea, usa \n en lugar de endl.
El flush tiene un costo de rendimiento. En loops que imprimen miles de líneas,
la diferencia puede ser significativa.
return 0;
Retorna 0 al sistema operativo, indicando que el programa terminó
exitosamente sin errores.
El proceso de compilación
Cuando ejecutas g++ Main.cc -o programa, suceden 4 etapas:
Etapas de Compilación
flowchart TB
A["1. PREPROCESADOR<br/>Main.cc"] --> B["Procesa #include, #define<br/>Genera archivo .i"]
B --> C["2. COMPILADOR<br/>Main.i"]
C --> D["Traduce C++ a assembly<br/>Genera archivo .s"]
D --> E["3. ENSAMBLADOR<br/>Main.s"]
E --> F["Traduce assembly a código máquina<br/>Genera archivo .o"]
F --> G["4. LINKER<br/>Main.o + libstdc++.a"]
G --> H["Combina .o con bibliotecas<br/>Genera ejecutable final"]
H --> I["📦 programa (ejecutable)"]
style A fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style C fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style E fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style G fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style I fill:#c8e6c9,stroke:#388e3c,stroke-width:3px Dónde vive tu programa en memoria
Cuando ejecutas ./programa, el sistema operativo:
- Lee el archivo ejecutable del disco
- Crea un nuevo proceso
- Asigna memoria dividida en secciones
- Salta a la función
main()
Estructura de Memoria de un Proceso
flowchart TB
subgraph "Direcciones Altas"
A["Stack<br/>(crece hacia abajo)<br/>Variables locales, parámetros<br/>Memoria automática"]
end
A --> C["Espacio libre"]
C --> E["Heap<br/>(crece hacia arriba)<br/>Memoria dinámica (new/delete)<br/>Tú controlas la vida útil"]
E --> F["BSS<br/>Variables globales no inicializadas"]
F --> G["Data<br/>Variables globales inicializadas"]
G --> H["Text (Code)<br/>READ-ONLY<br/>main(), funciones, constantes"]
subgraph "Direcciones Bajas"
H
end
style A fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style E fill:#fce4ec,stroke:#c2185b,stroke-width:2px
style F fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style G fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style H fill:#e8f5e9,stroke:#388e3c,stroke-width:2px Checklist
- Puedo compilar el programa con
g++ -std=c++20 -Wall Main.cc -o programa - Entiendo las 4 etapas de compilación
- Sé por qué
main()retornaint - Entiendo qué es un namespace y por qué usamos
std:: - Conozco la diferencia entre
endly\n - Puedo explicar qué hace el preprocesador
Preguntas para reflexionar
¿Qué pasa si cambio return 0; por return 1;?
El valor de retorno de main() es el código de salida del programa.
return 0;→ Éxito (convención estándar)return 1;(o cualquier número ≠ 0) → Error
Los scripts de shell y sistemas operativos usan este código para detectar si tu programa falló. Por ejemplo:
./mi_programa
if [ $? -ne 0 ]; then
echo 'El programa falló'
fi ¿Por qué std::cout usa << y no paréntesis como cout('texto')?
<< es el operador de inserción (operator overloading). En C++ puedes redefinir operadores para que funcionen con tus propios tipos.
cout es un objeto de tipo ostream, y el operador << está sobrecargado para insertar datos en el stream.
Ventajas:
- Puedes encadenar:
cout << 'Hola' << 123 << endl; - Es extensible: puedes definir
<<para tus propios tipos
¿Si borro el #include <iostream>, en qué etapa de compilación fallaría?
Fallaría en la etapa de compilación (no en el preprocesador ni en el enlazador).
Proceso completo:
- Preprocesador: Procesa
#include(si no está, no se expande nada) - Compilador: ❌ ERROR AQUÍ - No encuentra la declaración de
std::cout - Enlazador: No llega a esta etapa
Error típico: error: 'cout' is not a member of 'std'
¿Cuál es más rápido: endl o \n?
\n es más rápido que endl.
Diferencia:
\n→ Solo inserta una nueva líneaendl→ Inserta nueva línea Y hace flush del buffer (fuerza escritura inmediata)
El flush es costoso. Usa \n en loops:
// ❌ Lento (1000 flushes)
for (int i = 0; i < 1000; i++)
cout << i << endl;
// ✅ Rápido (1 flush al final)
for (int i = 0; i < 1000; i++)
cout << i << '\n'; En el Capítulo 2 vamos a profundizar en los tipos de datos y cómo se representan en memoria. Aprenderás sobre bits, bytes, signed/unsigned, y por qué el tamaño importa en C++.