Para poder explicar algunos conceptos importantes de este apartado se van a presentar dos ejemplos sencillos de funciones, una con valor de retorno y argumentos pasados por valor y la otra sin valor de retorno y con los argumentos pasados por referencia.:
double max(double a, double b) {
if (a>=b)
return a;
else
return b;
}
void permutar(double* px, double* py) {
double temp;
temp = *px;
*px = *py;
*py = temp;
}
Se utilizarán estas funciones en las explicaciones que siguen.
Una función es una porción de código independiente del exterior (es decir de otros programas) y que se puede re-utilizar (es decir llamar muchas veces con datos diferentes). Aunque el concepto de función es general y existe en todos los lenguajes de programación, aquí se hará referencia a las funciones de C/C++.
Se distingue entre argumentos formales y argumentos actuales. Los argumentos formales son los que aparecen en la definición de la función, mientras que los actuales son los que aparecen en la llamada a la función. Se podría también decir que los argumentos formales son los argumentos vistos desde dentro de la función, y los argumentos actuales son los argumentos vistos desde el programa que llama a la función, esto es desde fuera de la función. Los argumentos formales son siempre variables de la función que recogen los valores que se les pasan desde el exterior; los argumentos actuales pueden ser variables o expresiones cuyos valores se pasan a los argumentos formales.
Cuando una función tiene valor de retorno puede ser utilizada en una sentencia aritmética (por ejemplo, la función seno en la expresión: y=sin(x)*x-1.0;). En este caso el valor de retorno se sustituye en el lugar ocupado por la llamada a la función en dicha sentencia aritmética. Cuendo una función no tiene valor de retorno, se llama simplemente colocando la llamada en una línea del programa seguida de punto y coma (por ejemplo: permutar(&x, &y);).
En C/C++ el paso de argumentos a una función se hace de la siguiente manera. En el programa que llama a la función, se evalúan los argumentos actuales y sus valores se pasan a las variables de la función que constituyen los argumentos formales. En realidad siempre se pasan copias de dichos valores. Los argumentos formales (los argumentos vistos desde dentro de la función son variables nuevas, que se crean cuando la función es llamada, y que toman los valores que les pasan los argumentos actuales.
Cuando de dice que en C los argumentos de las funciones se pasan siempre por valor, lo que se quiere decir es que las variables argumentos formales reciben copias de los valores de los argumentos actuales, pero son siempre variables diferentes, propias de la función. Por eso, si se modifica un argumento formal dentro de la función, se está modificando una copia del argumento actual y si el argumento actual es una variable, dicha variable no queda modificada. Esto es un mecanismo de seguridad de C, de modo que una función no pueda modificar facilmente las variables externas a ella.
En muchas ocasiones, las funciones tienen que modificar las variables pasadas como argumentos actuales. Como el valor de retorno es único, ésta es la única forma por ejemplo de que una función trasmita al exterior varios resultados en una sola llamada. Cuando se desea que la función modifique los argumentos actuales (las variables externas a la función que aparecen en la llamada), hay que pasárselos por referencia. ¿Qué quiere decir esto: que a veces se pasan copias de los argumentos actuales y otras veces se pasan los originales? Ya se verá en otro lugar que en C++ es así (a través de un tipo de variables que no tiene C: las variables reference), pero en C a las funciones siempre se les pasan copias de los argumentos actuales.
¿Como puede una función modificar una variable externa si las funciones sólo pueden recibir copias de las variables del programa que llama a la función? Esto se puede conseguir por medio de los punteros. La forma de modificar una variable externa es disponer de su dirección en memoria. Las funciones de C son capaces de recibir copias de las direcciones en memoria de las variables externas, pero para modificar el contenido de una dirección de memoria la dirección o una copia de esa dirección son igual de válidas.
La función printf() imprime los valores de las variables o expresiones que se le pasan como argumentos. La función printf() no modifica los valores de sus argumentos, simplemente los imprime por pantalla. Por tanto, el paso de argumentos por valor es perfectamente satisfactorio para dicha función.
El caso de scanf() es muy diferente: esta función lee valores del teclado y asigna dichos valores a las variables del programa principal que se le han pasado como argumentos. Es decir, la función scanf() modifica las variables que se le pasan como argumentos (recibe unas variables sin valor asignado y guarda en ellas lo que el usuario teclea). Por eso, a la función printf() hay que pasarle siempre los argumentos por referencia.
Por este motivo, mientras que a printf() se le pueden pasar expresiones además de variables, a scanf() sólo se le pueden pasar direcciones de variables. nunca expresiones.
En C/C++ el nombre de un vector, de una matriz o de una cadena de caracteres es siempre un puntero (al primer elemento del vector, al primer elemento del vector de punteros que contiene las direcciones de los primeros elementos de cada fila de la matriz, y al primer carácter de la cadena, respectivamente). Cuando dentro de una función se dispone de la dirección de una variable externa, se puede cambiar el valos de dicha variable por medio del operador indirección (*). Por tanto, las funciones siempre pueden modificar el valor de los elementos de un vector, de una matriz, o de una cadena de caracteres cuando sus nombres le han sido pasados como argumentos.
Un puntero es una variable que contiene o almacena la dirección en memoria de otra variable. De ordinario no interesa el valor de la variable puntero, sino el valor de la variable cuya dirección se almacena en el puntero. Los punteros constituyen una forma alternativa de manejar la información (con una dirección de memoria, en vez de con un nombre de variable).
En C los punteros son importantes, entre otras, por las siguientes razones:
Una función se comunica con el exterior a través de los argumentos (entrada de datos en la funcion y salida de resultados) y del valor de retorno (sólo un valor resultado de la función, devuelto por medio de la sentencia return). Así pues, una función C/C++ tiene una forma de recibir datos (los argumentos), pero dos para dar resultados (los argumentos pasados por referencia y el valor de retorno). En muchos casos no hay una única forma de programar la función (ni tampoco una forma claramente superior a las demás); en otras ocasiones, sí existe una forma más natural y apropiada de programarla..
Cuando una función tiene valor de retorno puede ser utilizada en una sentencia aritmética (por ejemplo, la función seno en la expresión: y=sin(x)*x-1.0;). En este caso el valor de retorno se sustituye en el lugar ocupado por la llamada a la función en dicha sentencia aritmética. Cuando una función no tiene valor de retorno, se llama simplemente colocando la llamada en una línea del programa seguida de punto y coma (por ejemplo: permutar(&x, &y);).
Por ejemplo, la función cos(x) también podría utilizarse sin valor de retorno y devolviendo el resultado por medio de un argumento pasado por referencia. La función podría declararse en la forma:
void coseno(double x, double *pc);
y ser llamada del modo:
coseno(x, &c);
donde la variable c, pasada por referencia, recogería el valor de coseno de x. Es evidente que, puesta en esta forma, la función calcula el coseno, pero no puede utilizarse en una expresión aritmética porque no tiene valor de retorno.
La memoria estática es memoria que se reserva en el momento de la compilación, antes de comenzar a ejecutarse el programa. Por ejemplo, para una resolución de un sistema de ecuaciones lineales, la memoria estática se puede reservar con sentencias del tipo:
double a[100][100], x[100], b[100];
El inconveniente de la reserva estática es que la cantidad de memoria se reserva siempre antes de conocer los datos concretos del problema a resolver. Eso lleva a reservar siempre un máximo de memoria que en la mayor parte de las ocasiones no se va a necesitar. La reserva estática tampoco se adapta bien a la memoria real disponible en el ordenador en que se está ejecutando el programa.
Por el contrario, la reserva dinámica de memoria se hace en tiempo de ejecución, después de leer los datos y de conocer el tamaño exacto del problema a resolver. Como consecuencia, la reserva dinámica de memoria se adapta mucho mejor a las necesidades de cada caso. Como contrapartida, es algo más difíicil de programar.
Cuando se reserva memoria dinámicamente, es muy importante liberar la memoria que ya no se necesita. De otra forma los programas van creciendo a lo largo de una ejecución hasta agotar la memoria disponible.
La memoria dinámica sirve para que los programas se adapten siempre al tamaño del problema que tienen que resolver, sin desperdiciar recursos de memoria. Esto se traduce asimismo en una mayor eficiencia en la ejecución de los programas.
Las funciones calloc() y malloc() son la forma clasica de reservar memoria dinámicamente en C. Ambas devuelven un puntero al comienzo de la memoria reservada. La función calloc() tiene 2 argumentos (el número de datos a reservar y los bytes que ocupa cada dato), mientras que malloc() sólo tiene uno (el total bytes de memoria a reservar).
Todo lo que hacen estas funciones lo puede realizar el operador new de C++, que tiene además otras ventajas que resultan patentes en la programación orientada a objetos de C++.
Las FAQs (Frequently Asked Questions) son un tipo de documento típico de la Internet. A medida que nuevos usuarios se iban incorporando a la red, las preguntas que dirigían a los usuarios veteranos eran casi siempre las mismas. Para no estar siempre repitiendo lo mismo, los usuarios expertos realizaron compilaciones de esas preguntas más frecuentes con las correspondientes respuestas.
El resultado ha sido un tipo de documento muy didáctico y cómodo de utilizar. El deseo de los profesores de esta asignatura es ir poco a poco recogiendo las dudas más frecuentes sobre C con las respuestas adecuadas. Las preguntas se irán clasificando por temas