FUNCIONES

Permiten modular los programas, en otras palabras permite dividir un programa en secciones más pequeñas (módulos) que realizan una tarea específica. Son equivalentes a las funciones (function) del Pascal.

Sintaxis:

[Tipo de retorno] Nombre ([Declaración de Parámetros])
{    instrucción;
     ...
     ...
     [return expr;]
}

De omitirse el tipo de retorno, la función deberá devolver un valor de tipo int . La cláusula return expr; permite devolver el resultado de la función al módulo que la invocó, expr es una expresión aritmética cuyo resultado será valor devuelto.

Consideraciones:

  1. En C, los parámetros de la función pasan exclusivamente por valor, en C++ los parámetros pasan por valor o por referencia. Un parámetro por referencia se indica colocando el símbolo & entre el tipo de dato y el parámetro.

  2. Toda función debe retornar un valor por medio de la instrucción "return", a menos que el tipo de retorno haya sido definido como void.

  3. Las funciones pueden ser colocadas en cualquier orden dentro del programa, pero si se van a invocar antes de su declaración, se deberá colocar el "prototipo" de la función al inicio del programa. El "prototipo no es más que el encabezado de la función, el cual termina con un ";".

Ejemplo:

Donde X0 es una primera aproximación de la raíz. Con X0 se calcula Xn; si Xn da como resultado un valor diferente de X0, se reemplaza X0 por Xn en la expresión y el proceso se repite hasta que X0 sea igual a Xn.

Grabar este ejemplo

#include <stdio.h>
const float LIMITE = 0.001; const int FIN = 0;
//Prototipos:
float raiz_n (int, float); float potencia (float, int); float abs (float); int LeeNum (int &);  //Parámetro por referencia
void main (void) { float s = 0.0, a; int coef, raiz; printf("Ingrese el valor de \"a\": "); scanf("%f",&a); printf("Ingrese los coeficientes y potencias por parejas \n (cero para terminar)"); while ( LeeNum (coef)) { LeeNum(raiz); s += coef*raiz_n (raiz,a); } printf("s= %10.3f\n", s); }
int LeeNum(int &coef) { scanf("%d", &coef); return coef; }
float raiz_n (int n, float Q) { float Xo = 0.0, Xn = 1.0; while (abs (Xo - Xn) > LIMITE) { Xo = Xn; Xn = ((n-1)*Xo+Q/potencia(Xo,n-1))/n; } return Xo; }
float abs (float X) { return X>0? X:-X; }
float potencia (float X, int n) { float r = 1; while (n--) r *= X; return r; }

Ámbito y Alcance de la Declaración de Variables

Variables globales

Se definen fuera de cualquier bloque de instrucciones. Estas variables pueden ser utilizadas a partir de su dfinición en cualquier parte del programa.

Por ejemplo:

