Page 8
Los lenguajes ló gicos son los que trabajan directamente con la ló gica formal, se trata de
representar relaciones entre conjuntos, para luego poder determinar si se verifican determinados
predicados. El lenguaje ló gico más extendido es el Prolog.
PROGRAMACIÓN IMPERATIVA
Como ya hemos mencionado anteriormente, la programació n imperativa trata con tipos de datos
y algoritmos, los primeros representan la informació n utilizada por los programas, mientras que
los segundos se refieren a la manera en que tratamos esa informació n.
En los puntos que siguen revisaremos de forma breve los conceptos fundamentales de la
programació n imperativa clásica, tambié n llamada programació n procedural. La idea básica de
esta aproximació n es la de definir los algoritmos o procedimientos más eficaces para tratar los
datos de nuestro problema.
Tipos de datos
Cuando nos planteamos la resolució n de problemas mediante computador lo más usual es que
queramos tratar con datos que son variables y cuantificables, es decir, que toman un conjunto
de valores distintos entre un conjunto de valores posibles, además de poder almacenar los
valores de estos datos en alguna forma aceptable para el computador (ya sea en la memoria o en
perifé ricos de almacenamiento externo).
En un lenguaje de programació n el concepto de tipo de datos se refiere al conjunto de valores
que puede tomar una variable. Esta idea es similar a la que se emplea en matemáticas, donde
clasificamos las variables en funció n de determinadas características, distinguiendo entre
nú meros enteros, reales o complejos. Sin embargo, en matemáticas, nosotros somos capaces de
diferenciar el tipo de las variables en funció n del contexto, pero para los compiladores esto
resulta mucho más difícil. Por este motivo debemos declarar explícitamente cada variable como
perteneciente a un tipo. Este mecanismo es ú til para que el computador almacene la variable de
la forma más adecuada, además de permitir verificar que tipo de operaciones se pueden realizar
con ella.
Se suelen diferenciar los tipos de datos en varias categorías:
Tipos elementales, que son aquellos cuyos valores son ató micos y, por tanto, no
pueden ser descompuestos en valores más simples. Entre las variables de estos tipos
siempre encontramos definidas una serie de operaciones básicas: asignació n de un valor,
copia de valores entre variables y operaciones relacionales de igualdad o de orden (por lo
tanto, un tipo debe ser un conjunto ordenado).
Los tipos más característicos son:
booleanos = {verdadero, falso}
enteros
= {… -2, -1, 0, +1, +2, …}
reales
= {… -1.0, …, 0.0, …, +1.0, …}
caracteres = {… 'a', 'b', …, 'Z', …}
Generalmente existen mecanismos para que el usuario defina nuevos tipos elementales por
enumeració n, es decir, definiendo el conjunto de valores explícitamente. Por ejemplo
podríamos definir el tipo día como {lunes, martes, mié rcoles, jueves, viernes, sábado,
domingo}, las variables definidas como día só lo podrían tomar estos valores.
Por ú ltimo mencionaremos otro tipo de datos elemental de características especiales, el
puntero, que es el tipo que almacena las direcciones de las variables (la direcció n de
memoria en la que se almacena su valor). Analizaremos este tipo más adelante.
2

