Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.4.6 Invocación de funciones y conversión de argumentos

§1 Sinopsis

Las invocación de funciones se realiza colocando el nombre de la función y los argumentos actuales en el mismo orden que los parámetros formales correspondientes. Ejemplo:

void funcion (int x, char c);
...
int z = 12;
char ch = 'Z';
funcion(ch, z);   // Error: orden de argumentos cambiado !!
funcion(z, ch);   // Ok: invocación con orden correcto


Recordemos que la sintaxis del lenguaje permite también la invocación de funciones a través de punteros a funciones ( 4.2.4b) e incluso de referencias ( 4.2.3), aunque esto último sea menos frecuente.

Cuando las funciones son miembros de clases la invocación sigue una sintaxis especial ( 4.11.2e). En estos casos incluso existen operadores especiales para invocarlas a través de sus punteros ( 4.9.16).

§2 Evaluación de argumentos

La gramática C++ permite utilizar expresiones como argumentos en la invocación de funciones. Estas expresiones son evaluadas, y sus posibles efectos laterales tienen efecto, antes que la función sea cargada en la pila ( 4.4.6b). Sin embargo, tales prácticas son en general desaconsejadas, pues dan lugar a código difícil de leer. Ejemplo:

int x foo (int x) { return x+2; }
...
int main() {
  int n = 3;
  cout << "foo -> " << foo(n++) << endl;
  cout << "foo -> " << foo(++n) << endl;
  ...
}

Salida:

foo ->5
foo ->7

Hay que advertir que el orden de evaluación de los argumentos es indefinido (depende del compilador), por lo que no es recomendable utilizar expresiones que dependan del orden de evaluación de los parámetros. Ejemplo:

int x foo (int x, int y) { return x + y; }
...
int main() {
  int n = 3;
  cout << "foo -> " << foo(n++, n) << endl;
  ...
}

En estas condiciones es imposible predecir si la salida será 7 u 8.

§3 Conversión de argumentos

A continuación de la evaluación, los valores resultantes de las expresiones son convertidos automáticamente a los mismos tipos que los declarados para los parámetros formales.

Ejemplo (suponiendo la definición del caso anterior):

cout << foo(x + 3.5);    // -> 8


§3.1 Cuando no se ha declarado previamente un prototipo de la función, C++ realiza una invocación a la función convirtiendo la totalidad de los argumentos según las reglas de Conversiones aritméticas estándar ( 2.2.5). En cambio, si existe un prototipo de función en el ámbito, C++ convierte los argumentos al tipo declarado para los parámetros.


§3.2 Cuando un prototipo incluye puntos suspensivos (...), el compilador convierte todos los argumentos (si los hay) como en cualquier otro caso hasta la elipsis, después conforma todos los demás parámetros (que han sido declarados variables) según las reglas usuales para argumentos de funciones que no tienen prototipo.


§3.3 Si existe un prototipo, el número de argumentos debe coincidir con los declarados (a menos que existan puntos suspensivos). El tipo de los argumentos también debe coincidir, pero solo hasta el punto en que una asignación pudiera realizar legalmente una conversión (del tipo realmente pasado al tipo esperado). También existe el recurso de hacer una conversión explícita ("cast") para convertir un argumento en un tipo que sea aceptable por el prototipo ( 4.9.9 Modelado de tipos).


§4 En C++ los parámetros son pasados por valor, lo que significa que existen copias locales de los argumentos formales, estas copias son variables locales de la función invocada.

Nota: recordemos que cuando se utiliza una matriz como argumento en la invocación a una función, el valor pasado es un puntero a la dirección de memoria del principio de la matriz, pero habida cuenta que el identificador de una matriz puede ser tomado como puntero a su primer elemento ( 4.3.2), también es válida la regla general. El argumento es pasado por valor, y existe una copia local del argumento formal; en este caso la copia de un puntero.

Ejemplo:

#include <iostream.h>

void fun1(char* str) {           // recibe puntero a carácter
   cout << str << endl;
}
void fun2(char str[]) {          // recibe matriz de caracteres
   cout << str << endl;
}

int main() {                      // ======================
   char ch[10] = "Hola mundo";
   fun1(ch);
   fun2(ch);
}

Salida:

Hola mundo
Hola mundo

Observe que el compilador acepta con total naturalidad que ch es un puntero a carácter en la invocación func1(ch), y una matriz de caracteres en func2(ch). La razón es que en ambos casos, el compilador supone que ch es un puntero a carácter (en el ejemplo, al primer elemento de ch[], una matriz de caracteres), lo que coincidiría con la definición de func1, y que en la definición de func2, char str[] es tomado como char* str.


§5 Cuando se desea que la función llamada pueda alterar el valor de las variables de la función que la invoca, los argumentos actuales utilizados son punteros a las variables respectivas en la función invocante. A su vez la función invocada debe declarar el parámetro como un puntero y acceder a la variable indirectamente a través de él. En el ejemplo que sigue deseamos que la función func pueda alterar el valor de la variable x. Observe que pasamos un puntero a dicha variable, y como func trata el argumento recibido p como tal puntero:

{
  int x = 10;
  int* ptr = &x;
  func(ptr);           // pasamos puntero a x
}                      // ahora x = 30
...
void func (int* p) {   // p es declarado como puntero
  *p = 3 * (*p);       // y tratado como tal puntero!!
}

§6  Recordar que C++ también permite pasar los argumentos por referencia (Paso de argumentos 4.4.5).

§7 Más sobre conversión de parámetros

Es posible incluir sentencias de asignación en la lista de argumentos de invocación de funciones, aunque sea una práctica desaconsejable, pues da lugar a código difícil de interpretar y en ocasiones sin mucho sentido.

Por ejemplo, en C++ es válido el siguiente código que compila sin problema:

#include <iostream.h>

void fun (char* p, int n) {
  for (int i = 1; i <= n ; i++) cout << p << i << endl;
}

void main() {              // =============
  char* pt1 = "Hola, que tal!! ";
  char* pt2;
  int x;
  fun(pt2 = pt1, x =5);   // desaconsejado !!
}

Salida:

Hola, que tal!! 1
Hola, que tal!! 2
Hola, que tal!! 3
Hola, que tal!! 4
Hola, que tal!! 5


En los siguientes epígrafes se detallan algunos aspectos relativos al tratamiento de Identificadores, la convención utilizada para el paso de parámetros ( 4.4.6a); el rendimiento ( 4.4.6b) y la recursión ( 4.4.6c).