Tipos, Valores y el Sistema de Tipos
Explora los tipos fundamentales de C++, los enteros de ancho fijo, las formas de inicializacion, la deduccion con auto y el sistema const que impone correccion en tiempo de compilacion.
El Sistema de Tipos: Tu Primera Linea de Defensa
C++ es un lenguaje de tipado estatico — cada expresion tiene un tipo conocido en tiempo de compilacion. El sistema de tipos no es solo contabilidad; es tu herramienta mas poderosa para detectar errores antes de que tu codigo se ejecute. Un programa C++ bien tipado lleva sus invariantes en el propio sistema de tipos, haciendo imposibles clases enteras de errores.
El tipo de una variable determina tres cosas: cuanta memoria ocupa, que valores puede representar y que operaciones son validas sobre ella. Elegir los tipos correctos es la base del codigo C++ correcto.
Tipos Fundamentales
C++ proporciona un conjunto de tipos integrados que se mapean directamente a las capacidades del hardware. Sus tamanos exactos varian segun la plataforma, pero el estandar garantiza rangos minimos:
#include <iostream>
#include <cstdint> // Fixed-width integer types
#include <limits> // std::numeric_limits
int main() {
// Boolean
bool is_valid = true; // true or false, typically 1 byte
// Character types
char letter = 'A'; // At least 8 bits, holds ASCII
char8_t u8ch = u8'A'; // C++20: exactly 8 bits, UTF-8
char16_t u16ch = u'\u00E9'; // At least 16 bits, UTF-16
char32_t u32ch = U'\U0001F600'; // At least 32 bits, UTF-32
// Integer types (sizes are minimums, typical x86-64 shown)
short s = 32000; // At least 16 bits (16)
int i = 2'000'000; // At least 16 bits (32) — digit separator C++14
long l = 100'000L; // At least 32 bits (64 on Linux, 32 on Windows)
long long ll = 9'000'000'000'000'000'000LL; // At least 64 bits
// Unsigned variants
unsigned int ui = 42u; // Cannot be negative
// Floating-point types
float f = 3.14f; // ~7 decimal digits precision
double d = 3.141592653589793; // ~15 decimal digits precision
long double ld = 3.14159265358979323846L;
// Fixed-width types from <cstdint> — use these when size matters
std::int32_t exact = 42; // Exactly 32 bits, guaranteed
std::uint64_t big = 18'446'744'073'709'551'615ULL; // Exactly 64 bits
std::int_fast32_t fast = 42; // At least 32 bits, fastest available
std::size_t index = 0; // Unsigned, suitable for sizes and indices
std::cout << "int: " << sizeof(int) << " bytes, max = "
<< std::numeric_limits<int>::max() << '\n';
}Pulsa Ejecutar para compilar y ejecutar este ejemplo.
Inicializacion: La Forma Correcta
C++ ha evolucionado multiples sintaxis de inicializacion. La inicializacion uniforme (con llaves) {} es preferida en C++ moderno porque previene conversiones estrechas:
#include <iostream>
#include <string>
int main() {
// Direct initialization
int a(42); // OK but can be confused with function declaration
// Copy initialization
int b = 42; // OK, familiar from C
// Uniform (brace) initialization — preferred in modern C++
int c{42}; // Direct-list-initialization
int d = {42}; // Copy-list-initialization
// Brace initialization PREVENTS narrowing conversions
// double to int loses precision — the compiler will ERROR:
// int narrow{3.14}; // ERROR: narrowing conversion
int ok = 3.14; // Compiles! Silently truncates to 3 (dangerous)
// Value initialization (zero for built-in types)
int zero{}; // Guaranteed to be 0
double dzero{}; // Guaranteed to be 0.0
bool bfalse{}; // Guaranteed to be false
// auto deduction
auto x = 42; // int
auto y = 3.14; // double
auto z = 'A'; // char
auto s = std::string{"hello"}; // std::string (not const char*!)
// decltype — queries the type of an expression
decltype(x) another = 100; // same type as x → int
decltype(auto) ref = (x); // int& because (x) is an lvalue expression
std::cout << "zero-initialized int: " << zero << '\n';
std::cout << "auto-deduced string: " << s << '\n';
}Pulsa Ejecutar para compilar y ejecutar este ejemplo.
En C, el puntero nulo se representa con la macro NULL, que tipicamente se expande a 0 o (void*)0. Esto causa ambiguedad en C++ porque 0 tambien es un int valido. Considera una funcion sobrecargada tanto para int como para un tipo puntero — pasar NULL (que es 0) llamaria a la sobrecarga de int, no a la del puntero.
C++11 introdujo nullptr, una palabra clave de tipo std::nullptr_t que es implicitamente convertible a cualquier tipo puntero pero no a enteros. Usa siempre nullptr en lugar de NULL o 0 para punteros nulos.
De manera similar, prefiere '\0' (el caracter nulo) cuando te refieres al caracter con valor cero, en lugar de 0, para comunicar la intencion claramente.
- Usa siempre nullptr en lugar de NULL o 0 para punteros nulos
- nullptr tiene su propio tipo (std::nullptr_t) y evita la ambiguedad de sobrecarga
- NULL es una macro de C que se expande a 0 — es un entero, no un puntero
- Usa '\0' para el caracter nulo para distinguirlo del entero 0
const y Calificadores de Tipo
El calificador const le dice al compilador (y a los lectores humanos) que un valor no debe ser modificado despues de la inicializacion. Es una de las herramientas mas importantes en C++ para escribir codigo seguro y autodocumentado.
const se lee de derecha a izquierda: const int* es un puntero a un int const (el int no puede cambiarse, el puntero si). int* const es un puntero const a int (el puntero no puede cambiarse, el int si). const int* const es un puntero const a un int const.
constexpr (C++11) va mas alla: significa que el valor debe ser computable en tiempo de compilacion. Esto permite al compilador evaluar expresiones durante la compilacion, eliminando completamente el costo en tiempo de ejecucion. Usa constexpr para constantes que realmente se conocen en tiempo de compilacion.
consteval (C++20) es aun mas estricto: marca una funcion que debe ser evaluada en tiempo de compilacion — llamarla en tiempo de ejecucion es un error de compilacion.
El calificador volatile le dice al compilador que el valor de una variable puede cambiar fuera del control del programa (por ejemplo, registros de hardware, E/S mapeada en memoria). Previene ciertas optimizaciones. No uses volatile para multihilo — usa std::atomic en su lugar.
Uno de los errores mas insidiosos en C y C++ antiguo proviene de las conversiones estrechas — conversiones implicitas que pierden informacion. Por ejemplo, asignar un double a un int trunca silenciosamente la parte fraccionaria. Asignar un int grande a un short silenciosamente desborda.
La inicializacion con llaves {} previene las conversiones estrechas en tiempo de compilacion. Esta es una de las razones mas fuertes para preferirla sobre la inicializacion con =. Si escribes int x{3.14};, el compilador emitira un error. Con int x = 3.14;, trunca silenciosamente a 3.
De manera similar, mezclar tipos con signo y sin signo en aritmetica causa conversiones implicitas que pueden producir resultados sorprendentes. Un int negativo comparado con un unsigned int se convertira primero a unsigned, convirtiendose potencialmente en un numero positivo muy grande.
- La inicializacion con llaves {} detecta conversiones estrechas en tiempo de compilacion
- La inicializacion por asignacion (=) permite silenciosamente el estrechamiento — se pierden datos sin advertencia
- Mezclar tipos con signo y sin signo causa conversion implicita a unsigned
- Un int negativo convertido a unsigned se convierte en un numero positivo muy grande
Alias de Tipo
Los alias de tipo mejoran la legibilidad y facilitan el cambio de tipos posteriormente. Prefiere using sobre typedef en C++ moderno:
#include <cstdint>
#include <vector>
#include <string>
// Modern style (using) — preferred
using Byte = std::uint8_t;
using StudentId = std::int32_t;
using Names = std::vector<std::string>;
// Old style (typedef) — equivalent but harder to read with complex types
typedef std::uint8_t OldByte;
typedef std::vector<std::string> OldNames;
// using works with templates; typedef does not easily
template <typename T>
using Vec = std::vector<T>;
int main() {
Byte b{255};
StudentId sid{12345};
Names names{"Alice", "Bob", "Charlie"};
Vec<double> values{1.0, 2.0, 3.0};
}Pulsa Ejecutar para compilar y ejecutar este ejemplo.
- Cada expresion de C++ tiene un tipo conocido en tiempo de compilacion — el tipo determina el tamano, los valores validos y las operaciones validas
- Usa tipos de ancho fijo de
(int32_t, uint64_t) cuando el tamano exacto importa - Prefiere la inicializacion con llaves {} — previene conversiones estrechas peligrosas
- Usa nullptr (no NULL ni 0) para punteros nulos para evitar ambiguedad de sobrecarga
- auto deduce el tipo del inicializador; decltype consulta el tipo de una expresion sin evaluarla
- Usa constexpr para constantes en tiempo de compilacion y 'using' para alias de tipo
Quiz — Pon a prueba tus conocimientos
(15 XP)1. Cual es la ventaja de la inicializacion con llaves `int x{42};` sobre la inicializacion por asignacion `int x = 42;`?
2. Por que deberias usar `nullptr` en lugar de `NULL` en C++?
3. A que tipo deduce `auto x = 42;` el tipo de `x`?