Page 9
Tipos compuestos o estructurados, que son los tipos formados a partir de los
elementales. Existen varias formas de agrupar los datos de tipos elementales:
La más simple es la estructura indexada, muy similar a los vectores o matrices de
matemáticas, en donde lo que hacemos es relacionar unos índices (pertenecientes a un tipo
de datos) con los valores de un tipo determinado. Sobre estas estructuras se pueden
realizar las operaciones de consulta o asignació n de un valor (a travé s de su índice).
Otra estructura compuesta muy importante es el registro, que no es más que una sucesió n
de elementos de distintos tipos, denominados campos, que llevan asociados un
identificador. Sobre estos tipos se definen las operaciones de asignació n y de acceso a un
campo. Algunos lenguajes tambié n soportan la operació n de asignació n para toda la
estructura (la copia de todos los campos en un solo paso).
El tipo cadena de caracteres es un caso especial de tipo de datos, ya que algunos lenguajes
lo incorporan como tipo elemental (con un tamaño fijo), mientras que en otros lenguajes
se define como un vector de caracteres (de longitud fija o variable) que es una estructura
indexada. Como se ve, los campos de un registro pueden ser de otros tipos compuestos,
no só lo de tipos elementales.
Tipos recursivos, que son un caso especial de tipos compuestos, introduciendo la
posibilidad de definir un tipo en funció n de sí mismo.
Para terminar nuestro análisis de los tipos de datos, hablaremos de la forma en que los lenguajes
de programació n relacionan las variables con sus tipos y las equivalencias entre distintos tipos.
Hemos dicho que el empleo de tipos de datos nos sirve para que el computador almacene los
datos de la forma más adecuada y para poder verificar las operaciones que hacemos con ellos.
Sería absurdo que intentáramos multiplicar un carácter por un real pero, si el compilador no
comprueba los tipos de los operandos de la multiplicació n, esto sería posible e incluso nos
devolvería un valor, ya que un carácter se representa en binario como un nú mero entre 0 y 255.
Para evitar que sucedan estas cosas los compiladores incorporan mecanismos de chequeo de
tipos, verificando antes de cada operació n que sus operandos son de los tipos esperados. El
chequeo se puede hacer estática o dinámicamente. El chequeo estático se realiza en tiempo de
compilació n, es decir, antes de que el programa sea ejecutable. Para realizar este chequeo es
necesario que las variables y los parámetros tengan tipos fijos, elegidos por el programador. El
chequeo dinámico se realiza durante la ejecució n del programa, lo que permite que las variables
puedan ser de distintos tipos en tiempo de ejecució n.
Por ú ltimo señalaremos que existe la posibilidad de que queramos realizar una operació n
definida sobre un tipo de datos, por ejemplo reales, aplicada a una variable de otro tipo, por
ejemplo entera. Para que podamos realizar esta operació n debe de existir algú n mecanismo para
compatibilizar los tipos, convirtiendo un operando que no es del tipo esperado en é ste, por
ejemplo transformando un entero en real, añadiendo una parte decimal nula, o transformando un
real en entero por redondeo.
Operadores y expresiones
Como ya hemos dicho con los datos de un tipo podemos realizar determinadas operaciones
pero, ¿ có mo las expresamos en un lenguaje de programació n? Para resolver este problema
aparecen lo que llamamos operadores. Podemos decir que un operador es un símbolo o
conjunto de símbolos que representa la aplicació n de una funció n sobre unos operandos.
Cuando hablamos de los operandos no só lo nos referimos a variables, sino que hablamos de
cualquier elemento susceptible de ser evaluado en alguna forma. Por ejemplo, si definimos una
variable entera podremos aplicarle operadores aritméticos (+, -, *, /), de asignació n (=) o
relacionales (>, <, …), si definimos una variable compuesta podremos aplicarle un operador de
campo que determine a cual de sus componentes queremos acceder, si definimos un tipo de
datos podemos aplicarle un operador que nos diga cual es el tamaño de su representació n en
memoria, etc.
3

