CP-UPV Bootcamp - Capítulo 1

Capítulo 1: Tipos de datos, entrada y salida

PROBLEMAS DEL CAPÍTULO
Para acceder a los problemas de este capítulo, accede con tu usuario y contraseña a cpupv.contest.codeforces.com. Si no tienes usuario y contraseña, inscríbete aquí.

Introducción a los tipos de datos y entrada/salida

Bienvenido al Capítulo 1 de nuestro Bootcamp de Competitive Programming. En este capítulo, exploraremos los fundamentos de los tipos de datos y las operaciones de entrada/salida, conceptos esenciales para cualquier programador. Al final de este capítulo, tendrás un conocimiento sólido sobre cómo trabajar con diferentes tipos de datos y cómo realizar operaciones básicas de entrada y salida en C++. A través de diversos ejemplos y explicaciones detalladas, aprenderás a combinar estos conceptos para resolver problemas simples.

Contenidos

Tipos comunes en C++

char

El tipo char se utiliza para almacenar un solo carácter. Este tipo de dato ocupa 1 byte de memoria y puede representar cualquier carácter de la tabla ASCII.

Detalles y usos

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    char letter = 'A';
    cout << "Character: " << letter << endl;
    return 0;
}

int

El tipo int se utiliza para almacenar números enteros. La cantidad de memoria que ocupa puede variar dependiendo del compilador y la arquitectura del sistema, pero generalmente es de 4 bytes.

Detalles y usos

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    int age = 25;
    cout << "Age: " << age << endl;
    return 0;
}

long long

El tipo long long se utiliza para almacenar números enteros grandes. Este tipo de dato ocupa 8 bytes de memoria y puede representar valores mucho mayores que el tipo int.

Detalles y usos

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    long long distance = 123456789012345LL;
    cout << "Distance: " << distance << endl;
    return 0;
}

float

El tipo float se utiliza para almacenar números de punto flotante de precisión simple. Este tipo de dato ocupa 4 bytes de memoria y es adecuado para representar números decimales con una precisión limitada.

Detalles y usos

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    float pi = 3.14f;
    cout << "Pi: " << pi << endl;
    return 0;
}

double

El tipo double se utiliza para almacenar números de punto flotante de doble precisión. Este tipo de dato ocupa 8 bytes de memoria y ofrece mayor precisión que el tipo float.

Detalles y usos

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    double e = 2.718281828459;
    cout << "e: " << e << endl;
    return 0;
}

long double

El tipo long double se utiliza para almacenar números de punto flotante de precisión extendida. La cantidad de memoria que ocupa puede variar, pero generalmente es mayor que la de double.

Detalles y usos

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    long double largeNumber = 1.2345678901234567890L;
    cout << "Large Number: " << largeNumber << endl;
    return 0;
}

bool

El tipo bool se utiliza para almacenar valores booleanos, que pueden ser true o false. Este tipo de dato ocupa 1 byte de memoria.

Detalles y usos

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    bool isRaining = false;
    cout << "Is it raining? " << isRaining << endl;
    return 0;
}

Variables signed y unsigned

En C++, las variables pueden ser signed (con signo) o unsigned (sin signo). Una variable signed puede almacenar tanto números positivos como negativos, mientras que una variable unsigned solo puede almacenar números positivos. La elección entre signed y unsigned depende del contexto en el que se usará la variable.

signed

Una variable signed puede almacenar valores negativos y positivos. Por defecto, las variables de tipo int, char, y long son signed a menos que se especifique lo contrario.

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    signed int negativeNumber = -10;
    cout << "Signed Variable: " << negativeNumber << endl;
    return 0;
}

unsigned

Una variable unsigned solo puede almacenar valores positivos, lo que permite representar el doble de valores positivos en comparación con una variable signed del mismo tamaño.

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    unsigned int positiveNumber = 10;
    cout << "Unsigned Variable: " << positiveNumber << endl;

    unsigned int positiveNumber = -10; // Dará problemas!!!

    return 0;
}

String

