1 PEC1
1.1 Lenguaje algorítmico
El lenguaje algorítmico debemos entenderlo como una aproximación al mundo real, el cual utiliza unas normas definidas por nosotros mismos. En este punto todavía no hablamos de programas escritos en C, en Java, en Python o en PHP, por decir algunos lenguajes de programación.
Por ejemplo, en el lenguaje algorítmico que utilizamos en la asignatura definimos un bloque de variables de la siguiente forma:
var
edad: integer;
peso: real;
end var
Que se trate de un lenguaje más cercano al mundo real no significa que no tenga que cumplir unas determinadas reglas. Como se puede ver en este ejemplo, una de estas reglas es que cuando definimos variables lo comenzamos con var
y lo finalizamos conend var
.
Hemos decidido utilizar esta forma de lenguaje algorítmico, aunque también lo podríamos haber planteado de la siguiente forma:
variable
enter edad
decimal peso
fvariable
Cabe remarcar que este segundo ejemplo es incorrecto, no sigue la nomenclatura del lenguaje algorítmico definido en la asignatura. El correcto es el primer ejemplo.
El lenguaje algorítmico es como hacer una aproximación formal a la realidad, no es un lenguaje de programación en sí como es C, Java o Python. Por lo tanto no es un lenguaje que se pueda compilar y ejecutar con el Integrated Development Environment (IDE) utilizado en la asignatura, el cual está preparado únicamente para interpretar y ejecutar código programado en lenguaje C.
En este punto viene la gran pregunta: ¿y por qué es necesario primero diseñar el algoritmo, si puedo directamente programarlo en C?
Un algoritmo nos permite diseñar un programa sin considerar las particularidades de cada lenguaje de programación. Esta aproximación formal a la realidad de los algoritmos nos facilitan poder hacer posteriormente una traducción rápida a cualquier lenguaje de programación simplemente conociendo las equivalencias correspondientes. Por ejemplo, el primer caso si lo programamos en C equivale a:
int edad;
float peso;
El código en C no lo podemos cambiar, ya que si en vez de poner int
utilizamos entero
, el compilador de C no comprende la palabra y nos dará un error de código.
Si nunca hemos programado es normal que este planteamiento sorprenda al principio, pero es importante que poco a poco se vaya viendo las diferencias entre lenguaje algorítmico y lenguaje C.
1.2 Lenguaje algorítmico vs lenguaje C
En general:
- Lenguaje algorítmico: cercano al lenguaje natural, se trata de una convención que adoptamos nosotros mismos para definir un programa formalmente. Los algoritmos tienen una serie de normas y sentencias que nosotros definimos (Nomenclátor), pero que no son de ninguna forma interpretables por un ordenador. Por lo tanto un algoritmo no puede ser compilado ni ejecutado.
- Lenguaje C: se trata de un lenguaje de programación que sí comprende un ordenador. Esto significa que únicamente podemos utilizar sus comandos y sus normas para que el código pueda ser compilado y ejecutado sin problemas.
El lenguaje algorítmico es un pseudocódigo que nos ayuda a definir cómo funciona un programa. No está ligado a ningún lenguaje de programación, con lo que las acciones que realizará, la forma de definir variables, etc., es genérica. Funciones como writeString()
, readInteger()
o writeChar()
forman parte del lenguaje algorítmico: indican una acción genérica a realizar, como es escribir una cadena de caracteres, leer un entero o escribir un carácter. Cuando se quiera codificar este algoritmo en un lenguaje de programación concreto como es C, solo será necesario saber los comandos propios de C que nos permiten implementar el algoritmo.
La programación en C funciona exclusivamente con la sintaxis definida por este lenguaje de programación. Instrucciones como scanf()
y print ()
son propias de C.
A modo de ejemplo:
Algoritmo: queremos introducir la lectura de la luz de nuestra casa; una posible implementación es:
algorithm lecturaLuz
var
lecturaMensual: integer;
end var
writeString("Introduce la lectura mensual de la luz (kWh): ");
lecturaMensual := readInteger();
end algorithm
Lenguaje C: en este lenguaje no existen las funciones algorítmicas writeString()
ni readInteger()
, pero en cambio sí tenemos varias funciones propias de C que nos permiten leer un valor por teclado y asignarlo a una variable de entorno. Por tanto, las acciones algorítmicas anteriores tendrán la siguiente correspondencia en C:
/* Ejemplo ES0101 */
#include <stdio.h>
int main(int argc, char **argv) {
int lecturaMensual;
printf("Introduce la lectura mensual de la luz (kWh): ");
scanf("%d", &lecturaMensual);
return 0;
}
Es muy importante que se vea claramente qué es un algoritmo y que es un programa en C.
1.3 Equivalencias entre lenguaje algorítmico y lenguaje C
A continuación se indican algunas de las equivalencias existentes entre lenguaje algorítmico y el lenguaje de programación C:
Lenguaje algorítmico | Lenguaje C | |
---|---|---|
¿Sigue unas normas? | sí | sí |
¿Se puede compilar? | no | sí |
¿Se puede ejecutar? | no | sí |
Asignación de valores a variables | := |
= |
Tipo booleano | boolean |
bool |
Tipo entero | integer |
int |
Tipo decimal | real |
float |
Tipo carácter | char |
char |
Operador igual | = |
== |
Operador diferente | ≠ |
! = |
Operador mayor | > |
> |
Operador mayor o igual | ≥ |
> = |
Operador menor | < |
< |
Operador menor o igual | ≤ |
<= |
Operador lógico de conjunción | and |
&& |
Operador lógico de disyunción | or |
|| |
Operador lógico de negación | not |
! |
1.4 Impresión de valores incorrecta
Cuando se muestra por pantalla el contenido de alguna variable con printf()
es importante eliminar el prefijo &
de la variable. Por ejemplo, si no lo hacemos tenemos que:
/* Ejemplo ES0102 */
#include <stdio.h>
int main(int argc, char **argv) {
int idAvion;
printf("Introduce el identificador del avión : ");
scanf("%d", &idAvion);
printf(">> Has elegido el avión con id %d \n", &idAvion);
return 0;
}
El resultado de la ejecución es:
Introduce el identificador del avión : 9
>> Has elegido el avión con id -1078693464
¿Por qué obtenemos el valor extraño en el identificador de avión? Cuando hacemos referencia a &idAvion
estamos obteniendo realmente la posición de memoria donde reside la variable idAvion
, no el valor de la variable. Para obtener su valor es necesario eliminar dentro de printf()
el prefijo &
de la variable idAvion
:
/* Ejemplo ES0103 */
#include <stdio.h>
int main(int argc, char **argv) {
int idAvion;
printf("Introduce el identificador del avión : ");
scanf("%d", &idAvion);
printf(">> Has elegido el avión con id %d \n", idAvion);
return 0;
}
La salida generada ahora sí es correcta:
Introduce el identificador del avión : 9
>> Has elegido el avión con id 9
1.5 Los enumerativos en codificación algorítmica
La definición de un tipo enumerativo en lenguaje algorítmico se hace de la siguiente forma:
type
typeName = {ELEMENT1, ELEMENT2, ELEMENT3, ... , ELEMENTn}
end type
Los elementos ELEMENT1
, ELEMENT2
, etc. los tenemos que ver como si de constantes se trataran, que podremos usar para evaluar expresiones y/o realizar comparaciones.
1.6 Los enumerativos en lenguaje C
Una enumeración es una asignación de un valor entero a la serie de elementos que se ha definido, empezando por 0 e incrementándose en 1 en cada elemento.
Por ejemplo, podemos tener la siguiente definición:
typedef enum {MALE, FEMALE} tGender;
Esto significa que MALE=0
y FEMALE=1
. Si el orden de la definición se hubiera hecho al revés, {FEMALE, MALE}
, tendríamos que FEMALE=0
y MALE=1
.
Una posible forma de utilizar los enumerativos es leer un entero y compararlo con el elemento correspondiente definido dentro del enum
, para realizar una acción u otra. Una posible implementación en lenguaje C sería:
/* Ejemplo ES0104 */
#include <stdio.h>
typedef enum {MALE, FEMALE} tGender;
int main(int argc, char **argv) {
tGender gender;
printf("Type patient gender: 0 for MALE, 1 for FEMALE\n");
scanf("%u", &gender);
if (gender == MALE) {
printf("Patient gender MALE\n");
} else {
if (gender == FEMALE) {
printf("Patient gender FEMALE\n");
} else {
printf("Incorrect option\n");
}
}
return 0;
}
Es posible modificar la asignación de valores que por defecto se aplica a los elementos de un enumerativo.
typedef enum {MALE, FEMALE} tGender;
/* Valores asociados al enumerativo tGender:
* MALE = 0
* FEMALE = 1
* Es el comportamiento que por defecto tiene un enumerativo.
*/
typedef enum {LUN=1, MAR, MIE, JUE, VIE, SAB, DOM} tDias;
/* Valores asociados al enumerativo tDias:
* LUN = 1, valor 1 asignado manualmente; el resto
* de elementos incrementarán en +1
* su valor respecto al elemento anterior:
* MAR = 2
* MIE = 3
* JUE = 4
* VIE = 5
* SAB = 6
* DOM = 7
*/
typedef enum {MOTO = 2, COCHE = 4, FURGONETA, CAMION} tCategoria;
/* Valores asociados al enumerativo tCategoria:
* MOTO = 2, valor 2 asignado manualmente.
* COCHE = 4, valor 4 asignado manualmente. Los elementos posteriores
* del enumerativo irán tomando los valores 5, 6, etc.:
* FURGONETA = 5
* CAMION = 6
*/
1.7 Especificador de un enumerativo
Los enumerativos en lenguaje C, enum
, utilizan el especificador %u
.
Ejemplo:
/* Ejemplo ES0105 */
#include <stdio.h>
typedef enum {PRIVAT, PUBLIC} tTransporte;
int main(int argc, char **argv) {
tTransporte tipoTransporte;
printf("¿Con qué tipo de transporte vas al trabajo (0 = privado, 1 = público)?: ");
scanf("%u", &tipoTransporte);
printf("Vas al trabajo en transporte (0 = privado, 1 = público): ");
printf("%u\n", tipoTransporte);
return 0;
}
1.8 Lectura de caracteres en C
En el lenguaje C la lectura de un char
puede comportarse de forma inadecuada si previamente el buffer de entrada contiene algún carácter previo.
Imaginemos que queremos crear un programa muy sencillo que dado un número de DNI y su letra, nos concatene los dos valores y lo muestre por pantalla. Una posible forma de implementar este programa en C sería:
/* Ejemplo ES0106 */
#include <stdio.h>
int main(int argc, char **argv) {
int dniNum; /* número del DNI */
char dniChar; /* letra del DNI */
printf("Introduce el número del DNI: ");
scanf("%d", &dniNum);
printf("Introduce la letra del DNI: ");
scanf("%c", &dniChar);
printf("\nEl DNI introducido es: %d-%c\n", dniNum, dniChar);
return 0;
}
¿Qué pasa si ejecutamos este código? Que vemos que se comporta de forma incorrecta, ya que no nos llega a pedir la letra del DNI, mostrando directamente el resultado:
Introduce el número del DNI: 12345678
Introduce la letra del DNI:
El DNI introducido es: 12345678-
Cuando tecleamos el primer entero lo que hacemos realmente es introducir un número + un intro
al final de todo. El número queda asignado a la variable dniNum
, y elintro
es leído como un carácter y se asigna a la variable dniChar
. Por este motivo C interpreta que las dos variables ya tienen valor y finaliza el programa.
¿Cómo podemos solucionar este comportamiento? Vaciando el intro
del buffer de entrada antes de leer el carácter, y una posible forma de hacerlo es mediante el comando getchar()
. Este comando lee un carácter del buffer de entrada y lo vacía del buffer.
Por lo tanto se puede corregir el programa anterior de la siguiente forma:
/* Ejemplo ES0107 */
#include <stdio.h>
int main(int argc, char **argv) {
int dniNum; /* número del DNI */
char dniChar; /* letra del DNI */
printf("Introduce el número del DNI: ");
scanf("%d", &dniNum);
getchar();
printf("Introduce la letra del DNI: ");
scanf("%c", &dniChar);
printf("\nEl DNI introducido es: %d-%c\n", dniNum, dniChar);
return 0;
}
Si ahora ejecutamos ya funcionará como deseamos:
Introduce el número del DNI: 12345678
Introduce la letra del DNI: B
El DNI introducido es: 12345678-B
En caso de necesidad, con getchar()
se puede guardar el carácter del buffer en una variable para tratarlo posteriormente:
char nombreVariable;
nombreVariable = getChar();
1.9 Lectura de float en C
El separador de valores decimales (tipo float
) en C es el punto, no la coma. De ahí que cuando se introduce un valor decimal desde teclado siempre lo haremos con un punto.
Ejemplo:
/* Ejemplo ES0108 */
#include <stdio.h>
int main(int argc, char **argv) {
/* Variable que contendrá el peso de una persona */
float peso;
/* Lectura del dato por teclado (el separador decimal es un . ) */
printf("Introduce el peso (kg) de una persona : ");
scanf("%f", &peso);
/* Se muestra el valor decimal por pantalla */
printf("Has introducido el peso = %.1f kg.\n", peso);
return 0;
}
La ejecución será:
Introduce el peso (kg) de una persona : 79.440
Has introducido el peso = 79.4 kg.
1.10 Segmentation fault
El error Segmentation fault (core dumped)
se produce cuando intentamos acceder a una posición de memoria incorrecta. Habitualmente sucede porque en la lectura de algún valor desde teclado mediante scanf()
nos olvidamos el caracter &
delante de la variable:
printf("Introduce tu edad : ");
scanf("%d", edad);
Para solucionarlo, añadimos el prefijo &
:
printf("Introduce tu edad : ");
scanf("%d", &edad);
1.11 Ejemplo: calcularIMC
Queremos un programa que calcule el índice de masa corporal, IMC, a partir del peso y de la altura de una persona. Estos dos valores se pedirán por el canal de entrada, y se mostrará el IMC por el canal de salida.
La codificación algorítmica del programa podría ser la siguiente:
algorithm calcularIMC
var
peso: real;
altura: real;
imc: real;
end var
writeString("Introduce el peso (kg): ");
peso := readReal();
writeString("Introduce la altura (m): ");
altura := readReal();
imc := peso / (altura*altura);
writeString("IMC = ");
writeReal(imc);
end algorithm
A continuación, lo traducimos a lenguaje C:
/* Ejemplo ES0109 */
#include <stdio.h>
int main(int argc, char **argv) {
float peso;
float altura;
float imc;
printf("Introduce el peso (kg): ");
scanf("%f", &peso);
printf("Introduce la altura (m): ");
scanf("%f", &altura);
imc = peso / (altura*altura);
printf("IMC = %.1f \n", imc);
return 0;
}
Un ejemplo de ejecución puede ser el siguiente:
Introduce el peso (kg): 63.3
Introducd la altura (m): 1.69
IMC = 22.2
1.12 Errores más frecuentes
1.12.1 Definición de tipos: tipo booleano
En lenguaje algorítmico, el tipo boolean
es un tipo básico, y como tal no es necesario definirlo en un bloque type
.
Pseudocódigo incorrecto:
type
boolean = {FALSE, TRUE}
end type
var
myNum: integer;
myBool: boolean;
end var
Se pueden declarar variables de tipo booleano directamente, al igual que si fuera un entero, un real o un carácter.
Pseudocódigo correcto:
var
myNum: integer;
myBool: boolean;
end var
1.12.2 Estilo y formato: ausencia de estilo y formato en lenguaje algorítmico
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:
algorithm precioHotel
var
precioHabitacion:real;
numDias:integer;
precioTotal:real;
end var
writeString("Precio de la habitación: ");
precioHabitacion:=readReal();
writeString("Número de días: ");
numDias:=readInteger();
precioTotal:=precioHabitación*realToInteger(numDias);
writeString("Precio total = ");
writeReal(precioTotal);
end algorithm
En el caso del lenguaje algorítmico, las reglas de formato y de estilo son arbitrarias y fijadas por convenio, pero hay que seguirlas para que este sea fácil de leer y revisar, de la misma forma que se hace cuando se programa en C u otros lenguajes. En el ejemplo anterior, no se aplica ninguna indentación y el pseudocódigo es muy difícil de leer. Fijaos cómo cambia cuando aplicamos correctamente unas mínimas reglas.
Pseudocódigo correcto:
algorithm precioHotel
var
precioHabitacion: real;
numDias: integer;
precioTotal: real;
end var
writeString("Precio de la habitación: ");
precioHabitacion := readReal();
writeString("Número de días: ");
numDias := readInteger();
precioTotal := precioHabitación * integerToReal(numDias);
writeString("Precio total = ");
writeReal(precioTotal);
end algorithm
Cabe mencionar que las indentaciones del código (sangrado del texto, tabulaciones) son especialmente importantes para la lectura de los programas, ya que permiten identificar rápidamente los bloques de código, las funciones y acciones, las estructuras iterativas y alternativas, y su dependencia jerárquica. Por este motivo, el uso de indentaciones en el pseudocódigo es absolutamente necesario.
1.12.3 Declaración de variables: identificadores no permitidos
Pseudocódigo incorrecto:
var
1Hotel_ID: integer;
2Hotel_ID: integer;
end var
El nombre de las variables puede contener números siempre que no estén en la primera posición. Utilizaremos el modelo camelCase para definir el nombre de las variables.
Pseudocódigo correcto:
var
hotelId1: integer;
hotelId2: integer;
end var
1.12.4 Declaración de variables: operador de declaración
Pseudocódigo incorrecto:
var
id = integer;
brand = string;
name = string;
end var
El siguiente error podría parecer un error leve, pero es importante respetar el Nomenclátor y utilizar los operadores correctamente. En lenguaje algorítmico, el operador de declaración de tipo es :
y no =
, que es el operador de asignación de valor.
Pseudocódigo correcto:
var
id: integer;
brand: string;
name: string;
end var
1.12.5 Declaración de variables: nombres inadecuados
Este no es un error sintáctico o semántico, pero sí un error de diseño muy frecuente.
Pseudocódigo incorrecto:
const
aux: integer = 7;
end const
var
aux2: integer;
result1: integer;
end var
aux2 := 189;
result1 := aux1 * aux2;
{ ... }
Los nombres de las variables (y constantes) deben aportar el máximo de información posible sobre su contenido y tipo de los datos, así como de su función dentro del algoritmo. Hay que evitar siempre el uso de nombres genéricos que no aportan ninguna información y se pueden confundir fácilmente con otras variables con nombres similares (por ejemplo aux
, aux2
, result1
, flag
, number
, etc.).
El algoritmo anterior calcula el número de horas realizadas por un trabajador; el total se obtiene a partir de los días trabajados durante el año y de su jornada laboral de siete horas diarias. Como se puede comprobar al leer el algoritmo, resulta imposible entender la función de cada variable, así como el significado de los datos que contienen. Este problema se resuelve usando nombres descriptivos:
Pseudocódigo correcto:
const
dailyWorkHours: integer = 7;
end const
var
yearlyWorkDays: integer;
yearlyWorkHours: integer;
end var
yearlyWorkDays := 189;
yearlyWorkHours := yearlyWorkDays * dailyWorkHours;
{ ... }