2 PEC2
2.1 Booleanos en C
Algunos puntos que debemos considerar con los booleanos en C:
- Cuando utilizamos el tipo
bool
de C necesitamos importar la librería<stdbool.h>
, ya que el tipobool
no se definió en las primeras versiones del lenguaje C. - Los valores que puede tomar una variable booleana en C son
false
ytrue
. El lenguaje C trata internamente estos valores como enteros:false
equivale a0
ytrue
a1
. - Cuando queramos introducir el valor de un booleano por teclado o bien mostrarlo por pantalla, utilizaremos el entero
0
para referirnos afalse
y1
paratrue
. - El especificador de tipo de los booleanos es
%d
. - Para mostrar el valor de una variable booleana en C lo podemos hacer de la siguiente forma:
bool isVocal;
printf("La letra %c es una vocal (0=false, 1=true) ? %d\n", letra, isVocal);
- Para leer un booleano desde teclado, lo haremos de la siguiente forma:
bool variable;
int intToBool;
scanf("%d", &intToBool);
variable = (bool)intToBool;
El lenguaje C no dispone de ningún especificador de tipo que trabaje solo con 1 bit, por lo que utilizaremos una variable auxiliar que nos ayude a hacer una conversión intermedia a int
, a fin de transformar posteriormente el valor a bool
.
En caso de no leer los booleans de esta manera, puede derivar en dos situaciones distintas, según el compilador de C que se haya usado:
- Provocar que la variable de tipo
bool
no contenga el valor esperado, lo que provocará que nuestro programa se comporte de forma incorrecta. - Generar un warning del siguiente tipo:
warning: formato '% de expects argumento of type' int * ', but argumento 2 has type '_Bool *' [-Wformat =]
. Este aviso significa que estamos utilizando un especificador de tipo (%d
) distinto del que le correspondería al tipobool
, el cual trabaja únicamente con 1 bit.
Por lo tanto, para asegurar el correcto funcionamiento de nuestro programa en cualquier caso, usaremos siempre la variable auxiliar para leer un booleano desde teclado.
2.2 Booleanos definidos como enumerativos
En semestres anteriores de la asignatura de Fundamentos de Programación, se utilizaba un enumerativo para definir el tipo booleano:
typedef enum {FALSE, TRUE} boolean;
Esta forma de definir el tipo booleano es obsoleta y no se utiliza este semestre; tal y como se ha comentado en el apartado Booleanos en C, los booleanos los definiremos mediante la librería <stdbool.h>
. Tenedlo presente cuando consultéis PEC, PR y PS de semestres anteriores, en los que se utilizaba la nomenclatura ahora obsoleta.
2.3 Constantes: define vs const
La definición de constantes se puede hacer tanto con define
como con const
. Sin embargo, la forma de comportarse de estas dos opciones es completamente diferente, aunque el resultado final acabe siendo el mismo:
define
: cuando utilizamos esta opción no se guarda en ninguna posición de memoria el valor de la constante. Lo que se hace realmente es que en los pasos previos a la propia compilación del programa, el preprocesador sustituye todas las referencias deldefine
por el valor indicado.
Por ejemplo, si tenemos el siguiente programa con una constante creada con define
:
/* Ejemplo ES0201 */
#include <stdio.h>
#define MEDIDA 8
char letras[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int vertical, horizontal;
int main(int argc, char **argv) {
/* fuente: https://en.wikipedia.org/wiki/Chess */
for (vertical=MEDIDA; vertical>=1; vertical--) {
for (horizontal=0; horizontal<=MEDIDA-1; horizontal++) {
printf("%c%d ", letras[horizontal], vertical);
}
printf("\n");
}
return 0;
}
Antes de la compilación, el preprocesador, entre otras acciones, elimina comentarios y sustituye todas las referencias MEDIDA
por 8
:
/* Ejemplo ES0202 */
#include <stdio.h>
char letras[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int vertical, horizontal;
int main(int argc, char **argv) {
for (vertical=8; vertical>=1; vertical--) {
for (horizontal=0; horizontal<=8-1; horizontal++) {
printf("%c%d ", letras[horizontal], vertical);
}
printf("\n");
}
return 0;
}
Por lo tanto la definición de constantes con define
se comporta como si se tratara de un “buscar-reemplazar” de un procesador de textos. No se guarda ninguna constante en memoria, pero por el contrario, el programa ocupará un poco más por la sustitución directa de referencias que hace; la sustitución la hace en todo el programa, no se puede limitar a un ámbito concreto (por ejemplo solo dentro de una función).
const
: en este caso sí que se reserva una posición de memoria. En C se comporta igual como si fuera una variable, pero la que únicamente funciona en modo lectura: no le podemos modificar el valor.
Además, const
nos permite también decir qué tipo de valor tendrá la constante: si es de tipo float
, int
, char
… con lo que este hecho nos da un punto adicional de control, ya que nos aseguramos de que el tipo de valor asignado será el correcto para el programa.
Con este tipo de definición de constante, el ejemplo anterior quedaría de la siguiente forma:
/* Ejemplo ES0203 */
#include <stdio.h>
const int MEDIDA = 8;
char letras[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int vertical, horizontal;
int main(int argc, char **argv) {
/* La constante MEDIDA está guardada en memoria */
printf("posición en memoria de la constante MEDIDA : %p \n", &MEDIDA);
for (vertical=MEDIDA; vertical>=1; vertical--) {
for (horizontal=0; horizontal<=MEDIDA-1; horizontal++) {
printf("%c%d ", letras[horizontal], vertical);
}
printf("\n");
}
return 0;
}
Como se puede ver, es posible obtener la dirección en memoria donde se guarda la constante MEDIDA
. En este caso, sí que podéis definir una constante con const
y hacer que solo afecte a un ámbito determinado (por ejemplo, que la constante esté definida únicamente dentro de una función).
Estas son las principales diferencias entre define
y const
a la hora de definir una constante. Durante el curso, será más habitual utilizar define
por encima de const
debido a los problemas que const
ocasiona en determinadas situaciones con vectores (ver el apartado Variable-sized object may not be initialized de las FAQ).
2.4 Cómo mostrar el valor de una constante
En C podemos mostrar por pantalla el valor de una constante definida con #define
mediante printf()
. Por ejemplo:
/* Ejemplo ES0204 */
#include <stdio.h>
#define WORD "world"
#define YEAR 2021
#define EXCLAMATION '!'
int main(int argc, char **argv) {
printf("hello %s and happy %d %c\n", WORD, YEAR, EXCLAMATION);
return 0;
}
El resultado que se mostrará por pantalla será:
hello world and happy 2021 !
2.5 Precisión en variables float
Hay algunos valores decimales determinados que no se pueden representar de forma precisa en una variable de tipo float
. La mejor solución para los casos que tratamos es redondear al número de decimales que realmente necesitamos.
Si en cambio queremos sí o sí trabajar con todos los decimales, podemos optar por utilizar un tipo de dato que tenga mayor precisión que float
: double
.
Por ejemplo, el siguiente programa devuelve el resultado esperado si se guarda en un double
, y no si se hace en un float
:
/* Ejemplo ES0205 */
#include <stdio.h>
int main(int argc, char **argv) {
float num1;
float num2;
float resultado1;
double resultado2;
num1 = 1.3;
num2 = 17;
resultado1 = num1 + num2;
printf("resultado con float : %f\n", resultado1);
resultado2 = num1 + num2;
printf("resultado con double: %f\n", resultado2);
return 0;
}
La salida generada es:
resultado con float : 18.299999
resultado con double: 18.300000
2.6 Semántica de una expresión
Si recordamos lo que se comenta en el punto 3.2. Semántica de una expresión del módulo de la xWiki Tipos básicos de datos, tenemos que conseguir que las expresiones y comparaciones realizadas en los algoritmos sean semánticamente correctas.
Con un ejemplo se verá más claro: tenemos el siguiente algoritmo que indica si una persona es mayor de edad. Fijaos que la expresión realiza una comparación entre dos enteros: la variable edad
y el número 17
.
var
edad: integer;
isMayorEdad: boolean;
end var
algorithm serMayorEdad
writeString("Introduce edad del conductor :");
edad := readInteger();
isMayorEdad := (edad > 17);
writeString("¿El conductor es mayor de edad? :");
writeBoolean(isMayorEdad);
end algorithm
Esta expresión es semánticamente correcta.
En cambio, imaginemos ahora que nuestro algoritmo acepta decimales para la edad
; por ejemplo, 19.5
indicaría que la edad es de 19 años y 6 meses. Así tenemos el siguiente planteamiento, donde ahora la variable edad
es de tipo real
:
var
edad: real;
isMayorEdad: boolean;
end var
algorithm serMayorEdad
writeString("Introduce edad del conductor :");
edad := readReal();
isMayorEdad := (edad > 17);
writeString("¿El conductor es mayor de edad? :");
writeBoolean(isMayorEdad);
end algorithm
El algoritmo ahora no es correcto ya que contiene una expresión semánticamente incorrecta, en la que se compara edad
(real) con17
(entero). Para solucionarlo, podemos utilizar alguna de las funciones de conversión comentadas en el apartado 4. Funciones de conversión de tipos del mismo módulo:
{ opción 1: hacemos que ambos valores sean de tipo entero }
isMayorEdad: = (realToInteger(edad) > 17);
{ opción 2: hacemos que ambos valores sean de tipo real }
isMayorEdad: = (edad > integerToReal(17));
2.7 Ejemplos de expresiones
2.7.1 Ejemplo 1: esPar
Imaginemos que nos piden un algoritmo que indique si un número es par.
Una posible solución sería:
algorithm esPar
var
numero: integer;
isPar: boolean;
end var
writeString("Introduce un número : ");
numero:= readInteger();
isPar:= (numero mod 2 = 0);
writeString("¿El número ");
writeInteger(numero);
writeString(" es par? ");
writeBoolean(isPar);
end algorithm
La variable isPar
tomará el valor true
si el número es par y false
en caso contrario. No ha sido necesario utilizar ninguna estructura if-else
para resolver el algoritmo.
Una posible forma de codificar en lenguaje C es:
/* Ejemplo ES0206 */
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
int numero;
bool isPar;
printf("Introduce un número : ");
scanf("%d", &numero);
isPar = (numero % 2 == 0);
printf("¿El número %d es par? (0=false, 1=true) : %d \n", numero, isPar);
return 0;
}
2.7.2 Ejemplo 2: finDeSemana
Imaginemos que queremos hacer un programa muy sencillo que nos diga si hoy es fin de semana o no. Su algoritmo sería el siguiente:
type
dias = {LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO}
end type
algorithm finDeSemana
var
isFinDeSemana: boolean;
diaSemana: dias;
end var
writeString("¿Qué día de la semana es hoy?");
{ se usa la función ad hoc readDiaSemana() para leer el día }
diaSemana:= readDiaSemana();
isFinDeSemana:= (diaSemana = SABADO or diaSemana = DOMINGO);
writeString("¿Hoy es fin de semana?");
writeBoolean(isFinDeSemana);
end algorithm
La variable boolean
esFinDeSemana
tomará el valor de true
o false
en función del resultado de evaluar la expresión. No es necesario utilizar las estructuras condicionales if-else
que veremos más adelante en el curso.
Una posible forma de codificarlo en lenguaje C es:
/* Ejemplo ES0207 */
#include <stdio.h>
#include <stdbool.h>
typedef enum {LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO} dias;
int main(int argc, char **argv) {
bool isFinDeSemana;
dias diaSemana;
printf("\n¿Qué día de la semana es hoy? \n");
printf("Para LUNES teclea 0\n");
printf("Para MARTES teclea 1\n");
printf("Para MIERCOLES teclea 2\n");
printf("Para JUEVES teclea 3\n");
printf("Para VIERNES teclea 4\n");
printf("Para SABADO teclea 5\n");
printf("Para DOMINGO teclea 6\n");
scanf("%u", &diaSemana);
isFinDeSemana = (diaSemana == SABADO || diaSemana == DOMINGO);
printf("¿Hoy es fin de semana (0 == false, 1 == true)? %d\n", isFinDeSemana);
return 0;
}
Varios puntos a considerar:
- Recordemos que inicialmente en C no existía el tipo booleano. Para poder utilizar
bool
, y los valorestrue
yfalse
necesitamos importar previamente la librería<stdbool.h>
. - El especificador de tipo de un
bool
es%d
. - Cuando definimos una variable de tipo
enum
utilizamos el especificador de tipo%u
. Lo podríamos hacer como%d
, pero devolverá un warning aunque el resultado sea correcto. El tipo%u
es igual que un entero%d
pero sin signo: esto significa que con%d
podemos tratar valores negativos como -12 y con%u
no es posible, pero como sabemos que los valores que puede tomar unenum
siempre serán >= 0 nos conviene utilizar%u
. - Para las particularidades de los
bool
en el lenguaje de programación C que ya hemos comentado anteriormente, la entrada y salida de valores de unboolean
será numérica. Para facilitar la comprensión podemos mostrar por pantalla un literal que nos indique que0
equivale afalse
y1
atrue
.
2.7.3 Ejemplo 3: esVocal
Queremos hacer un programa que, entrado un carácter por el canal de entrada, nos indique si se trata o no de una vocal.
Una posible solución para el algoritmo es la siguiente:
var
letra: char;
isVocal: boolean;
end var
algorithm esVocal
writeString("Teclea una letra: ");
letra := readChar();
{ En este ejemplo únicamente tratamos las vocales minúsculas }
isVocal := letra = 'a' or letra = 'e' or
letra = 'i' or letra = 'o' or letra = 'u';
writeString("¿La letra ");
writeChar(letra);
writeString(" es una vocal? ");
writeBoolean(isVocal);
end algorithm
Como se puede ver, el planteamiento del algoritmo es:
- Leemos un carácter desde el canal de entrada.
- Comparamos el carácter con
a
,e
,i
,o
,u
.- Si coincide con alguna de estas vocales, la variable
isVocal: = true
. - Si no coincide con ninguna de las vocales, la variable
isVocal: = false
.
- Si coincide con alguna de estas vocales, la variable
- Se muestra el resultado por pantalla.
¿Cómo lo podemos traducir en lenguaje C? Una posible opción es:
/* Ejemplo ES0208 */
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
char letra;
bool isVocal;
printf("Introduce una letra: ");
scanf("%c", &letra);
/* En este ejemplo únicamente tratamos las vocales minúsculas */
isVocal = letra == 'a' || letra == 'e' ||
letra == 'i' || letra == 'o' || letra == 'u';
printf("¿La letra %c es una vocal (0=false, 1=true)? %d\n", letra, isVocal);
return 0;
}
2.7.4 Ejemplo 4: votaciones
Imaginemos que nos piden un programa que valide si una persona puede ir a votar o no; la condición que nos dicen que hay que cumplir es que la persona sea mayor de edad y además esté en el censo electoral de la localidad donde está votando.
El algoritmo podría ser el siguiente:
algorithm votaciones
var
isMayorEdad: boolean;
isCensado: boolean;
isVotante: boolean;
end var
writeString("¿Eres mayor de edad? ");
isMayorEdad := readBoolean();
writeString("¿Estás en el censo electoral? ");
isCensado := readBoolean();
{ Expresión }
isVotante := isMayorEdad and isCensado;
writeString("Puedes votar: ");
writeBoolean(isVotante);
end algorithm
Una posible codificación en C sería:
/* Ejemplo ES0209 */
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
bool isMayorEdad;
bool isCensado;
bool isVotante;
int intToBool;
printf("¿Eres mayor de edad (0=false, 1=true)? ");
scanf("%d", &intToBool);
isMayorEdad = (bool)intToBool;
printf("¿Estás en el censo electoral (0=false, 1=true)? ");
scanf("%d", &intToBool);
isCensado = (bool)intToBool;
/* Expresión */
isVotante = isMayorEdad && isCensado;
printf("Puedes votar (0=false, 1=true): %d\n", isVotante);
return 0;
}
La ejecución de este ejemplo sería:
¿Eres mayor de edad (0=false, 1=true)? 1
¿Estás en el censo electoral (0=false, 1=true)? 0
Puedes votar (0=false, 1=true): 0
2.7.5 Ejemplo 5: ginTonicPreparation
Imaginemos que queremos preparar un gin-tonic. Sabemos el volumen que usaremos de ginebra y de tónica, y cuál es la capacidad de la copa de balón que lo contendrá.
Hemos visto una oferta por internet y hemos comprado cubitos metálicos de acero inoxidable… pero se nos ha ido un poco la cabeza y hemos comprado un total de 20 unidades.
Queremos hacer un programa que, utilizando únicamente expresiones, nos diga si podemos preparar o no el gin-tonic en función del número de cubitos que le queremos poner:
- Si el número de cubitos caben dentro de la copa, devolverá
true
. - En caso contrario, devolverá
false
.
Por tanto lo que tiene que hacer nuestro programa básicamente es validar si el volumen de ginebra + tónica + (hielo) * número de cubitos supera o no el volumen de la copa.
El algoritmo podría ser el siguiente:
const
GIN: real = 50.0; { in ml }
TONIC: real = 200.0; { in ml }
GLASS: real = 620.0; { in ml }
METAL_ICE_CUBE: real = 42.875; { in ml }
end const
algorithm ginTonicPreparation
var
numMetalIceCubes: integer;
isPossible: boolean;
end var
writeString("Number of metal ice cubes ? (integer) : ");
numMetalIceCubes:= readInteger();
isPossible:= (GLASS ≥
(GIN + TONIC + METAL_ICE_CUBE * integerToReal(numMetalIceCubes)));
writeString("Can you make a gin & tonic? : ");
writeBoolean(isPossible);
end algorithm
La expresión que da valor a isPossible
se encarga de evaluar el volumen de la copa respecto al resultante de ginebra, tónica y cubitos.
Su traducción a C podría ser:
/* Ejemplo ES0210 */
#include <stdio.h>
#include <stdbool.h>
#define GIN 50.0 /* in ml */
#define TONIC 200.0 /* in ml */
#define GLASS 620.0 /* in ml */
#define METAL_ICE_CUBE 42.875 /* in ml */
int main(int argc, char **argv) {
int numMetalIceCubes;
bool isPossible;
printf("Number of metal ice cubes ? (integer) : ");
scanf("%d", &numMetalIceCubes);
isPossible = GLASS >= (GIN + TONIC + METAL_ICE_CUBE * numMetalIceCubes);
printf("Can you make a gin & tonic? (0=false, 1=true) : %d\n", isPossible);
return 0;
}
Para realizar el cálculo en C también se ha utilizado una expresión.
2.7.6 Ejemplo 6: ginTonicFreeMl
Vamos a evolucionar el ejemplo anterior del gin-tonic: imaginemos ahora que queremos que nuestro programa nos diga el volumen (en mililitros) que queda libre en la copa una vez puesto un determinado número de cubitos.
El algoritmo quedaría de la siguiente forma:
const
GIN: real = 50.0; { in ml }
TONIC: real = 200.0; { in ml }
GLASS: real = 620.0; { in ml }
METAL_ICE_CUBE: real = 42.875; { in ml }
end const
algorithm ginTonicFreeMl
var
numMetalIceCubes: integer;
volumeFree: real;
end var
writeString("Number of metal ice cubes ? (integer) : ");
numMetalIceCubes:= readInteger();
volumeFree:= GLASS -
(GIN + TONIC + METAL_ICE_CUBE * integerToReal(numMetalIceCubes));
writeString("How many free ml in the glass? : ");
writeReal(volumeFree);
end algorithm
Y la codificación en C :
/* Ejemplo ES0211 */
#include <stdio.h>
#define GIN 50.0 /* in ml */
#define TONIC 200.0 /* in ml */
#define GLASS 620.0 /* in ml */
#define METAL_ICE_CUBE 42.875 /* in ml */
int main(int argc, char **argv) {
int numMetalIceCubes;
float volumeFree;
printf("Number of metal ice cubes ? (integer) : ");
scanf("%d", &numMetalIceCubes);
volumeFree = GLASS - (GIN + TONIC + METAL_ICE_CUBE * numMetalIceCubes);
printf("How many free ml in the glass ? : %.3f ml \n", volumeFree);
return 0;
}
2.7.7 Ejemplo 7: scoutingBasquet
Imaginemos que hacemos tareas de scouting para las secciones de baloncesto femenino y masculino de nuestro club, y nos han encargado cubrir alguna de las tres plazas siguientes:
- Para el equipo femenino: una pívot que como mínimo mida 195 cm de altura.
- Para el equipo femenino: una base, cuya altura sea inferior a 170 cm.
- Para el equipo masculino: un base que sea más alto de 175 cm pero a la vez que no supere los 190 cm.
Nuestro programa pedirá por teclado si se trata de una jugadora o un jugador, y cuál es su altura. A continuación, con expresiones, evaluará las condiciones introducidas y si las cumple para alguna de las tres plazas disponibles, lo escogerá (isDrafted
).
Una posible forma de codificar en C este programa sería:
/* Ejemplo ES0212 */
#include <stdio.h>
#include <stdbool.h>
typedef enum {MALE, FEMALE} tGender;
int main(int argc, char **argv) {
bool isPointGuard; /* Point Guard = base */
bool isCenter; /* Center = pívot */
bool isDrafted;
int height;
tGender gender;
printf("Gender (0=MALE, 1=FEMALE) : ");
scanf("%u", &gender);
printf("Heigth (integer value) : ");
scanf("%d", &height);
/* Primero miramos si podemos cubrir la posición de base,
ya sea femenino o masculino */
isPointGuard =
(height < 170 && (gender == FEMALE)) ||
(height < 190 && height > 175 && (gender == MALE));
/* A continuación comprobamos si se trata de la pívot femenina que buscamos */
isCenter = (height >= 195 && (gender == FEMALE));
/* Únicamente si se cumple alguna de las dos expresiones anteriores
(que isPointGuard sea true o que isCenter sea true), el jugador/a
será elegido/a para formar parte de nuestras secciones de baloncesto */
isDrafted = isPointGuard || isCenter;
printf("\nIs drafted (0=false, 1=true) ? : ");
printf("%d\n", isDrafted);
return 0;
}
Fijaos en que isPointGuard
yisCenter
son variables de tipo bool
, ya que la evaluación de las expresiones también será de tipo bool
.
Puede haber otras codificaciones igual de válidas, esta no es la única solución posible.
2.8 Errores más frecuentes
2.8.1 Interfaz de usuario: ausencia de textos informativos
El siguiente no es un error sintáctico o semántico, sino un error de diseño muy frecuente.
Pseudocódigo incorrecto:
writeInteger(room);
writeInteger(totalPrice);
En el ejemplo mostrado, parece que la intención es mostrar en el canal estándar dos variables (room
y totalPrice
), y efectivamente las sentencias para imprimirlas son correctas. El problema es que el usuario no tiene ninguna información sobre el significado de los valores que se le muestran. Hay que complementar siempre los datos mostrados con mensajes informativos, indicando también las unidades cuando sea necesario.
Pseudocódigo correcto:
writeString("Room number: ");
writeInteger(room);
writeString("Total price [€]: ");
writeInteger(totalPrice);
Este error de diseño es aún más grave en el caso (también real) de no poner ningún texto informativo a la hora de pedir al usuario que introduzca datos por el teclado.
Pseudocódigo incorrecto:
readInteger(room);
readReal(totalPrice);
El usuario no tiene ninguna información sobre los valores que debe introducir (tipos de datos, intervalos válidos, etc.).
Pseucódigo correcto:
writeString("Enter room number [1-100]: ");
readInteger(room);
writeString("Enter total price [€]: ");
readInteger(totalPrice);
2.8.2 Caracteres: uso de comillas simples
Pseudocódigo incorrecto:
var
fastpass: char;
areaMap: char;
allowsFastPass: boolean;
end var
{...}
fastPass := (allowsFastPass = y or allowsFastPass = Y) and
(areaMap = B or areaMap = C);
{...}
En el algoritmo anterior, se quiere comparar la variable allowsFastPass
con los caracteres y
, Y
, y la variable areaMap
con los caracteres B
, C
. El problema aparece cuando no se usan las comillas simples para indicar que se trata de caracteres, y por lo tanto lo que hace la expresión es comparar con las variables y
, Y
, B
y C
respectivamente (que además en este algoritmo no existen). Este es un error semántico grave, que puede no provocar ningún error de compilación en lenguaje C, y en cambio puede conllevar comportamientos inesperados.
Pseudocódigo correcto:
var
fastpass: char;
areaMap: char;
allowsFastPass: boolean;
end var
{...}
fastPass := (allowsFastPass = 'y' or allowsFastPass = 'Y') and
(areaMap = 'B' or areaMap = 'C');
{...}
2.8.3 Sintaxis propia de C: funciones de lectura / escritura
Pseudocódigo incorrecto:
writeString("Code value: %d\n", codeValue);
Se ha intentado aplicar a la función writeString()
en lenguaje algorítmico la sintaxis propia de la función printf()
de C, lo cual evidentemente no es correcto. Recordad que el lenguaje algorítmico es un lenguaje de diseño de propósito general, que debe permitir posteriormente la codificación en cualquier lenguaje de programación.
Pseudocódigo correcto:
writeString("Code value");
writeInteger(codeValue);
2.8.4 Conversión de tipos: funciones inexistentes
Pseudocódigo incorrecto:
b1:= characterToCode(myChar);
Las funciones de conversión de tipo están definidas en el Nomenclátor de la asignatura, y sirven para asegurar la coherencia semántica entre los tipos de variables de una expresión. En este caso, la función characterToCode()
no existe, ya que la que hemos declarado es characterToInteger()
. Es habitual encontrar errores de este tipo, con diversas variantes de nombres de función. Hay evitarlo y usar el Nomenclátor como guía.
Pseudocódigo correcto:
b1:= characterToInteger(myChar);
2.8.5 Expresiones: variables intermedias
Este no es un error sintáctico o semántico, sino una mala práctica de diseño.
Pseudocódigo incorrecto:
var
bool1: boolean;
bool2: boolean;
bool3: boolean;
resultBool: boolean;
end var
bool1 := hasGym or hasPool;
bool2 := closeToSubway or distanceFromCityCentre < 5;
bool3 := priceDouble ≤ 100;
resultBool := bool1 and bool2 and bool3;
En el código anterior, lo que queremos obtener al final es una variable de tipo booleano que nos indique si un objeto (suponemos que un hotel), tiene piscina o gimnasio, está cerca del metro o a una distancia menor de 5 km del centro de la ciudad, y el precio es inferior a 100 (euros). Queremos reunir toda esta información en una variable booleana, porque seguramente esto nos indicará alguna cualidad del objeto (por ejemplo, que es un buen hotel). Ahora bien, fijaos en cómo para llegar al resultado final, se han declarado hasta tres variables auxiliares, que no tienen otra finalidad que construir la expresión final.
Declarar variables auxiliares o temporales no es una mala práctica siempre que se haga con moderación, y aplicando el sentido común (algo que solo se adquiere con la práctica del diseño y la programación). Recordemos que las variables ocupan espacio de memoria, y por lo tanto hay que ser cuidadosos a la hora de declararlas. Una alternativa sería la que presentamos a continuación.
Pseudocódigo correcto:
var
boolFinal: boolean;
end var
resultBool := hasGym or hasPool and
closeToSubway or distanceFromCityCentre < 5 and
priceDouble ≤ 100;
Recordemos que el delimitador de las sentencias es el punto y coma ;
, y que hay flexibilidad a la hora de escribir las sentencias en varias líneas (también en C). Finalmente, hay que recordar una vez más que no hay ninguna norma que nos indique cuántas variables auxiliares debemos utilizar. En la solución alternativa hemos optado por no utilizar ninguna y dar un formato comprensible a la expresión, pero esto habrá que decidirlo en cada caso, a partir de la práctica y la experiencia.
2.8.6 Declaración de variables: declaración mal ubicada (bloque central de código)
Pseudocódigo incorrecto:
for i:= 1 to selectedHotels->nHotels do
if selectedHotels[i].city = city then
var
scorePoints: integer;
end var
scorePoints := scorePoints + 1;
end if
end for
Las variables deben declararse al principio del bloque de pseudocódigo, ya sea una función, una acción o un algoritmo directamente. No es correcto abrir un bloque de declaración de variables dentro de un bloque central de pseudocódigo (y menos abrir varios bloques de declaración a medida que necesitamos variables). Lo mismo ocurre en C: las variables deben declararse siempre en el bloque inicial de código (ya sea una función, acción o algoritmo).
Pseudocódigo correcto:
var
scorePoints: integer;
end var
for i:= 1 to selectedHotels->nHotels do
if selectedHotels[i].city = city then
scorePoints := scorePoints + 1;
end if
end for
2.8.7 Declaración de variables: declaración mal ubicada (variables globales)
Este no es un error sintáctico o semántico, sino una mala práctica de programación que hay que evitar.
Código incorrecto:
#include <stdio.h>
#include <stdbool.h>
float maxPrice;
int bestHotel;
int main(int argc, char **argv) {
/* ... */
return 0;
}
Las variables se declararán siempre dentro de una función o acción (ya sea el main
o cualquier otra), y al principio de su bloque de código. No es correcto declarar variables fuera de cualquier función, ya que se convierten en variables globales, y su uso no se considera una buena práctica de programación. El motivo es que las variables globales dificultan la lectura y la comprensión del código, y pueden provocar errores de ejecución inesperados o actualizaciones involuntarias, al no estar protegidas dentro de funciones o acciones.
Código correcto:
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
float maxPrice;
int bestHotel;
/* ... */
return 0;
}
2.8.8 Tipo booleano: valores numéricos
Código incorrecto:
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
bool myBool1;
bool myBool2;
bool resultBool;
/* ... */
resultBool = (myBool1 == 1) && (myBool2 == 0);
}
Si disponemos de la librería <stdbool.h>
para el tratamiento de booleanos en C, no tiene ningún sentido que utilicemos sus equivalentes numéricos en el código, especialmente en el caso de las expresiones. En su lugar se deben utilizar las palabras reservadas true
y false
.
Como regla general, debemos procurar usar el mínimo número posible de valores numéricos, y utilizar en su lugar variables y constantes.
Código correcto:
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
bool myBool1;
bool myBool2;
bool resultBool;
/* ... */
resultBool = myBool1 && !myBool2;
return 0;
}
2.8.9 Tipo booleano: cadenas de caracteres
Código incorrecto:
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
bool myBool1;
bool myBool2;
bool resultBool;
/* ... */
resultBool = (myBool1 =="true") && (myBool2 == "false");
return 0;
}
Si disponemos de la librería <stdbool.h>
para el tratamiento de booleanos en C, podemos utilizar las palabras reservadas true
y false
, sin ningún tipo de modificador ni carácter. Si añadimos las comillas, "true"
y "false"
se convierten en cadenas de caracteres, que son algo muy diferente y no tienen nada que ver con los booleanos (ni con sus posibles valores). Este error muy grave también se da en lenguaje algorítmico.
Hay que añadir, además, que en C cualquier valor que no sea 0 es interpretado como true
, por lo que se producirán errores de ejecución, ya que las cadenas de caracteres serán interpretadas siempre como un valor cierto.
Código correcto:
#include <stdio.h>
#include <stdbool.h>
int main(int argc, char **argv) {
bool myBool1;
bool myBool2;
bool resultBool;
/* ... */
resultBool = myBool1 && !myBool2;
return 0;
}
2.8.10 Sintaxis propia de C: operadores
Pseudocódigo incorrecto:
if (acceptable == 1) or (acceptable == 2) then
writeString("Hotels cannot be compared");
end if
El operador ==
es propio del lenguaje C, y su uso en lenguaje algorítmico no es correcto. El operador correcto en este caso es =
.
Pseudocódigo correcto:
if (acceptable) = 1 or (acceptable = 2) then
writeString("Hotels cannot be compared");
end if
2.8.11 Semántica de las expresiones
Este tema se trata en el apartado Semántica de una expresión. A continuación, reproducimos uno de los errores habituales.
Pseudocódigo incorrecto:
const
NUM_CASH_DESKS: integer = 4;
end const
var
cashDesksTotalIncome: real;
cashDesksAverageIncome: real;
end var
cashDesksAverageIncome := cashDesksTotalIncome / NUM_CASH_DESKS;
La semántica de una expresión hace referencia a la coherencia entre los tipos de datos que se usan. En este sentido, cuando queremos construir expresiones debemos asegurarnos que las operaciones se realicen siempre entre variables con el mismo tipo de datos.
En caso de que tengamos variables de diferentes tipos, hay que utilizar las funciones de conversión de tipo disponibles. Si no se hace de esta manera, se pueden producir resultados inesperados debidos a redondeos de datos, pérdida de precisión o conversiones implícitas incorrectas.
En el ejemplo anterior, se quiere calcular la media de ingresos de un número determinado de cajas de un supermercado, pero se mezclan de forma incorrecta variables y constantes de tipo entero y real.
Pseudocódigo correcto:
const
NUM_CASH_DESKS: integer = 4;
end const
var
cashDesksTotalIncome: real;
cashDesksAverageIncome: real;
end var
cashDesksAverageIncome := cashDesksTotalIncome / integerToReal(NUM_CASH_DESKS);
2.8.12 Tipo booleano: operaciones con otros tipos
Pseudocódigo incorrecto:
const
POINTS_DELIVERY: integer = 4;
end const
var
hasDelivery: boolean;
pointsDelivery: integer;
end var
pointsDelivery := 0;
pointsDelivery := POINTS_DELIVERY * hasDelivery;
El algoritmo anterior intenta actualizar la variable pointsDelivery
si se cumple la condición establecida por la variable booleana hasDelivery
. De esta forma, la variable tomaría el valor POINTS_DELIVERY
en caso de que hasDelivery
fuera true
, y cero en caso contrario. Esta operación no es semánticamente correcta, ya que se están mezclando variables y operaciones lógicas con aritméticas.
La semántica de una expresión hace referencia a la coherencia entre los tipos de datos que se usan. En este sentido, cuando queremos construir expresiones debemos asegurar que las operaciones se realicen siempre entre variables con el mismo tipo de datos. En este caso, tampoco sería posible una conversión de tipo, ya que los booleanos tienen un valor lógico (true
, false
), que en lenguaje algorítmico no tiene ninguna equivalencia numérica. En su lugar, tendremos que montar una estructura alternativa.
Pseudocódigo correcto:
const
POINTS_DELIVERY: integer = 4;
end const
var
hasDelivery: boolean;
pointsDelivery: integer;
end var
pointsDelivery := 0;
if hasDelivery then
pointsDelivery := POINTS_DELIVERY;
end if
En el caso del lenguaje C, los booleanos sí tienen una equivalencia numérica, pero el algoritmo original tampoco se consideraría correcto a pesar de que el programa compilase y funcionara correctamente. Como se ha mencionado, hay que respetar siempre la semántica de expresiones y datos.
2.8.13 Inicialización de variables mal ubicada
Este no es un error sintáctico o semántico, sino una mala práctica de diseño y programación muy frecuente.
Pseudocódigo incorrecto:
const
POINTS_DELIVERY: integer = 4;
end const
var
hasDelivery: boolean;
pointsDelivery: integer = POINTS_DELIVERY;
end var
{ ... }
if hasDelivery then
pointsDelivery := POINTS_DELIVERY + 1;
end if
{ ... }
En el algoritmo anterior, la variable entera pointsDelivery
se inicializa en el mismo momento de la declaración, tomando el valor POINTS_DELIVERY
, hecho que no está permitido en la codificación algorítmica y que se desaconseja en el lenguaje C, ya que dificulta el seguimiento del valor de las variables a lo largo del código. Como norma general, las variables deben inicializarse antes de usarlas. De este modo, nos aseguramos que tienen un valor por defecto antes de evaluadas en cualquier expresión, y es más fácil identificar dicho valor y los siguientes que pueda tener.
Pseudocódigo correcto:
const
POINTS_DELIVERY: integer = 4;
end const
var
hasDelivery: boolean;
pointsDelivery: integer;
end var
{ ... }
pointsDelivery := POINTS_DELIVERY;
if hasDelivery then
pointsDelivery := POINTS_DELIVERY + 1;
end if
{ ... }
2.8.14 Uso incorrecto del operador lógico not
Pseudocódigo incorrecto:
var
canDrive: boolean;
isDrunk: boolean;
isDrugged: boolean;
end var
{ ... }
canDrive := not isDrunk and isDrugged;
{ ... }
En el algorimo anterior, el operador not
no afecta a la totalidad de la expresión isDrunk and isDrugged
, ya que únicamente se aplica sobre la variable isDrunk
. Por lo tanto, la expresión es incorrecta, ya que será true en el caso que no haya bebido pero sí haya consumido otras drogas.
Para solucionarlo, aplicamos not
a las dos variables booleanas.
Pseudocógido correcto:
var
canDrive: boolean;
isDrunk: boolean;
isDrugged: boolean;
end var
{ ... }
canDrive := not isDrunk and not isDrugged;
{ ... }
Una expresión equivalente e igualmente correcta sería la siguiente:
var
canDrive: boolean;
isDrunk: boolean;
isDrugged: boolean;
end var
{ ... }
canDrive := not (isDrunk or isDrugged);
{ ... }
En este último algoritmo los paréntesis permiten que el operador not
se aplique sobre el conjunto isDrunk or isDrugged
.