En C++, las cadenas de caracteres (string) se manejan utilizando la clase std::string de la biblioteca estándar. Esta clase proporciona una forma sencilla y eficiente de trabajar con cadenas de texto.

Declaración y asignación

Para declarar y asignar una cadena de caracteres, usamos la clase std::string.

Ejemplo
#include <bits/stdc++.h>
using namespace std;

int main() {
    string greeting = "Hello, World!";
    cout << greeting << endl;

    string name;
    cout << "Enter your name: ";
    cin >> name;
    cout << "Hello, " << name << "!" << endl;

    return 0;
}

Operaciones comunes

La clase std::string ofrece una variedad de operaciones útiles, como la concatenación, la búsqueda y la extracción de subcadenas.

Concatenación

#include <bits/stdc++.h>
using namespace std;

int main() {
    string firstName = "John";
    string lastName = "Doe";
    string fullName = firstName + " " + lastName;

    cout << "Full Name: " << fullName << endl;

    return 0;
}

Acceso a caracteres

#include <bits/stdc++.h>
using namespace std;

int main() {
    string text = "Hello";
    char firstChar = text[0];
    char lastChar = text[text.length() - 1];

    cout << "First Character: " << firstChar << endl;
    cout << "Last Character: " << lastChar << endl;

    return 0;
}

Longitud de la cadena

#include <bits/stdc++.h>
using namespace std;

int main() {
    string text = "Hello, World!";
    cout << "Length of the string: " << text.length() << endl;

    return 0;
}

Entrada y salida de Datos

Entrada con cin

La entrada de datos en C++ se realiza con el objeto cin (entrada estándar), que pertenece a la biblioteca iostream. cin lee datos desde la entrada estándar (generalmente el teclado).

Leer Enteros y Flotantes
#include <bits/stdc++.h>
using namespace std;

int main() {
    int age;
    float height;

    cout << "Enter your age: ";
    cin >> age;

    cout << "Enter your height: ";
    cin >> height;

    cout << "Your age is: " << age << endl;
    cout << "Your height is: " << height << endl;

    return 0;
}

Salida con cout

La salida de datos en C++ se realiza con el objeto cout (salida estándar). cout escribe datos en la salida estándar (generalmente la pantalla). Utilizamos el operador << para enviar datos a cout.

Ejemplo de Salida
#include <bits/stdc++.h>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;

    int year = 2024;
    cout << "The current year is: " << year << endl;

    return 0;
}

Lectura de líneas enteras con getline

Para leer una línea completa de texto, utilizamos la función getline, que lee hasta encontrar un salto de línea (\n).

Leer una Línea Completa de Texto

#include <bits/stdc++.h>
using namespace std;

int main() {
    string fullName;

    cout << "Enter your full name: ";
    cin.ignore(); // Ignorar el salto de línea restante
    getline(cin, fullName);

    cout << "Your full name is: " << fullName << endl;

    return 0;
}

Diferencias entre << y >>

En C++, << y >> son operadores que se utilizan para la entrada y salida de datos, respectivamente. Aunque pueden parecer simples, tienen un funcionamiento muy detallado y potente.

<< Operador de inserción

El operador << se utiliza con cout para insertar datos en la salida estándar (pantalla).

#include <bits/stdc++.h>
using namespace std;

int main() {
    int age = 25;
    cout << "Age: " << age << endl;
    return 0;
}

En este ejemplo, << toma el valor de age y lo inserta en la secuencia de salida.

>> Operador de extracción

El operador >> se utiliza con cin para extraer datos de la entrada estándar (teclado).

#include <bits/stdc++.h>
using namespace std;

int main() {
    int age;
    cout << "Enter your age: ";
    cin >> age;
    cout << "You entered: " << age << endl;
    return 0;
}

En este ejemplo, >> extrae el valor introducido por el usuario y lo almacena en la variable age.

Funcionamiento interno de cin

El objeto cin en C++ es extremadamente versátil y puede manejar diferentes tipos de entrada de manera inteligente. Esto se logra a través de una combinación de técnicas de lectura y manejo de flujo de entrada.

