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 función siguiente acepta dos enteros de un llamador y devuelve su suma; a y b son parámetros de tipo .

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

La función se puede invocar, o llamar, desde cualquier número de lugares del programa. Los valores que se pasan a la función son los argumentos , cuyostipos deben ser compatibles con los tipos de parámetro de la definición de 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 un buen diseño tiene como objetivo 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 gratuitaso funciones que no son miembro; se usan ampliamente en la biblioteca estándar.

Las funciones pueden sobrecargarse,lo que significa que diferentes versiones de una función pueden compartir el mismo nombre si difieren en el número o el tipo de parámetros formales. Para obtener más información, vea 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 pueden estar vacíos), junto con palabras clave opcionales que proporcionan instrucciones adicionales al compilador. El ejemplo siguiente es una declaración de función:

int sum(int a, int b);

Una definición de función consta de 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. Tipo de valor devuelto, que especifica el tipo del valor que devuelve la función o si void no se devuelve ningún valor. En C++11, es un tipo de valor devuelto válido que indica al compilador que infiera el tipo de auto la instrucción return. En C++14, decltype(auto) también se permite . Para obtener más información, consulte más adelante Deducción de tipos en tipos de valor devueltos.

  2. El nombre de función, que debe comenzar con una letra o un carácter de subrayado y no puede contener espacios. En general, un carácter de subrayado inicial en los nombres de función de la biblioteca estándar indica funciones de miembro privado o funciones no miembro que no están pensadas para que las use 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 extern vinculación, o static .

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

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

  3. inline, que indica al compilador que reemplace todas las llamadas a la función con el propio código de la 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, vea Funciones insertadas.

  4. Expresión que especifica si la función puede producir o noexcept no 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) Calificadores cv, que especifican si la función es const o volatile .

  6. (Solo funciones miembro) virtualoverride , o final . virtual especifica que una función se puede reemplazar 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 reemplazar en ninguna otra clase derivada. Para obtener más información, vea 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) Calificador ref, que especifica al compilador qué sobrecarga de una función elegir cuando el parámetro de objeto implícito ( ) es una referencia rvalue frente a una *this referencia lvalue. Para obtener más información, vea Sobrecarga de funciones.

La ilustración siguiente muestra las partes de una definición de función. El área sombreada es el cuerpo de la función.

Diagrama de las partes de una definición de función.
Elementos de una definición de función

Definiciones de función

Una definición de función consta de la declaración y el cuerpo de la función, entre llaves, que contiene declaraciones de variables, instrucciones y expresiones. En el ejemplo siguiente se 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 para especificar que la función no puede cambiar los valores de los miembros const de datos de la clase . Al declarar una función miembro como , se ayuda al compilador a const aplicar const. Si alguien intenta modificar por error el objeto mediante una función declarada como , se produce un const error del compilador. Para obtener más información, vea const.

Declare una función como cuando el valor que genera se puede determinar constexpr posiblemente en tiempo de compilación. Por lo general, una función constexpr se ejecuta más rápido que una función normal. 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, vea Plantillas de función.

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 parámetros adicionales de tipo de 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 los objetos grandes, puede resultar costoso realizar una copia y no siempre es necesario. Para hacer que los argumentos se pasen por referencia (en concreto, la referencia lvalue), agregue un cuantificador 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 tipo de argumento, califique el parámetro como const & :

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

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

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

Una función declarada con la palabra clave única en la lista de declaraciones de parámetros no toma ningún argumento, siempre y cuando la palabra clave sea el primer y único miembro de la lista de voidvoid declaraciones de argumentos. Los argumentos de tipo void en otra parte de la lista generan errores. Por ejemplo:


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

Tenga en cuenta que, aunque no es válido especificar un argumento excepto como se describe aquí, los tipos derivados del tipo (como punteros a y matrices de ) pueden aparecer en cualquier lugar de la lista de declaraciones voidvoid de voidvoid 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, vea Argumentos predeterminados.

Tipos de valor devuelto de función

Es posible que una función no devuelva otra función o una matriz integrada; sin embargo, puede devolver punteros a estos tipos, o una expresión lambda, que genera 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 no devolver ningún valor, en cuyo caso el tipo de valor devuelto 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 más a la derecha de la firma y está 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 se usa junto con un tipo de valor devuelto final, solo sirve como marcador de posición para cualquier expresión decltype que produzca y no realiza la deducción auto de tipos.

Variables locales de función

Una variable que se declara dentro de un cuerpo de función se denomina variable local o simplemente local. Las variables locales no estáticas solo son visibles dentro del cuerpo de función y, si se declaran en la pila, se salen del ámbito cuando finaliza la función. Al construir una variable local y devolverla por valor, el compilador normalmente puede realizar la optimización del valor devuelto 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 crea un objeto estático porque el flujo de control de programa omitió su declaración, no se realiza ningún intento de destruir ese objeto.

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

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

En este ejemplo, se deducirá como una copia de valor no const de la auto 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 auto cuenta que no conserva la const-ness del tipo que deduce. Para las funciones de reenvío cuyo valor devuelto necesita conservar la const-ness o ref-ness de sus argumentos, puede usar la palabra clave , que usa las reglas de inferencia de tipos y conserva toda la información de decltype(auto)decltype tipo. decltype(auto)Puede usar como valor de retorno normal a la izquierda o como valor devuelto final.

En el ejemplo siguiente (basado en código de N3493),se muestra cómo se usa para habilitar el reenvío perfecto de argumentos de función en un tipo de valor devuelto que no se conoce hasta que se crea una instancia de la plantilla.

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());
}

Devolver varios valores de una función

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

  1. Encapsular los valores de una clase con nombre o un objeto de estructura. Requiere que la definición de 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::p air:

    #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 posteriores (disponible en modo y versiones posteriores): use 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 instrucción auto[x, y, z] = f(); , los corchetes introducen e inicializan nombres que están en el ámbito de todo el bloque de función.

    #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 usar el propio valor devuelto, puede "devolver" valores definiendo cualquier número de parámetros para usar pass-by-reference para que la función pueda modificar o inicializar los valores de los objetos que proporciona el autor de la llamada. Para obtener más información, vea Reference-Type Function Arguments.

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 para declarar un alias para el tipo de puntero de función si se declara una función que typedef devuelve un tipo de puntero de función. Por ejemplo

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

Si no es así, la sintaxis correcta para la declaración de la función se puede deducir de la sintaxis de declarador del puntero a función, mediante la sustitución del identificador (fp en el ejemplo anterior) por el nombre y la lista de argumentos de las funciones, como sigue:

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

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

Vea también

Sobrecarga de funciones
Funciones con listas de argumentos variables
Funciones predeterminadas y eliminadas explícitamente
Búsqueda de nombres dependientes de argumentos (Koenig) en funciones
Argumentos predeterminados
Funciones insertadas