Page 10
Los operadores están directamente relacionados con los tipos de datos, puesto que se definen en
funció n del tipo de operandos que aceptan y el tipo del valor que devuelven. En algunos casos
es fácil olvidar esto, ya que llamamos igual a operadores que realizan operaciones distintas en
funció n de los valores a los que se apliquen, por ejemplo, la divisió n de enteros no es igual que
la de reales, ya que la primera retorna un valor entero y se olvida del resto, mientras que la otra
devuelve un real, que tiene decimales.
Un programa completo está compuesto por una serie de sentencias, que pueden ser de distintos
tipos:
declarativas, que son las que empleamos para definir los tipos de datos, declarar las
variables o las funciones, etc., es decir, son aquellas que se emplean para definir de forma
explícita los elementos que intervienen en nuestro programa,
ejecutables, que son aquellas que se transforman en có digo ejecutable, y
compuestas, que son aquellas formadas de la unió n de sentencias de los tipos
anteriores.
Llamaremos expresió n a cualquier sentencia del programa que puede ser evaluada y devuelve un
valor. Las expresiones más simples son los literales, que expresan un valor fijo explícitamente,
como por ejemplo un nú mero o una cadena de caracteres. Las expresiones compuestas son
aquellas formadas por una secuencia de té rminos separados por operadores, donde los té rminos
pueden ser literales, variables o llamadas a funciones (ya que devuelven un resultado).
Algoritmos y estructuras de control
Podemos definir un algoritmo de manera general como un conjunto de operaciones o reglas bien
definidas que, aplicadas a un problema, lo resuelven en un nú mero finito de pasos. Si nos
referimos só lo a la informática podemos dar la siguiente definició n:
Un procedimiento es una secuencia de instrucciones que pueden realizarse
mecánicamente. Un procedimiento que siempre termina se llama algoritmo.
Al diseñar algoritmos que resuelvan problemas complejos debemos emplear algú n mé todo de
diseño, la aproximació n más sencilla es la del diseño descendente (top-down). El mé todo
consiste en ir descomponiendo un problema en otros más sencillos (subproblemas) hasta llegar
a una secuencia de instrucciones que se pueda expresar en un lenguaje de alto nivel. Lo que
haremos será definir una serie de acciones complejas y dividiremos cada una en otras más
simples. Para controlar el orden en que se van desarrollando las acciones, utilizaremos las
estructuras de control, que pueden ser de distintos tipos:
condicionales o de selecció n, que nos permiten elegir entre varias posibilidades en
funció n de una o varias condiciones,
de repetició n (bucles), que nos permiten repetir una serie de operaciones hasta que se
verifique una condició n o hayamos dado un nú mero concreto de vueltas, y
de salto, que nos permiten ir a una determinada línea de nuestro algoritmo
directamente.
Funciones y procedimientos
En el punto anterior hemos definido los algoritmos como procedimientos que siempre terminan,
y procedimiento como una secuencia de instrucciones que pueden realizarse mecánicamente,
aquí consideraremos que un procedimiento es un algoritmo que recibe unos parámetros de
entrada, y una funció n un procedimiento que, además de recibir unos parámetros, devuelve un
valor de un tipo concreto. En lo que sigue emplearé los té rminos procedimiento y funció n
indistintamente.
4

