Funciones (C++)

Una función es un bloque de código que realiza alguna operación. Una función puede definir opcionalmente parámetros de entrada que permiten a los llamadores pasar argumentos a la función. Una función también puede devolver un valor como salida. Las funciones son útiles para encapsular las operaciones comunes en un solo bloque reutilizable, idealmente con un nombre que describa claramente lo que hace la función. La siguiente función acepta dos enteros de un autor de llamada y devuelve su suma; a y b son parámetros de tipoint.

int sum(int a, int b)
{
    return a + b;
}

La función puede ser invocada, o llamada, desde cualquier lugar del programa. Los valores que se pasan a la función son los argumentos, cuyos tipos deben ser compatibles con los tipos de los parámetros en la definición de la función.

int main()
{
    int i = sum(10, 32);
    int j = sum(i, 66);
    cout << "The value of j is" << j << endl; // 108
}

No hay ningún límite práctico para la longitud de la función, pero el buen diseño tiene como objetivo las funciones que realizan una sola tarea bien definida. Los algoritmos complejos deben dividirse en funciones más sencillas y fáciles de comprender siempre que sea posible.

Las funciones definidas en el ámbito de clase se denominan funciones miembro. En C++, a diferencia de otros lenguajes, una función también pueden definirse en el ámbito de espacio de nombres (incluido el espacio de nombres global implícito). Estas funciones se denominan funciones gratuitas o funciones que no son miembro; se usan ampliamente en la biblioteca estándar.

Las funciones pueden ser sobrecargadas, lo que significa que diferentes versiones de una función pueden compartir el mismo nombre si difieren por el número y/o tipo de parámetros formales. Para obtener más información, consulte Sobrecarga de funciones.

Elementos de una declaración de función

Una declaración de función mínima consta del tipo de valor devuelto, el nombre de la función y la lista de parámetros (que puede estar vacío), junto con palabras clave opcionales que proporcionan más instrucciones al compilador. El siguiente ejemplo es una declaración de función:

int sum(int a, int b);

Una definición de función consiste en una declaración, más el cuerpo, que es todo el código entre las llaves:

int sum(int a, int b)
{
    return a + b;
}

Una declaración de función seguida de un punto y coma puede aparecer en varios lugares de un programa. Debe aparecer antes de cualquier llamada a esa función en cada unidad de traducción. La definición de función debe aparecer solo una vez en el programa, según la regla de una definición (ODR).

Los elementos necesarios de una declaración de función son los siguientes:

  1. El tipo de retorno, que especifica el tipo del valor que devuelve la función, o void si no se devuelve ningún valor. En C++11, auto es un tipo de retorno válido que indica al compilador que infiera el tipo de la sentencia de retorno. En C++14, decltype(auto) también está permitido. Para obtener más información, consulte más adelante Deducción de tipos en tipos de valor devueltos.

  2. El nombre de la función, que debe comenzar con una letra o un carácter de subrayado y no puede contener espacios. En general, los caracteres de subrayado iniciales en los nombres de función de biblioteca estándar indican funciones miembro privadas o funciones que no son miembro que no están diseñadas para su uso por el código.

  3. La lista de parámetros, que es un conjunto delimitado por llaves y separado por comas de cero o más parámetros que especifican el tipo y, opcionalmente, un nombre local mediante el cual se puede acceder a los valores de dentro del cuerpo de la función.

Los elementos opcionales de una declaración de función son los siguientes:

  1. constexpr, que indica que el valor devuelto de la función es un valor constante que se puede calcular en tiempo de compilación.

    constexpr float exp(float x, int n)
    {
        return n == 0 ? 1 :
            n % 2 == 0 ? exp(x * x, n / 2) :
            exp(x * x, (n - 1) / 2) * x;
    };
    
  2. Su especificación de vinculación, extern o static.

    //Declare printf with C linkage.
    extern "C" int printf( const char *fmt, ... );
    
    

    Para obtener más información, consulte Unidades de traducción y vinculación.

  3. inline, que indica al compilador que reemplace todas las llamadas a la función por el propio código de función. La inserción en línea puede mejorar el rendimiento en escenarios donde una función se ejecuta rápidamente y se invoca varias veces en una sección del código crítica para el rendimiento.

    inline double Account::GetBalance()
    {
        return balance;
    }
    

    Para obtener más información, consulte Funciones en línea.

  4. Una noexcept expresión que especifica si la función puede o no lanzar una excepción. En el ejemplo siguiente, la función no produce una excepción si la is_pod expresión se evalúa como true.

    #include <type_traits>
    
    template <typename T>
    T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
    

    Para obtener más información, vea noexcept.

  5. (solo funciones miembro) Los calificadores cv, que especifican si la función es const o volatile.

  6. (solo funciones de los miembros) virtual, override, o final. virtual especifica que una función se puede invalidar en una clase derivada. override significa que una función de una clase derivada reemplaza una función virtual. final significa que una función no se puede invalidar en ninguna clase derivada adicional. Para más información, consulte Funciones virtuales.

  7. (solo funciones miembro) static aplicado a una función miembro significa que la función no está asociada a ninguna instancia de objeto de la clase .

  8. (solo funciones miembro no estáticas) El calificador de referencia, que especifica al compilador qué sobrecarga de una función, debe elegir cuando el parámetro implícito del objeto (*this) es una referencia rvalue frente a una referencia lvalue. Para obtener más información, consulte Sobrecarga de funciones.

