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.