CP-UPV Bootcamp - Capítulo 3
Capítulo 3: Expresiones y control de flujo
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
En este capítulo se explican los bloques de control de flujo. Estos sirven para hacer programas más interesantes, que sepan tomar decisiones basadas en el valor de las variables del programa y repetir segmentos de código mientras alguna condición sea verdadera. Este capítulo es donde empieza la programación divertida, quizás después de acabarlo te entren ganas de hacer un programa por tu cuenta.
Contenidos
Expresiones e instrucciones
Expresiones
Una expresión es una combinación de operadores, constantes y variables que producen un valor. Se interpreta de izquierda a derecha, y a veces el orden de los operandos y operadores importa ya que algunos tienen preferencia
Aritméticas
Los paréntesis tienen prioridad; después van las multiplicaciones y divisiones, por último sumas y restas. En caso de duda se pueden usar paréntesis.
// evalúación por pasos
6 * 2 / ( 2 + 1 * 2 / 3 + 6 ) + 8 * ( 8 / 4 )
12 / ( 2 + 2 / 3 + 6 ) + 8 * ( 2 )
12 / ( 2 + 0 + 6 ) + 16
12 / ( 8 ) + 16
1 + 16
17
Los operadores binarios también se pueden combinar con los aritméticos, pero los binarios siempre tienen menos prioridad, más abajo se muestra una tabla con las prioridades de los operadores
Como curiosidad, en C++ cualquier expresión que se evalue a 0 es false, mientras que si es otra cosa es true, por ejemplo este código es válido (se ven los bloques if más adelante):
if (2+2) cout << "Aparezco en la terminal";
if (2-2) cout << "No aparezco en la terminal";
Relacionales
Las expresiones relacionales usan operadores de comparación como >, <, >=, <=, ==, != o ! y siempre se evalúan a verdadero o falso. Estas expresiones son las que se usarán en los bloques de control de flujo para tomar decisiones
int x, y; // Variables que usaremos
bool resultado;
// COMPARADORES
// > : mayor que
// < : menor que
// >= : mayor o igual que
// <= : menor o igual que
// == : igual que
// != : no igual que
// ! : inversión
x = 4, y = 5;
resultado = x > y; // false
resultado = y > x; // true
resultado = x < y; // true
resultado = y < x; // false
resultado = y > 5; // false !!!
x = 2, y = 2;
resultado = x <= y; // true
resultado = y <= x; // true
y = 3;
resultado = y >= x; // true
x = 2, y = 2;
resultado = x == y; // true
resultado = x != y; // false
x = 2, y = 3;
resultado = x == y; // false
resultado = x != y; // true
resultado = true;
resultado = !resultado; // false
resultado = !(2*1 == 0); // true
Lógicas
Las expresiones lógicas combinan expresiones relacionales usando los operadores && y ||
// && : condicion1 Y condición2
// || : condicion1 O condición2
x >= 4 && x <= 6 // Verdadero si x está entre 4 y 6 inclusive, falso en caso contrario
x > 10 || y <11 // Verdadero si x es mayor que 10 o y es menor que 11
!(x > 10) && y == 2 // Verdadero si x es menor o igual a 10 y además y es igual a 2
Condicionales
Las expresiones condicionales asignan un valor u otro a una expresión sin utilizar un bloque if. El operador que se utiliza se llama operador ternario:
int n = 5;
string status = (n%2 == 0) ? "Par" : "Impar";
// Si la condición `n%2 == 0` se evalúa a verdadero, la expresión se evalúa al primer valor ("Par") en caso contrario al segundo ("Impar")
// Los operadores ternarios se pueden combinar
char resultado = (n>10) ? (n%2 == 0 ? 'A' : 'B') : (n%2 == 0 ? 'C' : 'D');
Instrucciones
Una instrucción es más que una expresión, es una asignación, una acción, la modificación de una variable o la llamada a una función, por ejemplo:
int x = 5+2; // La expresión 5+2 se evalúa a 7, pero esta instrucción asigna el 7 a la variable x
cout << x; // Esta instrucción imprime x en la terminal
Pueden haber múltiples instrucciones en una misma línea, una instrucción acaba con un punto y coma ;.
Declaraciones
Cuando se declara una variable o función, se especifica el tipo de la variable o los parámetros de la función (las funciones se dan más adelante). Algo que está declarado debe ser implementado o adquirir un valor antes de poderse usar, de caso contrario las variables tendrán valores aleatorios o la llamada a funciones dará error. El propósito de una declaración es que hay intención de usar la variable o función declarada. Una función o variable ya declarada no se puede volver a declarar en el mismo alcance.
// Declarar múltiples variables del mismo tipo en una instrucción
int a, b;
// Declarar una función (pero aún no se puede utilizar)
int suma(int a, int b);
int a; // Error, ya existe `a`
Definiciones
La implementación de una función declarada o la asignación de una variable.
// Declarar múltiples variables del mismo tipo en una instrucción
a = 5;
// Declarar una función (pero aún no se puede utilizar)
int suma(int a, int b) {
return a+b;
}
int c = 4; // Declaración + definición
Bloques de control de flujo
Un bloque de control de flujo es una estructura en los lenguajes de programación que permite controlar el orden en que se ejecutan las instrucciones en un programa. Estos bloques permiten tomar decisiones, repetir operaciones y realizar saltos condicionales. Los bloques de control de flujo son fundamentales para la creación de algoritmos y la implementación de lógica en un programa.
A continuación se explicarán los bloques de control de flujo en C++, pero antes es importante conocer el concepto de alcance de una variable y recordar para qué sirven los diferentes tipos de paréntesis
Tipos de paréntesis en C++
Estos paréntesis se usan de la misma manera en varios lenguajes de programación.
// PARÉNTESIS ():
// En las funciones, para definir y pasar parámetros (las funciones se darán más adelante)
int suma(int a, int b) { return a+b; }
int n = suma(2, 3); // n = 5
// Para dar prioridad a parte de una expresión
6 + 8 / 2 // 10
(6 + 8) / 2 // 7
// En los bloques de control de flujo que vamos a ver ahora es obligatorio usarlos
while ( ... ) { ... }
for ( ... ) { ... }
if ( ... ) { ... }
switch ( ... ) { ... }
// CORCHETES []:
// Para acceder a las posiciones de un array (se verá en el próximo capítulo)
string s = "Buenas";
// s[0] = 'B'
// s[5] = 's'
// s[6] // ERROR
// LLAVES {}:
// Para encapsular bloques de instrucciones
if (true) {
instrucción 1;
instrucción 2;
{
instrucción 3;
instrucción 4;
}
instrucción 5;
}
// Cabe destacar que, en un bloque de control de flujo, si solo hay una instrucción en el bloque de instrucciones, las llaves se pueden omitir, por ejemplo estos dos bloques `if` hacen exactamente lo mismo
if (true) cout << "Hola";
if (true) { cout << "Hola"; }
// OJO: "Adiós" siempre se imprime porque se considera fuera del bloque `if`
if (condicion) cout << "Hola"; cout << "Adiós";
Alcance de una variable
Sobre las llaves que se mencionan antes, una variable declarada dentro de un bloque de instrucciones (dentro de unas llaves) no es visible fuera de ese bloque, pero si dentro de los bloques anidados, por ejemplo:
int x = 2;
cout << x; // 2
if (true) {
int y = 3;
cout << x; // 2
cout << y; // 3
{
cout << x; // 2
cout << y; // 3
}
}
cout << y; // ERROR, no sé qué es `y`
if
Este bloque sirve para ejecutar un bloque u otro dependiendo de si una condición es verdadera o falsa.
if (llueve) {
cout << "Llevate paraguas" << endl;
}
En el caso que se quiera hacer algo si la condición es falsa se puede añadir un else { ... }
if (llueve) {
cout << "Llevate paraguas" << endl;
} else {
cout << "No te lleves paraguas" << endl;
}
Se pueden encadenar múltiples bloques if y solo se debería ejecutar un bloque de código
double nota = 8.75;
if (nota >= 9) {
cout << "Sobresaliente" << endl;
} else if (nota >= 7) {
cout << "Notable" << endl;
} else if (nota >= 5) {
cout << "Aprobado" << endl;
} else {
cout << "Suspenso" << endl;
}
Dentro de un bloque if puede haber otro bloque if
int n = -5, m = 4;
if (n == 0) {
cout << "n es 0 ";
if (m == 0) cout << "y m es 0";
else if (m > 0) cout << "y m es mayor que 0";
else cout << "y m es menor que 0";
} else if (n > 0) {
cout << "n es mayor que 0";
if (m == 0) cout << "y m es 0";
else if (m > 0) cout << "y m es mayor que 0";
else cout << "y m es menor que 0";
} else {
cout << "n es menor que 0";
if (m == 0) cout << "y m es 0";
else if (m > 0) cout << "y m es mayor que 0";
else cout << "y m es menor que 0";
}
for
Un bloque for sirve para ejecutar un bloque de código múltiples veces mientras una condición sea verdadera. La estructura del for es la siguiente:
for (inicialización; condiciones; actualización) { /* Código */ }
// Inicialización: Solo se ejecuta una vez, antes de comenzar el loop. Es el espacio para definir nuevas variables que solo se usarán dentro del loop. Al finalizar, estas no serán visibles
// Condiciones: Una (o más condiciones combinadas con `&&`) que se tienen que dar para entrar al bucle una vez. Cada vez que se ejecute el bloque de codigo dentro del loop se volverá a evalúar la condición, la primera vez que sea falsa se acaba el bucle y no se vuelve a ejecutar
// Actualización: Después de ejecutar el bloque de código del loop, se pueden actualizar variables para estar preparadas para la próxima posible iteración
"""
FLUJO DE UN BUCLE FOR
(true)
──► inicialización ──► condición ──► codigo del bucle ──► actualización
│ ▲ │
(false) │ └────────────────────────────────────┘
▼
seguir con
el programa
"""
// Ejemplos
for ( int i = 0 ; i < n ; ++i ) { ... } // Típico bloque `for`
for (int i=0, j=10 ; i<n && j>5 ; ++i, --j ) { ... } // Dos variables y dos actualizaciones
for ( ; i < n ; ) { ... } // Sin inicialización ni actualización (básicamente un bloque `while`)
// Suma de numeros impares entre el 1 y n
int sum = 0;
for (int i = 1; i < n; ++i) {
if (i%2 == 1) sum += i;
}
// sum = 25
Nota: Para los bloques
forywhilese pueden utilizar las instruccionesbreakycontinue
break: Detiene la ejecución del loop y sale del bloque por completo para seguir con el código que hay debajo del bloquecontinue: Detener la ejecución del código en una iteración y saltar a la condición de entrada del loop
// Imprimir los números del n al 1 saltándose los pares
for (int n = 8; n > 0; --n) {
if (n%2 == 0) continue; // Par
cout << n << endl;
}
/*
7
5
3
1
*/
// Imprimir del n al 1 pero parar en 5
for (int n = 10; n > 0; --n) {
cout << n << endl;
if (n == 5) break;
}
/*
10
9
8
7
6
5
*/
while
Este bloque permite ejecutar código mientras la expresión dentro de los () sea verdadera. Es lo mismo que un for, cualquier cosa que se pueda hacer con uno se puede hacer con el otro, solo que a veces uno es más conveniente, por ejemplo un while para contar los casos de prueba que faltan por leer en un problema
// Programa que imprime del n al 1
int casosDePrueba; cin >> casosDePrueba;
while (casosDePrueba--) {
// Procesar caso de prueba
}
Dentro de este bloque se pueden utilizar las instrucciones break y continue
// Imprimir del n al 1 pero saltándose el 3
int n = 5;
while (n > 0) {
cout << "Numero: ";
if (n == 3) continue;
cout << n << endl;
n--;
}
/*
Numero: 5
Numero: 4
Numero:
Numero: 2
Numero: 1
*/
// Imprimir del N hasta el anterior múltiplo de 10
int n = 46;
while (true) {
cout << n << endl;
if (n%10 == 0) break;
n--;
}
/*
46
45
44
43
42
41
40
*/
do while
do while es una pequeña variante de while, con este bloque todo lo que haya dentro del do { ... } siempre se ejecuta al menos una vez. Si al finalizar el bloque do la condición del while es verdadera se vuelve a repetir el bloque do.
bool condicion = false;
do {
cout << "Solo me verás una vez" << endl;
} while (condicion);
int imprimirNVeces = 3;
do {
cout << "Hola" << endl;
--imprimirNVeces;
} while (imprimirNVeces > 0);
/*
Hola
Hola
Hola
^/
switch
Este bloque permite ejecutar un bloque u otro de código dependiendo del valor exacto de una variable. En el caso de debajo, la variable brujula debería contener N, E, S o O, entonces se ejecutará el bloque correspondiente e imprimirá la orientación.
Opcionalmente abajo del switch hay un bloque default: que se ejecuta si no hay ningún case con el valor correspondiente. Es muy importante poner break; después de cada bloque de código porque de lo contrario si el programa ejecuta un bloque pasará a ejecutar los siguientes también aunque el valor de la variable no corresponda con esos casos. Prueba a ver el resultado del siguiente bloque switch con y sin break.
char brujula = 'S';
switch (brujula) {
case 'N':
cout << "Norte" << endl;
break;
case 'E':
cout << "Este" << endl;
break;
case 'S':
cout << "Sur" << endl; // <--
break;
case 'O':
cout << "Oeste" << endl;
break;
case '1':
case '2':
case '3':
case '4':
cout << "Un número" << endl;
break;
default:
cout << "Y entonces a donde miras?" << endl;
}
// Bloque equivalente con bloques if
if (brujula == 'N') cout << "Norte" << endl;
else if (brujula == 'E') cout << "Este" << endl;
else if (brujula == 'S') cout << "Sur" << endl;
else if (brujula == 'O') cout << "Oeste" << endl;
else if (brujula == '1' || brujula == '2' || brujula == '3' || brujula == '4') cout << "Un número" << endl;
else cout << "Y entonces a donde miras?" << endl;
try
El bloque try se utiliza para manejar excepciones (o errores) que pensamos que pueden ocurrir, permitiendo que el programa responda en tiempo de ejecución sin interrumpir su flujo. En programación competitiva es probable que nunca utilices este bloque ya que siempre sabes el tipo de dato que va a entrar y no hay factores externos como redes o archivos, pero en desarrollo de software es mucho más común.
Su estructura siempre emieza por try { ... } que contiene todas las instrucciones que podrían causar un error. Lo siguen uno o más catch (exception e) { ... } que contienen un bloque de codigo a ejecutar solo si el bloque try provoca un error, donde exception es un tipo de error como "no es un número" o "número demasiado grande". Por último, un bloque opcional finally { ... } que se ejecuta pase lo que pase. Se muestra un ejemplo de un bloque try catch finally más abajo.
En cuanto se produce un error dentro del bloque try se para la ejecución del bloque y se salta al primer bloque catch que corresponda con el error.
Su sintaxis es:
try {
// código que puede lanzar una excepción, por ejemplo convertir una string a un int
int i = stoi(posibleNum); // ???
} catch (invalid_argument e) {
// código para manejar el caso en el que la string no sea un número, como "hola"
cout << "La string que has intentado convertir no es un número" << endl;
} catch (out_of_range e) {
// código para manejar el caso en el que el número sea muy grande, como 10000000000000000
cout << "El número que has intentado convertir no cabe en 32 bits" << endl;
} catch (exception e) { // Si no es alguno de los errores de encima, no hacer nada
} finally {
// código que se ejecuta siempre, haya o no haya excepción
cout << "Pase lo que pase, me vas a ver" << endl;
}
Anexo
Prioridad de operadores

Operador de incremento y decremento
Quizás en el temario has visto esto: ++i. Esto es la operación de incremento, incrementa la variable por 1:
int a = 4;
++a; // 5 (preincremento)
a++; // 6 (postincremento)
--a; // 5 (predecremento)
a--; // 4 (postdecremento)
¿Y cuál es la diferencia entre pre y post incremento/decremento? En una expresión, el pre incremento/decremento ocurre antes de la evalúación de esa expresión, mientras que el post incremento/decremento ocurre después de la evaluación:
int a, b;
a = 2;
b = 1 + a++ // b = 3, luego a = a+1
a = 2;
b = 1 + ++a // primero a = a+1 (3), luego b = 4