1. República Bolivariana de Venezuela
Ministerio del poder popular para la defensa
Universidad experimental politécnica de la
Fuerza armada Bolivariana.
Profesor:
Luis Sequera.
Integrante:
Mariannis Ortega.
2. Apuntadores
Los apuntadores son una parte fundamental de C. Si
usted no puede usar los apuntadores apropiadamente
entonces está perdiendo la potencia y la flexibilidad que
C ofrece básicamente. El secreto para C está en el uso
de apuntadores.
C usa los apuntadores en forma extensiva. ¿Por qué?
Es la única forma de expresar algunos cálculos.
Se genera código compacto y eficiente.
Es una herramienta muy poderosa.
C usa apuntadores explícitamente con:
Es la única forma de expresar algunos cálculos.
Se genera código compacto y eficiente.
Es una herramienta muy poderosa.
C usa apuntadores explícitamente con:
Arreglos,
Estructuras y
Funciones
3. Definición de un apuntador
Un apuntador es una variable que contiene la
dirección en memoria de otra variable. Se pueden tener
apuntadores a cualquier tipo de variable.
El
operador unario o monódico & devuelve
dirección de memoria de una variable.
la
El operador de indirección o de referencia * devuelve
el ``contenido de un objeto apuntado por un apuntador''.
Para declarar un apuntador para una variable entera
hacer:
int *apuntador;
Se debe asociar a cada apuntador un tipo particular.
Por ejemplo, no se puede asignar la dirección de
un short int a un long int.
Para tener una mejor idea, considerar el siguiente
código:
main ()
{
int x = 1, y = 2;
int *ap;
ap = &x;
y = *ap;
x = ap;
*ap = 3;
}
4. Cuando se compile el código se mostrará el
siguiente
mensaje:
warning: assignment makes integer from pointer without
a cast.
Con el objetivo de entender el comportamiento del
código supongamos que la variable x esta en la localidad
de la memoria 100, y en 200 y ap en 1000. Nota: un
apuntador es una variable, por lo tanto, sus valores
necesitan ser guardados en algún lado.
int x = 1, y = 2;
int *ap;
ap = &x;
100 200 1000
x 1 y 2 ap 100
Las variables x e y son declaradas e inicializadas
con 1 y 2 respectivamente, ap es declarado como un
apuntador a entero y se le asigna la dirección de x (&x).
Por lo que ap se carga con el valor 100.
y = *ap;
100 200 1000
x 1 y 1 ap 100
Después y obtiene el contenido de ap.
En el ejemplo ap apunta a la localidad de memoria 100 - la localidad de x. Por lo tanto, y obtiene el valor de x -el cual es 1.
5. x = ap;
100
200 1000
x 100 y 1 ap 100
Como se ha visto C no es muy estricto en la
asignación de valores de diferente tipo (apuntador a
entero). Así que es perfectamente legal (aunque el
compilador genera un aviso de cuidado) asigna el valor
actual de ap a la variable x. El valor de ap en ese
momento es 100.
*ap = 3;
100 200 1000
x 3 y 1 ap 100
Finalmente se asigna un valor al contenido de un
apuntador (*ap).
Importante: Cuando un apuntador es declarado
apunta a algún lado. Se debe inicializar el apuntador
antes de usarlo. Por lo que:
main()
{
int *ap;
*ap = 100;
}
Puede generar un error en tiempo de ejecución o
presentar un comportamiento errático.
6. El uso correcto será:
main()
{
int *ap;
int x;
ap = &x;
*ap = 100;
}
Con los apuntadores se puede realizar también
aritmética entera, por ejemplo:
main()
{
float *flp, *flq;
*flp = *flp + 10;
++*flp;
(*flp)++;
flq = flp;
}
Algo muy importante que debemos notar: Un
apuntador a cualquier tipo de variables es una dirección
en memoria -- la cual es una dirección entera, pero un
apuntador NO es un entero.
La razón por la cual se asocia un apuntador a un
tipo de dato, es porque se debe conocer en cuantos
bytes está guardado el dato. De tal forma, que cuando
se incrementa un apuntador, se incrementa el apuntador
7. por un ``bloque'' de memoria, en donde el bloque está en
función del tamaño del dato.
Por lo tanto para un apuntador a un char, se agrega
un byte a la dirección y para un apuntador a entero o a
flotante se agregan 4 bytes. De esta forma si a un
apuntador a flotante se le suman 2, el apuntador
entonces se mueve dos posiciones float que equivalen a
8 bytes.
Apuntadores y Funciones
Cuando C pasa argumentos a funciones, los
pasa por valor, es decir, si el parámetro es modificado
dentro de la función, una vez que termina la función el
valor pasado de la variable permanece inalterado.
Hay muchos casos que se quiere alterar el
argumento pasado a la función y recibir el nuevo valor
una vez que la función ha terminado. Para hacer lo
anterior se debe usar una llamada por referencia, en C
se puede simular pasando un puntero al argumento. Con
esto se provoca que la computadora pase la dirección
del argumento a la función.
Para entender mejor lo anterior consideremos la
función swap() que intercambia el valor de dos
argumentos enteros:
void swap(int *px, int *py);
main()
{
int x, y;
x = 10;
8. y = 20;
printf("x=%dty=%dn",x,y);
swap(&x, &y);
printf("x=%dty=%dn",x,y);
}
void swap(int *px, int *py)
{
inttemp;
temp = *px; /* guarda el valor de la direccion x */
*px = *py; /* pone y en x */
*py = temp; /* pone x en y */
}
Apuntadores y arreglos
Existe una relación estrecha entre los punteros y los
arreglos. En C, un nombre de un arreglo es un índice a
la dirección de comienzo del arreglo. En esencia, el
nombre de un arreglo es un puntero al arreglo.
Considerar lo siguiente:
int a[10], x;
int *ap;
ap = &a[0];
/* ap apunta a la direccion de a[0] */
x = *ap;
/* A x se le asigna el contenido de ap
(a[0] en este caso) */
*(ap + 1) = 100; /* Se asigna al segundo elemento de
'a' el valor 100 usando ap*/
Como se puede observar en el ejemplo la
sentencia a[t] es idéntica a ap+t. Se debe tener cuidado
9. ya que C no hace una revisión de los límites del arreglo,
por lo que se puede ir fácilmente más alla del arreglo en
memoria y sobreescribir otras cosas.
C sin embargo es mucho más sútil en su relación
entre arreglos y apuntadores. Por ejemplo se puede
teclear solamente:
ap = a; en vez de ap = &a[0]; y también *(a + i) en vez de a[i],
esto es, &a[i] es equivalente con a+i.
Y como se ve en el ejemplo, el direccionamiento de
apuntadores se puede expresar como:
a[i] que es equivalente a *(ap + i)
Sin embargo los apuntadores y los arreglos son
diferentes:
Un apuntador es una variable. Se puede hacer ap =
a y ap++.
Un arreglo NO ES una variable. Hacer a = ap y a++ ES
ILEGAL.
Este parte es muy importante, asegúrese haberla
entendido.
Con lo comentado se puede entender como los
arreglos son pasados a las funciones. Cuando un arreglo
es pasado a una función lo que en realidad se le esta
pasando es la localidad de su elemento inicial en
memoria.
Por lo tanto:
strlen(s) es equivalente a strlen(&s[0])
10. Esta es la razón por la cual se declara la función
como:
intstrlen(char
s[]); y
es intstrlen(char *s);
una
declaración
equivalente
ya que char s[] es igual que char *s.
La función strlen() es una función de la biblioteca
estándar que regresa la longitud de una cadena. Se
muestra enseguida la versión de esta función que podría
escribirse:
intstrlen(char *s)
{
char *p = s;
while ( *p != '0' )
p++;
return p - s;
}
Se muestra enseguida una función para copiar una
cadena en otra. Al igual que en el ejercicio anterior
existe en la biblioteca estándar una función que hace lo
mismo.
voidstrcpy(char *s, char *t)
{
while ( (*s++ = *t++) != '0' );
En los dos últimos ejemplos se emplean apuntadores y
asignación por valor.
Nota: Se emplea el uso del carácter nulo con la
sentencia while para encontrar el fin de la cadena.
11. Arreglos de apuntadores
En C se pueden tener arreglos de apuntadores ya
que los apuntadores son variables.
A continuación se muestra un ejemplo de su
uso: ordenar las líneas de un texto de diferente
longitud.
Los
arreglos
de
apuntadores
son
una
representación de datos que manejan de una forma
eficiente y conveniente líneas de texto de longitud
variable.
¿Cómo se puede hacer lo anterior?
1. Guardar todas las líneas en un arreglo de
tipo char grande. Observando que n marca el fin de
cada línea. Ver figura 8.1.
2. Guardar los apuntadores en un arreglo diferente donde
cada apuntador apunta al primer caracter de cada línea.
3. Comparar dos líneas usando la función de la biblioteca
estándar strcmp().
4. Si dos líneas están desacomodadas -- intercambiar
(swap) los apuntadores (no el texto).
Arreglos de apuntadores (Ejemplo de ordenamiento de
cadenas).
Con lo anterior se elimina:
el manejo complicado del almacenamiento.
alta sobrecarga por el movimiento de líneas.
12. DIRECCIONES DE MEMORIA
En informática, una dirección de memoria es un
identificador para una localización de memoria con la
cual
un programa
informático o
un
dispositivo
de hardware pueden
almacenar
un dato para
su
posterior reutilización.
Una forma común de describir la memoria
principal de un ordenador es como una colección de
celdas que almacenan datos e instrucciones. Cada celda
está identificada unívocamente por un número o
dirección de memoria.
Para poder acceder a una ubicación específica de
la memoria, la CPU genera señales en el bus de
dirección, que habitualmente tiene un tamaño de 32 bits
en la mayoría de máquinas actuales. Un bus de
dirección de 32 bits permite especificar a la CPU
=
4.294.967.296 direcciones de memoria distintas.
Debido a la estructura de 32 bits de
un procesador común como los de Intel, las direcciones
de memoria se expresan a menudo en hexadecimal. Por
ejemplo,
para
no
tener
que
escribir
111111010100000000000010101100 podemos escribir
3F5000AC en hexadecimal.
13. Modos de direccionamiento.
En aplicaciones informáticas las direcciones son
asignadas por el sistema operativo a cada programa en
ejecución, asegurándose éste, comúnmente por medio de
un daemon, que las direcciones utilizadas por un ejecutable u
otro proceso no se solapen o se escriba en posiciones
protegidas de memoria, por ejemplo, en el sector de
arranque.
Los sistemas operativos actuales son comúnmente
diferenciados según el ancho de palabra soportado por sus
registros, es decir 32 y 64 bits. Estas cifras se refieren a la
máxima capacidad que dichos sistemas operativos pueden
direccionar, así un sistema de 32 bits podría acceder y
direccionar, sin utilizar memoria virtual, un máximo de
232 posiciones de memoria, usualmente designadas por un
código hexadecimal. Debido a esto, el rango de valores
naturales que pueden ser almacenados en 32 bits es de 0
hasta 4.294.967.295 (0h - FFFFFFFFh), que vienen a ser los
famosos 4 gigabytes de capacidad límite de los sistemas
operativos de 32 bits.
Para los sistemas de 64 bits, siguiendo el razonamiento
anterior, obtendríamos 264 posibilidades, lo que se traduce en
un
rango
de
valores
desde
0
hasta
18.446.744.073.709.551.615 (0h- FFFFFFFFFFFFFFFFh),
18,4 exabytes o
18.400.000.000.000
de
gigabytes
direccionables.
14. En los lenguajes de programación, se puede acceder a
las direcciones de memoria utilizando punteros. Si bien
algunos sistemas operativos y lenguajes actuales no permiten
acceder a determinadas direcciones de memoria (o incluso,
lenguajes como Java que no implementan punteros), esto no
significa que dichas direcciones no existan o no sean
correctas, por ejemplo, la posición de memoria 0h es una
posición válida y correcta y es normal que se trabaje sobre
ella por ejemplo cuando se modifica la tabla descriptora de
interrupciones.
Pero cuando se trabaja en modo protegido, los
programas ejecutándose como aplicaciones de usuario no
tienen acceso a algunas posiciones (entre ellas la 0h), pero
en el sistema operativo DOS que trabaja en modo real, se
puede acceder a toda la memoria disponible con un simple
programa de usuario.
Ejercicio
Ejercicio 1: Crear un programa llamado paresImpares que
cree un array de 100 números aleatorios del 1 al 1000. Una
vez creado, mostrar el contenido y después organizarlo de
forma que estén juntos los elementos pares y los impares.
Después, volver a mostrar el array
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
15. #define TAM 100
void escribirArray(int a[], int tamanio);
int main(){
clrscr();
int a[TAM];
int par[TAM]={0};
int impar[TAM]={0};
int i,j;
srand(time(NULL));
/*Relleno inicial del array a*/
16. for(i=0;i<TAM;i++){
a[i]=rand()%100+1;
}
/* Escritura del contenido del primer array*/
printf(" Primer array:n");
printf("n");
escribirArray(a,TAM);
/* Grabación de los pares y los impares en los otros
arrays*/
for(i=0;i<TAM;i++){
if(a[i]%2==0)
par[i]=a[i];
17. else
impar[i]=a[i];
}
/* Escritura del array par e impar*/
printf("n");
printf(" Par:n");
printf("n");
escribirArray(par,TAM);
printf("n");
printf(" Impar:n");
printf("n");
escribirArray(impar,TAM);
/* Mezcla de ambos arrays en el array a
y escritura del resultado final*/
18. j=0;/*j es el índice del array a.
Sólo se mueve cuando se insertan en él números*/
/*recorrido del array par e impar e inserción de los números
pares que contenga (todos los que sean distintos de 0)
se añade al array a desde la primera posición del mismo*/
for(i=0;i<TAM;i++){
if(par[i]!=0) {
a[j]=par[i];
j++;
}
}
/*recorrido del array impar e inserción de los números
impares que contenga desde la posición en la que quedó
j*/
20. /* Escribe el contenido de un array de enteros por pantalla */
void escribirArray(int a[], int tamanio){
int i;
for(i=0;i<tamanio;i++){
printf("%d ",a[i]);
}
printf("n");
}
Ejercicio 2: Crear un programa que contenga una función
llamada copiarArray ) que reciba dos arrays y el tamaño de
los mismos (deben de ser del mismo tamaño) y que consiga
copia en el segundo array el contenido del primero
#include <conio.h>
#include <stdio.h>
21. void copiarArray(int a[], int b[], int tamanio);
void escribirArray(int a[], int tamanio);
int main(){clrscr();
/* Comprobación de la función*/
int uno[]={2,4,5,6,7,8,9,11};
int dos[8];
printf("n");
printf("Array uno:n");
printf("n");
escribirArray(uno,8);
copiarArray(uno,dos,8);
printf("n");
printf("Array dos:n");
printf("n");
22. escribirArray(dos,8);
getch();
}
/* Escribe el contenido de un array de enteros por pantalla */
void escribirArray(int a[], int tamanio){
int i;
for(i=0;i<tamanio;i++)
printf("%d ",a[i]);
printf("n");
23. }
/* Copia el contenido del array b en el array a. Se supone
que ambos arrays son del mismo tamanio*/
void copiarArray(int a[], int b[], int tamanio){
int i;
for(i=0;i<tamanio;i++){
b[i]=a[i];
}
}