Está en la página 1de 51

1

21/10/2012

Universidad Nacional Mayor de San Marcos

Facultad de Ingeniera Industrial

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

Qu es un puntero?
Un

puntero es una variable que guarda o contiene la direccin de memoria de otra variable. Cuando una variable contiene la direccin de memoria de otra variable; entonces se dice que la primera variable apunta a la segunda variable. Al respecto examine la figura 1.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

La variable aptrX apunta a la variable x


Para entender mejor el concepto de punteros suponga que la variable x contiene el valor 150 y su direccin de memoria es 0x22ff74; entonces la variable aptrX almacena 0x22ff74.
Variables en memoria aptrX 0x22ff74 Direccin de memoria 0x22ff70 0x22ff71 0x22ff72 0x22ff73 x 150 0x22ff74

Figura 1
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

Operadores de punteros

C y C++ posee dos operadores para trabajar con punteros: Operador &, llamado tambin de referencia Operador *, llamado tambin de desreferencia

Los punteros se declaran con el operador *:


int *aptrX;// puntero a un entero. float*aptrF:// puntero a un flotante char *cad;// puntero a un arreglo de caracteres

Y se asignan con &:


aptrX = &x; //asigna la direccin de x a aptrX
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

Ejemplo 1: Programa que utiliza una variable entera y una variables puntero a entero. Note el uso de los operadores * y &.
#include <iostream> //mostrar el uso de punteros using namespace std; int main() // punter0.cpp { int x; // variable entera int *ptrX; // puntero a entero x = 150; // asignacion ptrX = &x; // asignacion cout<<"Variables y punteros"<<endl; cout<<"\nValor en x =\t"<<x<<endl; cout<<"valor apuntado por ptrX = "<< *ptrX <<endl; cout<<"Direccion de X =\t"<< &x <<endl; cout<<"Contenido de ptrX =\t"<< ptrX <<endl; cout<<"Direccion de ptrX =\t"<< &ptrX <<endl<<endl; return 0; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

Figura 2: Salida del programa punter0.cpp


x 150 0x22ff74 x 150

ptrX

0x22ff74 0x22ff70

ptrX

Figura 3: Abstraccin de la memoria para lo que ocurre con el programa punter0.cpp


Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

Ejemplo 2:Ejemplo de punteros y asignacin de punteros


#include <iostream> #include <conio.h> //punteros y sentencias de asignacion using namespace std; int main() //punter2.cpp { int i, j; // variables enteras int *pi, *pj; //variables puntero a entero // parte 1 i = 220; pi = &i; //asignar la direccion de i a pi pj = pi; //asignar pi a pj cout<<"Parte 1"<<endl; cout<<"Valor en i: "<<i<<"\t\tDireccion de i: "<<pi<<endl; cout<<"Valor apuntado por pi: "<<*pi<<" Direccion en pi: " <<pi<<endl; cout<<"Valor apuntado por pj: "<<*pj<<" Direccion en pj: " <<pj<<endl;
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

//parte 2 j = 500; pj = &j; cout<<"\nParte 2"<<endl; cout<<"Valor en j: "<<j<<"\tDireccion de j: "<<pj<<endl; cout<<"Valor apuntado por pj: "<<*pj<<" Direccion en pj: " <<pj<<endl;; pi = pj; cout<<"Valor apuntado por pi: "<<*pi<<"\tValor apuntado por pj: " <<*pj<<endl;

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21/10/2012

//parte 3 *pj = *pi; cout<<"\nParte 3"<<endl; cout<<"Direccion almacenada en pi: "<<pi<<"\tDireccion almacenada en pj: " <<pj<<endl; cout<<"Valor apuntado por pi: "<<*pi<<"\tValor apuntado por pj: " <<*pj<<endl; cout<<"Valor en i: "<<i<<"\t\t\tValor en j: "<<j<<endl; cout<<"Direccion de pi: "<<&pi<<"\tDireccion de pj: "<<&pj<<endl; getche(); return 0; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

10

21/10/2012

Figura 4: Salida del programa punter2.cpp

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

11

21/10/2012

Figura 5: Abstraccin de memoria del programa punter2.cpp


Parte 1:
i = 220; pi = &i; pj = pi;
pi 0x22ff74 pj 0x22ff74 pi pj i 220 0x22ff74 i 220