#include <stdio.h>
int a; //variable global
void main (void) { int p = 3 ; //variable local o automática a = p; printf("%5d %5d\n", a, p); }

También se puede definir en forma explíta dentro de un bloque de instrucciones, como se muestra a continuación:

int a; 	//variable global
void main (void) { int p = 3; extern int a; //declaración explícita a = p; printf("%5d %5d\n", a, p); }

El ámbito de las variables globales se puede apreciar a continuación:

Variables Automáticas y Estáticas

Las variables automáticas son aquellas variables que se definen dentro de un bloque de instrucciones { ... }, su nombre viene debido a que se crean cuando se declara la variable y se destruyen cuando se sale del bloque.

Las variables automáticas se pueden ser definidas en cualquier parte del programa.

Así:

void main (void)
{ int a, b = 7;	//variables automáticas, sólo se pueden usar dentro de la función main.
  a=b-7;
  float p; 	//variable automática, no se puede hacer en C pero si en C++.
  ...
  }

 void f(void)
 { inx x;	//variable automática, sólo se pueden usar dentro de la función f.
   x = a;	//ERROR: no puede usas la variable a dentro de la función f.
   ...
 }

Se puede hacer también lo siguiente:

void main (void)
{ int a, b = 7;
  while (b3)
  { int f = 5; 	//variable local al bloque;
    a += f;
    printf("a= %d\n",a);
    b--;
  }
  b = f;	//ERROR: no puefe usar la variable f aquí
}

Observe el siguiente ejemplo:

void main (void)
{ int b = 7;
  while (b>3)
  { float f = 5;
    f *= 10;
    printf("f= %d\n", f);
    b--;
  }
}

El programa imprime 4 veces 50 y no 50, 500, 5000 y 50000.


Las variables estáticas se crean la primera vez que se ingresa al bloque, pero no se destruyen, permanecen definidas en la memoria hasta que se acabe el programa, su valor se mantiene. Sin embargo el ámbito de esta variable se limita al bloque en el que fue definida.

Sintaxis:

static Tipo Lista de variables;

Ejemplo:

Grabar este ejemplo

#include <stdio.h>
void muestra (int a) { static int b=1; b += a; printf("a= %5d b= %5d\n", a, b); }
void main (void) { int a; for (a=0; a<4; a++) muestra (a); }

Inicialización de variables

Cuando una variable no se inicializa explícitamente, se garantiza que las variables externas (globales) y estáticas se inicializan con cero. Por otro lado, las variables automáticas y los datos de tipo registro (las que se verán más adelante) no se inicializan, tienen valores iniciales indefinidos (basura) hasta que se le asigne un valor explícitamente.

Cuando se inicializa explícitamente una variable, las variables externas y estáticas sólo se pueden inicializar con expresiones constantes, esto es:

int i = 0;
char tab = '\t';
long dia = 24L * 60L * 60L; 	//segundos

Las variables automáticas y de tipo registro se pueden inicializar con cualquier expresión, estas expresiones pueden contener valores constantes, variables que tenga valores previamente definidos, incluso llamadas a funciones.

Ejemplo:

int función x (int a, int n)
{ int menor = 0;
  int mayor = n-1;
  int peor = menor/abs(a) * mayor;
     ...

Funciones Recursivas

El lenguaje C/C++ permite la implementación de funciones recursivas.

Ejemplos:

Grabar este ejemplo

/*factorial de un número*/
long fact (int n) { if (!n) return 1; }
/*fibonacci*/
int fib (int n) { if (n<=1) return n; return fib(n-2) + fib(n-1); }
/*imprime un número n, sólo con putchar*/
void imprimedec (int n) { if (n<0) { putchar ('-'); n = -n; } if (n/10) imprimedec (n/10); putchar (n%10 + '0'); }

El Preprocesador

El preprocesador es un programa que se ejecuta automáticamente antes que el complilador empiece la tarea de traducir el programa. El preprocesador se ejecuta cuando en un programa se encuentran instruciones denominadas cláusulas de preprocesador.

Una cláusula de preprocesador, la cual ya se ha explicado su uso anteriormente, es la cláusula #define que permite definir constantes sibólicas. En este caso antes de compilarse un programa que presente esta cláusula, el preporcesador procederá a reemplazar la palabra definida allí por el texto de reemplazo, en todas las ocurrencias en el programa.

A continuación se describen algunas de las cláusulas de preprocesador más utilizadas:

Inclusión de Archivos

La cláusula #include, permite agregar un archivo de texto, que contiene código en C/C++, en el programa que se desea compilar. Este archivo puede contener declaraciones de variables, prototipos de funciones, implementación de funciones, declaraciones de tipos de datos, etc. e incluso otras cláusulas de preprocesador.

A pesar de esto último, se recomienda que en los archivos que van a ser incluidos se coloque sólo protipos de funciones, declaraciones de tipos de datos y cláusulas depreprocesador, la implementación de funciones no debe colocarse en estos archivos por efectos de eficiencia en la compilacón, es por esta razón que por lo general la extensión de estos archivos es .h que viene de "header files" o "archivo de encabezados o prototipos". Si la función está implementada en otro archivo, éste debe ser colocado dentro de lo que se denomina un proyecto, tema que se analizará más adelante.

La cláusula #include tiene dos formatos:

#include <stdio.h>
#include <conio.h>

En general #include < nombre de archivo>

También se puede escribir:

#include "Lib1.c"
#include "B:\Ejemplos\Lib3.h"

En general #include "nombre del archivo"

La primera forma incluye archivos que se encuentren en un directorio predeterminado por el ambiente integrado de desarrollo (IDE) que se esté utilizando.

Por ejemplo, la forma de predeterminar un directorio de inclusión de archivos en el Borland C++ V3.1 se realiza mediante las opciones:

Options
Directories...
include Directories

La segunda forma incluye archivos que se encuentran en el directorio actual (si no se indica la ruta).

Un ejemplo de inclusión de archivos lo podemos implementar en el programa que elaboramos al inicio de este capítulo con el cálculo de la serie de raíces n_ésimas. Para esto podemos crear un archivo denominado raiz_n.h con el siguiente código:


const float LIMITE = 0.001;
const int   FIN = 0;
//Prototipos:
  float raiz_n (int, float);
  float potencia (float, int);
  float abs (float);
  int LeeNum (int &);

Luego el archivo principal presentaría el siguiente código:


#include <stdio.h>
#include "raiz_n.h"
void main (void) { float s = 0.0, a; int coef, raiz; printf("Ingrese el valor de \"a\": "); scanf("%f",&a); printf("Ingrese los coeficientes y potencias por parejas \n (cero para terminar)"); while ( LeeNum (coef)) { LeeNum(raiz); s += coef*raiz_n (raiz,a); } printf("s= %10.3f\n", s); }
int LeeNum(int &coef) { scanf("%d", &coef); ...

Inclusión condicional

Permite incluir código selectivamente, de modo que no se incluya en un programa un mismo archivo varias veces. Normalmente este problema se presenta cuando se coloca en estos archivos la definición;n de tipos de datos como estructuras y clases.

El siguiente ejemplo muestra el problema de la doble incusión de archivos. El ejemplo se desarrolla empleando variables globales, esto debido a que aun no hemos definido el uso de estructuras o clases que nos permitan elaborar un problema más idóneo.

Grabar este ejemplo


// Archivo Lib1.h
// Se definen variables globales
int a,b,c;

// Archivo Lib2.CPP // Se implementa una función donde se inicializa las variables globales #include "Lib1.h" void f1(void) { a=1; b=2; c=3; }

// Archivo Main1.cpp // Utiliza la función f1, obsérvese que el programa compila y se ejecuta // sin errores. #include #include "Lib2.CPP" void main (void) { f1 ( ); printf("%5d %5d %5d\n", a, b, c); }

// Archivo Lib3.CPP // Se implementa otra función donde se inicializa las variables globales #include "Lib1.h" void f2(void) { a=28; b=35; c=47; }

// Archivo Main2.cpp // Utiliza la funci¢n f2, obsérvese que el programa compila y se ejecuta // sin errores de igual manera que en el programa anterior. #include #include "Lib3.CPP" void main (void) { f2 ( ); printf("%5d %5d %5d\n", a, b, c); }

Los archivos Main1 y Main2 pueden representar aplicaciones independientes que emplean cada uno una librería de funciones en la que no hay relación una con la otra pero que se basan en un tipo de dato común.

Si se diera el caso que en un momento dado se deseara realizar una aplicación en donde se necesiten utilizar ambas librerías como en el siguiente programa:

// Archivo Main3.cpp
// Utiliza las funciones f1 y f2, obsérvese que el programa tiene errores
// de compilación.
#include 
#include "Lib2.CPP"
#include "Lib3.CPP"
void main (void)
{ f1 ( );
  printf("%5d %5d %5d\n", a, b, c);
  f2 ( );
  printf("%5d %5d %5d\n", a, b, c);
}

Al compilar este programa se obtienen errores similares a los siguientes:

Variable  'a' is initialized more than once
Variable  'b' is initialized more than once
Variable  'c' is initialized more than once

Esto se debe a que en este último programa se incluye el archivo Lib1.h dos veces, una cuando se incluye el archivo Lib2.cpp y la otra cuando se incluye el archivo Lib3.cpp. Esto hace que se declare dos veces cada una de las variables a, b y c, produciendo una ambigüedad que el compilador no puede resolver.

Para solucionar este problema no es suficiente con eliminar la cláusula #include "Lib.h" en uno de los archivos Lib2. cpp o Lib3.cpp. Esto arregaría el problema para el programa Main3.cpp, sin embargo hay que recordar que existen Main1.cpp y Main2.cpp y si se elimina la cáusula de inclusión en alguno de ellos ocasionaría que éste ya no podría compilar si se le hiciera alguna modificación.

La solución a este problema estará en condicionar la inclusión del archivo Lib1.h, de modo que se pueda detectar que si el archivo ya fue incluido con anterioridad en el mismo programa y bloquear esta nueva inclusión.

El lenguaje C/C++ implementa una seri de cáusulas de preprocesador que permiten condicionar la inclusión de archivos, a continuación se muestran algunas de ellas:

#if
#else
#elif              //forma abreviada de "ELSE IF..."
#ifdef            //si está definido ...;
#ifndef          //si no está definido ...;
#endif           //Fin de la cláusula #IF

Empleando estas cláusulas, el problema anterios quedaría solucionado haciendo las siguientes modifivciones a los archivos:

Grabar este ejemplo


// Archivo Lib1.h
// Se definen variables globales
int a,b,c;

// Archivo Lib2.CPP // Se implementa una función donde se inicializa las variables globales // Sólo se incluye Lib1.h la constante simbólica no ha sidodefinida aun #ifndef CONTROL #include "Lib1.h" #define CONTROL 0 #endif void f1(void) { a=1; b=2; c=3; }

// Archivo Main1.cpp // Utiliza la función f1, obsérvese que el programa compila y se ejecuta // sin errores. #include #include "Lib2.CPP" void main (void) { f1 ( ); printf("%5d %5d %5d\n", a, b, c); }

// Archivo Lib3.CPP // Se implementa otra función donde se inicializa las variables globales // Sólo se incluye Lib1.h la constante simbólica no ha sidodefinida aun #ifndef CONTROL #include "Lib1.h" #define CONTROL 0 #endif void f2(void) { a=28; b=35; c=47; }

// Archivo Main2.cpp // Utiliza la funci¢n f2, obsérvese que el programa compila y se ejecuta // sin errores de igual manera que en el programa anterior. #include #include "Lib3.CPP" void main (void) { f2 ( ); printf("%5d %5d %5d\n", a, b, c); }

Finalmente:

// Archivo Main3.cpp
// Utiliza las funciones f1 y f2, obsérvese que el programa yo no presenta
// errores de compilación.
#include 
#include "Lib2.CPP"
#include "Lib3.CPP"
void main (void)
{ f1 ( );
  printf("%5d %5d %5d\n", a, b, c);
  f2 ( );
  printf("%5d %5d %5d\n", a, b, c);
}

Macros

La cláusula #define permite definir, como se indicó anteriormente, constantes simbólicas; sin embargo, también permite la definición de lo que se denominan "macros".

Una macro es también un texto de reemplazo, sin embargo, a diferencia de las constantes simbólicas el reemplazo se hace en forma más inteligente, dando la impresión que se estuvieran pasando parámetros.

Así por ejemplo se puede escribir:

#define SUMA(x,y)    x+y
#define PRODUCTO(x,y)    x*y

Luego en un programa se puedrá escribir:

cont = SUMA (cont, 1);
actual = PRODUCTO (2, actual);
total = SUMA (total, actual);

Estas instrucciones serán reeplazadas por el preprocesador por las siguientes líneas:

cont = cont + 1;
actual = 2 * actual;
total = total + actual;

Se debe tener mucho cuidado cuando se quiere implementar una macro, muchas veces se confunde una macro con una funcíón sin tener en cuenta que no lo es y que sólo se trata de un reemplazo de texto. Un ejemplo de esto se muestra a continuación:

#define  CUADRADO(x)   x*x

Si se escribe en un programa una expresión sencilla como la que se muestra a continuación:

printf("%d\n", CUADRADO(4));         obviamente se obtiene 16

Pero si se escribiera lo siguiente:

printf("%d\n", CUADRADO(3+5));        se obtiene 23 !!!!!, y si se escribe:
printf("%d\n" 5.0 / CUADRADO(8) );         da como resultado 5 !!!!!

Esto se debe a que, como hemos dicho anteriormente, cuando el preprocesador reemplaza el texto lo hace literalmente, así:

CUADRADO(3+5)    se reemplaza por 3+5*3+5        y esto da coo resultado 23,

Por la misma razón:

5.0/CUADRADO(8)    se reemplaza por 5.0/8*8        que resulta 5.0

Para solucionar el primer caso se deberá agregar en la definició de la macro paréntesis, de la siguiente forma:

#define   CUADRADO(x)    (x)*(x)

al ejecutarlo se tiene:

CUADRADO(3+5)    se reemplaza por (3+5)*(3+5)    dando como resultado: 64

sin embargo para la segunda expresión:

5.0 / CUADRADO(8)    se reemplaza por 5.0 / (8)*(8)    dando como resultado: 5, que es un resultado incorrecto.

Para solucionar esto se deberá agregar un nuevo juego de paréntesis en la definició de la macro, de la siguiente forma:

#define   CUADRADO(x)    ((x)*(x))

con lo que se tendría:

5.0 / CUADRADO(8)    se reemplaza por 5.0 / ((8)*(8))    dando como resultado: 0.078125, resultado correcto.

Funciones en Línea

Lo complicado de la definición de las macros en C y los problemas que se presentan al implementar un programa con macros, ha hecho que en C++ se implementen otro tipo de herramienta, más eficiente y simple. Esta herramienta se denomina "Función en Línea".

Una función en línea, a diferencia de las macros, sí es compilada, es decir se transforma en un código objeto (lenguaje máquina). Sin embargo, la ubicación de este código no se realiza en forma similar al de una función común. El código generado se inserta, en el programa objeto, en cada llamado a la función en línea.

Sintaxis:

inline   Tipo de Retorno   Nombre de la Función   ([Declaración de Parámetros])
{ instrucción;
...
...
[return n;]
}

Ejemplo:

inline int abs (int x) { return x0?x:-x; }
inline float cuadrado (int x) { return x*x; }
inline float potencia (float*, int n)
{    float r=1;
     while (n--) r * x;
     return r;
}

La compilación de las funciones en línea se realizán de un modo peculiar, es el compilador y no el usuario quien decide que una función en línea se compile como tal o como una función común y corriente. El compilador descartará la posibilidad de compilarla como función en línea si encuentra que:


Proyectos

Como se mencionó anterioermente, emplear la cláusula #include para incluir archivos que contengan código no es una manera óptima de modular un programa. Esto se debe a que en el momento de compilar el programa, todos los archivos que se encuentren en las cláusulas de inclusión se agregan al texto del programa para luego compilarse por completo todos. Si se quisiera hacer una modificación en uno de los archivos, al momento de compilar el programa, todos los archivos serán traducidos nuevamente, haciendo la tarea de compilación muy engorrosa y lenta.

Por otro lado, como la cláusula #include incluye un archivo en el texto de un programa, este archivo debe ser escrito empleando la sintaxis de C/C++, si se tuviera una función escrita en otro lenguaje de programación, ésta no podría agregarse al programa mediante la cláusula #include.

Un proyecto es la reunón de un grupo de archivos que generarán un único archivo ejecutable. Cada archivo debe ser compilado independientemente de modo que se cree un archivo en lenguaje máquina por cada, uno denominado archivo objeto ( archivo con extensión .obj). Luego se reunen todos estos archivos mediante un programa denominado "linker" (enlazador), quien crea finalmente el archivo ejecutable ( archivo con extensión .exe).

Como cada archivo es compilado independientemente y luego se enlazan, si se tuviera que hacer una modicficación a uno de los archivos, sólo se tendría que compilar nuevamente ese archivo, los demás no. Esto mejora notablemente la eficiencia del proceso de compilación. Por la misma razón, al compilarse independientemente cada archivo, en teoría cada archivo podría estar escrito en un lenguaje de programación distinto.

Para elaborar un proyecto, se debe remitir al ambiente integrado de desarrollo (IDE) que se esté utilizando en ese momento, cada IDE tiene un modo particular de elaboar los proyectos.

Por ejemplo en el Borland C++ versión 3.1, la elaboración de un proyecto se realiza mediante la opción:

Project


La opción Open project permite abrir un proyecto ya existente o crear uno nuevo. La ventana que aparece es similar a la que permite abrir o guardar un archivo cualquiera.

Luego de abierto, a un proyecto se le pueden agregar los archivos que desean enlazar en conjunto mediante la opción Add Item.

Los archivos pueden estar precompilados (con extensión obj).

Luego para compilar un proyecto hay que elegir:

compile Build All.


Bibliotecas de funciones

Una biblioteca de funciones (LIBRARY) es la agrupación de un conjunto de funciones en un archivo. Teóricacmente, un módulo creado para la elaboración de un proyecto que contiene un conjunto de funciones es un biblioteca de funciones, sin embargo el concepto de biblioteca de funciones está ligado a los archivos con extensión .LIB.

Un archivo .LIB es muy parecido a un archivo .OBJ, sin embargo diferencia de los archivos .OBJ, cuando un archivo .LIB es colocado dentro de un proyecto, al momento del enlace, sólo se añade al programa ejecutable aquellas funciones que se encuentren en la biblioteca y que efectivamente se están empleando en los demás módulos. Un archivo .OBJ se añade completamente al programa ejecutable, así sólo se emplee una función de las cientos de funciones que pueda contener el archivo.

La eleboración de una bibloteca de funciones también depende del IDE que se estÉ utilizando. En el Borland C++ versión 3.1, la elaboración de un archivo .LIB se realiza, apartir de un archivo .OBJ, mendiante el programa tlib.



Volver a contenidos AtrásSiguiente