Detección automática de tipos

cin puede diferenciar automáticamente entre varios tipos de datos debido a la sobrecarga de operadores en C++. El operador >> está sobrecargado para diferentes tipos de datos como int, float, string, etc.

#include <bits/stdc++.h>
using namespace std;

int main() {
    int a;
    float b;
    string c;

    cout << "Enter an integer: ";
    cin >> a;

    cout << "Enter a float: ";
    cin >> b;

    cout << "Enter a string: ";
    cin >> c;

    cout << "Integer: " << a << ", Float: " << b << ", String: " << c << endl;
    return 0;
}

En este ejemplo, cin reconoce y maneja diferentes tipos de datos basándose en el tipo de las variables.

Manejo de espacios y saltos de línea

Por defecto, cin ignora los espacios en blanco y los saltos de línea al leer datos. Esto significa que cin detendrá la lectura de una cadena de texto cuando encuentre un espacio en blanco, a menos que utilicemos getline.

#include <bits/stdc++.h>
using namespace std;

int main() {
    string name;
    cout << "Enter your full name: ";
    cin.ignore();
    getline(cin, name);
    cout << "Hello, " << name << "!" << endl;
    return 0;
}

En este ejemplo, getline lee la línea completa de entrada, incluyendo espacios en blanco.

Operaciones de formateo y manipulación de datos

Conversiones de tipos

En C++, podemos convertir explícitamente un tipo de dato a otro utilizando conversiones explícitas (esto se llama 'cast'), pero generalmente solo se puede hacer entre tipos que tienen sentido, por ejemplo float a int (se pierde la parte decimal), pero no se puede convertir una string a un int de la misma manera (claramente incompatibles en muchos casos, pero si que existe la función stoi("45") para hacer la conversión de una string que contiene un número a un int).

Conversión de int a float

#include <bits/stdc++.h>
using namespace std;

int main() {
    int num = 10;
    float numFloat = ((float) num);

    cout << "Integer: " << num << endl;
    cout << "Float: " << numFloat << endl;

    return 0;
}

Conversión de float a int

#include <bits/stdc++.h>
using namespace std;

int main() {
    float pi = 3.14;
    int piInt = ((int) pi);

    cout << "Float: " << pi << endl;
    cout << "Integer: " << piInt << endl;

    return 0;
}

Manipulación de texto

Convertir texto a minúsculas

#include <bits/stdc++.h>
using namespace std;

int main() {
    string text = "Hello, World!";
    transform(text.begin(), text.end(), text.begin(), ::tolower);

    cout << "Lowercase: " << text << endl;

    return 0;
}

Convertir texto a mayúsculas

#include <bits/stdc++.h>
using namespace std;

int main() {
    string text = "Hello, World!";
    transform(text.begin(), text.end(), text.begin(), ::toupper);

    cout << "Uppercase: " << text << endl;

    return 0;
}

Curiosidades

Cómo se guardan los enteros

Los números enteros, aquellos que no tienen punto decimal, se guardan en binario como una suma de potencias de \(2\) de la siguiente manera: de derecha a izquierda cada bit representa una potencia de \(2\) de mayor peso empezando por el \(1\) que es igual a \(2^0\). Este ejemplo con 16 bits lo simplifica un poco (El número \(100\) que conocemos de toda la vida es igual a \(1100100\) en binario):

Posición :   ...   15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
  ...   \(2^{15}\) \(2^{14}\) \(2^{13}\) \(2^{12}\) \(2^{11}\) \(2^{10}\) \(2^{9}\) \(2^{8}\) \(2^{7}\) \(2^{6}\) \(2^{5}\) \(2^{4}\) \(2^{3}\) \(2^{2}\) \(2^{1}\) \(2^{0}\)
\(2^{Posición}\) :   ...   32768 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1
Número \(100\) en binario:   ...   0 0 0 0 0 0 0 0 0 1 1 0 0 1 0 0
Suma de potencias de \(2\):   ...   0 0 0 0 0 0 0 0 0 64 32 0 0 4 0 0