Definiciones de función

Una definición de función consiste en la declaración y el cuerpo de la función, encerrado entre llaves, que contiene declaraciones de variables, sentencias y expresiones. El siguiente ejemplo muestra una definición de función completa:

    int foo(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       if(strcmp(s, "default") != 0)
       {
            value = mc.do_something(i);
       }
       return value;
    }

Las variables declaradas dentro del cuerpo se denominan variables locales. Se salen del ámbito cuando finaliza la función; por lo tanto, una función nunca debe devolver una referencia a una variable local.

    MyClass& boom(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       mc.Initialize(i,s);
       return mc;
    }

funciones const y constexpr

Puede declarar una función miembro como const para especificar que la función no puede cambiar los valores de los miembros de datos de la clase . Al declarar una función miembro como const, se ayuda al compilador a imponer la const-corrección. Si alguien intenta por error modificar el objeto utilizando una función declarada como const, se produce un error del compilador. Para obtener más información, consulte const.

Declarar una función como constexpr cuando el valor que produce puede ser determinado en tiempo de compilación. Una función constexpr generalmente se ejecuta más rápido que una función regular. Para obtener más información, vea constexpr.

Plantillas de función

Una plantilla de función es parecida a una plantilla de clase; genera funciones concretas que se basan en los argumentos de plantilla. En muchos casos, la plantilla es capaz de inferir los argumentos de tipo, por lo que no es necesario especificarlos de forma explícita.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs;
}

auto a = Add2(3.13, 2.895); // a is a double
auto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string

Para obtener más información, consulte Plantillas de funciones

Parámetros de función y argumentos

Una función tiene una lista de parámetros separados por comas de cero o más tipos, cada uno de los cuales tiene un nombre mediante el cual se puede acceder a ellos dentro del cuerpo de la función. Una plantilla de función puede especificar más parámetros de tipo o valor. El llamador pasa argumentos, que son valores concretos cuyos tipos son compatibles con la lista de parámetros.

De forma predeterminada, los argumentos se pasan a la función por valor, lo que significa que la función recibe una copia del objeto que se pasa. En el caso de objetos grandes, la realización de una copia puede ser costosa y no siempre es necesaria. Para hacer que los argumentos se pasen por referencia (concretamente por referencia lvalue), agregue un calificador de referencia al parámetro:

void DoSomething(std::string& input){...}

Cuando una función modifica un argumento que se pasa por referencia, modifica el objeto original, no una copia local. Para evitar que una función modifique este argumento, califique el parámetro como const&:

void DoSomething(const std::string& input){...}

C++11: Para controlar explícitamente los argumentos que se pasan mediante rvalue-reference o lvalue-reference, use una referencia doble y en el parámetro para indicar una referencia universal:

void DoSomething(const std::string&& input){...}

Una función declarada con la palabra clave única void en la lista de declaración de parámetros no toma argumentos, siempre que la palabra clave void sea el primer y único miembro de la lista de declaración de argumentos. Los argumentos del tipo void en otra parte de la lista producen errores. Por ejemplo:

// OK same as GetTickCount()
long GetTickCount( void );

Aunque no es válido especificar un void argumento excepto como se describe aquí, los tipos derivados del tipo void (como punteros a void y matrices de ) pueden aparecer en cualquier lugar de la lista de voiddeclaraciones de argumentos.

Argumentos predeterminados

Es posible asignar un argumento predeterminado al último parámetro o parámetros de una firma de función, lo que significa que el llamador puede omitir el argumento cuando se llama a la función, a menos que desee especificar otro valor.

int DoSomething(int num,
    string str,
    Allocator& alloc = defaultAllocator)
{ ... }

// OK both parameters are at end
int DoSomethingElse(int num,
    string str = string{ "Working" },
    Allocator& alloc = defaultAllocator)
{ ... }

// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
    string str,
    Allocator& = defaultAllocator)
{...}

Para obtener más información, consulte Argumentos predeterminados.

Tipos de valor devuelto de función

Una función no puede devolver otra función, o una matriz incorporada; sin embargo, puede devolver punteros a estos tipos, o una lambda, que produce un objeto de función. Excepto en estos casos, una función puede devolver un valor de cualquier tipo que esté en el ámbito, o puede no devolver ningún valor, en cuyo caso el tipo de retorno es void.

Tipos de valor devueltos finales

Un tipo de valor devuelto "normal" se encuentra en el lado izquierdo de la firma de función. Un tipo de valor devuelto final se encuentra en el lado derecho de la firma y va precedido por el -> operador . Los tipos de valor devueltos finales son especialmente útiles en plantillas de función cuando el tipo del valor devuelto depende de los parámetros de plantilla.

template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