Page 11
Lo más importante de estas abstracciones es saber como se pasan los parámetros, ya que segú n
el mecanismo que se emplee se podrá o no modificar sus valores. Si los parámetros se pasan
por valor, el procedimiento recibe una copia del valor que tiene la variable parámetro y por lo
tanto no puede modificarla, sin embargo, si el parámetro se pasa por referencia, el
procedimiento recibe una referencia a la variable que se le pasa como parámetro, no el valor que
contiene, por lo que cualquier consulta o cambio que se haga al parámetro afectará directamente
a la variable.
¿ Por qué surgieron los procedimientos y las funciones? Sabemos que un programa segú n el
paradigma clásico es una colecció n de algoritmos pero, si los escribié ramos todos seguidos,
nuestro programa sería ilegible. Los procedimientos son un mé todo para ordenar estos
algoritmos de alguna manera, separando las tareas que realiza un programa. El hecho de escribir
los algoritmos de manera independiente nos ayuda a aplicar el diseño descendente; podemos
expresar cada subproblema como un procedimiento distinto, viendo en el programa cual ha sido
el refinamiento realizado. Además algunos procedimientos se podrán reutilizar en problemas
distintos.
Por ú ltimo indicaremos que el concepto de procedimiento introduce un nivel de abstracció n
importante en la programació n ya que, si queremos utilizar un procedimiento ya implementado
para resolver un problema, só lo necesitamos saber cuáles son sus parámetros y cuál es el
resultado que devuelve. De esta manera podemos mejorar o cambiar un procedimiento sin
afectar a nuestro programa, siempre y cuando no cambie sus parámetros, haciendo mucho más
fácil la verificació n de los programas, ya que cuando sabemos que un procedimiento funciona
correctamente no nos debemos volver a preocupar por é l.
Constantes y variables
En los puntos anteriores hemos tratado las variables como algo que tiene un tipo y puede ser
pasado como parámetro pero no hemos hablado de có mo o dó nde se declaran, de có mo se
almacenan en memoria o de si son accesibles desde cualquier punto de nuestro programa.
Podemos decir que un programa está compuesto por distintos bloques, uno de los cuales será el
principal y que contendrá el procedimiento que será llamado al comenzar la ejecució n del
programa. Serán bloques el interior de las funciones, el interior de las estructuras de control,
etc.
Diremos que el campo o ámbito de un identificador es el bloque en el que ha sido definido. Si el
bloque contiene otros bloques tambié n en estos el identificador será válido. Cuando hablo de
identificador me refiero a su sentido más amplio: variables, constantes, funciones, tipos, etc.
Fuera del ámbito de su definició n ningú n identificador tiene validez.
Clasificaremos las variables en funció n de su ámbito de definició n en globales y locales. Dentro
de un bloque una variable es local si ha sido definida en el interior del mismo, y es global si se
ha definido fuera de el bloque pero podemos acceder a ella.
Como es ló gico las variables ocupan memoria pero, como só lo son necesarias en el interior de
los bloques donde se definen, durante la ejecució n del programa serán creadas al entrar en su
ámbito y eliminadas al salir de é l. Así, habrá variables que existirán durante todo el programa (si
son globales para todos los bloques) y otras que só lo existan en momentos muy concretos. Este
mecanismo de creació n y destrucció n de variables permite que los programas aprovechen al
máximo la memoria que les ha sido asignada.
Todo lo dicho anteriormente es válido para las variables declaradas estáticamente, pero existe
otro tipo de variables cuya existencia es controlada por el programador, las denominadas
variables dinámicas. Ya hablamos anteriormente de los punteros y dijimos entonces que eran las
variables empleadas para apuntar a otras variables, pero ¿ a qué nos referimos con apuntar?
Sabemos que las variables se almacenan en memoria, luego habrá alguna direcció n de memoria
en la que encontremos su valor (que puede ocupar uno o varios bytes). Los punteros no son
más que variables cuyo contenido es una direcció n de memoria, que puede ser la de la posició n
del valor de otra variable.
5

