1
INTRODUCCIÓN
Estos apuntes son los que se entrega a los
alumnos como material básico de apoyo en el curso. No
obstante, el curso es de contenido principalmente
práctico, y la colección de ejercicios completa,
que es la esencia del curso en sí, no aparece en el
presente documento.
Este curso está pensado para
ingenieros que deseen repasar los aspectos más delicados
de C, así como introducirse en la programación
orientada a objetos en C++. Resulta ideal para todas aquellas
empresas que se dediquen al desarrollo de aplicaciones en C
y C++, ya que, a menudo, cuando los
ingenieros terminan la carrera suelen haber olvidado la
mayoría de los conceptos importantes para la
programación.
Como puede observarse, el curso empieza
directamente con el tema de punteros, dando por hecho que los
alumnos conocen todo lo referente a tipos de datos en C,
operadores en C, sentencias de control de flujo, arrays,
funciones, ámbitos de las variables, definición de
constantes…
Si usted quisiera contratar este curso para
su empresa podría solicitar la modificación del
temario para adaptarlo a las características de su
empresa.
Tal y como está estructurado en el
presente documento, el curso constaría de unas 40 horas,
según la profundidad con que se desee abordar cada uno de
los puntos.
Puede utilizarse cualquier compilador C/C++
(DevCpp, Visual C++, Borland Builder, Turbo Cpp, CVI,
MinGWStudio, LCC…).
2
PUNTEROS
El concepto de puntero está basado en la idea de
una variable cuya misión es contener la dirección
de otra variable o la dirección de memoria de un dato. Se
dice, entonces, que el puntero apunta a la variable. Según
el tipo de variable al que apunte tendremos un puntero a entero,
un puntero a float, char, etc.
Para trabajar con punteros es importante
conocer el significado de los operadores de dirección-de
(símbolo &) e in-dirección (símbolo *),
que también se puede leer como lo- apuntado-por. De este
modo &var puede leerse como "dirección de
var", y *pun puede leerse como "lo apuntado por pun" o
"lo que hay en la dirección contenida en pun".
En el siguiente gráfico se puede
observar lo descrito. La memoria está dividida en bloques
numerados. Imaginemos que en la posición de memoria 50
tenemos la variable "a", cuyo valor es 10. A continuación,
si declaramos el puntero "p", que contiene la dirección de
la variable "a", vemos como el puntero "p" apunta a la
dirección que contiene el valor 10.
El código correspondiente a este
gráfico es el siguiente:
Como vemos, no tendría sentido
asignar directamente la dirección 50 al puntero "p", ya
que no sabemos cuál es la dirección donde
está almacenado el valor de "a". Por lo tanto, utilizamos
el operador de dirección & para poder obtener la
dirección de la variable "a".
Además, un puntero puede contener la
dirección de memoria de un dato, sin necesidad de tener
declarada una variable intermedia. Para ello se hace necesaria la
reserva de memoria, no podemos elegir las posiciones que queramos
arbitrariamente, ya que podrían estar ocupadas por otros
datos.
2.1 Operaciones
básicas
2.1.1 Declaración
Un puntero se declara anteponiendo el
modificador *, que se suele leer como "puntero
a". int *pun; Se puede leer como
"int es lo apuntado por pun" o un "int es lo que hay en la
dirección contenida en pun". Esta es la declaración
de un puntero a entero. Es decir, si aparece una variable "a"
declarada de la siguiente forma, y asignamos al puntero "pun" la
dirección de dicha variable "a", podemos utilizar *pun en
su lugar: int a; Por lo tanto, *pun es
un entero que está localizado en la dirección de
memoria especificada en pun, así como "a" es un entero
localizado en la dirección de memoria que podemos obtener
mediante &a.
Un puntero inicializado siempre apunta a un
dato de un tipo en particular. Un puntero no inicializado, no se
sabe a dónde ni a qué apunta.
Del mismo modo tenemos:
float *pun2// puntero a float char *pun3//
puntero a char
El espacio de memoria requerido para
almacenar un puntero es el número de bytes necesarios para
especificar una dirección de la máquina en la que
se esté compilando el código. Hoy día suele
ser el valor típico de 4 bytes.
2.1.2 Asignación
A un puntero se le puede asignar la
dirección de una variable del tipo adecuado. Esta
dirección puede encontrarse bien en otro puntero,
obtenerse de la propia variable mediante el operador
"dirección de" (&), a través del nombre de un
array (lo veremos posteriormente), o a través del valor
devuelto por una función.
Algo que no tiene demasiada lógica
es asignar directamente una dirección a un puntero, dado
que no sabemos si esa dirección de memoria estará
ocupada o no. Más adelante se verá el mecanismo
para asignar una posición de memoria a un puntero. Como
hemos visto en el ejemplo de la introducción, sin
necesidad de reservar memoria para un contenido apuntado por un
puntero, podemos asignar el valor de una dirección
válida, como por ejemplo la dirección de una
variable ya declarada, mediante el operador &.
Un ejemplo de asignación es el
siguiente:
Al puntero "q" se le asigna la
dirección de la variable "a". Esta asignación es
correcta y cada vez que se actúe sobre *q, es como si
estuviéramos actuando sobre "a".
El puntero p no ha sido inicializado y a su
contenido se le está asignando el valor 20. Es decir, a
una dirección de memoria que no sabemos cuál es,
especificada por "p", está siendo modificada al valor 20.
Esto no tiene mucho sentido puesto que esa posición de
memoria puede pertenecer incluso a otro proceso. En los sistemas
operativos actuales esto daría error si esa
posición de memoria no estuviera libre, por lo que no
causaríamos daño a otras aplicaciones que
estén ejecutándose, pero no es la forma adecuada de
asignación.
2.1.3 Desreferencia de un puntero (operador
"*").
El operador * también se conoce como
el operador de indirección. Si en el ejemplo anterior
accedemos a *q, obteniendo los mismos resultados que si accedemos
a la variable "a", se dice que estamos accediendo al "contenido
de q". Así que también se lee como "contenido
de".
Si hemos asignado correctamente un valor a
un puntero, como en el ejemplo anterior hicimos con el puntero
"q", al escribir *q será como si escribiéramos
"a".
A continuación se proponen dos
ejemplos sencillos para comprobar los resultados que se
obtendrían al ejecutar el código:
Ejemplo 1.
Ejemplo 2
2.1.4 Punteros a punteros
Del mismo modo que un puntero puede
contener la dirección de un entero, de una char o un
float, también puede contener la dirección de otro
puntero. Se trata entonces de un puntero a puntero a int, puntero
a puntero a char etc.
2.2 Ejemplos de
uso
Una de las utilidades más
importantes de los punteros es la de referenciar los datos sin
necesidad de copiarlos. Como ventaja obtenemos no tener que
copiar los datos a los cuales apunta un puntero. Y como
inconveniente, se pueden cometer problemas de ambigüedad de
los datos, dado que al modificar los datos apuntados por un
puntero, esos mismos datos están siendo apuntados por otro
puntero.
En un caso concreto, este tipo de
referencia se utiliza mucho en las llamadas a funciones, cuyos
argumentos serán punteros a datos.
2.2.1 Parámetros por referencia a
funciones
Las funciones en C sólo devuelven un
valor, es decir, que si sólo usáramos el valor de
retorno, cada llamada a una función solamente
podría modificar una variable del ámbito desde el
que es llamada. Una forma de solventar esta limitación es
usando los punteros. Por medio de los punteros podemos indicarle
a la función la dirección donde se encuentran las
variables que queremos que modifique. De este modo una
función podrá modificar tantas variables del
ámbito de la llamada como queramos.
Cuando se llama a una función, el
valor de los parámetros reales se pasa a los
parámetros formales. Todos los argumentos, excepto los
arrays, por defecto se pasan por valor. Por ejemplo:
En este caso, "arg1" y "arg2" son los
parámetros reales. Los parámetros formales son
"argumento1" y "argumento2". En este caso, lo que está
ocurriendo es que se hace una copia del valor del argumento, no
se le pasa la dirección a la función. La
función "nombre_funcion" no puede alterar las
variables que contienen los valores pasados, es decir, "arg1" y
"arg2" seguirán teniendo el mismo valor en la
función "main".
Si lo que se desea es alterar los
contenidos de los argumentos especificados en la llamada a la
función, hay que pasar esos argumentos por referencia. Se
le pasa la dirección de cada argumento y no su valor. Por
lo tanto, los argumentos formales deben ser punteros, y
utilizaremos el operador "&" en la llamada de la
función.
EL PRESENTE TEXTO ES SOLO UNA SELECCION DEL TRABAJO
ORIGINAL.
PARA CONSULTAR LA MONOGRAFIA COMPLETA SELECCIONAR LA OPCION
DESCARGAR DEL MENU SUPERIOR.