Parte 2:
j = 500; pj = &j; pi = pj;
pi 0x22ff70 pj 0x22ff70 pi pj j 500 0x22ff70 j 500

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

12

21/10/2012

Parte 3:
i 220 0x22ff74 j 500 0x22ff70 pi 0x22ff70 0x22ff6c pj 0x22ff70 0x22ff68 pi pj j 500 i 220

*pj = *pi;

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

13

21/10/2012

Aritmtica de punteros
Las operaciones de suma y resta son las nicas que estn permitidas a nivel de punteros, aunque su significado no es el mismo que en los tipo_dato base. Asuma que ptr1 es un puntero a entero y almacena 0xA100 y que adems, los enteros ocupan una longitud de cuatro bytes. Despus de realizar la expresin ptr1++; Har que ptr1 ahora contenga 0xA104. Esto significa que cada vez que ptr1 se incrementa en 1, apunta al siguiente entero en la memoria. Lo inverso tambin es cierto. Por ejemplo si ptr1 contiene 0xA100, la expresin. ptr1--;

hace que ptr1 tenga almacenado 0xA096.


Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

14

21/10/2012

Reglas de la aritmtica de punteros


Regla

1: Cada vez que se incrementa un puntero, apunta a la posicin de memoria del siguiente elemento de su tipo_dato. 2: Cada vez que se decrementa un puntero apunta a la posicin de memoria del elemento anterior al de su tipo_dato.

Regla

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

15

21/10/2012

Estas reglas no son exclusivas para los operadores de incremento y disminucin, tambin se aplican a los operadores de suma y resta, por ejemplo se pueden sumar y restar enteros a punteros. De all que si ptrC es una variable que guarda la direccin de memoria 0xA110 y es un puntero a caracter (un caracter se almacena en un byte); entonces la expresin. ptrC = ptrC + 5; hace que ptrC apunte al elemento que se encuentra en la quinta posicin despus de ptrC; es decir, ptrC almacena ahora 0xA115;

Las dems operaciones aritmticas como multiplicar y dividir punteros no estn definidas; tampoco, se pueden aplicar la suma o resta del tipo float o double a los punteros.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

16

21/10/2012

Ejemplo 3: Programa que utiliza la aritmtica de punteros


#include <iostream> const int N = 10; using namespace std; int main() //punter6.cpp { int *px, i; int x[N]; //cargando el array for (i=0; i<N; i++) x[i]=2*i; //mostrando el array px = &x[0]; // asignacion for (i = 0; i < N; i++) cout<<"Elemento "<<i<<" valor "<<x[i]<<" direccion = " << px + i <<endl; cout<<endl; return 0; } Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

17

21/10/2012

Figura 6: Salida del programa punter6.cpp

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

18

21/10/2012

Comparacin de punteros
Dos punteros pueden compararse en una expresin relacional. Por ejemplo si p y r se han declarado y definido como punteros, la expresin.
if (p < r) cout<<p apunta a menor memoria que r\n; else cout<< p apunta a mayor memoria que r\n;

Es perfectamente vlida y correcta. La comparacin de punteros puede resultar til cuando dos punteros apuntan a un mismo objeto, como puede ser un array o una estructura de datos compuesta.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

19

21/10/2012

Punteros a arrays
Es posible declarar un puntero a un array de enteros o de cualquier otro tipo de datos y pasarlos como argumentos a una funcin. El programa del ejemplo 4, muestra el paso de un array como un puntero al arreglo. En este caso el identificador del array es un puntero al array. Observe el primer argumento de la funcin pasarArray_3.

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

20

21/10/2012

Ejemplo 4: Paso de un puntero a un array de enteros


#include<iostream> const int MAX = 30; //prototipo de funcion void pasarArray_3( int *num, int n ); using namespace std; int main() //funarrayPunter.cpp { int i, n, num[MAX]; cout<<"Cuantos datos para el arreglo? "; cin>>n; //leer los elementos del array cout<<"\nIngrese "<<n<<" datos: "; for (i = 0; i < n; i++) cin >> num[i]; pasarArray_3(num, n); //llamada a la funcion return 0; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

21

21/10/2012

//Paso de un array como una referencia o puntero a el void pasarArray_3( int *num, int n ) { cout << "\nElementos en el array: "; int i; for ( i = 0; i <n; i++) cout<<num[i]<<" "; cout << endl; cout << "\nElementos apuntados: "; for ( i = 0; i < n; i++) cout<<*num++<<" "; cout << endl; cout << "\nDirecciones de los elementos: "; for (i = 0; i < n; i++) cout << num++<<" "; cout << endl << endl; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