Cuando auto se usa junto con un tipo de valor devuelto final, sirve como marcador de posición para cualquier expresión decltype que genere la expresión decltype y no realiza la deducción de tipos.

Variables locales de función

Una variable que se declara dentro del cuerpo de una función se llama variable local o simplemente local. Las variables locales no estáticas solo son visibles dentro del cuerpo de la función y, si se declaran en la pila salen del ámbito cuando se cierra la función. Cuando se construye una variable local y se devuelve por valor, el compilador suele realizar la optimización del valor de retorno con nombre para evitar operaciones de copia innecesarias. Si una variable local se devuelve por referencia, el compilador emitirá una advertencia, ya que cualquier intento por parte del llamador de usar esa referencia se producirá después de la destrucción de la variable local.

En C++, una variable local se puede declarar como estática. La variable solo es visible dentro del cuerpo de la función, pero existe una copia única de la variable para todas las instancias de la función. Los objetos estáticos locales se destruyen durante la finalización especificada por atexit. Si no se construyó un objeto estático porque el flujo de control del programa omite su declaración, no se intenta destruir ese objeto.

Deducción de tipos en tipos devueltos (C++14)

En C++14, se puede usar auto para indicar al compilador que deduzca el tipo de retorno del cuerpo de la función sin tener que proporcionar un tipo de retorno al final. Tenga en cuenta que auto siempre se deduce a un retorno por valor. Use auto&& para indicar al compilador que deduzca una referencia.

En este ejemplo, auto se deducirá como un valor no-const copia de la suma de lhs y rhs.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs; //returns a non-const object by value
}

Tenga en cuenta que auto no conserva la const-ness del tipo que deduce. Para las funciones de reenvío cuyo valor de retorno necesita preservar como const o refencia de sus argumentos, puede usar la decltype(auto)palabra clave, que usa las decltype reglas de inferencia de tipos y preserva toda la información de tipos. decltype(auto) se puede usar como un valor devuelto normal en el lado izquierdo o como un valor devuelto final.

El siguiente ejemplo (basado en el código de N3493), muestra decltype(auto) que se utiliza para permitir el reenvío perfecto de los argumentos de la función en un tipo de retorno que no se conoce hasta que la plantilla es instanciada.

template<typename F, typename Tuple = tuple<T...>, int... I>
decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple = tuple<T...>,
    typename Indices = make_index_sequence<tuple_size<Tuple>::value >>
   decltype( auto)
    apply(F&& f, Tuple&& args)
{
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

Devolución de varios valores desde una función

Hay varias formas de devolver más de un valor desde una función:

  1. Encapsular los valores en una clase u objeto struct con nombre. Requiere que la definición de la clase o estructura sea visible para el autor de la llamada:

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        S s = g();
        cout << s.name << " " << s.num << endl;
        return 0;
    }
    
  2. Devuelve un objeto std::tuple o std::pair:

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    
    int main()
    {
        auto t = f();
        cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl;
    
        // --or--
    
        int myval;
        string myname;
        double mydecimal;
        tie(myval, myname, mydecimal) = f();
        cout << myval << " " << myname << " " << mydecimal << endl;
    
        return 0;
    }
    
  3. Visual Studio 2017 versión 15.3 y posterior (disponible en modo /std:c++17 y posterior): Utilizar enlaces estructurados. La ventaja de los enlaces estructurados es que las variables que almacenan los valores devueltos se inicializan al mismo tiempo que se declaran, lo que en algunos casos puede ser significativamente más eficaz. En la declaración auto[x, y, z] = f();, los corchetes introducen e inicializan nombres que están en el ámbito de todo el bloque de funciones.

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        auto[x, y, z] = f(); // init from tuple
        cout << x << " " << y << " " << z << endl;
    
        auto[a, b] = g(); // init from POD struct
        cout << a << " " << b << endl;
        return 0;
    }
    
  4. Además de utilizar el valor de retorno en sí mismo, puede "devolver" valores definiendo cualquier número de parámetros para utilizar el pasado por referencia de manera que la función pueda modificar o inicializar los valores de los objetos que el autor de la llamada proporciona. Para obtener más información, consulte Argumentos de la función de tipo referencia.

Punteros de función

C++ admite punteros de función de la misma manera que el lenguaje C. Sin embargo, una alternativa con mayor seguridad de tipos suele ser usar un objeto de función.

Se recomienda usar typedef para declarar un alias para el tipo de puntero de función si declara una función que devuelve un tipo de puntero de función. Por ejemplo

typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer

Si esto no se hace, la sintaxis adecuada para la declaración de función se puede deducir de la sintaxis del declarador para el puntero de función reemplazando el identificador (fp en el ejemplo anterior) por el nombre de las funciones y la lista de argumentos, como se indica a continuación:

int (*myFunction(char* s))(int);

La declaración anterior es equivalente a la declaración que usa typedef anteriormente.

Consulte también

Sobrecarga de funciones
Funciones con listas de argumentos de variable
Funciones establecidas como valor predeterminado y eliminadas explícitamente
Búsqueda de nombres dependientes de argumentos (Koenig) en las funciones
Argumentos predeterminados
Funciones insertadas