Page 12
Cuando deseamos crear variables de tipo dinámico el lenguaje de programació n nos suele
proporcionar alguna funció n estándar para reclamarle al S.O. espacio de memoria para
almacenar datos, pero como no hemos definido variables que denoten a ese espacio, tendremos
que trabajar con punteros. Es importante señalar que el espacio reservado de esta forma se
considera ocupado durante todo el tiempo que se ejecuta el programa, a menos que el
programador lo libere explícitamente, pero los punteros que contienen la direcció n de ese
espacio si son variables estáticas, luego dejan de existir al salir de un campo. Si salimos de un
campo y no hemos liberado la memoria dinámica, no podremos acceder a ella (a menos que
alguno de los punteros fuera global al ámbito abandonado), pero estaremos ocupando un
espacio que no será utilizable hasta que termine nuestro programa.
Para terminar só lo diré que existen variables constantes (que ocupan memoria). Son aquellas
que tienen un tipo y un identificador asociado, lo que puede ser ú til para que se hagan chequeos
de tipos o para que tengan una direcció n de memoria por si algú n procedimiento requiere un
puntero a la constante.
PROGRAMACIÓN MODULAR
Con la programació n procedural se consigue dar una estructura a los programas, pero no
diferenciamos realmente entre los distintos aspectos del problema, ya que todos los algoritmos
están en un mismo bloque, haciendo que algunas variables y procedimientos sean accesibles
desde cualquier punto de nuestro programa.
Para introducir una organizació n en el tratamiento de los datos apareció el concepto de
mó dulo
,
que es un conjunto de procedimientos y datos interrelacionados. Aparece el denominado
principio de ocultació n de informació n, los datos contenidos en un mó dulo no podrán ser
tratados directamente, ya que no serán accesibles desde el exterior del mismo, só lo
permitiremos que otro mó dulo se comunique con el nuestro a travé s de una serie de
procedimientos pú blicos definidos por nosotros. Esto proporciona ventajas como poder
modificar la forma de almacenar algunos de los datos sin que el resto del programa sea alterado
o poder compilar distintos mó dulos de manera independiente. Además, un mó dulo bien
definido podrá ser reutilizado y su depuració n será más sencilla al tratarlo de manera
independiente.
TIPOS ABSTRACTOS DE DATOS
Con los mecanismos de definició n de tipos estructurados podíamos crear tipos de datos más
complejos que los primitivos, pero no podíamos realizar más que unas cuantas operaciones
simples sobre ellos. Sin embargo, los procedimientos nos permiten generalizar el concepto de
operador. En lugar de limitarnos a las operaciones incorporadas a un lenguaje, podemos definir
nuestros propios operadores y aplicarlos a operandos que no son de un tipo fundamental (por
ejemplo, podemos implementar una rutina que multiplique matrices y utilizarla como si fuera un
operador sobre variables del tipo matriz). Además, la estructura modular vista en el apartado
anterior nos permite reunir en un solo bloque las estructuras que componen nuestro tipo y los
procedimientos que operan sobre é l. Surgen los denominados Tipos Abstractos de Datos
(TAD).
Un TAD es una encapsulació n de un tipo abstracto de datos, que contiene la definició n del tipo
y todas las operaciones que se pueden realizar con é l (en teoría, algú n operando o el resultado
de las operaciones debe pertenecer al tipo que estamos definiendo). Esto permite tener localizada
toda la informació n relativa a un tipo de datos, con lo que las modificaciones son mucho más
sencillas, ya que desde el resto del programa tratamos nuestro TAD como un tipo elemental,
accediendo a é l só lo a travé s de los operadores que hemos definido.
El problema de esta idea está en que los lenguajes que soportan mó dulos pero no están
preparados para trabajar con TAD só lo permiten que definamos los procedimientos de forma
independiente, es decir, sin asociarlos al TAD más que por la pertenencia al mó dulo. Las
6