22

21/10/2012

Figura 7: Salida del programa funarrayPunter.cpp


Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

23

21/10/2012

Arrays de punteros
Al igual que cualquier otro tipo de datos, los punteros tambin pueden configurarse como arrays de punteros. La siguiente sentencia.

int *x[4];
declara x como un array de punteros a enteros(int) de tamao 4. Ver figura 8.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

24

21/10/2012

int *x[4]; 0 1 2 3 int int int int

Figura 8: Un arreglo de punteros a int


Si queremos asignar la direccin de una variable entera llamada prueba al tercer elemento del array de punteros a enteros de la figura 7.13 escribimos x[2] = &prueba; Ahora, para encontrar el valor de prueba, escribimos *x[2]; El siguiente ejemplo muestra el paso de un array de punteros a una funcin.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

25

21/10/2012

Ejemplo 5: muestra el paso de un array de punteros a enteros a una funcin


#include<iostream> //prototipo de funcion void pasarArrayPunteros( int *num[], int n ); using namespace std; int main() //funarrayPunter2.cpp { int i, n = 5; int *x[5]; // array de punteros a cinco int //asignacion de enteros al array de punteros a int int prueba[] = {10,20,30,40,50}; for ( i = 0; i < 5; i++) x[i] = &prueba[i]; pasarArrayPunteros(x, n); //llamada a la funcion return 0; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

26

21/10/2012

//como una referencia o puntero void pasarArrayPunteros( int *num[], int n ) { cout<<"Enteros apuntados por el array de punteros:\n\n"; int i; for ( i = 0; i < n; i++) cout<<"num["<<i<<"] -> entero:<<*num[i] <<endl; cout<<endl; }

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

27

21/10/2012

Figura 9: Salida para funarrayPunter2.cpp


No olvide que num en realidad no es un puntero a entero, sino un puntero a un array de punteros. Por tanto, el parmetro num se declara como un array de punteros a enteros como se ve en la funcin pasarArrayPunteros, es decir; num no es un puntero a enteros.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

28

21/10/2012

Indireccin simple versus Indireccin mltiple.


Al escribir cdigo como el siguiente

int x, *ptrX; x = 50; ptrX = &x;


trabajamos con la indireccin simple. Pero podemos hacer que un puntero apunte a otro puntero que apunte a un valor de destino final. A esto se conoce como indireccin mltiple o punteros a punteros.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

29

21/10/2012

I ) Indireccin simple
ptrX 0x22ff74 0x22ff70 x 500 0x22ff74

II ) Indireccin mltiple
ptrA 0x22ff70 0x22ff66 ptrB 0x22ff78 0x22ff70 ab 2500 0x22ff78
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

30

21/10/2012

Para que una variable sea un puntero a puntero tiene que ser declarada como tal. Para lograrlo se coloca un * adicional delante del identificador o nombre de la variable. Por ejemplo la declaracin. float **sueldoNuevo;

Indica al compilador que sueldoNuevo es un puntero a un puntero a float y no un simple puntero a float.

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

31

21/10/2012

Ejemplo 6: Indireccin simple Vs. Indireccin mltiple


#include <iostream> using namespace std; int main() //punteroPuntero.cpp { float pago = 1250.50; float *sueldo, **sueldoNuevo; sueldo = &pago; sueldoNuevo = &sueldo; cout<<"pago = "<<pago<<endl; cout<<"sueldo = "<<*sueldo<<endl; cout<<"sueldoNuevo ="<<**sueldoNuevo*1.10 <<endl; return 0; Mg. Edgar Ruiz Lizama } eruizl@industrial.unmsm.pe

32

21/10/2012

Punteros, arrays y cadenas


Se dice que existe una relacin muy estrecha entre los punteros y los arrays. Al respecto veamos las siguientes lneas de cdigo. char cad[80], *ptrC; ptrC = cad; El efecto de estas lneas es asignar a ptrC la direccin del primer elemento del array de caracteres cad.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

33

21/10/2012

