Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Apuntadores PDF
Apuntadores PDF
Capítulo 1. Apuntadores
Observe que el operador de indirección utiliza el mismo símbolo que el operador de multiplicación. En
este caso el asterisco le indica al sistema que se define una variable apuntador. Ejemplos:
int *x; a es un apuntador de tipo entero.
char *y; c es un apuntador de tipo carácter.
double *p, *q; p y q son apuntadores de tipo real doble precisión.
• * toma su operando como una dirección de memoria y retorna la información almacenada en ese
lugar.
100
apunt ---->
Un apuntador es una variable que solo puede contener un valor a la vez, por lo que solo puede apuntar
a un objeto al mismo tiempo. Por otro lado, una variable cualquiera puede ser apuntada (referenciada)
por varios apuntadores, ya que su dirección de memoria puede ser almacenada en distintas variables a
la vez. Al declarar un apuntador, se está especificando el tipo de variable al que va a apuntar. Por
ejemplo, no podrá declararse un apuntador a objetos de tipo int y después intentar utilizarlo para
apuntar a objetos de tipo float. Cuando se desee manejar un apuntador a cualquier tipo de objeto, se
puede declarar de tipo void, como en: void *multiusos;
Progra3.cpp
#include <iostream.h>
#include <conio.h>
#define NL
cout << "\n";
void main()
{
int varent="0" ;
float varflot="0.0" ;
void *apmulti="&varent”; // apmulti APUNTA A varent
*(int *)apmulti="2" ; // ASIGNA 2 AL OBJETO NL; // APUNTADO POR apmulti
cout << varent ;
4
(int *)apmulti está forzando a que apmulti apunte a objetos de tipo int.
Esto mismo puede hacerse por medio de apuntadores, como se muestra en siguiente ejemplo.
Progra4.cpp
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
void main()
{
char *nombre = "COMERCIO" ;
clrscr();
gotoxy(30,12);
cout<< "!! HOLA, " ; puts(nombre); gotoxy(43,12); cout << " !!";
getch();
}
Progra5.cpp
#include <iostream.h>
#include <conio.h>
#include <string.h>
void main()
{
char *nombres[ ] = { "HUGO", "PACO", "LUIS" } ;
char invitado[11];
int bandera;
clrscr();
gotoxy(30,10);
cout << "CUAL ES SU NOMBRE ? " ; gotoxy(50,10); cin>> invitado ;
gotoxy(30,12);
for( int x = 0 ; x <3 ; x++ ) if(strcmp(invitado, nombres[x])="=" 0)
bandera="0;" if(bandera="=" 0) cout << "!! PASE, ESTIMADO "
<< invitado << " !!"; else cout << "!! FUERA DE AQUI, " << invitado << "
!!"; getch();
}
Al pasar un parámetro correspondiente a un arreglo, se pasa la dirección del primer elemento, por lo
que la función invocada puede modificar cualquier elemento del arreglo. El siguiente programa maneja
una función llamada nputs(), la cual recibe como parámetro un arreglo de caracteres.
Progra6.cpp
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
void nputs(char *);
void main()
{
char cadena[81];
clrscr();
gotoxy(10,10);
cout << "ESCRIBA UNA CADENA: "; gets(cadena); gotoxy(10,12); nputs(cadena); getch(); } void
nputs(char cad[ ]) { int x="0;" while(cad[x]) { cout << cad[x] ; x++; } }
6
Progra7.cpp
#include <iostream.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
void nputs(char *);
void main()
{
char cadena[81];
clrscr();
gotoxy(10,10);
cout << "ESCRIBA UNA CADENA: ";
gets(cadena);
gotoxy(10,12);
nputs(cadena);
getch();
}
void nputs(char *cad)
{
while(*cad) cout << *cad++ ;
}
Progra8.cpp
#include <iostream.h>
#include <string.h>
#include <conio.h>
int cmpcad(char*, char*);
void compara(char*, char*, int(*)(char*, char*));
void main()
{
char cadx[80], cady[80];
clrscr();
gotoxy(10,5);
cout << "ESCRIBA UNA CADENA : " ; cin>> cadx;
gotoxy(10,7);
cout << "ESCRIBA OTRA CADENA : " ; cin>> cady;
gotoxy(10,9);
compara(cadx, cady, cmpcad);
gotoxy(1,24);
7
}
void compara(char *cad1, char *cad2,
int (*cmpcad)(char*, char*))
{
if(!(*cmpcad)(cad1,cad2))
cout << "LAS CADENAS SON IGUALES";
else cout << "LAS CADENAS SON DISTINTAS";
}
int cmpcad(char *x, char *y)
{
return(strcmp(x,y));
}
Expliquemos la expresión que puede ser un tanto desconocida del listado anterior, la expresión:
int(*cmpcad)(char*, char*) establece que cmpcad es un apuntador a una función, la cual devuelve un
valor de tipo entero.
Como se vio al principio de la unidad, un apuntador también es una variable. Su dirección puede ser
almacenada por otra variable apuntador, por lo que puede hablarse de un apuntador a un apuntador.
Esto puede extrapolarse para dos o más variables, como se observa en el ejemplo siguiente de
apuntadores a apuntadores.
Progra9.cpp
#include <iostream.h>
#include <conio.h>
void main()
{
int x, *a, **b, ***c ; // 1
clrscr();
a = &x ; // 2
*a = 100 ; // 3
b = &a ; // 4
**b += *a ; // 5
c = &b ; // 6
***c += **b + *a ; // 7
cout << " *a=" << *a << " \n" ;
cout << " **b=" << **b << " \n" ;
cout << "***c=" << ***c << " \n" ;
getch();
}
Se declaran:
x como una variable de tipo entero. a como un apuntador a objetos de tipo entero. b como un apuntador
a un apuntador, el cual a su vez apuntará a objetos de tipo entero. Se dice que b es "el apuntador del
apuntador". c como un apuntador a un apuntador que apunta a otro apuntador, el cual a su vez apunta
a objetos de tipo entero. Se dice que c es "el apuntador del apuntador del apuntador".
La pila luciría así:
a = &x ; // 2
*a = 100 ; // 3
Al objeto apuntado por a se le asigna el valor 100. La pila luciría así:
b = &a ; // 4
Al apuntador b se le asigna la dirección del apuntador a. La pila luciría así:
**b += *a ; // 5
Al objeto apuntado por el apuntador apuntado por b se le suma el valor del objeto apuntado por a. La
pila luciría así:
9
c = &b ; // 6
Al apuntador c se le asigna la dirección del apuntador b. La pila luciría así:
***c += **b + *a ; // 7
Se asigna al objeto apuntado por el apuntador apuntado por el apuntador c, el valor del objeto apuntado
por el apuntador apuntado por el apuntador b más el valor del objeto apuntado por el apuntador a. La
pila luciría así:
Por ejemplo :
En realidad, el lenguaje manejará al arreglo a través de un apuntador llamado calif, el cual tiene
almacenado el valor 65494, que a su vez corresponde a la dirección de inicio del elemento calif[0].
Calif 65494
En el siguiente listado se presenta el manejo del arreglo calif[ ], a través de la notación de arreglos, y en
el listado subsiguiente llamado Progra11.cpp el manejo con la notación de apuntadores.
Progra10.cpp
#include <iostream.h>
void main()
{
int calif[ ] = { 100,90,95,80,90};
Debido a que la ejecución de los programas de ambos listados producen resultados iguales, se deduce
que:
calif[i] == *(calif+i)
Para entender esto que a simple vista no es obvio, revisaremos algunos conceptos:
1. El nombre del arreglo corresponde al de un apuntador que apunta al primer elemento del arreglo, por
lo que:
calif apunta a calif[0]
Visto gráficamente:
2. Para hacer referencia a un elemento específico del arreglo, se toma como base la dirección del
primer elemento y, con el subíndice del elemento específico, se calcula su dirección. Por ejemplo, para
referirse al segundo elemento del
el arreglo puede procederse así
así:
*(calif+1) // Notación de apuntadores, donde la expresión calif+1sirve para calcular la dirección del
elemento que está una posición más allá del elemento apuntado por calif.
Para referirse a calif[2] ( tercer elemento ), puede escribirse:
*(calif+2)
Lo que significa: "El objeto que se encuentra dos posiciones después del objeto apuntado por calif". En
este caso, una posición es el espacio requerido por cada uno de los elementos, de tal manera que, si
calif apunta a la dirección 65494, entonces calif+2 es la expresión que calcula la dirección 65498. La
figura muestra los elementos del arreglo calif[ ] con sus nombres en notación de arreglos y en notación
de apuntadores.
por lo que:
&calif[i] == calif+i
Esto es que, la dirección del i-ésimo elemento de un arreglo se calcula sumándole el subíndice a la
dirección del primer elemento.
Como regla, podemos establecer que el subíndice se refiere a la posición del elemento en el arreglo. Es
por esto que al calcular la dirección por medio del subíndice, éste debe multiplicarse por el número de
bytes que representan el tamaño de cada elemento (dado por el tipo utilizado en la declaración del
arreglo).
En el siguiente código se realiza un programa que emplea los operadores (& y *).
Progra12.cpp
#include <iostream.h>
#include <conio.h>
int main()
{
clrscr();
int a; //a es un puntero
13
Para iniciar este capítulo es importante que le demos un vistazo a la memoria de la computadora. Si
usted ya sabe cómo funciona la memoria de la computadora, se puede saltar esta sección. Sin
embargo, si no está seguro, le sugiero que la lea, le ayudará a comprender mejor ciertos aspectos de la
programación.
La computadora usa memoria de acceso aleatorio (RAM) para guardar información mientras está en
funcionamiento. La RAM se encuentra en circuitos integrados o chips en el interior de la computadora.
La RAM es volátil, lo que significa que es borrada y reemplazada con nueva información tan pronto
como se necesita. La volatilidad también significa que la RAM “recuerda” solamente mientras la
computadora está encendida, y pierde su información cuando se apaga la computadora.
Cada computadora tiene una determinada cantidad de RAM instalada. La cantidad de RAM en un
sistema se especifica por lo general en Megabytes (Mb) por ejemplo 256 Mb, 512 Mb, en ese orden de
ideas se dice un byte es la unidad de medida fundamental de la memoria de una computadora, de los
cuales se obtiene los Kilobytes, Megabytes, Gigabytes, siendo estos los más usados. Un kilobytes de
memoria equivale a 1,024 bytes.
Para darse una idea de que tantos bytes se necesitan para guardar determinados tipos de datos lo
invito a que revise la siguiente tabla de espacios requeridos para guardar datos.
La RAM en la computadora está organizada en forma secuencial, un byte tras otro. Cada byte de
memoria tiene una dirección única mediante la cual es identificado, una dirección que también lo
14
distingue de todos los otros bytes de la memoria. Las direcciones son asignadas a la memoria en orden,
comenzando en 0 y aumentando hasta llegar al límite del sistema.
Para ampliar un poco más la conceptualización a cerca de los tipos de datos se define todo el posible
rango de valores que una variable puede tomar al momento de ser ejecutada en el programa al igual
que en toda la vida útil del propio programa.
Alguna vez nos hemos hecho la siguiente pregunta ¿Para qué se usa la memoria RAM de la
computadora? Tiene varios usos, pero solamente uno, el almacenamiento de datos, le interesa al
programador. Los datos significan la información con la cual trabaja un programa. Ya sea que el
programa esté trabajando con una lista de direcciones, monitoreando la bolsa de valores, manejando un
presupuesto o cualquier otra cosa, la información (nombres, precios de acciones, gastos) es guardada
en la RAM de la computadora mientras el programa esté ejecutando.
15
Hasta el momento, la mayoría de los programas los hemos realizado definiendo variables, sin
preocuparnos de que se realiza internamente en el computador, muchas veces en forma indiscriminada,
es decir sin una verdadera depuración, pero existen ocasiones en que no sabemos cuanta memora
necesitaremos para ejecución de determinado programa, por ejemplo si deseamos realizar un
procesador de textos, no sabemos cuál va hacer la longitud del texto.
Por eso a veces es necesario poder reservar memoria según se va necesitando. Además de esta forma
nuestros programas aprovecharán mejor la memoria del computador en el que se ejecuten, usando
sólo los recursos necesarios.
Realmente la utilidad de asignación dinámica de memoria será aplicada en gran medida en los
capítulos relacionados con las estructuras lineales.
Dependiendo el uso que se le dé a las variables por parte del programador, en una rutina o tarea
específica se pueden identificar dos tipos de variables ellas son variables dinámicas y variables
estáticas.
Las variables estáticas como recordamos en los inicios de los fundamentos de programación, son
aquellas que el programador les asigna memoria antes de la ejecución del programa o de una función,
las variables estáticas se llaman mediante el nombre de la misma, que ha sido declarado por el
programador.
Progra13.cpp
#include <stdio.h>
#include <iostream.h>
#include <stdlib.h>
#include <conio.h>
void main()
{
clrscr();
priNumero = 136;
segNumero = 369;
suma = priNumero + segNumero;
16
En el código del programa se hace uso de tres variables de tipo entero que son variables estáticas ellas
son: priNumero que se le asigna el valor 136, mientras que a la variable segNumero se le asigna el
valor 369, de igual manera a la variable suma calcula el resultado de sumar el valor de las dos
variables.
Para el manejo de variables dinámicas se hace indispensable la utilización de apuntadores, así como
de funciones especiales para la asignación y liberación de la memoria correspondiente a dichas
variables.
2.4.1 Aplicación de las variables dinámicas
En el listado del progra14.cpp se declaran tres variables apuntador de tipo entero ellas son *a, **b y **c
el apuntador a almacena la dirección de la variable x, mientras que **b precedida de dos asteriscos
indica que es una variable que apunta a un apuntador y ***c es un apuntador a apuntador a apuntador.
17
En el lenguaje C existen entre otras las funciones Malloc() y Free() para la asignación y liberación de
memoria dinámicamente respectivamente.
Cuando se ejecuta un programa, el sistema operativo reserva una zona de memoria para el código o
instrucciones del programa y otra para las variables que se usan durante la ejecución. A menudo estas
zonas son la misma zona, es lo que se llama memoria local. También hay otras zonas de memoria,
como la pila, que se usa, entre otras cosas, para intercambiar datos entre funciones. El resto, la
memoria que no se usa por ningún programa es lo que se conoce como "heap" o montón.
Cuando nuestro programa use memoria dinámica, normalmente usará memoria del montón, y no se
llama así porque sea de peor calidad, sino porque suele haber realmente un montón de memoria de
este tipo.
Ejemplo utilizar el operador sizeof en una función con el propósito de determinar el tamaño en bytes de
un parámetro.
En el siguiente listado se evidencia la aplicación y uso del operador sizeof en cada variable el cual
devuelve el número de bytes dependiendo del tipo de variable.
Progra15.cpp
#include <iostream.h>
#include <conio.h>
void main()
{
char c;
short s;
int i;
long l;
float f;
double d;
long double ld;
int arreglo[20], * pt = arreglo;
clrscr();
gotoxy(20,2);
cout<<"valores utilizando sizeof para cada una de la varibles \n\n";
cout<<" variable c = " <<sizeof c;
cout<<"\t tipo char = " <<sizeof (char);
18
El operador New: Realiza una labor parecida a la de la función malloc(), asignando un bloque de
memoria según sea requerido.
El operador Delete: Libera un bloque de memoria asignada por New en tiempo de ejecución, de
manera semejante a como lo hace la función free().
La sintaxis para el uso del operador new es:
Este operador hace una petición al sistema operativo para que se le asigne un espacio de memoria, con
tamaño de acuerdo al tipo de datos (recordemos la función sizeof), si este espacio está disponible, la
operación regresa la dirección real que se otorga, en caso de no haber espacio regresa el valor de
NULL (0),
delete apuntador;
La ejecución de este operador provoca que se libere espacio, dejando como valor indefinido, es decir el
sistema operativo lo considera como memoria disponible.
Hay una regla de oro cuando se usa memoria dinámica, toda la memoria que se reserve durante el
programa hay que liberarla antes de salir del programa. No seguir esta regla es una actitud muy
irresponsable, y en la mayor parte de los casos tiene consecuencias desastrosas. No os fiéis de lo que
diga el compilador, de que estas variables se liberan solas al terminar el programa, no siempre es
verdad.
19
Veamos un ejemplo de utilización de los operadores de c++ utilizados para asignar y liberar memoria
dinámicamente.
“En el listado se declaran las variables index de tipo entero y los apuntadores point1 y point2 ambos de
tipo entero.
Progra16.cpp
# include <iostream.h>
main()
{
int index, *point1, *point2;
point1 = &index;
*point1 = 77;
point2 = new int;
*point2 = 173;
cout <<"Los valores son " << index <<" " << *point1 << " "<< *point2 <<'\n';
point1 = new int;
point2 = point1;
*point1 = 999;
cout <<"Los valores son " << index <<" " << *point1 << " "<< *point2 <<'\n';
delete point1;
float *float_point1, *float_point2 = new float;
float_point1 = new float;
*float_point2 = 3.14159;
*float_point1 = 2.4 * (*float_point2);
delete float_point2;
delete float_point1;
char *c_point;
c_point = new char;
delete c_point;
c_point = new char [sizeof(int) + 133];
delete c_point;
}
• point2 ilustra el uso del operador new. Este operador requiere un modificador que debe ser un
tipo. La parte new int significa que se crea un nuevo entero en la memoria, y devuelve la
localización del entero creado. Esta localización es asignada a point2. La siguiente línea asigna
173 al entero al que apunta point2. Es importante distinguir entre point2, la localización del
20
entero, y *point2, el entero. El puntero point2 apunta ahora a una variable entera que se ha
reservado dinámicamente, y que puede utilizarse de igual forma que se hacía en C. Como
ejemplo, se imprime el valor al que apunta.
• A continuación, se reserva memoria para una nueva variable, y point2 se refiere a la misma
variable reservada dinámicamente a la que apunta point1. En este caso, la referencia a la
variable a la que point2 apuntaba previamente se ha perdido, y nunca podrá ser utilizada o su
memoria liberada. Sólo cuando se vuelva al sistema operativo se liberará la memoria que
ocupaba. Por tanto, no debe utilizarse.
• Ya que el puntero point1 en sí no ha cambiado, apunta realmente al dato original. Este dato
podría referenciarse otra vez utilizando point1, pero no es una buena práctica de programación,
ya que no hay garantía de lo que el sistema pueda hacer con el puntero o el dato. La localización
del dato queda libre para ser reservada en una llamada subsiguiente, y será pronto reutilizada en
cualquier programa.
• Ya que el operador delete está definido para no hacer nada si se le pasa un valor NULL, se
puede liberar la memoria ocupada por un dato al que apunta un puntero NULL, ya que realmente
no se está haciendo nada. El operador delete sólo puede utilizarse para liberar memoria
reservada con el operador new. Si se usa delete con cualquier otro tipo de dato, la operación no
está definida, y por tanto nada sucede.
• Finalmente, ya que el operador new requiere un tipo para determinar el tamaño de un bloque
dinámicamente reservado, se muestra cómo reservar un bloque de tamaño arbitrario. Esto es
posible utilizando la construcción de las últimas líneas del programa, donde un bloque de 37
caracteres de tamaño (37 bytes) es reservado. Un bloque de 133 bytes mayor que el tamaño de
un entero se reserva posteriormente. Por tanto, el operador new se puede utilizar con la misma
flexibilidad de la función malloc() de C.
• Cuando los datos reservados dinámicamente son borrados con delete, todavía quedan en
memoria. Si repetimos la instrucción cout inmediatamente después de utilizar delete, veremos
que todavía se conservan los valores. Si la repetimos de nuevo antes de dejar el programa,
cuando el espacio que ocupaban debe haber sido sobre escrito, veremos que ya no es así.
Incluso aunque el compilador nos dé los números correctos, no es una buena práctica pensar
que esos datos están ahí todavía, porque en un programa dinámico largo la memoria se usará
continuadamente.
• Las funciones estándar utilizadas en C para manejo dinámico de memoria, malloc(), calloc() y
free(), también se pueden utilizar en C++ de la misma forma que en C. Los operadores new y
delete no deben mezclarse con estas funciones, ya que los resultados pueden ser
impredecibles. Si se está partiendo de código C, lo mejor es continuar utilizando las funciones en
las nuevas líneas de programa. Si no es así, se deben utilizar los nuevos operadores, ya que se
21
han construido como parte del lenguaje en sí, más que añadirse, y por tanto son más
eficientes”1.
• Cuando se utiliza new para reservar memoria para un vector, el tamaño del vector se sitúa entre
corchetes, siguiendo al tipo:
int *intvector;
intvector = new int [20];
y se libera:
delete [ ] intvector;
Progra17.cpp
#include <iostream.h>
void main()
{
int *apent; // Declara un apuntador a entero
apent = new int ; // Reserva un bloque de memoria dinámica
// de 2 bytes para manejarlo por medio de apent.
*apent = 10 ; // Asigna el valor 10 al objeto apuntado por apent.
cout << *apent ; // Despliega el contenido del objeto apuntado por apent.
delete apent ; // Libera el espacio de memoria manejado por apent.
}
En el listado llamado Progra 17.cpp, se supone que la reservación será exitosa porque existe espacio
suficiente en el montículo.
Pero ¿quién asegura que el espacio requerido por new está disponible?. Para controlar esta situación y
evitar un mensaje de error por parte del sistema en tiempo de ejecución, en el listado siguiente se
propone una nueva versión del programa.
Progra18.cpp
#include <iostream.h>
#include <stdlib.h> // Para exit().
void main()
{
int *apent; // Declara un apuntador a entero
1
Fuente http://www.pablin.com.ar/computer/cursos/c1/allocate.html
22
}
*apent="10" ; // Asigna el valor 10 al objeto apuntado por apent.
cout << *apent ; // Despliega el contenido del objeto apuntado por apent.
delete apent ; // Libera el espacio de memoria manejado por apent.
}
En caso de que el montículo no disponga del espacio requerido, new retorna el valor NULL ( nulo ).
La función malloc () regresa una dirección, y su tipo de retorno es un apuntador a tipo void. ¿Por qué
void?. Un apuntador a tipo void es compatible con todos los tipos de datos. Como la memoria asignada
por malloc () puede ser usada para guardar cualquiera de los tipos de datos de C, es adecuado el tipo
de retorno void.
Al igual que malloc(), free() es una función del lenguaje de programación C, utilizado para liberar la
memoria asignada por malloc (). Al usar la función free () se debe tener en cuenta la regla de oro
explicada en el apartado del operador delete “toda la memoria que se reserve durante el programa hay
que liberarla antes de salir del programa”
Al usar las funciones malloc () y free () se debe incluir el archivo de encabezado STDLIB.H
Ejemplo 1
// asigna memoria para un arreglo de 50 enteros
Int *números;
Números = (int * ) malloc (50 * sizeof (int));
23
Ejemplo 2
// asigna memoria para un arreglo de 10 valores float
float *números;
Números = (float * ) malloc (10 * sizeof (float));
Progra19.cpp
#include <conio.h>
#include <stdlib.h>
#include <stdio.h>
void main()
{
clrscr();
int *p==NULL;
int nbytes=100;
p=(int *)malloc(nbytes);
if(p=NULL)
{
cout<<"Insuficiente espacio en memoria\n");
return -1;
}
cout<<"se han asignado %d bytes de memoria\n", nbytes);
free(p);
getch();
}
El resultado del programa muestra un mensaje en pantalla confirmando que se realizó la asignación
dinámica de memoria con éxito.
Ahora veamos otro ejemplo de aplicación de la asignación dinámica de memoria utilizando las
funciones malloc () y free (), con un ingrediente adicional el uso de una estructura llamada persona que
nos permita almacenar el registro de una persona, dos tipos de datos diferentes, un dato de tipo
carácter que almacena el nombre de la persona y otro dato de tipo entero para almacenar la edad.
Progra20.cpp
// Listado de librerías o archives de cabecera
#include<iostream.h>
#include<stdlib.h>
#include<conio.h>
int n, i;
p =(persona *)malloc(sizeof(persona));
AGUILAR, Luis (2000). Programación en C++, Algoritmos, estructura de datos y Objetos . España:
McGRAW-HILL.
DEYTEL Y DEYTEL (1999). Como programar C++(segunda Edición). Mexico D.F. : Prentice Hall.
McGRAW-HILL.
FARREL, Joyce (2000). Introducción a la programación lógica y diseño. Mexico D.F : Thomson.
http://www.programacionfacil.com/estructura_de_datos/manejo_de_memoria
http://www.conclase.net/c/fuentes.php?tema=3
http://www.ilustrados.com/publicaciones/EpZVVEZpyEdFpAKxjH.php
2
http://www.fismat.umich.mx/mn1/manual/node10.html consultado en Junio 18 de 2009