Page 13
variables del tipo ú nicamente se podrán declarar como estructuras, por lo que los
procedimientos necesitarán que las incluyamos como parámetro. La solució n adoptada por los
nuevos lenguajes es incorporar mecanismos de definició n de tipos de usuario que se comportan
casi igual que los tipos del lenguaje.
PROGRAMACIÓN ORIENTADA A OBJETOS
La diferencia fundamental entre la programació n pocedural y la orientada a objetos está en la
forma de tratar los datos y las acciones. En la primera aproximació n ambos conceptos son cosas
distintas, se definen unas estructuras de datos y luego se define una serie de rutinas que operan
sobre ellas. Para cada estructura de datos se necesita un nuevo conjunto de rutinas. En la
programació n orientada a objetos los datos y las acciones están muy relacionadas. Cuando
definimos los datos (objetos) tambié n definimos sus acciones. En lugar de un conjunto de
rutinas que operan sobre unos datos tenemos objetos que interactuan entre sí.
Objetos y mensajes
Un Objeto es una entidad que contiene informació n y un conjunto de acciones que operan sobre
los datos. Para que un objeto realice una de sus acciones se le manda un mensaje. Por tanto, la
primera ventaja de la programació n orientada a objetos es la encapsulació n de datos y
operaciones, es decir, la posibilidad de definir Tipos Abstractos de Datos.
De cualquier forma la encapsulació n es una ventaja mínima de la programació n orientada a
objetos. Una característica mucho más importante es la posibilidad de que los objetos puedan
heredar características de otros objetos. Este concepto se incorpora gracias a la idea de clase.
Clases
Cada objeto pertenece a una clase, que define la implementació n de un tipo concreto de objetos.
Una clase describe la informació n de un objeto y los mensajes a los que responde. La
declaració n de una clase es muy parecida a la definició n de un registro, pero aquí los campos se
llaman instancias de variables o datos miembro (aunque utilizaré el té rmino atributo, que no
suena tan mal en castellano). Cuando le mandamos un mensaje a un objeto, este invoca una
rutina que implementa las acciones relacionadas con el mensaje. Estas rutinas se denominan
métodos o funciones miembro. La definició n de la clase tambié n incluye las implementaciones
de los mé todos.
Se puede pensar en las clases como plantillas para crear objetos. Se dice que un objeto es una
instancia de una clase. Tambié n se puede decir que un objeto es miembro de una clase.
Herencia y polimorfismo
Podemos definir clases en base a otra clase ya existente. La nueva clase se dice que es una
subclase o clase derivada, mientras que la que ya existía se denomina superclase o clase base.
Una clase que no tiene superclase se denomina clase raíz.
Una subclase hereda todos los mé todos y atributos de su superclase, además de poder definir
miembros adicionales (ya sean datos o funciones). Las subclases tambié n pueden redefinir
(override) mé todos definidos por su superclase. Redefinir se refiere a que las subclase responde
al mismo mensaje que su superclase, pero utiliza su propio mé todo para hacerlo.
Gracias al mecanismo de redefinició n podremos mandar el mismo mensaje a objetos de
diferentes clases, esta capacidad se denomina polimorfismo.
Programació n con objetos
A la hora de programar con objetos nos enfrentamos a una serie de problemas: ¿ Qué clases debo
crear?, ¿ Cuándo debo crear una subclase? ¿ Qué debe ser un mé todo o un atributo?
7

Page 14
Lo usual es crear una jerarquía de clases, definiendo una clase raíz que da a todos los objetos un
comportamiento comú n. La clase raíz será una clase abstracta, ya que no crearemos instancias
de la misma. En general se debe definir una clase (o subclase de la clase raíz) para cada
concepto tratado en la aplicació n. Cuando necesitemos añadir atributos o mé todos a una clase
definimos una subclase. Los atributos deben ser siempre privados, y deberemos proporcionar
mé todos para acceder a ellos desde el exterior. Todo lo que se pueda hacer con un objeto debe
ser
un
mé todo.
8

Page 15
El lenguaje C++
INTRODUCCIÓN
En este bloque introduciremos el lenguaje de programació n C++. Comenzaremos por dar una
visió n general del lenguaje y despué s trataremos de forma práctica todos los conceptos
estudiados en el bloque anterior, viendo como se implementan en el C++.
Trataremos de que el tema sea fundamentalmente práctico, por lo que estos apuntes se deben
considerar como una pequeña introducció n al lenguaje, no como una referencia completa. Los
alumnos interesados en conocer los detalles del lenguaje pueden consultar la bibliografía.
CONCEPTOS BÁSICOS
Comenzaremos estudiando el soporte del C++ a la programació n imperativa, es decir, la forma
de definir y utilizar los tipos de datos, las variables, las operaciones aritmé ticas, las estructuras
de control y las funciones. Es interesante remarcar que toda esta parte está heredada del C, por
lo que tambié n sirve de introducció n a este lenguaje.
Estructura de los programas
El mínimo programa de C++ es:
main() { }
Lo ú nico que hemos hecho es definir una funció n (main) que no tiene argumentos y no hace
nada. Las llaves { } delimitan un bloque en C++, en este caso el cuerpo de la funció n main.
Todos los programas deben tener una funció n main() que es la que se ejecuta al comenzar el
programa.
Un programa será una secuencia de líneas que contendrán sentencias, directivas de compilació n
y comentarios.
Las sentencias simples se separan por punto y coma y las compuestas se agrupan en bloques
mediante llaves.
Las directivas serán instrucciones que le daremos al compilador para indicarle que realice alguna
operació n antes de compilar nuestro programa, las directivas comienzan con el símbolo # y no
llevan punto y coma.
9