Como ya sabemos el ndice de un array comienza en 0. De all que cad [8] *(ptrC + 8) se refieren al noveno elemento del array cad, esto es; para acceder al noveno elemento puede usarse 8 como ndice de cad, o tambin se puede sumar 8 al puntero ptrC; porque en el instante de su asignacin ptrC apunta realmente al primer elemento de cad.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

34

21/10/2012

Se dice que el identificador de una cadena es un puntero a dicha cadena, de all que las declaraciones
char s[] = AMOR; s A M O R \0

y
char *s = AMOR; s A M O R \0

Se consideran equivalentes.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

Ejemplo 7:Dos versiones para calcular la longitud de una cadena


// Version 1 int longcad( char *s ) { int i = 0; while (*s++) ++i; return i; }
21/10/2012 Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe 35

// Version 2 int longcad( char *s ) { int i = 0; while (s[i] != 0) ++i; return i; }

21/10/2012

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe 36

37

21/10/2012

Devolucin de cadenas
Las cadenas pueden ser devueltas por una funcin siempre que se haga como un puntero a ella. La siguiente declaracin de funcin
char* devolverCadena(char cad1[], char cad2[]);

Indica al compilador que se devolver un puntero a un array de cadena de caracteres. Examine el ejemplo 8.

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

38

21/10/2012

Ejemplo 8: Devolviendo la cadena que resulta de concatenar dos cadenas recibidas como argumento.
#include <iostream> #include <string.h> #include <stdio.h> char* devolverCadena( char cad1[], char cad2[] ); using namespace std; int main() // cadena25.cpp { char cad1[80], cad2[80]; cout<<"Ingrese una cadena 1: "; gets(cad1); cout<<"Ingrese una cadena 2: "; gets(cad2); cout<<"Resultado: "<<devolverCadena(cad1,cad2)<<endl; return 0; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

39

21/10/2012

char* devolverCadena(char cad1[], char cad2[])

