Está en la página 1de 10

Accede a apuntes, guías, libros y más de tu carrera

Punteros II

pag.

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Unidad 5

Punteros II

Objetivos de aprendizaje: Al finalizar esta unidad el estudiante es capaz


de

Comprender los punteros a vacı́o y saber utilizarlos para almacenar direc-


ciones de memoria e implementar funciones capaces de recibir punteros de
cualquier tipo.

Entender que estos punteros a vacı́o son genéricos en el sentido que pueden
recibir implı́citamente el valor de cualquier puntero.

Conocer el orden de precedencia de los operadores de punteros.

Definir un puntero a puntero y manipularlo.

Visualizar una matriz como un arreglo de punteros y comprender que es


la perspectiva más fidedigna a cómo la computadora almacena matrices.

Definir funciones capaces de manipular matrices sólo al recibir el nombre


de las mismas.

5.1. Continuando con punteros y operadores de


punteros
5.1.1. Puntero a vacı́o
En la unidad anterior vimos que un puntero es un sistema y que hay diversos
tipos de punteros, e.g., punteros a enteros (int*, punteros a flotantes (double*.
Existe un tipo especial de puntero llamado puntero a vacı́o: void*. Si declara-
mos ptr como un puntero del tipo void*, significa literalmente que ptr “apunta
algún lugar de la memoria de la computadora tal que el compilador no sabe el
tipo de este lugar.” Por consiguiente, no podemos realizar (bajo la aritmética de
punteros visto en la unidad anterior) la siguiente sentencia: ptr++;, i.e., no se

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

sabe “el tamanõ (cantidad de bytes) de los pasos cuando nos movemos a través
de ptr.” La razón de la existencia de estos punteros es para guardar en ellos
direcciones de memoria y manipular esta información de manera independiente
a los tipos de estos lugares, i.e., si son direcciones de lugares que guardan valo-
res enteros o flotanes o caracteres, etc. Veamos su uso a través de las siguientes
sentencias.

1 int nota = 20;


2 void* p_void = &nota;

En la lı́nea 2 se declara e inicializa el puntero a vacı́o ptr asignándole la


dirección de memoria de la variable nota.

1 int nota = 20, *p_int = &nota;


2 void* p_void = p_int;

La sentencia de la lı́nea 2 nos resalta que es posible asignar a un puntero a


vacı́o el valor de cualquier tipo de puntero.

1 int nota = 20, *p_int = nullptr;


2 void* p_void = &nota;
3 p_int = p_void;

La sentencia de la lı́nea 3 presenta un error del tipo: invalid conversion from ’void*’ to ’int*’.
En efecto, en dicha lı́nea se produce un error de sintaxis al intenter asignar el
valor de un puntero a vacı́o a un puntero a entero. No obstante, aunque no es
lo recomendado, es posible realizar esta tarea mediante una conversión de tipos
explı́cita:

p_int = static_cast<int*>(p_void);

Esto guarda coherencia con la idea literal del puntero a vacı́o, pues este se
define como aquel tipo de puntero que no apunta a un tipo especı́fico de dato:
es un puntero que puede guardar cualquier dirección de memoria en general.

El operador unario de conversión de tipos static_cast<T>(v) realiza la


conversión al tipo T del valor v siempre que T puede ser convertido (implı́cita-
mente) al tipo de v. Sin embargo, no se motiva el uso de este operador porque
puede originar errores (lógicos) sutiles, i.e., difı́ciles de detectar. A continua-
ción, veamos una aplicación del puntero a vacı́o para definir una función capaz
de recibir cualquier tipo de puntero. En este sentido, los punteros a vacı́o son
punteros genéricos.

Ejemplo 5.1. En las lı́neas 8-10 se llama a la función mostrar, la cual recibe
punteros del tipo entero y flotante.

Página 2 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

1 #include <iostream>
2 using namespace std;
3 void mostrar(void* p, void* q);
4

5 int main() {
6 int nota = 20;
7 double promedio = 20;
8 mostrar(&nota,&promedio);
9 mostrar(&nota,&nota);
10 mostrar(&promedio,&nota);
11 }
12

13 void mostrar(void* p, void* q) {


14 cout << "dir1:\t" << p << endl;
15 cout << "dir2:\t" << q << endl;
16 if (p < q)
17 cout << "dir2 esta a la derecha de dir1.\n";
18 else if (p == q)
19 cout << "direcciones iguales.\n";
20 else
21 cout << "dir2 esta a la izquierda de dir1.\n";
22 cout << endl;
23 }

Listado 1: Programa que muestra el uso del puntero vacı́o como parámetro.

Luego, al trasladarse el flujo del programa a la lı́nea 13 acontece una conver-


sión (implı́cita) de tipos, aprovechándose la flexibilidad de este tipo de punteros:
ser capaces de guardar direcciones de memoria de cualquier lugar. Sin embargo,
la contraparte a esta flexibilidad es la limitación de estos punteros a las opera-
ciones aritméticas (bajo la lógica de punteros), e.g., no podemos sumar un valor
entero a un puntero a vacı́o ni restar dos punteros a vacı́o. ♦

5.1.2. Reglas de precedencia de los operadores de punte-


ros
La siguiente tabla resume el orden de precedencia de los operadores de pu-
teros; ası́ como, el valor de la expresión donde este es utilizado:

Página 3 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

Expresión Descripción
p[x] operador de suscripción; equivalente a *(p+x)
v++ operador post-incremento; el valor de v++ es el valor de v
antes del incremento.
v-- operador post-disminución; el valor de v-- es el valor de v
antes de la disminución.
static_cast<T>(x) operador de conversión de C++; el valor de
static_cast<T>(x) es x y el tipo es T.
sizeof(T) el tamaño de T en bytes.
sizeof(x) el tamaño en bytes de una expresión del tipo de x.
++v operador pre-incremento; el valor de ++v es el valor de v
después del incremento.
--v operador pre-disminución; el valor de --v es el valor de v
después de la disminución.
&v operador dirección; el valor de &v es la dirección de memoria
del sistema cuyo nombre (identificador) es v.
*p operador indirección; el valor de *p es el valor del sistema
apuntado por p.
(T)x operador de conversión de tipos de C; el valor de (T)x es x
y el tipo es T.

Cuadro 5.1: Orden de precendencia –de arriba hacia abajo– de algunos opera-
dores relacionados al manejo de punteros.

Arriba se presentan dos cuadros; los operadores de cada cuadro tienen la


misma precedencia. Los operadores del cuadro superior tienen mayor prioridad
que los operadores del cuadro inferior. Por ejemplo, *p++ significa *(p++), no
(*p)++.

1 int notas[CAPACIDAD] = {18,19,20}, *p = notas;


2 for (int j = 0; j < CAPACIDAD; ++j)
3 cout << "notas[" << j << "] = " << *p++ << endl;

En la lı́nea 3 se evalua la expresión *p++ como *(p++).

5.2. Matrices y punteros


5.2.1. Punteros a punteros
En un puntero podemos guardar en general una dirección de memoria; en
particular, podemos guardar la dirección de memoria de otro puntero. A este
tipo particular de puntero se le conoce como puntero a puntero. Más adelante
veremos que la matriz, estudiada en la unidad 3, cae en esta categorı́a.

Página 4 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

1 int nota = 20;


2 int* p_int = &nota;
3 int** pp_int = &p_int;
4 cout << "nota = " << *p_int << endl;
5 cout << "nota = " << **pp_int << endl;

En las lı́neas 2 y 3 se declaran p_int y p_p_int como puntero a entero y


puntero a puntero a entero, respectivamente, i.e., p_p_int puede apuntar a un
lugar de la memoria que a su vez apunta a un lugar donde se guardan valores
enteros. Además, hacemos que p_int apunte a nota y p_p_int apunte a p_int.
Finalmente, en la lı́nea 5 se ve cómo accedemos al valor de nota desde un puntero
a puntero.

5.2.2. Una matriz es un arreglo de punteros


A continuación, veamos el nexo entre un arreglo de punteros y una matriz a
través del siguiente programa.
Ejemplo 5.2. En la lı́nea 6 se declara arreglo como un sinónimo de arreglo
de tres enteros.
1 #include <iostream>
2 #include <iomanip>
3 using namespace std;
4 const int LABORATORIOS = 3;
5 const int ESTUDIANTES = 2;
6 typedef int arreglo[LABORATORIOS];
7

8 int main() {
9 arreglo cc112[ESTUDIANTES] = {{15,16,17},{18,19,20}};
10 //arreglo* p_arreglo = cc112;
11 for (int j = 0; j < ESTUDIANTES; ++j) {
12 cout << "Estudiante " << j << ":";
13 for (int k = 0; k < LABORATORIOS; ++k) {
14 cout << setw(4) << cc112[j][k];
15 }
16 cout << endl;
17 }
18 }

Listado 2: Programa que muestra que un arreglo bidimensional es un arreglo de


punteros a arreglos.

Luego, en la lı́nea 9 se declara cc112 como un arreglo bidimensional; como


todo nombre de arreglo, es es un puntero constante, pero esta vez dicho puntero
es un puntero a arreglo, i.e., un puntero a un arreglo de tres enteros. ♦

Página 5 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

5.2.3. Manejo de matrices


Finalmente, veamos cómo definir funciones que reciban las coordenadas (di-
recciones de memoria) de matrices ya sea para acceder a los valores guardados
en ellas o para modificar dichos valores.

Ejemplo 5.3. En la lı́nea 6 se declara arreglo como un sinónimo de arreglo


de tres enteros.
1 #include <iostream>
2 #include <iomanip>
3 using namespace std;
4 const int FILAS = 2;
5 const int COLUMNAS = 3;
6 typedef int arreglo[COLUMNAS];
7 void mostrar(arreglo* p_arreglo);
8

9 int main() {
10 arreglo notas[FILAS] = {{15,16,17},{18,19,20}};
11 mostrar(notas);
12 }
13

14 void mostrar(arreglo* p_arreglo) {


15 for (int j = 0; j < FILAS; ++j) {
16 cout << "Fila " << j << ":";
17 for (int k = 0; k < COLUMNAS; ++k) {
18 cout << setw(4) << p_arreglo[j][k];
19 }
20 cout << endl;
21 }
22 }

Listado 3: Programa que muestra el paso de un puntero a arreglo como paráme-


tro.

Luego, en la lı́nea 11 se llama a la función mostrar y se le pasa una copia


del valor de notas, i.e., un puntero a un arreglo de tres enteros. Esto queda en
evidencia al asignarle a p_arreglo dicho argumento. ♦

5.3. Ejercicios
5.3.1. Nivel bajo de dificultad
Ejercicio 5.1. Indique lo que se muestra en la pantalla al ejecutar el siguiente
programa y explique por qué.

Página 6 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

1 #include <iostream>
2 using namespace std;
3

4 int main() {
5 int *p, *q, nota = 20;
6 p = &nota;
7 q = &nota;
8 cout << "*p\t=\t" << *q << endl;
9 cout << "*q\t=\t" << *q << endl;
10 return 0;
11 }
Ejercicio 5.2. Indique lo que se muestra en la pantalla al ejecutar el siguiente
programa y explique por qué.
1 #include <iostream>
2 using namespace std;
3

4 int main() {
5 int **p, *p_int, nota = 20;
6 p = &p_int;
7 p_int = &nota;
8 cout << "**p\t\t=\t" << **p << endl;
9 cout << "p[0][0]\t=\t" << p[0][0] << endl;
10 return 0;
11 }
Ejercicio 5.3. Indique lo que se muestra en la pantalla al ejecutar el siguiente
programa y explique por qué.
1 #include <iostream>
2 using namespace std;
3

4 int main() {
5 int***r, **q, *p, j = 8;
6 p = &j;
7 q = &p;
8 r = &q;
9 cout << *p << "\t" << **q << "\t" << ***r << endl;
10 return 0;
11 }
Ejercicio 5.4. Indique lo que se muestra en la pantalla al ejecutar el siguiente
programa y explique por qué.
1 #include <iostream>
2 #include <iomanip>

Página 7 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

3 using namespace std;


4 const int FILAS = 2;
5 const int COLUMNAS = 3;
6 typedef int arr[COLUMNAS];
7

8 void mostrar(arr* p_arr);


9

10 int main() {
11 arr notas[FILAS] = {{15,16,17},{18,19,20}};
12 cout << "notas\t\t\t=\t" << notas << endl;
13 cout << "notas + 1\t\t=\t" << notas + 1 << endl;
14 cout << "notas[0]\t\t=\t" << notas[0] << endl;
15 cout << "notas[0] + 1\t=\t" << notas[0] + 1 << endl;
16 }

Ejercicio 5.5. Imprima los elementos de la matriz del ejemplo anterior sin usar
el operador de suscripción: [].

5.3.2. Nivel medio de dificultad


Ejercicio 5.6. Se sabe que los primos menores que 100 son 25. Defina el vector
primos donde se debe guardar en forma creciente todos los primos menores
que 100. Defina la función void mostrar(int* p, int n) tal que al ajecutar
mostrar(primos,25) se muestre en la pantalla los elementos del vector primos.

Ejercicio 5.7. Genere el mismo vector primos y defina la misma función

void mostrar(int* p, int n)

de la pregunta anterior. Complete la siguiente función:

void desordenar(int* p, int n) {


int k;
// ...
for (int j = n; j > 1; j--) {
k = rand() % j;
// intercambiar los valores de p[n - 1] y p[k]
}
}

Luego, ejecute: desordenar(primos,25); mostrar(primos,25); Finalmente,


imprima un mensaje explicando lo que se hace al ejecutar desordenar(primos,25).

Ejercicio 5.8. Genere el mismo vector primos y defina las mismas funciones

void mostrar(int* p, int n) y


void desordenar(int* p, int n)

Página 8 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com


Fundamentos de Programación Juan Espejo

de la pregunta anterior. Luego, ejecute: desordenar(primos,25); mostrar(primos,25);


y ordene el vector primos según el método de ordenamiento de burbuja. Final-
mente, muestre la cantidad de comparaciones que se realizó para dicho ordena-
miento y vuelva a ejecutar mostrar(primos,25);
Ejercicio 5.9. Genere el mismo vector primos y defina las mismas funciones
void mostrar(int* p, int n) y
void desordenar(int* p, int n)
de la pregunta anterior. Luego, ejecute: desordenar(primos,25); mostrar(primos,25);
y ordene el vector primos según el método de ordenamiento rápido. Finalmente,
muestre la cantidad de comparaciones que se realizó para dicho ordenamiento y
vuelva a ejecutar mostrar(primos,25);
Ejercicio 5.10. Implemente una función tal que al recibir el nombre de una
matriz de enteros de 7 filas y 3 columnas, llene los elementos de dicha matriz con
valores enteros aleatorios del intervalo [3, 8]. Esta matriz representa la cantidad
de plumones vendidos en cada una de las 3 tiendas de una empresa los 7 dı́as
de la semana. Se sabe que el precio de dicho plumón en las tiendas A, B y C
son 8, 9 y 10 soles respectivamente. Finalmente, implemente otra función que
al recibir el nombre de una matriz de ventas, imprima el monto facturado por
la empresa en cada dı́a de la semana.

5.3.3. Nivel alto de dificultad


Ejercicio 5.11. Se dice que una matriz A es simétrica (antisimétrica) si
AT = A (AT = −A). Toda matriz cuadrada puede expresarse de manera única
como la suma de una matriz simétrica y una matriz antisimétrica. Genere alea-
toriamente una matriz A cuadrada de orden 2 cuyos elementos son enteros del
intervalo [2; 5]. Finalmente, implemente una función tal que al recibir el nombre
de una matriz cuadrada de orden 2 muestre dicha matriz A como la suma de
una matriz simétrica y otra antisimétrica. Sugerencia:
1 1
A= (A + AT ) + (A − AT ).
2 2
Ejercicio 5.12. Genere los coeficientes de un sistema lineal de dos ecuaciones
con una incógnita donde las entradas de la matriz aumentada de dicho sistema
son números enteros del intervalo [0,2]. Finalmente, implemente una función tal
que al recibir el nombre de una matriz aumentada, resuelva dicho sistema e
imprima el conjunto solución del mismo.
Ejercicio 5.13. Genere aleatoriamente las notas de los 6 laboratorios calificados
de 7 estudiantes. Defina una función tal que al recibir el nombre de una matriz de
notas (de 6 laboratorios de 7 estudiantes) calcule el máximo entero del promedio
de laboratorios para cada estudiante (recuerde que la nota más baja se elimina)
e imprima los tres mayores valores (de todos estos 7 valores enteros).

Página 9 de 9

Descargado por Mariavirginia (detodounpocomarivi@gmail.com)

Encuentra más documentos en www.udocz.com

También podría gustarte