Vemos como para el número \(100\), los bits en las posiciones \(2\), \(5\) y \(6\) son \(1\) y el resto \(0\). Ahora \(2^2 + 2^5 + 2^6 = 4 + 32 + 64 = 100\), y así se guardan los números enteros en binario, como sumas de potencias de \(2\).

Otro ejemplo, tenemos el siguiente número binario que hay que pasar a base-10 (los números que conocemos de toda la vida): \(1101101\). Vemos que de derecha a izquierda los bits en las posiciones \(0\), \(2\), \(3\), \(5\) y \(6\) están en 1. Si sumamos \(2^0\), \(2^2\), \(2^3\), \(2^5\) y \(2^6\) obtenemos \(109\) que es correcto.

También se puede convertir un número como \(66\) a binario, solo hay que descomponerlo en la suma de potencias de \(2\): \(66 = 64 + 2 = 2^6 + 2^1\). En binario si las posiciones \(1\) y \(6\) están en 1 obtendríamos \(1000010\), que es la representación correcta de \(66\)

Para los números negativos cambia un poco la cosa, el peso del primer 1 en el número binario pasa a ser esa base de \(2\) en negativo, por ejemplo el número \(111\) en binario es \(2^2 + 2^1 + 2^0 = 7\) si se interpreta como un número positivo pero \(-2^2 + 2^1 + 2^0 = -1\) si se interpreta como un número negativo, hay más detalles en la sección de Signed o Unsigned

Al principio no hay que saber estos detalles, pero sí es importante saber los números más grandes y pequeños que se pueden guardar en los tipos de datos más comunes de de C++ (hay más de los que se muestra en la tabla):

Tipo de dato Tamaño en bits Mínimo valor que se puede guardar Máximo valor que se puede guardar
char 8 bits -128 127
int 32 bits -2,147,483,648 2,147,483,647
long long 64 bits -9,223,372,036,854,775,808 9,223,372,036,854,775,807

Diferencia entre endl y “\n”

Muchas veces verás en el código de otras personas, que cuando tratan de hacer un salto de línea, algunos utilizan endl mientras que otros utilizan "\n". La diferencia entre estos dos es que: mientras el primero añade el salto de línea y vacía el buffer de salida, el segundo solamente añade el salto de línea.

Ten en cuenta que la salida, para reducir operaciones, puede ir almacenando la información en un buffer que se vaciará cuando llegue a un límite, pero al usar endl estás forzando que imprima sus contenidos, pudiendo incrementar el tiempo que tarda tu programa en ejecutarse, sobre todo si se realizan muchas operaciones de entrada y salida.

Entrada y salida más rápida

Relacionado con lo anterior, si quieres aprovechar este buffer, y estás más familiarizado con C++, puedes utilizar la siguiente configuración:

std::ios::sync_with_stdio(0); // Deshabilita sincronización con stdio
std::cin.tie(0);            // Separa cin de cout para que el buffer no se vacíe al cambiar de uno a otro

Redondear decimales

Para esta primera semana del bootcamp seguramente necesitarás redondear valores, existen 3 funciones útiles round(), ceil() y floor()

// Redondear un valor al entero más cercano
round(3.3333)       // = 3
round(3.5)          // = 4
round(3.4999999999) // = 4

// Redondear un valor al siguiente entero
ceil(3.3333)        // = 4
ceil(3.5)           // = 4
ceil(4.0)           // = 4

// Redondear un valor al anterior entero
floor(3.3333)       // = 3
floor(3.5)          // = 3
floor(4.0)          // = 4

Conclusión

En este capítulo, hemos cubierto los conceptos básicos de los tipos de datos y las operaciones de entrada/salida en C++. Estos fundamentos son cruciales para escribir programas eficientes y correctos en C++. Practica estos conceptos con los ejemplos proporcionados y experimenta con variaciones para fortalecer tu comprensión. En los siguientes capítulos, exploraremos temas más avanzados para expandir tus habilidades en programación competitiva, el viaje acaba de empezar.