{ char blanco[] = " "; //adicionando un blanco al final de cad1 strcat(cad1,blanco); return (strcat(cad1,cad2)); }

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

Ejemplo 9: Dos versiones para una funcin que copia cadenas


// Version 1 char* copiacad(char *dest, char *orig) { int tam = strlen(orig); dest = new char[tam+1]; int i = 0; for ( ; orig[i] != 0; i++) dest[i] = orig[i]; dest[i]='\0'; // marca de fin de cadena return dest; }
21/10/2012 Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe 40

// Version 2 char* copiacad(char *dest, char *orig) { int tam = strlen(orig); dest = new char[tam+1]; int i = 0; while( dest[i] = orig[i]) i++; return dest; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe 41

21/10/2012

42

21/10/2012

Ejemplo 10:Programa que crea un array de punteros a cadenas de caracteres, segn la figura 10.
char 0 1 2 3 4 5 *p[];
G E A D M a s n a e b t a v l i i r r i e L d s s e l u l l c M a a i e \0 a l J a C \0 e n d e z \0 r o a r \0 i \0

NULL

Figura 10: Un array de punteros a cadenas


Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

43 #include <iostream> #include <string.h> /* Objetivo: Arrays de punteros a cadenas */ //prototipo de las funciones int buscaNombres(char *p[], char *nomb); void printArrayCadenas(char *p[]); using namespace std;

21/10/2012

int main() // Array_Punteros_Cadenas.cpp { char *nombres[] = {"Gabriel Jara", "Estrella Cori", "Ana Lucia", "David Melendez", "Melissa", NULL}; printArrayCadenas(nombres); cout<<"Buscando en el array de cadenas:"<<endl<<endl; if ( buscaNombres(nombres,"Estrella Cori") != -1) cout<<"Estrella Cori; si esta en lista\n"; else cout<<"Estrella Cori; no esta en lista\n"; if ( buscaNombres(nombres,"Jose Carlos") == -1) cout<<"Jose Carlos; no esta en Lista\n"; else cout<<"Jose Carlos; si esta en lista\n"; return 0; } Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

int buscaNombres(char *p[], char *nomb) { for (int i = 0; p[i] ; ++i) if ( strcmp(p[i], nomb) == 0 ) return i; return -1; //codigo de error: no se encuentra } void printArrayCadenas(char *p[]) { cout << "\nCadenas en el array o vector:\n"<<endl; int i = 0; while (p[i] != NULL) { cout<<p[i]<<endl; i++; } cout<<endl<<endl; }
21/10/2012 Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe 44

Vectores de cadena
Es posible crear un vector de cadenas, por ejemplo la declaracin char vectorCads[4][80]; crea un vector de 4 cadenas cada una de longitud hasta 80 caracteres. Asumiendo que estas cuatro cadenas son: Hola como, estan, todos ustedes, por alli; se mapean en memoria como:
vectorCads Hola como estan todos ustedes por alli

21/10/2012

Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe 45

46

21/10/2012

Ejemplo 11: Escribir la funcin cuyo prototipo es el siguiente:


int GeneraVectorCadena( char C[], char nC[N][M], char car);

donde C es la cadena original, nC es el vector de cadenas a generar y car es un carcter alfabtico cualquiera. La funcin genera un vector de cadenas cuyo contenido son las palabras que empiezan con el carcter car y finalmente devuelve la cantidad de elementos que se guardaron en el vector de cadenas. As por ejemplo, si car es el carcter e y la cadena C es:
Electron, tipo de particula elemental de carga negativa que forma parte de la familia de los leptones y que, junto con los protones y los neutrones, forma los atomos y las molculas. Los electrones estan presentes en todos los atomos y cuando son arrancados del atomo se llaman electrones libres". Tomado de Microsoft Encarta 2007. Se han omitido las tildes.
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

47

21/10/2012

El vector nC generado por la funcin ser:


elemental electrones estan en electrones

#include <iostream> #include <string.h> const int N = 20; const int M = 80; int GeneraVectorCadena( char C[], char nC[N][M], char car); using namespace std; int main() // Matriz_Cadenas_071.cpp { char *C = "Electron, tipo de particula elemental de carga negativa que forma parte de la familia de los leptones y que, junto con los protones y los neutrones, forma los atomos y las molculas. Los electrones estan presentes en todos los atomos y cuando son arrancados del atomo se llaman electrones libres";
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

48

21/10/2012

cout<<"\nLongitud de la cadena C = "<<strlen(C)<<endl; char nC[N][M]; char car = 'e'; cout<<"\nPalabras en el vector de cadenas:"<<endl; int npals = GeneraVectorCadena(C, nC, car); cout<<"\nNumero de palabras almacenadas en el vector = " <<npals<<endl<<endl; return 0; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

49

21/10/2012

int GeneraVectorCadena( char C[], char nC[N][M], char car) { int i = 0, j, k = 0, contPal = 0; char palab[20]; while ( C[i] != 0 ) { // sacando cada palabra j = 0; while ( C[i] != ' ' ) { palab[j] = C[i]; j++; i++; } palab[j] = '\0'; // si primer caracter de palabra es igual a car if ( palab[0] == car ) { // se mete en el vector de cadenas strcpy(nC[k], palab); cout<<nC[k]<<endl;//imprime palabra almacenada en el vector k++; contPal++; } i++; } return contPal; }
Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

50

21/10/2012

Figura 11: Salida del programa matrizCadenas071.cpp


Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

Referencias
1.

51

21/10/2012

Deitel H.M. y Deitel P.J. (2003). "C++ Cmo Programar". Mxico. Prentice-Hall Hispanoamericana., 1320 p. 2. Kernighan Brian W. & Ritchie Dennis M. (1991). El lenguaje de Programacin C Mxico. 2da. Edicin. Prentice-Hall Hispanoamericana., 294 p. 3. Ruiz Lizama Edgar (2009). Programacin con C++ Lima, 1ra. Edicin, fondo editorial UNMSM, 434 p. 4. Ruiz Lizama, Edgar (1999). "Curso de Lenguaje C" Lima, UNMSM Facultad de Ingeniera Industrial 150 p. 5. Schildt Herbert (2000). C Manual de Referencia Espaa. 4ta. Edicin, Osborne McGraw-Hill S.A. 709p. 6. Schildt Herbert (1996). C++ Para Programadores Madrid. McGraw Hill/Interamericana de Espaa S.A. 398p. 7. Stroustrup, Bjarne (1993). "El Lenguaje de Programacin C++". U.S.A. Addison Wesley Iberoamericana. 710 p. 8. Stroustrup, Bjarne (2002) "El Lenguaje de Programacin C++ Edicin especial". Espaa. Addison Wesley PEARSON EDUCACION S.A. 1050 p. E.R.L. Mg. Edgar Ruiz Lizama eruizl@industrial.unmsm.pe

También podría gustarte