Métodos de Programación Básica
Métodos de Programación Básica
MODULO II
Contenido
¿Qué es un Algoritmo?
Dados un estado inicial y una entrada, siguiendo los pasos sucesivos se llega a un estado final y se
obtiene una solución.
Muchos autores definen a los algoritmos como listas de instrucciones para resolver un problema
abstracto, es decir, que un número finito de pasos convierten los datos de un problema (entrada)
en una solución (salida).
Los diagramas de flujo son descripciones gráficas de algoritmos; usan símbolos conectados con
flechas para indicar la secuencia de instrucciones y están regidos por ISO.
Los diagramas de flujo son usados para representar algoritmos pequeños, ya que abarcan mucho
espacio y su construcción es laboriosa. Por su facilidad de lectura son usados como introducción a
los algoritmos, descripción de un lenguaje y descripción de procesos a personas ajenas a la
computación.
¿Qué es Programar?
Es la acción de escribir instrucciones correctas para que sean interpretadas por una máquina.
¿Qué es el Software?
Son programas que para que tengan sentido de software deben ser ejecutados sobre una máquina.
¿Qué es un compilador?
Es aquel programa de software que traduce todo el código fuente escrito y genera un código objeto
que es entendible por la máquina y se encuentra listo para funcionar
Los ordenadores son uno más de los inventos del hombre, aunque debemos decir que las
tecnologías para su fabricación y explotación han tenido un desarrollo sorprendente a partir de la
segunda mitad del siglo XX. Esta herramienta por sí sola no es capaz de efectuar ninguna tarea, es
tan sólo un conjunto de cables y circuitos que necesitan recibir instrucción por parte de los
humanos para desempeñar alguna tarea. El problema entonces, se puede fijar en ¿cómo vamos a
poder hacer que un conjunto de circuitos desempeñen una determinada tarea y nos entreguen los
resultados que nosotros esperamos?, es decir, ¿de qué manera se puede lograr la comunicación
entre el hombre y el ordenador?
Así pues, tratando de dar una solución al problema planteado, surgieron los lenguajes de
programación, que son como un lenguaje cualquiera, pero simplificado y con ciertas normas, para
poder trasmitir nuestros deseos al ordenador.
Por otro lado, como se sabe, un conjunto de circuitos no entendería ningún lenguaje que nosotros
conozcamos, por más sencillo que éste parezca. Los circuitos en todo caso, sólo reconocen
presencia o ausencia de energía, es decir que debemos hablarle a la máquina en su propio
lenguaje (presencia y ausencia de energía, 0 y 1), o nuestro lenguaje deberá de ser traducido a un
lenguaje binario cuyo alfabeto es el 0 y el 1, mediante las herramientas desarrolladas para llevar a
cabo esta tarea, las cuales reciben el nombre de traductores, y como veremos más adelante, los
hay de muchos tipos, dependiendo de características más específicas del lenguaje a traducir y de la
manera de llevar a cabo su traducción.
1946: Konrad Zuse , un ingeniero Alemán mientras trabajaba en los Alpes de Bavaria,
desarrolló el lenguaje Plankalkul, el cual, fue aplicado entre otras cosas para jugar al ajedrez.
1949: Aparece Short Code, que viene a ser el primer lenguaje que fue usado en un dispositivo
de cómputo electrónico, aunque se debe decir que se trata de un lenguaje traducido a mano.
1951: Grace Hopper , trabajando para Remington Rand, comenzó el trabajo de diseño del
primer compilador conocido ampliamente, el A-0, el cual, al ser liberado por la compañía en
1957, lo hizo con el nombre de MATH-MATIC.
A partir de los años sesenta, empiezan a surgir diferentes lenguajes de programación, atendiendo a
diversos enfoques, características y propósitos, que más adelante describiremos. Por lo pronto,
puede decirse, que actualmente existen alrededor de más 2000 lenguajes de programación y
continuamente, están apareciendo otros más nuevos, que prometen hacer mejor uso de los
recursos computacionales y facilitar el trabajo de los programadores.
Tratando de resumir un poco, se presenta a continuación un cuadro donde aparecen los lenguajes
que por su uso y comercialización, han resultado ser los más populares a lo largo de este medio
siglo.
1.3 Tipos de lenguajes de programación
Los lenguajes más próximos a la arquitectura hardware se denominan lenguajes de bajo nivel y los
que se encuentran más cercanos a los programadores y usuarios se denominan lenguajes de alto
nivel. A continuación hacemos una breve descripción de ambos tipos.
La desventaja es que son bastantes difíciles de manejar y usar, además de tener códigos
fuente enormes donde encontrar un fallo es casi imposible.
Un paradigma de programación es una propuesta tecnológica que es adoptada por una comunidad
de programadores cuyo núcleo central es incuestionable en cuanto a que unívocamente trata de
resolver uno o varios problemas claramente delimitados. La resolución de estos problemas debe
suponer consecuentemente un avance significativo en al menos un parámetro que afecte a la
ingeniería de software. Tiene una estrecha relación con la formalización de determinados lenguajes
en su momento de definición. Un paradigma de programación está delimitado en el tiempo en
cuanto a aceptación y uso ya que nuevos paradigmas aportan nuevas o mejores soluciones que la
sustituyen parcial o totalmente.
Si bien existen varios paradigmas de programación haremos una descripción de aquellos que
consideramos más importantes como son la programación imperativa y la orientada a objetos.
El paradigma imperativo debe su nombre al papel dominante que desempeñan las sentencias
imperativas, es decir aquellas que indican realizar una determinada operación que modifica los
datos guardados en memoria. Su esencia es resolver un problema complejo mediante la ejecución
repetitiva y paso a paso de operaciones y cálculos sencillos con la asignación de los valores
calculados a posiciones de memoria.
La programación en este paradigma consiste en determinar qué datos son requeridos para el
cálculo, asociar a esos datos una dirección de memoria, y efectuar, paso a paso, una secuencia de
transformaciones en los datos almacenados, de forma que el estado final represente el resultado
correcto.
Los conceptos básicos del paradigma son representados por la arquitectura Von Neumann, ya que
utiliza este modelo de máquina para conceptualizar las soluciones.
Principales características
• Celdas de memoria
• Asignación
La base teórica de la programación estructurada plantea que cualquier programa, por más
grande y complejo que fuera, puede representarse mediante tres tipos de estructuras de
control: secuencia, selección e iteración.
• Modularización
También propone desarrollar el programa en forma TOP-DOWN, de arriba hacia abajo. Es decir,
modularizar el programa creando porciones más pequeñas de programas con tareas
espeIífiIas, ケ ue se suHdivide ミ a su vez e ミ ot ヴ os さ suHp ヴ og ヴ a マ as ざ, Iada vez マ ás pe ケ
ueños ┞ operativos. Estos subprogramas, denominados rutinas, funciones, procedimientos,
módulos y de otras maneras en los diferentes lenguajes, deben tener sólo un punto de entrada
y uno de salida, y realizar conceptualmente una sola tarea.
Existen numerosos lenguajes del paradigma imperativo, como son PASCAL, C, C++, FORTRAN,
ALGOL, COBOL, ADA, CLIPPER, FOX, PL/1, etc. Muchos de ellos incluyen extensiones que permiten
soportar otros paradigmas, en mayor o menor grado; no obstante, sigue predominando en ellos el
modelo de máquina imperativa.
La programación Orientada a objetos (POO) es una forma especial de programar, más cercana a
como expresaríamos las cosas en la vida real que otros tipos de programación.
La POO no es difícil, pero es una manera especial de pensar, a veces subjetiva de quien la
programa, de manera que la forma de hacer las cosas puede ser diferente según el programador.
Aunque podamos hacer los programas de formas distintas, no todas ellas son correctas, lo difícil no
es programar orientado a objetos sino programar bien. Programar bien es importante porque así
nos podemos aprovechar de todas las ventajas de la POO.
Actualmente el paradigma de programación más usado es el de la programación orientada a
objetos, donde podemos mencionar los siguientes lenguajes: C++, Objective C, Java, Smalltalk,
Eiffel, Ruby, Python, Object Pascal, Visual .net, Actionscript, Perl, C#, PHP, Delphi, etc.
Otros paradigmas.
Programación Funcional (Tarea)
Programación por Eventos (Tarea)
Programación Concurrente (Tarea)
Programación en la Nube (Tarea)
• Secuencia
Indica que las instrucciones de un programa se ejecutan una después de la otra, en el
mismo orden en el cual aparecen en el programa. Se representa gráficamente como una
caja después de otra, ambas con una sola entrada y una única salida.
B
Las cajas A y B pueden ser definidas para ejecutar desde una simple instrucción hasta un
módulo o programa completo, siempre y cuando que estos también sean programas
apropiados.
• Selección
También conocida como la estructura SI-CIERTO-FALSO, plantea la selección entre dos
alternativas con base en el resultado de la evaluación de una condición o predicado;
equivale a la instrucción IF de todos los lenguajes de programación y se representa
gráficamente de la siguiente manera:
V F
C
A B
• Iteración
También llamada la estructura HACER-MIENTRAS-QUE, corresponde a la ejecución repetida
de una instrucción mientras que se cumple una determinada condición. El diagrama de
flujo para esta estructura es el siguiente:
V
C
F
Aquí el bloque A se ejecuta repetidamente mientras que la condición C se cumpla o sea
cierta. También tiene una sola entrada y una sola salida; igualmente A puede ser cualquier
estructura básica o conjunto de estructuras.
¿Qué es Pascal?
Turbo Pascal ha sido durante mucho tiempo la versión más extendida del lenguaje Pascal. Fue
desarrollada por la compañía Borland, para el sistema operativo Dos. Este puesto actualmente lo
ocupa una versión de libre distribución (y código fuente abierto) llamada Free Pascal, disponible
para varios sistemas operativos.
C++ (C plus plus o C más más) es una evolución del lenguaje C, que soporta la Programación
Orientada a Objetos.
La creación del diagrama de flujo es una actividad que agrega valor, pues el proceso que representa
está ahora disponible para ser analizado, no sólo por quienes lo llevan a cabo, sino también por
todas las partes interesadas que aportarán nuevas ideas para cambiarlo y mejorarlo.
Los Diagramas de flujo se dibujan generalmente usando algunos símbolos estándares; sin embargo,
algunos símbolos especiales pueden también ser desarrollados cuando séan requeridos. Los
símbolos tienen significados específicos y se conectan por medio de flechas que indican el flujo
entre los distintos pasos o etapas.
Algunos símbolos estándares, que se requieren con frecuencia para diagramar programas de
computadora se muestran a continuación:
Toma de decisiones
Líneas de flujo
- Menos
* Multiplicación
/ División
± Más o menos
= Equivalente a
<> Diferente de
Si
No
True
False
1. Los Diagramas de flujo deben escribirse de arriba hacia abajo, y/o de izquierda a derecha.
2. Los símbolos se unen con líneas, las cuales tienen en la punta una flecha que indica la
dirección que fluye la información procesos, se deben de utilizar solamente líneas de flujo
horizontal o verticales (nunca diagonales).
3. Se debe evitar el cruce de líneas, para lo cual se quisiera separar el flujo del diagrama a un
sitio distinto, se pudiera realizar utilizando los conectores. Se debe tener en cuenta que
solo se van a utilizar conectores cuando sea estrictamente necesario.
4. No deben quedar líneas de flujo sin conectar
5. Todo texto escrito dentro de un símbolo debe ser legible, preciso, evitando el uso de
muchas palabras.
6. Todos los símbolos pueden tener más de una línea de entrada, a excepción del símbolo
final.
7. Solo los símbolos de decisión pueden y deben tener más de una línea de flujo de salida.
Cabecera:
• Programa:
• Modulo:
• Tipos de datos:
• Constantes:
• Variables:
Cuerpo
Inicio
Instrucciones
Fin
Ejemplos
* Programa que calcula el área de un cuadrado a partir de un lado dado por teclado.
Programa: area_cuadrado
Modulo: main **( también se puede llamar principal)
Variables:
lado: natural
area: natural
Inicio
Visualizar "Introduce el lado del cuadrado"
Leer lado
Area<- lado * lado
Visualizar "El área del cuadrado es", area
Fin
* Programa que visualice la tabla de multiplicar del numero introducido por teclado
Programa: Tabla
multiplicar Modulo: main
Variables: t: entero num :
entero
Inicio
Visualizar "Introduce un número"
Leer num
Desde t=1 hasta t=10 repetir
Visualizar num, " X", t, "=", num*t
Fin desde
Fin
El compilador del cual se va a hacer uso durante el desarrollo del curso será el Dev C++ v. 4.9.9.2.
Dev C++ es totalmente gratuito que genera código objeto para DOS (modo consola) y para
Windows (95/98/2000/NT) con un entorno de programación visual integrado (IDE). Está basado en
el compilador Mingw (Minimalist GNU* for Windows) version MSCVRT 2.95.2-1 que está incluido
en el propio entorno, el cual a su vez es una particularización del compilador GCC (el compilador g+
+ del GNU).
También permite integrar el depurador gratuito para C++ Cygnus Insight Debugger.
Son también frecuentes las ventas de navegación que facilitan la manipulación de diferentes
archivos.
Barra de
Navegación Barra de
Estado
Como en la mayoría de los cursos de lenguajes de programación uno de los primeros programas ケ
ue ge ミ e ヴ al マ e ミ te se esI ヴ iHe ミ es el fa マ oso さ Hola Mu ミ do ざ, a t ヴ avés del Iual se ミ os pe ヴマ
ite da ヴ u ミ vistazo a la sintaxis y estructura del programa.
Pasos a seguir:
Una vez construido el proyecto, el editor de texto contiene las siguientes líneas de código.
#include <cstdlib>
#include <iostream>
using namespace std
int main(int argc, char *argv[])
{
system("PAUSE");
return EXIT_SUCCESS;
}
El significado de cada palabra se verá más adelante, simplemente nos concentramos en el cuerpo
del programa donde se establece todo lo necesario para construir nuestros programas.
Un acceso rápido a estas opciones es mediante la combinación de teclas CTRL + F9 para compilar y
F9 para ejecutar el programa.
1.11 Ejercicios
a) Hacer el diagrama de flujo para sumar dos números leídos por teclado y escribir el
resultado.
b) Modificar el anterior pero para sumar 100 números leídos por teclado.
c) Hacer un diagrama de flujo que permita escribir los 100 primeros pares
Vamos a ver como funciona paso a paso. Para ello vamos a numerar cada uno de los pasos
y ver como se van realizando. (1) Leemos N, supongamos N=4.
(2) ¿N£2? ® NO
(3) A=1
(4) B=1
(5) C=A+B=1+1=2
(6) A=B=1
(7) B=C=2
(8) N=N-1=4-1=3
(9) ¿N=2? ® NO
(5) C=A+B=1+2=3
TEMA 2. ESTRUCTURAS DE CONTROL.
Las estructuras de control determinan la secuencia en que deben ejecutarse las instrucciones de un
algoritmo.
Existen tres Estructuras de control básicas ó primitivas y combinándolas se puede escribir cualquier
algoritmo. Estas estructuras primitivas son: la secuencia, la bifurcación condicional y el ciclo.
2.1.1 Secuencia
La estructura de control más simple es la secuencia. Esta estructura permite que las instrucciones
que la constituyen se ejecuten una tras otra en el orden en que se listan. Por ejemplo, considérese
el siguiente fragmento de un algoritmo:
La estructura de bifurcación condicional permite elegir una de dos opciones en una alternativa,
dependiente del resultado obtenido al evaluar la condición. Véase el siguiente fragmento de
algoritmo:
La palabra clave si indica que estamos en una sentencia de bifurcación condicional. Si la condición
es verdadera se ejecuta la operación 1, de otra manera se ejecutará la operación 2.
if (condicion) {
// instrucciones que hay que ejecutar si la condición es verdadera
} else {
// Instrucciones que hay que ejecutar si la condición es falsa
}
b) La sentencia switch
Mediante la sentencia switch se puede seleccionar entre varias sentencias según el valor de cierta
expresión.
Cada sentencia case debe ser única y el valor que evalúa debe ser del mismo tipo que el devuelto
por la expresiónMultivalor de la sentencia switch.
Las sentencias break que aparecen tras cada conjunto de sSentencias provocan que el control salga
del switch y continúe con la siguiente instrucción al switch. Las sentencias break son necesarias
porque sin ellas se ejecutarían secuencialmente las sentencias case siguientes. Existen ciertas
situaciones en las que se desea ejecutar secuencialmente algunas o todas las sentencias case, para
lo que habrá que eliminar algunos break.
Finalmente, se puede usar la sentencia default para manejar los valores que no son explícitamente
contemplados por alguna de las sentencias case. Su uso es altamente recomendado.
En lenguaje C++ la estructura condicional switch seria de la siguiente manera:
Por ejemplo, supongamos un programa con una variable entera meses cuyo valor indica el mes
actual, y se desea imprimir el nombre del mes en que estemos. Se puede utilizar la sentencia
switch para realizar esta operación:
switch (meses){
case 1: cout<<"Enero"<<endl; break;
case 2: cout<<"Febrero"<<endl; break;
case 3: cout<<"Marzo"<<endl; break;
case 4: cout<<"Abril"<<endl; break;
case 5: cout<<"Mayo"<<endl; break;
case 6: cout<<"Junio"<<endl; break;
case 7: cout<<"Julio"<<endl; break;
case 8: cout<<"Agosto"<<endl; break;
case 9: cout<<"Septiembre"<<endl; break;
case 10: cout<<"Octubre"<<endl; break;
case 11: cout<<"Noviembre"<<endl; break;
case 12: cout<<"Diciembre"<<endl; break;
default: cout<<"No existe el mes"<<endl; break; }
Los ciclos son estructuras de control que permiten ejecutar varias veces una operación. Existen
varios tipos de ciclos:
Este ciclo repite una operación, mientras se cumpla una cierta condición. Por ejemplo:
La palabra clave mientras, señala que se trata de un ciclo mientras. La condición se verifica antes de
ejecutar la operación.
do {
En este ciclo se ejecuta una operación un cierto número de veces, especificando en un contador el
incremento unitario, desde un Valor Inicial hasta un Valor Final que marcará la condición de salida
del ciclo. Por ejemplo:
En lenguaje C++ la estructura さヴ epeti ヴざ seria de la siguiente manera:
Es uno de los conceptos fundamentales de cualquier lenguaje de programación. Estos definen los
métodos de almacenamiento disponibles para representar información, junto con la manera en
que dicha información ha de ser interpretada.
Todos los programas gestionan algunos tipos de información que normalmente se pueden
representar utilizando uno de los ocho (8) tipos de datos básicos de C y C++: texto o char, valores
enteros o int, valores de coma flotante o float, valores en como flotante de doble precisión o
doublé (long double), enumerados o enum, sin valor o void, punteros y booleanos.
1. Texto (tipo de dato char) está constituido por caracteres simples, como a, Z, ¿, 3 y Iade ミ as,
Io マ o さ Esto es u ミ a p ヴ ueHa ざ (ミ o ヴマ al マ e ミ te, de 8 Hits o u ミ H┞te po ヴ Ia ヴ áIte ヴ, Ion
un rango de 0 a 255).
2. Los valores enteros (tipo de dato int) son aquellos números que se aprendieron a contar (1,
4, -2, 1354); normalmente, tienen un tamaño de 16 bits, 2 bytes o una palabra, con rango
de -32768 a 32767. En Windows 98 y Windows NT, los valores enteros tienen un tamaño
de 32 bits con un rango de -2147483648 a 2147483647.
3. Los valores en coma flotante (tipo de datofloat) son números que tienen una parte
fraccional, como por ejemplo pi (3,14159), y exponentes (7,5631021). También se conocen
como números reales (normalmente, son de 32 bits, 4 bytes o 2 palabras, con un rango de
+/-3,4E-38 a 3,4E+38).
4. Los valores en coma flotante de doble precisión (tipo de datodouble) tienen un rango
superior (normalmente de 64 bits, 8 bytes ó 4 palabras, con un rango de 1, 7E-308 a 1,
7E+308). Los valores en coma flotante long double (doble precisión largos) son incluso más
precisos (normalmente, tamaño de 80 bits ó 5 palabras, con un rango de +/-1,18E-4932 a
1,18E-4932).
5. Los tipos de datos enumerados (tipo de dato enum) permiten al usuario definir tipos de
datos.
6. El tipo void se utiliza para especificar valores que ocupan cero bits y no tienen valor (este
tipo también se puede utilizar para la creación de punteros genéricos.
7. El tipo de dato puntero no contiene información en el mismo sentido que el resto de los
tipos de datos; en su lugar, cada puntero contiene la dirección de la posición de memoria
que almacena el dato actual.
8. El tipo de dato bool, al que se le puede asignar las constantes true (verdadero) y false
(falso)
Para crear una variable (de un tipo simple) en memoria debe declararse indicando su tipo de
variable y su identificador que la identificará de forma única. La sintaxis de declaración de variables
es la siguiente:
Esta sentencia indica al compilador que reserve memoria para dos variables del tipo de dato
TipodeDato con nombres Identificador1 e Identificador2.
Los comentarios son anotaciones, observaciones, recordatorios, etc. en el programa. Son para uso
exclusivo del programador, y eliminados del código fuente en la fase de preprocesado.
Aunque los comentarios son voluntarios (no es obligatorio escribirlos), representan una ayuda
inestimable durante la construcción del programa. Siendo imprescindibles para el programador
original, o los que le sucedan en las tareas de mantenimiento, cuando es necesario habérselas con
el código un tiempo después de que fue escrito. Además de clarificar ideas, los comentarios son
también un valioso instrumento de depuración, pues permiten eliminar provisionalmente
secciones enteras de código.
C++ también admite comentarios de una sola línea utilizando dos barras inclinadas (//) como señal
de comienzo. El comentario empieza en este punto (incluyendo las señales de comienzo) y
continúa hasta el próximo carácter de nueva línea.
Ejemplos de comentarios:
/* Esto es un comentario */
A = B + C;
3.1 Introducción
Uno de los métodos más conocidos para resolver un problema es dividirlo en problemas más
pequeños, llamados subproblemas. De esta manera, en lugar de resolver una tarea compleja y
tediosa, resolvemos otras más sencillas y a partir de ellas llegamos a la solución. Esta técnica se usa
mucho en programación ya que programar no es más que resolver problemas, y se le suele llamar
diseño descendente, metodología del divide y vencerás o programación top-down.
En el siguiente grafico se muestra con un ejemplo sencillo la programación modular, donde para
oHte ミ e ヴ la ミ ota fi ミ al ヴ eIu ヴヴ i マ os a utiliza ヴ u ミ a fu ミ Iió ミ o p ヴ oIedi マ ie ミ to さ p ヴ o マ edio ざ ケ
ue ヴ ealiza el cálculo.
3.2 Funciones
La función desde el punto de vista de programación, se define como un proceso que recibe valores
de entrada (llamados argumentos) y el cual retorna un valor resultado. Adicionalmente las
funciones son subprogramas que se pueden invocar (ejecutar) , desde cualquier parte del
programa, es decir desde otra función, desde la misma función o desde el programa principal,
cuantas veces sea necesario.
Las funciones se utilizan cuando dos o más porciones de un algoritmo que son iguales o similares,
para lo cual se extrae es parte de código similar y se crea una funciona que tiene como cuerpo
dicho codigo. Luego se reemplaza el código extraído en el algoritmo principal con el nombre de la
nueva función creada.
tipo ミ o マ bre ふ arg ヱ:tipo ヱ, arg ヲ:tipo ヲ,…, arg ミ:tipo ミぶ{
<instrucciones>
retornar <valor>
}
Donde:
NOTA: Toda función debe tener una instrucción return en su interior para devolver el resultado
calculado.
3.3 Procedimientos
La declaración de procedimientos en C++ es muy similar a la declaración de las funciones. Los más
puristas dirían que en C++ no existen los procedimientos como tales, ya que se declaran como
funciones pero que no devuelven nada (void). Entonces, en el cuerpo de un procedimiento no
devolveremos nada (no se escribe la instrucción return).
Veamos un ejemplo:
#include <cstdlib>
#include <iostream>
for (x=1;x<=10;x++){
cout<<x<<" X "<<num<<" = "<<x*num<<endl;
}
}
Como puedes comprobar la declaración del procedimiento es exactamente igual que la declaración
de una función. La única diferencia está en que el procedimiento devuelve un valor de tipo void, es
decir, vacío/nada.
Como los procedimientos no devuelven ningún tipo de valor en la llamada, la única forma de
devolver valores es incluir la posibilidad de modificar el valor de sus parámetros. Vamos a ver un
ejemplo de cómo hacer eso. El siguiente programa muestra cómo pasar 2 parámetros por
referencia, llamando a una función que intercambia los valores de los 2 parámetros dados.
#include <cstdlib>
#include <iostream>
Para indicar que los dos parámetros se pasan por referencia se coloca el símbolo & delante del
nombre de cada parámetro.
void intercambiar(int &a, int &b)
La utilización de dichos parámetros dentro del procedimiento es exactamente igual que el uso de
cualquier otra variable.
NOTA: La utilización del símbolo & para indicar el paso de parámetros por variable es propio de C+
+, pero por su facilidad es el método que usaremos para pasar parámetros por referencia. En C el
paso de parámetros por referencia es totalmente distinto.
TEMA 4. VECTORES Y MATRICES
Hasta ahora hemos trabajado con datos elementales (enteros, reales, caracteres; int, float, char)
donde podíamos almacenar 1 sólo dato del tipo especificado. En muchas ocasiones es necesario
trabajar con gran cantidad de datos de un mismo tipo.
Por ejemplo imagina que tienes que hacer un programa donde el usuario es un profesor que quiere
almacenar las notas de sus alumnos para posteriormente hacer estadísticas y cálculos como nota
máxima, mínima, la media, etc.
Podríamos hacerlo declarando una variable de tipo float para cada nota pero eso puede llegar a ser
intratable si la cantidad de notas a almacenar fuera muy grande. Además el programa sería muy
engorroso y difícil de entender.
Con los vectores y las matrices conseguimos, bajo un único nombre, es decir, con una única
variable, almacenar tantos valores de un mismo tipo como queramos (según el tamaño que
definamos en su declaración). En el ejemplo anterior, tendríamos un vector de float con longitud,
por ejemplo 100, donde podríamos almacenar hasta 100 valores reales.
4.1 Vectores
Los vectores, también llamados tablas unidimensionales, son tipos de datos compuestos o
estructurados caracterizados por:
- Todos los elementos del vector son referenciados con el mismo nombre y la diferencia
entre ellos es el índice de la posición que ocupan dentro del vector
- Todos sus elementos son almacenados en posiciones de memoria contiguas.
El formato general para la declaración de una variable de tipo vector es la siguiente:
Donde:
Por ejemplo:
float notas[100];
Sería la declaración de la variable notas donde podríamos almacenar hasta 100 números reales.
notas
Para acceder a cada casilla del vector utilizamos un número entero llamado subíndice, que indica la
posición del elemento dentro del vector. En C, los elementos empiezan a numerarse en el 0. Así el
primer elemento del vector notas será notas sub 0, el segundo notas sub 1, etc., y el último notas
sub 99. Fíjate que el último elemento es el que ocupa la posición tamaño_del_vector menos
1.
notas
0 1 2 3 99
…. notas[99] (último
elemento)
Operaciones que podemos hacer con un vector
...
nota_alu5 = notas[5];
En estos dos casos, el acceso al elemento del vector que queremos leer o en el que
queremos escribir es directo, porque solamente leemos o modificamos un elemento, el 5
en ambos ejemplos. Si queremos recorrer todo el vector, ya sea para leer como para
escribir, tendremos que utilizar un bucle.
c) Recorrido de un vector
El bucle más utilizado para recorrer un vector es el for, porque a priori sabemos cuántas
vueltas dará el bucle (tantas como elementos del vector a visitar).
main()
float notas[MAX];
int i;
return 1;
}
Este programa pide las notas de los alumnos y las almacena en el vector notas.
4.2 Matrices
Las matrices, también llamados tablas bidimensionales, son tipos de datos compuestos o
estructurados caracterizados por:
- Todos los elementos de la matriz son referenciados con el mismo nombre y la diferencia
entre ellos son los índices de la posición que ocupan dentro de la matriz
El formato general para la declaración de una variable de tipo matriz es la siguiente:
Donde:
Por ejemplo:
#define MAX_ALUM 40
...
float notas[MAX_ASIG][MAX_ALUM];
Sería la declaración de la variable notas donde almacenaríamos todas las notas de los alumnos de
cada asignatura. MAX_ASIG y MAX_ALUM serían dos constantes con valores enteros declaradas
anteriormente.
Gráficamente podríamos representarlo así:
notas columna
fila
Para acceder a cada elemento de la matriz utilizamos dos números enteros llamados subíndices,
que indica la fila y la columna donde se encuentra el elemento dentro de la matriz. En C/C++, los
elementos empiezan a numerarse en el 0. Así el primer elemento de la matriz notas será notas[0]
[0] y el último notas [MAX_ASIG -1][MAX_ALUM – 1].
Si te fijas notarás que es exactamente igual que los vectores, pero con 2 dimensiones (la fila y la
columna) en lugar de 1.
...
nota_alu5 = notas[0][5];
notas[0][5] = 8;
En estos dos casos, el acceso al elemento de la matriz que queremos leer o en el que
queremos escribir es directo, porque solamente leemos o modificamos un elemento, el fila
0, columna 5 en estos ejemplos. Si queremos recorrer toda la matriz, ya sea para leer como
para escribir, tendremos que utilizar un bucle.
Al igual que para recorrer un vector, para recorrer una matriz, el bucle más utilizado para
recorrer es el for, porque a priori sabemos cuántas vueltas dará el bucle (tantas como
elementos de la matriz a visitar).
Podemos recorrer la matriz por filas o por columnas. La manera de hacerlo dependerá del
ejercicio.
En el siguiente ejemplo se piden las notas por teclado haciendo un recorrido por filas,
pidiendo consecutivamente las notas de todos los alumnos de cada asignatura:
MAX_ALUM 40
int main()
int i,j;
for (i=0; i<MAX_ASIG; i++) /*Para cada asignatura (para cada fila)*/
cout>>notas[i][j];
return 1;
4.3 Estructuras
Una estructura es un grupo de variables las cuales pueden ser de diferentes tipos sostenidas o
mantenidas juntas en una sola unidad. La unidad es la estructura.
En C/C++ se forma una estructura utilizando la palabra reservada struct, seguida por un campo
etiqueta opcional, y luego una lista de miembros dentro de la estructura. La etiqueta opcional se
utiliza para crear otras variables del tipo particular de la estructura:
struct
nombre_estructura
{ tipo1 campo1;
tipo2 campo2;
tipo3 campo3;
.
.
.
TipoN campoN;
};
Un punto y coma finaliza la definición de una estructura puesto que ésta es realmente una
sentencia C/C++.
En un programa, podemos asociar una variable con una estructura utilizando una sentencia similar
a la siguiente:
nombre_estructura nombre_variable;
Para acceder a los miembros de las estructuras se usa el punto u operador miembro (.). La sintaxis
es:
nombre_variable.miembroNombre
#include <cstdlib>
#include <iostream>
struct
persona{ string
nombre; string
apellido; string
direccion; string
telefono; int
edad;
};
int main(int argc, char *argv[])
{
persona p1;
p1.nombre = "Roberto";
p1.apellido = "Ramos";
p1.direccion = "Calvo #44";
p1.telefono = "6433434";
p1.edad = 21;
cout<<"Nombre:"<<p1.nombre<<endl;
cout<<"Apellido:"<<p1.apellido<<endl;
cout<<"Direccion:"<<p1.direccion<<endl;
cout<<"Telefono:"<<p1.telefono<<endl; cout<<"Edad:"<<p1.edad<<endl;
system("PAUSE");
return EXIT_SUCCESS;
}
El valor de todas las variables que manejamos en nuestros programas se almacenan en memoria y
tienen una dirección. Un puntero es una variable especial que apunta a la dirección de memoria de
una variable. El puntero tiene a su vez su propia dirección. Todas estas direcciones tienen un
formato hexadecimal.
Los punteros son herramientas muy poderosas con muchas utilidades y enormes ventajas como
veremos más adelante. A grandes rasgos, un puntero me permite desplazarme en la memoria,
apuntar, redireccionar a ciertas variables, funciones, métodos, objetos sin necesidad de mover
grandes bloques de datos, lo cual nos ahorra muchísimo el consumo de memoria en los programas.
Una variable de tipo puntero debe ser de igual tipo que la variable cuya dirección en memoria
contiene, o dicho de otra forma, la variable a la que apunta.
Un puntero no tiene asociación directa con el dato actual, pero si una asociación indirecta. Por ese
マ otivo se usa el té ヴマ i ミ o さ di ヴ eIIió ミざ Iua ミ do se haIe ヴ efe ヴ e ミ Iia a u ミ a asoIiaIió ミ i ミ di ヴ eIta.
1 int *var; //Un puntero llamado var que podra apuntar a cualquier variable de tipo entero.
2 char *u;//puntero de tipo char
3 Persona *per;//puntero de tipo persona
Para determinar, asignar la dirección de una variable en c++, se usa el operador & y para obtener el
contenido de un puntero utilizamos el operador * Ejem:
1 int a;//entero
2 int *b;//puntero a entero
3 a = 20;//a tiene 20
4 b=&a;//asigno la dirección de a al puntero b
5
6 cout << b << endl; // imprimirá la dirección de memoria de a;
7 cout << *b;// imprimirá 20, es decir el contenido de a
p++; cout
<< *p; P--;
cout<<*p;
Pueden visualizar que estoy incrementando el puntero p en 1. Esto quiere decir que el puntero se
desplazara 4 bytes en memoria (en este caso por ser entero) y entonces apuntara a otra direccion.
Por eso es que el nuevo contenido de p es basura o bueno el contenido de lo que tiene esa nueva
direccion a la que apunta.
Supongamos que definir un entero y puntero de tipo char:
1 char c;
2 char *d;
3
4 d= &c;//asigno la direccion de c a d
5 c='u';//asigno el valor u a mi variable c
6 c--;//desplazo una posicion a c
7 cout << *d;//
01 int a=15;
02 int *p;
03
04 double *q;
05 void *r;
06 p = a; //No puedo hacer esto porque estoy asignando una variable a un puntero y un puntero es
07 una direccion. p = &50; // 50 es un valor constante en este caso y no una variable,por lo tanto
no tiene
08 direccion
09 p = &(a+1); //una expresion no tiene direccion
10 p = 30;//igual que el primer error, 30 es un entero.
11 &a = p;//no puedo cambiar la direccion de una variable
12 p = q;//p es puntero de tipo entero y q de tipo double
Un puntero de tipo void, es un puntero al cual le podemos asignar cualquier tipo de puntero. Por lo
tanto si podriamos hacer esto:
r = p;
Para la comparación de punteros se utilizan los operadores básicos de comparación que usábamos
con variables bases, tales como int. Por lo que para saber por ejemplo si un puntero apunta a la
misma dirección a la que apunta otro, utilizaríamos: p1 == p2, para saber si son distintos
utilizaríamos el operador !=, para saber si p1 apunta a una dirección de memoria mas baja que p2
colocaríamos p1 < p2, y así con los demás operadores de comparación.
VECTORES Y PUNTEROS
Cuando declaramos un vector int v[10]; el nombre del vector es un puntero al primer elemento del
vector, es decir a v[0]. Entonces como un vector es un puntero al primer elemento del mismo,
también podríamos hacer aritmética de punteros con el vector.
Para usar vectores dinámicos necesitamos gestionar memoria dinámica. Si bien es cierto que esto
trae enormes ventajas, el hacer un mal uso de la memoria dinámica nos podría traer problemas
desastrozos. Por eso es importante que cuando creemos vectores dinámicos también liberemos la
memoria utilizada. Obviamente eliminaremos la memoria utilizada cuando ya no necesitamos más
usar, en este caso, un determinado vector.
El operador new sirve para reservar memoria dinámica. El operador delete se usa para liberar la
memoria dinámica reservada con new. Para liberar memoria de un array dinámico usamos
delete[] .
El espacio de memoria que hemos reservado con new tendrá vida hasta que finalize la ejecución
del programa o cuando liberemos ese espacio con delete. Siempre es recomendable liberar
memoria para posteriormente no tener problemas con excesivo consumo de memoria.
Un simple ejemplo:
01 #include <iostream>
02 using namespace std;
03
04 int main()
05 {
06 int *pv;
07 int dim;
08
09 cout << "Ingresa el tamanyo del vector" << endl;
10 cin >>dim;
11 pv = new int[dim];
12
13 for(int i=0;i<dim;i++){
14 pv[i] = i * i;
15 cout << pv[i] << " ";
16 }
17
18 delete[] pv;
19 return 0;
20 }
Hasta ahora estamos estado "cuadriculando" todo para obtener algoritmos: tratábamos de
convertir cualquier cosa en funciones y variables que pudieramos emplear en nuestros programas.
Pero no todo lo que nos rodea es tan fácil de cuadricular. Supongamos por ejemplo que tenemos
que describir una puerta desde nuestro programa. En la zona de declaración de variables
detallaríamos datos como su tamaño o su color. Pero no basta con eso. De hecho, eso no es lo más
importante: lo que hace que una puerta sea una puerta es el hecho de que se pueda abrir y se
pueda cerrar. Por tanto, si queremos simular o controlar una puerta desde nuestro programa,
deberíamos crear también las funciones AbrirPuerta y CerrarPuerta en alguna otra parte de
nuestro fuente.
No está mal, pero es antinatural: una puerta es un conjunto: no podemos separar su color de su
tamaño, o de la forma en que debemos abrirla o cerrarla. Sus características son tanto las físicas (lo
que hasta ahora llamábamos variables) como sus comportamientos en distintas circunstancias (lo
que para nosotros eran las funciones). Todo ello va siempre unido, formando un OBJETO.
Por otra parte, si tenemos que explicar a alguien lo que es el portón de un garaje, y ese alguien no
lo ha visto nunca, pero conoce cómo es la puerta de su casa, le podemos decir "se parece a una
puerta de una casa, pero es más grande para que quepan los coches, está hecha de metal en vez
de madera..." . Las dos cosas son puertas: se trata de dos objetos que pertenecen a la misma
CLASE.
Finalmente, conviene recordar que "abrir" no se refiere sólo a una puerta. También podemos
hablar de abrir una ventana o un libro, por ejemplo.
Pues con esta discusión hemos comentado casi sin saberlo las tres características más importantes
de la Programación Orientada a Objetos (POO):
(Nota: en C++ es frecuente llamar también "variables de instancia" o "variables miembro" a los
atributos, y "funciones miembro" a los métodos).
Comentado esto, vamos a empezar a ver ejemplos en C++ para tratar de fijar estos primeros
conceptos y de ver la sintaxis de este lenguaje. A partir de unos primeros ejemplos sencillos,
iremos profundizando paulatinamente.
Una clase, es simplemente una abstracción que hacemos de nuestra experiencia sensible. El ser
humano tiende a agrupar seres o cosas -objetos- con características similares en grupos -clases-.
Así, aun cuando existen por ejemplo multitud de vasos diferentes, podemos reconocer un vaso en
cuanto lo vemos, incluso aun cuando ese modelo concreto de vaso no lo hayamos visto nunca. El
concepto de vaso es una abstracción de nuestra experiencia sensible.
Quizás el ejemplo más claro para exponer esto lo tengamos en las taxonomías; los biólogos han
dividido a todo ser (vivo o inerte) sobre la tierra en distintas clases.
Tomemos como ejemplo una pequeña porción del inmenso árbol taxonómico:
Ellos, llaman a cada una de estas parcelas reino, tipo, clase, especie, orden, familia, género, etc.; sin
embargo, nosotros a todas las llamaremos del mismo modo: clase. Así, hablaremos de la clase
animal, clase vegetal y clase mineral, o de la clase félidos y de las clases leo (león) y tigris (tigre).
Cada clase posee unas cualidades que la diferencian de las otras. Así, por ejemplo, los vegetales se
diferencian de los minerales -entre otras muchas cosas- en que los primeros son seres vivos y los
minerales no. De los animales se diferencian en que las plantas son capaces de sintetizar clorofila a
partir de la luz solar y los animales no.
Situémonos en la clase felinos (felis), aquí tenemos varias subclases (géneros en palabras de los
biólogos): león, tigre, pantera, gato, etc. cada una de estas subclases, tienen características
comunes (por ello los identificamos a todos ellos como felinos) y características diferenciadoras
(por ello distinguimos a un león de una pantera), sin embargo, ni el león ni la pantera en abstracto
existen, existen leones y panteras particulares, pero hemos realizado una abstracción de esos
rasgos comunes a todos los elementos de una clase, para llegar al concepto de león, o de pantera,
o de felino.
La clase león se diferencia de la clase pantera en el color de la piel, y comparte ciertos atributos
con el resto de los felinos -uñas retráctiles por ejemplo- que lo diferencian del resto de los
animales. Pero la clase león, también hereda de las clases superiores ciertas cualidades: columna
vertebral (de la clase vertebrados) y es alimentado en su infancia por leche materna (de la clase
mamíferos).
Vemos cómo las clases superiores son más generales que las inferiores y cómo, al ir bajando por
este árbol, vamos definiendo cada vez más (dotando de más cualidades) a las nuevas clases. Hay
cualidades que ciertas clases comparten con otras, pero no son exactamente iguales en las dos
clases. Por ejemplo, la clase hombre, también deriva de la clase vertebrado, por lo que ambos
poseen columna vertebral, sin embrago, mientras que en la clase hombre se halla en posición
vertical, en la clase león la columna vertebral está en posición horizontal.
En OOP existe otro concepto muy importante asociado al de clase, el de "clase abstracta". Una
clase abstracta es aquella que construimos para derivar de ella otras clases, pero de la que no se
puede instanciar. Por ejemplo, la clase mamífero, no existe como tal en la naturaleza, no existe
ningún ser que sea tan solo mamífero (no hay ninguna instanciación directa de esa clase), existen
humanos, gatos, conejos, etc. Todos ellos son mamíferos, pero no existe un animal que sea solo
mamífero.
Del mismo modo, la clase que se halla al inicio de la jerarquía de clases, normalmente es creada
sólo para que contenga aquellos datos y métodos comunes a todas las clases que de ella derivan:
Son clases abstractas. En árboles complejos de jerarquías de clases, suele haber más de una clase
abstracta.
Este es un concepto muy importante: el de "clase abstracta". Como hemos dicho, una clase
abstracta es aquella que construimos para derivar de ella otras clases, pero de la que no se puede
instanciar. Por ejemplo, la clase mamífero, no existe como tal en la naturaleza, no existe ningún ser
que sea tan solo mamífero (no hay ninguna instanciación directa de esa clase), existen humanos,
gatos, conejos, etc.
Todos ellos son mamíferos, pero no existe un animal que sea solo mamífero. Por último,
adelantemos algo sobre el concepto de objeto. El león, como hemos apuntado antes, no existe,
igual que no existe el hombre; existen leones en los circos, en los zoológicos y, según tenemos
entendido, aún queda alguno en la sabana africana. También existen hombres, como usted, que
está leyendo este libro (hombre en un sentido neutro, ya sea de la subclase mujer o varón), o cada
uno de los que nos encontramos a diario en todas partes.
Todos estos hombres comparten las características de la clase hombre, pero son diferentes entre sí,
en estatura, pigmentación de la piel, color de ojos, complexión, etc. A cada uno de los hombres
particulares los llamamos "objetos de la clase hombre". Decimos que son objetos de tipo hombre o
que pertenecen a la clase hombre. Más técnicamente, José Luis Aranguren o Leonardo da Vinci son
instanciaciones de la clase hombre; instanciar un objeto de una clase es crear un nuevo elemento
de esa clase, cada niño que nace es una nueva instanciación a la clase hombre.
En POO, un objeto es un conjunto de datos y métodos, donde los datos son lo que antes hemos
llamado características o atributos, los métodos son los comportamientos que pueden realizar. Lo
importante de un sistema OOP es que ambos, datos y métodos están tan intrínsecamente ligados,
que forman una misma unidad conceptual y operacional. En POO, no se pueden desligar los datos
de los métodos de un objeto. Así es como ocurre en el mundo real.
Vamos ahora a dar una serie de ejemplos en los que nos iremos acercando paulatinamente a los
objetos informáticos. Los últimos ejemplos son para aquellos que ya conocen Java y/o C++; sin
embargo, estos ejemplos que exigen conocimientos informáticos, no son imprescindibles para
entender plenamente el concepto de clase y el de objeto.
Observe que aunque los datos y los métodos se han enumerado verticalmente, no existe ninguna
correspondencia entre un dato y el método que aparece a su derecha, es una simple enunciación.
Ejemplo 1
Tomemos la clase león de la que hablamos antes y veamos cuales serían algunos de sus datos y de
sus métodos.
Ejemplo 2
Nuestros objetos (los informáticos), como hemos comentado antes, se parecen mucho a los del
mundo real, al igual que estos, poseen propiedades (datos) y comportamientos (métodos).
Tomemos para nuestro ejemplo un cassette. Veamos cómo lo definiríamos al estilo de POO.
Ejemplo 3
Pongamos otro ejemplo algo más próximo a los objetos que se suelen tratar en informática: un
recuadro en la pantalla. El recuadro pertenecería a una clase a la llamaremos marco. Veamos sus
datos y sus métodos.
6.4 Herencia
Esta es la cualidad más importante de un sistema POO, la que nos dará mayor potencia y
productividad, permitiéndonos ahorrar horas y horas de codificación y de depuración de errores.
Es por ello que me niego a considerar que un lenguaje es OOP si no incluye herencia, como es el
caso de Visual Basic (al menos hasta la versión 5, que es la última que conozco).
Como todos entendemos lo que es la herencia biológica, continuaremos con nuestro ejemplo
taxonómico del que hablábamos en el epígrafe anterior.
La clase león, como comentábamos antes, hereda cualidades -métodos, en lenguaje POO- de todas
las clases predecesoras -padres, en POO- y posee métodos propios, diferentes a los del resto de las
clases.
Es decir, las clases van especializándose según se avanza en el árbol taxonómico. Cada vez que
creamos una clase heredada de otra (la padre) añadimos métodos a la clase padre o modificamos
alguno de los métodos de la clase padre. Veamos qué hereda la clase león de sus clases padre:
La clase león hereda todos los métodos de las clases padre y añade métodos nuevos que forman su
clase distinguiéndola del resto de las clases: por ejemplo el color de su piel. Observemos ahora
algo crucial que ya apuntábamos antes: dos subclases distintas, que derivan de una misma clase
padre común, pueden heredar los métodos de la clase padre tal y como estos han sido definidos
en ella, o pueden modificar todos o algunos de estos métodos para adaptarlos a sus necesidades.
En el ejemplo que exponíamos antes, en la clase león la alimentación es carnívora, mientras que en
la clase hombre, se ha modificado éste dato, siendo su alimentación omnívora.
Pongamos ahora un ejemplo algo más informático: supongamos que usted ha construido una clase
que le permite leer números enteros desde teclado con un formato determinado, calcular su IVA y
almacenarlos en un fichero. Si desea poder hacer lo mismo con números reales (para que admitan
decimales), solo deberá crear una nueva subclase para que herede de la clase padre todos sus
métodos y redefinirá solo el método de lectura de teclado. Esta nueva clase sabe almacenar y
mostrar los números con formato porque lo sabe su clase padre.
Las cualidades comunes que comparten distintas clases, pueden y deben agruparse para formar
una clase padre -también llamada superclase-. Por ejemplo, usted podría derivar las clases
presupuesto, albarán y factura de la superclase pedidos, ya que estas clases comparten
características comunes. De este modo, la clase padre poseería los métodos comunes a todas ellas
y sólo tendríamos que añadir aquellos métodos propios de cada una de las subclases, pudiendo
reutilizar el código escrito en la superclase desde cada una de las clases derivadas. Así, si
enseñamos a la clase padre a imprimirse, cada uno de los objetos de las clases inferiores sabrán
automáticamente y sin escribir ni una solo línea más de código imprimirse.
La herencia como puede intuir, es la cualidad más importante de la POO, ya que le permite
reutilizar todo el código escrito para las superclases re-escribiendo solo aquellas diferencias que
existan entre éstas y las subclases.
Se heredan los datos y los métodos, por lo tanto, ambos pueden ser redefinidos en las clases hijas,
aunque lo más común es redefinir métodos y no datos. Muchas veces las clases –especialmente
aquellas que se encuentran próximas a la raíz en el árbol de la jerarquía de clases– son abstractas,
es decir, sólo existen para proporcionar una base para la creación de clases más específicas, y por
lo tanto no puede instanciarse de ellas; son las clases virtuales.
Una subclase hereda de su superclase sólo aquellos miembros visibles desde la clase hija y por lo
tanto solo puede redefinir estos.
Una subclase tiene forzosamente que redefinir aquellos métodos que han sido definidos como
abstractos en la clase padre o padres. Normalmente, como hemos comentado, se redefinen los
métodos, aun cuando a veces se hace necesario redefinir datos de las clases superiores. Al
redefinir un método queremos o bien sustituir el funcionamiento del método de la clase padre o
bien ampliarlo.
En el primer caso (sustituirlo) no hay ningún problema, ya que a la clase hija la dotamos con un
método de igual nombre que el método que queremos redefinir en la clase padre y lo
implementamos según las necesidades de la clase hija. De este modo cada vez que se invoque este
método de la clase hija se ejecutará su código, y no el código escrito para el método homónimo de
la clase padre.
Pero si lo que queremos es ampliar el funcionamiento de un método existente en la clase padre (lo
que suele ser lo más habitual), entonces primero tiene que ejecutarse el método de la cla se padre,
y después el de la clase hija. Pero como los dos métodos tienen el mismo nombre, se hace
necesario habilitar alguna forma de distinguir cuando nos estamos refiriendo a un método de la
clase hija y cuando al del mismo nombre de la clase padre.
Esto se hace mediante el uso de dos palabras reservadas, las cuales pueden variar dependiendo del
lenguaje que se utilice, pero que normalmente son: this (en algunos lenguajes se utiliza la palabra
reservada Self) y super:
this
En esencia, una clase en C++ es una estructura en el estilo de C con algunas ventajas sencillas pero
muy potentes.
6.5 Polimorfismo
Por polimorfismo entendemos aquella cualidad que poseen los objetos para responder de distinto
modo ante el mismo mensaje. Pongamos por ejemplo las clases hombre, vaca y perro, si a todos
les damos la orden -enviamos el mensaje- Come, cada uno de ellos sabe cómo hacerlo y realizará
este comportamiento a su modo.
Veamos otro ejemplo algo más ilustrativo. Tomemos las clases barco, avión y coche, todas ellas
derivadas de la clase padre vehículo; si les enviamos el mensaje Desplázate, cada una de ellas sabe
cómo hacerlo. Realmente, y para ser exactos, los mensaje no se envían a las clases, sino a todos o
algunos de los objetos instanciados de las clases. Así, por ejemplo, podemos decirle a los objetos
Juan Sebastián el Cano y Kontiqui, de la clase barco que se desplacen, con los que el resto de los
objetos de esa clase permanecerán inmóviles.
Del mismo modo, si tenemos en pantalla cinco recuadros (marcos) y tres textos, podemos decirle a
tres de los recuadros y a dos de los textos que cambien de color y no decírselo a los demás objetos.
Todos estos sabrán cómo hacerlo porque hemos redefinido para cada uno de ellos su método
Pintarse que bien podría estar en la clase padre Visual (conjunto de objetos que pueden
visualizarse en pantalla).
Si enviamos el mensaje Imprímete a objetos de distintas clases, cada uno se imprimirá como le
corresponda, ya que todos saben cómo hacerlo. El polimorfismo nos facilita el trabajo, ya que
gracias a él, el número de nombres de métodos que tenemos que recordar disminuye
extensiblemente.
La mayor ventaja la obtendremos en métodos con igual nombre aplicados a las clases que se
encuentran próximas a la raíz del árbol de clases, ya que estos métodos afectarán a todas las clases
que de ellas se deriven.
6.6 Sobrecarga
La sobrecarga puede ser considerada como un tipo especial de polimorfismo que casi todos los
lenguajes de POO incluyen. Varios métodos (incluidos los "constructores", de los que se hablará
más adelante) pueden tener el mismo nombre siempre y cuando el tipo de parámetros que recibe
o el número de ellos sea diferente.
De este modo, por ejemplo la clase File puede tener tantos método Write() como tipos de datos
queramos escribir (no se preocupe si no entiende la nomenclatura, céntrese en la idea):
Para declarar una clase, todo lo que se necesita es escribir una definición de estructura y sustituir la
palabra reservada struct por class. Por ejemplo, una clase empleado con campos como el nombre,
el departamento, la posición, el una función que nos imprima la información de este quedaría así:
Cuando usted declara una clase en C++, no se reserva memoria para la clase hasta que usted crea
un objeto de la clase. Crear un objeto de una clase se llama instanciar un objeto. Un objeto creado
de una clase de denomina instancia de una clase. Por ejemplo, yo puedo tener una instancia de
empleado con el valor en m_nombre=Jose, m_departamento=Sistemas, m_posicion=programador
y m_salario=3000000 por ejemplo.
C++ utiliza especificadores de acceso para permitir controlar a una clase el acceso a las variables de
datos de esa clase. Los especificadores de acceso permiten acceder a algunos miembros de la clase
y restringir el acceso a otros.
Hay tres especificadores de acceso en C++: public, private y protected. Cuando usted declara
público (public) un miembro de una clase, usted permite el acceso a tal miembro desde dentro y
fuera de la clase. Los miembros de datos que son declarados protegidos ( protected ) son
únicamente accesibles por funciones miembro de la clase, pero no se pueden acceder a ellos
desde otras clases. Cuando un miembro de una clase es declarado privado ( private ) es ináccesible
no sólo desde otras clases y otras partes del programa, sino también desde sus clases derivadas.
Las clases derivadas se explicaran posteriormente.
Miremos el siguiente programa de ejemplo. Se compone de tres partes: la primera una declaración
de una clase llamada Empleado:
class Empleado
{ private:
char* m_nombre;
char* m_departamento;
char* m_posicion;
long m_salario;
public: void
ImprimirInfo();
void SetNombre( char* nombre ) { m_nombre = nombre } void
SetDepartamento( char * departamento) { m_departamento = departamento }
void SetPosicion ( char* posicion ) { m_posicion = posicion }
void SetSalario ( long salario ) { m_salario = salario } const
char* GetNombre( ){ return m_nombre } const char*
GetDepartamento( ){ return m_departamento }
const char* GetPosicion( ){ return m_posicion }
const char* GetSalario( ){ return m_salario }
};
Las variables de miembro son declaradas privadas para que funciones de miembro de otras
funciones no tengan acceso a ellas sino a travez de la correspondiente funcion Get o Set. Las
funciones de miembro si son declaradas públicas de tal modo que se pueda acceder a ellas desde
otras funciones.
void Empleado::ImprimirInfo( )
{
cout << "Nombre: " << m_nombre << '\n'; cout <<
"Departamento: " << m_departamento << '\n';
cout << "Puesto: " << m_posicion << '\n';
cout << "Salario: " << m_salario << '\n';
}
Los dos puntos ( :: ) se denomina operador de resolución de ambito. Nos indica que la función que
estamos definiendo que en este caso es ImprimirInfo, pertenece a la clase Empleado.
void main()
{
//creacion de un objeto de la clase Empleado
Empleado empleado12;
Empleado empleado12;
Se instancia un objeto de la clase Empleado con nombre empleado12. Entonces empleado12 tiene
la estructura de la clase Empleado.
Luego, en las líneas siguientes a la instanciación del objeto, se le asignan los valores iniciales a sus
variables:
Permitir el acceso a las variables solo a través de funciones, que en la mayoría de los casos se
llaman SetXxx y GetXxx, se llama encapsulación de datos. Las funciones que necesitan valores de
otra clase, llaman a las funciones que les dan acceso y obtienen estos datos sin conocimiento de
detalles específicos de como se manipulan los datos.
Empleado::ImprimirInfo();
El operador de resolución de ambito se suele utilizar para llamar funciones que se encuentran
fuera del ambito de la función de llamada. Entonces, para llamar la función ImprimirInfo() de la
clase Empleado se fuera de su ambito se debe utilizar este operador.
La principal diferencia entre este operador y los operadores punto y flecha es que el operador de
resolución de ambito se utiliza para acceder a miembros de clases, y el operador punto y flecha
para acceder a miembros de objetos específicos.
Si el operador de resolución de ambito aparece sin un nombre de clase delante, significa que la
función que esta llamando ( MessageBox ) no es miembro de ninguna clase.
Este apuntador lo tiene todo objeto en C++, apuntando a sí mismo. Se puede utilizar este
apuntador en cualquier lado para acceder a todos los miembros del objeto al cual está apuntando
este apuntador this. Veamos el siguiente código:
#include <iostream.h>
class Miclase
{ public:
Miclase() {} //constructor por defecto
~Miclase() {} //destructor
void yoMismo() { return this }
};
int main()
{
void* pClase; Miclase
unObjeto; pClase =
unObjeto.yoMismo(); cout<<
"El puntero pClase es "
<< pClase <<'\n.';
return 0;
}
En este ejemplo la clase yoMismo() devuelve un apuntador al objeto que lo posee de la clase
Miclase. El main() crea un objeto de la clase Miclase y luego llama a yoMismo(). Lo almacena en
pClase y luego enseña el contenido, que en este caso es el valor de la referencia. Entonces este
apuntador nos permitira realizar muchas cosas sobre los propios objetos con esta referencia.