Вы находитесь на странице: 1из 23

Unidad 8, Funciones.

Las funciones permiten estructurar programas en segmentos de código para realizar tareas
individuales.

En C ++, una función es un grupo de instrucciones a las que se les asigna un nombre y que se
pueden invocar desde algún punto del programa. La sintaxis más común para definir una función
es:

<tipo de dato> identificador (parametro1, parametro2, ...) {instrucciones}

Donde:

- <tipo de dato> es el tipo del valor a devolver la.

- identificador es el nombre por el cual se puede llamar a la función.

- parámetro (tantos como sea necesario): cada parámetro consta de un tipo seguido de un
identificador, y cada parámetro está separado del siguiente por una coma. Cada parámetro se
parece mucho a una declaración de variable regular. El propósito de los parámetros es permitir
pasar argumentos a la función desde la ubicación desde donde se llama.

- instrucciones el cuerpo de la función. Es un bloque de declaraciones entre un juego de llaves


{} que especifican qué hace realmente la función.

Un Ejemplo.

// Ejemplo de function El resultado es 8


#include <iostream>

using namespace std;

int sumar(int a, int b)


{
int r;
r = a + b;
return r;
}

int main ()
{
int z;
z = sumar(5,3);
cout << "El resultado es " << z;
}

Este programa se divide en dos funciones: sumar y main. Recuerde que no importa el orden en
que se definan, un programa C++ siempre comienza llamando main. De hecho, main es la única
función llamada automáticamente, y el código en cualquier otra función solo se ejecuta si su
función se llama desde main(directa o indirectamente).

En el ejemplo anterior, main comienza declarando la variable z de tipo int, y justo después de
eso, realiza la primera llamada a la función sumar. La llamada a una función sigue una estructura
muy similar a su declaración. En el ejemplo anterior, la llamada a sumar se puede comparar con su
definición (Ver función sumar antes de la función main en el ejemplo anterior):
int sumar ( int a, int b )

z= sumar ( 5, 3 )

Los parámetros en la declaración de función tienen una correspondencia clara con los argumentos
pasados en la llamada de función. La llamada pasa dos valores 5 y 3, a la función; estos
corresponden a los parámetros a y b, declarados en la función sumar.

En el punto en el que se llama a la función sumar desde main, el control pasa a la función sumar:
aquí, la ejecución de main se detiene y solo se reanudará una vez sumar finalice la ejecución. En
el momento de la llamada a la función, el valor de ambos argumentos ( 5 y 3) se copia a las
variables locales int a y int b dentro de la función sumar.

Luego, dentro sumar, se declara otra variable local ( int r), y mediante la expresión r = a + b,
el resultado de a más b se asigna a r; que, para este caso, donde a es 5 y b es 3, significa que
se asignara el valor 8 a la variable r.

La declaración final dentro de la función:

return r;

Finaliza la función sumar y devuelve el control al punto donde se llamó a la función; en este caso:
a la función main. En este preciso momento, el programa transfiere el control de la ejecución a la
función main regresando exactamente al mismo punto en el que fue interrumpido la ejecución de
la función main por la llamada a la función sumar. Pero, debido a que sumar tiene un tipo de
retorno, la llamada se evalúa como que tiene un valor, y este valor es el valor especificado en la
declaración de retorno que finalizó sumar: en este caso particular, el valor de la variable local r,
que en el momento de return La instrucción tenía un valor de 8.

int sumar ( int a, int b )

z= sumar ( 5, 3 )

Por lo tanto, la llamada a sumar es una expresión con el valor devuelto por la función y, en este
caso, ese valor es 8 el cual se asigna a z.

Luego, main simplemente imprime este valor llamando a:

cout << "El resultado es " << z;


Una función puede ser llamar varias veces dentro de un programa, y su argumento, naturalmente,
no se limita solo a literales:

// Ejemplo de función

#include <iostream>

using namespace std;

int restar(int a, int b)


{
int r;
r = a - b;
return r;
}

int main ()
{
int x=5, y=3, z;
z = restar(7,2);

cout << "1er resultado = " << z << '\n';


cout << "2do resultado = " << restar(7,2) << '\n';
cout << "3er resultado = " << restar(x,y) << '\n';
z = 4 + restar(x,y);
cout << "4to resultado = " << z << '\n';
return 0;
}

Similar a la función sumar del ejemplo anterior, este ejemplo define la función restar, que
simplemente devuelve la diferencia entre sus dos parámetros. Esta vez, main llama a esta función
varias veces, demostrando más formas posibles de llamar a una función.

Funciones sin tipo de retorno. <Uso de void>.

Sintaxis de función
<tipo de dato> indentificador ( parametro1, parametro 2, ...) { instrucciones }

Requiere que la declaración comience con un tipo. Este es el tipo del valor devuelto por la
función. Pero, ¿qué pasa si la función no necesita devolver un valor? En este caso, el tipo que se
utilizará es void, que es un tipo especial para representar la ausencia de valor.

// Ejemplo de funciones retornan void


#include <iostream>
using namespace std;

void mostrarMensaje()
{
cout << "Yooo soy una funcion!";
}

int main ()
{
mostrarMensaje();
return 0;
}
void también se puede usar en la lista de parámetros de la función para especificar
explícitamente que la función no requiere parámetros cuando es invocada. Por
ejemplo, mostrarMensaje podría declararse como:

void mostrarMensaje(void)
{
cout << " Yooo soy una funcion!";
}

Argumentos pasados por valor y por referencia.

En las funciones de los ejemplos anteriores, los argumentos siempre se han pasado por valor. Esto
significa que, al llamar a una función, lo que se pasa a la función son los valores de estos
argumentos en el momento de la llamada, estos se copian en las variables que representan los
parámetros de la función. Por ejemplo:

int x=5;
int z;

z = sumar(x, y );

En este caso, a la función sumar se pasan los valores 5 y 3, que son copias de los valores de x
y y, respectivamente. Estos valores (5 y 3) se usan para inicializar las variables establecidas como
parámetros en la definición de la función, pero cualquier modificación de estas variables dentro de
la función no tiene efecto sobre los valores de las variables x e y, ellos no fueron pasados a la
función en la llamada, sino solo copias de sus valores en ese momento.

int sumar ( int a, int b )

z= sumar ( 5, 3 )

Sin embargo, en ciertos casos, puede ser útil acceder a una variable externa desde una
función. Para hacer eso, los argumentos se pueden pasar por referencia, en lugar de ser pasado
por valor. Por ejemplo, la función duplicar en este código duplica el valor de sus tres
argumentos, haciendo que las variables utilizadas como argumentos sean modificadas por la
función llamada:
// Paso de parametron(s) por referencia

#include <iostream>

using namespace std;

void duplicar(int& a, int& b, int& c)


{
a*=2;
b*=2;
c*=2;
}

int main ()
{
int x=1, y=3, z=7;
duplicar(x, y, z);
cout << "x=" << x << ", y=" << y << ", z=" << z;
return 0;
}
Para obtener acceso a sus argumentos, la función declara sus parámetros como referencias. En
C++, las referencias se indican con un signo de empresa ( &) después del tipo de parámetro, como
en los parámetros de la función duplicar en el ejemplo anterior.

Cuando una variable se pasa por referencia, lo que se pasa ya no es una copia es la variable en
sí, la variable identificada por el parámetro de la función, se asocia de alguna manera con el
argumento pasado a la función y cualquier modificación en sus variables locales correspondientes
dentro de la función se refleja en las variables pasadas como argumentos en la llamada.

void duplicar(int& a, int& b, int& c)

duplicar( x, y, z)

De hecho, a, b, y c son alias de los argumentos que se pasan en la llamada a la función duplicar
( x, y y z) y cualquier cambio en a dentro de la función modificara el valor de la variable x fuera
de la función. cualquier cambio en b dentro de la función modificara el valor de la variable y fuera
de la función, y cualquier cambio en c dentro de la función modificara el valor de la variable z
fuera de la función. Por eso, en el ejemplo, la función duplicar al modificar los valores de las
variables a, b y c, afecta los valores de x, y y z.

Consideraciones de eficiencia y referencia a constantes.

Llamar a una función con parámetros pasados por valor hace que se realicen copias de los
valores. Esta es una operación relativamente económica para tipos de datos básicos como int,
pero si el parámetro es de un tipo compuesto más grande (un string o una estructura), puede
resultar en cierto consumo de recursos. Por ejemplo, considere la siguiente función:

string concatenar(string a, string b)


{
return a+b;
}

Esta función toma dos cadenas como parámetros (por valor) y devuelve el resultado de
concatenarlas. Pasando los argumentos por valor, obliga que los parámetros a y b sean copias de
los argumentos que se pasan a la función cuando se le llama. Y si se trata de cadenas largas,
puede significar copiar grandes cantidades de datos solo para la llamada a la función.

Pero esta copia se puede evitar por completo si ambos parámetros son referencias:

string concatenar(string& a, string& b)


{
return a + b;
}

Los argumentos por referencia no requieren una copia. La función opera directamente sobre (alias
de) las cadenas pasadas como argumentos y, como máximo, podría significar la transferencia de
ciertos punteros a la función. A este respecto, la versión de concatenar con referencias es más
eficiente que la versión que acepta parámetros por valores, ya que no necesita copiar cadenas
costosas de copiar.

Por otro lado, las funciones con parámetros de referencia generalmente se perciben como
funciones que modifican los argumentos pasados, porque esa es la finalidad de los parámetros por
referencia.
La solución es que la función garantice que sus parámetros de referencia no serán modificados por
esta función. Esto se puede hacer calificando los parámetros como constantes:

string concatenar(const string& a, const string& b)


{
return a + b;
}

Al calificarlos los parámetros como const, la función tiene prohibido modificar los valores de a y
el de b, pero en realidad puede acceder a sus valores como referencias (alias de los argumentos),
sin tener que hacer copias reales de las cadenas.

Funciones inline

Llamar a una función generalmente causa un cierto consumo de recursos, por lo tanto, para
funciones muy cortas, puede ser más eficiente simplemente insertar el código de la función donde
se llama, en lugar de realizar el proceso de llamar formalmente a una función.

Preceder la declaración de una función con el especificador inline informa al compilador que se
prefiere el reemplazo (expansión en línea) de la función por el cuerpo de esta en lugar del
mecanismo de llamada a función. Esto no cambia en absoluto el comportamiento de una función,
sino que simplemente se utiliza para indicar al compilador que el código generado por el cuerpo de
la función se insertará en cada punto que se llama a la función, en lugar de ser invocado con una
llamada de función regular.

Ejemplo
inline string concatenar(const string& a, const string& b)
{
return a + b;
}

Esto informa al compilador que donde se invoca a concatenar, se reemplace la llamada de la


función por el código de la función (expansión), en lugar de realizar una llamada regular. inline
solo se especifica en la declaración de función, no cuando se llama.

Tenga en cuenta que la mayoría de los compiladores ya optimizan el código para generar
funciones en línea cuando ven la oportunidad de mejorar la eficiencia, incluso si no están
explícitamente marcados con el especificador inline. Por lo tanto, este especificador simplemente
indica al compilador que se prefiere la expansión de la función, aunque el compilador es libre de
optar por la expansión o no. En C++, la optimización es una tarea delegada al compilador, que es
libre de generar cualquier código mientras el comportamiento resultante sea el especificado por el
código.
Parámetros con valores por defecto.
En C++, las funciones también pueden tener parámetros opcionales, para los cuales no se
requieren argumentos en la llamada, de tal manera que, por ejemplo, una función con tres
parámetros puede llamarse con solo dos. Para esto, la función incluirá un valor predeterminado
para su último parámetro, que la función utiliza cuando se llama con menos argumentos. Por
ejemplo.

// Función con valor por defecto


#include <iostream>

using namespace std;

int dividir(int a, int b=2)


{
int r;
r=a/b;
return (r);
}

int main ()
{
cout << dividir(12) << '\n';
cout << dividir(20,4) << '\n';
return 0;
}

En este ejemplo, hay dos llamadas a funcionar divide. En el primero:

divide (12)

La llamada solo pasa un argumento a la función, aunque la función tiene dos parámetros. En este
caso, la función asume que el segundo parámetro es 2 (observe la definición de la función, que
declara su segundo parámetro como int b=2). Por lo tanto, el resultado es 6.

En la segunda llamada:

divide (20,4)

La llamada pasa dos argumentos a la función. Por lo tanto, el valor predeterminado para b( int
b=2) se ignora y b toma el valor pasado como argumento, es decir 4, produciendo un resultado de
5.

Declaración de función(es).
En C++, los identificadores solo pueden usarse en expresiones una vez que han sido
declarados. Por ejemplo, una variable x no se puede usar antes de declararse con una
declaración, como:

int x;

Lo mismo se aplica a las funciones. No se pueden invocar funciones antes de declararlas. Es por
eso que, en todos los ejemplos anteriores de funciones, las funciones siempre se definieron antes
que la función main, que es la función desde donde se llamaron las otras funciones. Si main se
definieran antes que las otras funciones, esto rompería la regla de que las funciones deben
declararse antes de ser utilizadas, y por lo tanto el programa no compilaría.
El prototipo de una función se puede declarar sin definir realmente la función por completo, dando
detalles suficientes para permitir que se conozcan los tipos involucrados en una llamada a
función. Naturalmente, la función se definirá en otro lugar, como más adelante en el código. Pero
al menos, una vez declarado así, ya se puede llamar a la función.

La declaración incluirá todos los tipos involucrados (el tipo de retorno y el tipo de sus
argumentos), utilizando la misma sintaxis que la utilizada en la definición de la función, pero
reemplazando el cuerpo de la función (el bloque de declaraciones) con un punto y coma final.

La lista de parámetros no necesita incluir los nombres de los parámetros, sino solo sus tipos. Sin
embargo, los nombres de los parámetros se pueden especificar, pero son opcionales y no es
necesario que coincidan necesariamente con los de la definición de la función. Por ejemplo, una
función llamada protofuncion con dos parámetros int puede declararse con cualquiera de estas
declaraciones:

int protofuncion (int first, int second);


int protofuncion (int, int);

De todos modos, incluir un nombre para cada parámetro siempre mejora la legibilidad de la
declaración.

// declarando funccion prototipo


#include <iostream>
using namespace std;

void par(int x);


void impar(int x);

int main()
{
int i;

do {
cout << "Favor introducir un numero (Introducir 0 para salir): ";
cin >> i;
impar(i);
} while (i != 0);

return 0;
}

void impar(int x)
{
if ((x%2) !=0 )
cout << "Es impar .\n";
else
par(x);
}

void par(int x)
{
if ((x%2) ==0 )
cout << "Es par.\n";
else
impar(x);
}
Este ejemplo, de hecho, no es un ejemplo de eficiencia. Probablemente pueda escribir una
versión de este programa con la mitad de las líneas de código. De todos modos, este ejemplo
ilustra cómo se pueden declarar las funciones antes de su definición:

Las siguientes líneas:


void par(int a);
void impar(int a);

Declarar el prototipo de las funciones. Ya contienen todo lo necesario para llamarlos, su nombre,
los tipos de sus argumentos y su tipo de retorno ( void en este caso). Con estas declaraciones de
prototipo en su lugar, se pueden llamar antes de que estén completamente definidas, lo que
permite, por ejemplo, colocar la llamada a la función dentro de la función main antes de la
definición real de estas funciones.

Recursividad.
La recursividad es la propiedad que poseen las funciones de llamarse ellas mismas. Es útil para
algunas tareas, como ordenar elementos o calcular la factorial de un número. Por ejemplo, para
obtener el factorial de un número ( n!) la fórmula matemática es:

n! = n * (n-1) * (n-2) * (n-3) ... * 1

Más concretamente, 5!(factorial de 5) sería:

5! = 5 * 4 * 3 * 2 * 1 = 120
Y una función recursiva para calcular esto en C ++ podría ser:

// Calcular factorial c
#include <iostream>
using namespace std;

long factorial (long a)


{
if (a > 1)
return (a * factorial (a-1));
else
return 1;
}

int main ()
{
long numero = 9;
cout << numero << "! = " << factorial (numero);
return 0;
}

Observe cómo en la función factorial incluimos una llamada a ella misma, pero solo si el
argumento aprobado fue mayor que 1, ya que, de lo contrario, la función realizaría un bucle
recursivo infinito, en el que una vez que llegara a 0, continuaría multiplicándose por todos los
números negativos.
Sobrecarga y plantillas.
En C ++, dos funciones diferentes pueden tener el mismo nombre o identificador si sus
parámetros son diferentes; ya sea porque tienen un número diferente de parámetros o porque
cualquiera de sus parámetros es de un tipo diferente. Por ejemplo:
// Sobrecarga de funciones
#include <iostream>
using namespace std;

int operera(int a, int b)


{
return (a*b);
}

double operar(double a, double b)


{
return (a/b);
}

int main ()
{
int x=5,y=2;
double n=5.0,m=2.0;
cout << operar(x,y) << '\n';
cout << operar(n,m) << '\n';
return 0;
}

En este ejemplo, hay dos funciones llamadas operar, pero una de ellas tiene dos parámetros de
tipo int, mientras que la otra las tiene de tipo double. El compilador sabe a cuál llamar en cada
caso examinando los tipos pasados como argumentos cuando se llama a la función. Si se llama con
dos argumentos int, llama a la función que tiene dos parámetros int, y si se llama con
dos doubles, llama la función que tiene dos doubles.

En este ejemplo, ambas funciones tienen comportamientos bastante diferentes, la versión int
multiplica sus argumentos, mientras que la versión double los divide. Esto generalmente no es
una buena idea. Generalmente se espera que dos funciones con el mismo nombre tengan, al
menos, un comportamiento similar, pero este ejemplo demuestra que es totalmente posible que
no lo hagan. Dos funciones sobrecargadas (es decir, dos funciones con el mismo nombre) tienen
definiciones completamente diferentes; son, en efecto, funciones diferentes, que solo tienen el
mismo nombre.

Tenga en cuenta que una función no se puede sobrecargar solo por su tipo de retorno. Al menos
uno de sus parámetros debe tener un tipo diferente.
Plantillas de funciones.
Las funciones sobrecargadas pueden tener la misma definición. Por ejemplo:
// Funcion sobrecargada
#include <iostream>
using namespace std;

int sumar(int a, int b)


{
return a+b;
}

double sumar(double a, double b)


{
return a+b;
}

int main ()
{
cout << sumar(10,20) << '\n';
cout << sumar(1.0,1.5) << '\n';
return 0;
}

Aquí, sumar está sobrecargado con diferentes tipos de parámetros, pero con el mismo cuerpo
exacto.

La función sumar podría estar sobrecargada para muchos tipos, y podría tener sentido que todos
tengan el mismo cuerpo. Para casos como este, C++ tiene la capacidad de definir funciones con
tipos genéricos, conocidos como plantillas de funciones. La definición de una plantilla de función
sigue la misma sintaxis que una función normal, excepto que está precedida por la palabra clave
template y una serie de parámetros de plantilla encerrados entre <>:

template <template-parametro> declaración-funcion


Los parámetros de plantilla son una serie de parámetros separados por comas. Estos parámetros
pueden ser los tipos genéricos, para especificar un tipo genérico se emplea una de las palabras
claves class o typename seguido de un identificador. Este identificador se puede usar en la
declaración de función como si fuera un tipo regular. Por ejemplo, una función genérica
sumar genérica podría definirse como:

template <class AlgunTipo>


AlgunTipo sumar (AlgunTipo a, AlgunTipo b)
{
return a+b;
}

No importa si el tipo genérico se especifica con palabra clave class o typename en la lista de
argumentos de la plantilla (son 100% sinónimos en las declaraciones de plantilla).

En el código anterior, la declaración AlgunTipo(un tipo genérico dentro de los parámetros de


plantilla encerrados entre paréntesis angulares) permite AlgunTipo usarse en cualquier lugar de la
definición de la función, como cualquier otro tipo; se puede usar como tipo para parámetros, como
tipo de retorno o para declarar nuevas variables de este tipo. En todos los casos, representa un
tipo genérico que se determinará en el momento en que se instancia la plantilla.

Instalar una plantilla es aplicar la plantilla para crear una función usando tipos o valores
particulares para sus parámetros de plantilla. Esto se hace llamando a la plantilla de función, con
la misma sintaxis que llamar a una función regular, pero especificando los argumentos de plantilla
encerrados entre corchetes angulares:
name <template-arguments> (function-arguments)
Por ejemplo, la plantilla sumar de función definida anteriormente se puede llamar como:

x = sumar<int>(10,20);

La función sumar<int> es solo una de las posibles instancias de la plantilla de función sumar. En
este caso, al usar int como argumento de plantilla en la llamada, el compilador crea
automáticamente una versión de la funcion sumar donde AlgunTipo se reemplaza donde quiera
que se encuentre (en la funcion) por int, como si se definiera como:

int sumar(int a, int b)


{
return a+b;
}

Ejemplo.
// function template
#include <iostream>

using namespace std;

template <class T>


T sumar(T a, T b)
{
T resultado;
result = a + b;
return result;
}

int main () {
int i=5, j=6, k;
double f=2.0, g=0.5, h;
k=sumar<int>(i,j);
h=sumar<double>(f,g);
cout << k << '\n';
cout << h << '\n';
return 0;
}

En este caso, hemos utilizado T como nombre de parámetro de plantilla, en lugar


de AlgunTipo. No hace ninguna diferencia, y T en realidad es un nombre de parámetro de
plantilla bastante común para los tipos genéricos.

En el ejemplo anterior, usamos la plantilla de función sumar dos veces. La primera vez con
argumentos de tipo int, y la segunda con argumentos de tipo double. El compilador ha
instanciado y luego llamado cada vez la versión apropiada de la función.

Observe también cómo T también se usa para declarar una variable local de ese tipo (genérico)
dentro de sumar:

T result;

Por lo tanto, el resultado será una variable del mismo tipo que los parámetros a y b, y como el
tipo devuelto por la función.

En este caso específico donde el tipo genérico T se usa como parámetro para sumar, el compilador
incluso puede deducir el tipo de datos automáticamente sin tener que especificarlo explícitamente
entre paréntesis angulares. Por lo tanto, en lugar de especificar explícitamente los argumentos de
la plantilla con:
k = sumar<int> (i,j);
h = sumar<double> (f,g);
Es posible simplemente escribir:
k = sum (i,j);
h = sum (f,g);

sin el tipo encerrado entre paréntesis angulares. Naturalmente, para eso, el tipo no será
ambiguo. Si sumar se llama con argumentos de diferentes tipos, es posible que el compilador no
pueda deducir el tipo T de forma automática.

Las plantillas son una característica potente y versátil. Pueden tener múltiples parámetros de
plantilla, y la función aún puede usar tipos regulares sin plantilla. Por ejemplo:
// Plantillas de funciones
#include <iostream>

using namespace std;

template <class T, class U>


bool sonIguales(T a, U b)
{
return (a==b);
}

int main ()
{
if (sonIguales(10,10.0))
cout << "X y Y son iguales\n";
else
cout << "X y Y no son iguales\n";

return 0;
}

Tenga en cuenta que este ejemplo utiliza la deducción automática de parámetros de plantilla en la
llamada a sonIguales:

sonIguales(10,10.0)

Es equivalente a:
sonIguales <int,double>(10,10.0)

No hay ambigüedad posible porque los literales numéricos son siempre de un tipo específico: a
menos que se especifique lo contrario con un sufijo, los literales enteros siempre producen valores
de tipo int, y los literales de coma flotante siempre producen valores de tipo double. Por lo
tanto, 10 siempre será int y 10.0 siempre será double.
Argumento de plantilla sin tipo.
Los parámetros de la plantilla no solo pueden incluir tipos introducidos por class o typename, sino
que también pueden incluir expresiones de un tipo específico:

// Argumentos de plantillas
#include <iostream>
using namespace std;

template <class T, int N>


T multiplicar(T val)
{
return val * N;
}

int main() {
std::cout << multiplicar<int,2>(10) << '\n';
std::cout << multiplicar<int,3>(10) << '\n';
return 0;
}

El segundo argumento de la plantilla de función multiplicar es de tipo int. Simplemente se ve


como un parámetro de función regular, y en realidad se puede usar como uno.

Sin embargo, existe una diferencia importante: el valor de los parámetros de plantilla se
determina en tiempo de compilación para generar una creación de instancias diferentes de la
función multiplicar, y por lo tanto el valor de ese argumento nunca se pasa durante el tiempo
de ejecución: Las dos llamadas a multiplicar en main llaman esencialmente dos versiones de
la función: una que siempre se multiplica por dos y otra que siempre se multiplica por tres. Por
esa misma razón, el segundo argumento de plantilla debe ser una expresión constante (no se
puede pasar una variable).

Alcance de las variables (Scopes).


Los nombres de las entidades u objetos, como las variables, las funciones y los tipos compuestos
deben declararse antes de usarse en C++. El punto en el programa donde ocurre esta declaración
influye en su visibilidad:

una entidad declarada fuera de cualquier bloque tiene alcance global, lo que significa que su
nombre es válido en cualquier parte del código. Mientras que una entidad declarada dentro de un
bloque, como una función, tiene alcance de bloque y solo es visible dentro del bloque específico en
el que se declara, pero no fuera de él.

Las variables con alcance de bloque se conocen como variables locales.

Por ejemplo, una variable declarada en el cuerpo de una función es una variable local que se
extiende hasta el final de la función (es decir, hasta la llave} que cierra la definición de la función),
pero no fuera de ella:

int baa; // Variable global

int funcionHacerAlgo ()
{
int bar; // variable local
bar = 0;
}

int otraFuncion()
{
baa = 1; // Bien: bass es una variable global
bar = 2; // Error: bar no se puede acceder en esta función
}
En cada ámbito, un nombre solo puede representar una entidad. Por ejemplo, no puede haber dos
variables con el mismo nombre en el mismo ámbito:
int funcionHacerAlgo()
{
int x;
x = 0;
double x; // Error: identificador empleado en este scope
x = 0.0;
}

La visibilidad de una entidad con alcance de bloque se extiende hasta el final del bloque, incluidos
los bloques internos. Sin embargo, un bloque interno, debido a que es un bloque diferente, puede
reutilizar un nombre existente en un ámbito externo para referirse a una entidad diferente; en
este caso, el nombre se referirá a una entidad diferente solo dentro del bloque interno, ocultando
la entidad que nombra fuera. Mientras esté fuera de él, todavía se referirá a la entidad
original. Por ejemplo:
#include <iostream>
using namespace std;

int main () {
int x = 10;
int y = 20;
{
int x; // ok, Alcance interno.
x = 50; // Asignacion de valor a variable interna x
y = 50; // Asignacion de valor a variable externa y
cout << "Bloque Interno:\n";
cout << "x: " << x << '\n';
cout << "y: " << y << '\n';
}
cout << "Bloque Externo:\n";
cout << "x: " << x << '\n';
cout << "y: " << y << '\n';
return 0;
}

Tenga en cuenta que la variable y no está oculto en el bloque interno, por lo tanto, acceder a la
variable y es un acceso a la variable externa.

Las variables declaradas a lo interno de un bloque, como los parámetros de función y las variables
declaradas en bucles (ciclos) y condiciones (como las declaradas en un for o un if) son locales al
bloque.

Espacios de nombres ( namespace )


Solo puede existir una entidad con un nombre particular en un ámbito particular. Esto rara vez es
un problema para los nombres locales, ya que los bloques tienden a ser relativamente cortos, y los
nombres tienen propósitos particulares dentro de ellos, como nombrar una variable de contador,
un argumento, etc.

Pero los nombres no locales brindan más posibilidades de colisión de nombres. , especialmente
considerando que las bibliotecas pueden declarar muchas funciones, tipos y variables, ninguna de
ellas de naturaleza local, y algunas de ellas muy genéricas.

Los espacios de nombres nos permiten agrupar entidades con nombre que de otro modo
tendrían un alcance global en ámbitos más estrechos, dándoles un alcance de espacio de
nombres . Esto permite organizar los elementos de los programas en diferentes ámbitos lógicos a
los que se hace referencia por nombres.
La sintaxis para declarar espacios de nombres es:
Namasoace identificador
{
nombre_entidades
}

Donde identificador es cualquier identificador válido y nombre_entidades es el conjunto de


variables, tipos y funciones que se incluyen dentro del espacio de nombres. Por ejemplo:

namespace miNombreDeEspacio
{
int a;
int b;
}

En este caso, las variables a y b son variables normales declaradas dentro de un espacio de
nombres llamado miNombreDeEspacio.

Normalmente se puede acceder a estas variables desde su espacio de nombres, con su


identificador ( a o bien b), pero si se accede desde fuera del namesoace miNombreDeEspacio,
deben estar debidamente calificados con el operador de ámbito ::. Por ejemplo, para acceder a las
variables anteriores desde el exterior miNombreDeEspacio, deben calificarse como:

miNombreDeEspacio::a
miNombreDeEspacio::b

Los espacios de nombres son particularmente útiles para evitar colisiones de nombres. Por
ejemplo:
#include <iostream>
using namespace std;

namespace nameSpaceA
{
int valor() { return 5; }
}

namespace nameSpaceB
{
const double pi = 3.1416;
double valor() { return 2*pi; }
}

int main () {
cout << nameSpaceA::value() << '\n';
cout << nameSpaceB::value() << '\n';
cout << nameSpaceB::pi << '\n';
return 0;
}

En este caso, hay dos funciones con el mismo nombre: valor. Uno se define dentro del espacio
de nombres nameSpaceA y el otro en nameSpaceB. No se producen errores de redefinición
gracias a los espacios de nombres. Observe también cómo pi se accede de manera no calificada
desde el espacio de nombres nameSpaceB, sin embargo se accede nuevamente a pi desde la
función main, pero aquí debe calificarse como nameSpaceB::pi.
Using
La palabra clave using introduce un nombre de espacio dentro de una región (como un bloque),
evitando así la necesidad de calificar el nombre. Por ejemplo:
// using
#include <iostream>
using namespace std;

namespace primero
{
int x = 5;
int y = 10;
}

namespace segundo
{
double x = 3.1416;
double y = 2.7183;
}

int main () {
using primero::x;
using segundo::y;
cout << x << '\n';
cout << y << '\n';
cout << primero::y << '\n';
cout << segundo::x << '\n';
return 0;
}

Alias de nombre de espacio


Los espacios de nombres existentes se pueden asignar a estos, con la siguiente sintaxis:

namespace nuevoNomombre = nombreActual;

El nombre de espacio estándar ( std ).


Todas las entidades (variables, tipos, constantes y funciones) de la biblioteca estándar de C++ se
declaran dentro del espacio de nombres std.

using namespace std;

Esto introduce la visibilidad directa de toda la entidad que pertenecen al nombre de espacio std en
el código.

Estructura con miembros tipo funciones.

Las estructuras (Anterior Unidad-6) admite miembros funciones, en el paradigma de programación


orientada a objeto le cambian el nombre y en lugar de llamarle funciones le denominan métodos.
Una función dentro de una estructura puede acceder a todos los miembros dentro de la estructura.

Existe una función con una finalidad específica, asignar valores iniciales a los miembros (datos) de
la estructura, estas funciones se conocen como constructores estos se caracterizan por:
1. Tiene como identificador el mismo nombre de la estructura.
2. No tienen valor de retorno (ni siquiera void )
3. Se pueden sobrecargar al igual que cualquier otra función
4. Solo se pueden invocar al momento de crear una variable tipo la estructura.
Ejemplo.
Crear una estructura que representé una calculadora simple, esta solo pude realizar las siguientes
operaciones aritméticas: sumar, restar, multiplicar y dividir.
//- Ejemplo Calculadora
#include <iostream>
struct sCaluladora
{
private:
int operacion;
float primerOperando;
float segundoOperando;
float sumar() { return primerOperando + segundoOperando; }
float restar() { return primerOperando - segundoOperando; }
float multiplicar() { return primerOperando * segundoOperando; }
float dividir() { return primerOperando / segundoOperando; }
float getValorPrimerOperando() { return primerOperando; }
float getValorSegundoOperando() { return segundoOperando; }
void asignarValorOperando(float& operando, const std::string& mensaje) {
std::cout << mensaje;
std::cin >> operando;
}

void opcionOperacion() {
do {
std::cout
<< std::endl << std::endl
<< "Seleccionar operacion Ejecutar " << std::endl
<< std::endl
<< "1- Sumar" << std::endl
<< "2- Restar" << std::endl
<< "3- Multiplicar" << std::endl
<< "4- Dividir" << std::endl
<< std::endl
<< "5- Salir"
<< std::endl
<< "Seleccionar Opcion .: ";
std::cin >> operacion;
} while (operacion < 1 || operacion > 5);
}
public:
sCaluladora(
const float valorPrimerOperando = 0,
const float valorSegundoOperando = 0
) : primerOperando(valorPrimerOperando),
segundoOperando(valorSegundoOperando) {}

void calcular() {
opcionOperacion();
while (operacion != 5) {
asignarValorOperando( primerOperando, "Valor Primer Operando " );
asignarValorOperando( segundoOperando, "Valor Segundo Operando " );
std::cout << std::endl << "Resultado ";
switch (operacion)
{
case 1: std::cout << sumar() << std::endl; break;
case 2: std::cout << restar() << std::endl; break;
case 3: std::cout << multiplicar() << std::endl; break;
case 4: std::cout << dividir() << std::endl; break;
}
opcionOperacion();
}
}
};

int main()
{
sCaluladora cal;
cal.calcular();
return 0;
}
Argumentos funcion main.
La función main puede recibir argumentos pasados desde la línea de comandos
a un programa. Existen dos variables predefinidas dentro del lenguaje que
reciben los argumentos que se pasan al ejecutar un programa.
Identificador Tipo de dato Descripcion
argc int
contiene el número de argumentos que se han
introducido.
argv array
array de punteros a caracteres.

contiene los argumentos que se han pasado desde el sistema operativo al


argv :

invocar el programa.
Argc: como mínimo valdrá 1, ya que el nombre del programa se toma como
primer argumento, almacenado con argv[0], que es el primer elemento de la
matriz. Cada elemento del array apunta a un argumento de la línea de ódenes.
Todos los argumentos de la línea de ordenes son cadenas.
Ejemplo
#include <iostream>

using namespace std;


int main(int argc, char *argv[])
{

if(argc != 2) {
cout << "Ha olvidado su nombre.\n";
exit(1);
}

cout << "Hola" << argv[1];


return 0;
}
Modularización y compilación por separado.
Tomando como punto de partida el ejemplo anterior ( Ejemplo Calculadora ) procederemos a crear versión
modular de este e indicaremos como poder compilar por separado el mismo.

1. Crearemos archivo cabecera denominado calculadora.h, este solo contendrá la declaración de la


estructura así como la declaración de los miembros que la componen, este quedaría de la siguiente
forma.

#ifndef _CALCULADORA
#define _CALCULADORA

struct sCaluladora
{
private:
int operacion;
float primerOperando;
float segundoOperando;

float sumar();
float restar();
float multiplicar();
float dividir();

float getValorPrimerOperando();
float getValorSegundoOperando();

void asignarValorOperando(float& operando, const std::string& mensaje);

void opcionOperacion();

public:
sCaluladora(
const float valorPrimerOperando = 0,
const float valorSegundoOperando = 0
) : primerOperando(valorPrimerOperando),
segundoOperando(valorSegundoOperando)
{}

void calcular();
};

#endif

Notar que en este archivo solo colocamos las declaraciones, no contiene las implementaciones.

2. Crearemos archivo con la implementación de los mimbros que forman la estructura, denominaremos
el archivo como calculadora.c++, el contenido de este será :
#include <iostream>
#include "calculadora.h"

float sCaluladora::sumar()
{
return primerOperando + segundoOperando;
}

float sCaluladora::restar()
{
return primerOperando - segundoOperando;
}

float sCaluladora::multiplicar()
{
return primerOperando * segundoOperando;
}

float sCaluladora::dividir()
{
return primerOperando / segundoOperando;
}
float sCaluladora::getValorPrimerOperando()
{
return primerOperando;
}

float sCaluladora::getValorSegundoOperando()
{
return segundoOperando;
}

void sCaluladora::asignarValorOperando(float& operando, const std::string& mensaje)


{
std::cout << mensaje;
std::cin >> operando;
}

void sCaluladora::opcionOperacion()
{
do {
std::cout
<< std::endl << std::endl
<< "Seleccionar operacion Ejecutar " << std::endl
<< std::endl
<< "1- Sumar" << std::endl
<< "2- Restar" << std::endl
<< "3- Multiplicar" << std::endl
<< "4- Dividir" << std::endl
<< std::endl
<< "5- Salir"
<< std::endl
<< "Seleccionar Opcion .: ";
std::cin >> operacion;
} while (operacion < 1 || operacion > 5);
}

void sCaluladora::calcular()
{
opcionOperacion();

while (operacion != 5)
{
asignarValorOperando( primerOperando, "Valor Primer Operando " );
asignarValorOperando( segundoOperando, "Valor Segundo Operando " );

std::cout << std::endl << "Resultado ";


switch (operacion)
{
case 1: std::cout << sumar() << std::endl; break;
case 2: std::cout << restar() << std::endl; break;
case 3: std::cout << multiplicar() << std::endl; break;
case 4: std::cout << dividir() << std::endl; break;
}

sCaluladora::opcionOperacion();

}
}

Nota que este archivo no contiene la función main()


3. Crearemos archivo calculadora_main.c++ con el siguiente contenido
#include <iostream>
#include "calculadora.h"

int main()
{
sCaluladora cal;
cal.calcular();
return 0;
}

Nota:
Con los archivos anteriores logramos modularizar el programa calculador.

Compilación por separado.


Para compilar por separado emplando el compilador de MinGw, utilizaremos
g++ -c nombre_programa_fuente -o nombre_programa-objeto.o
Esto permitirá compilar el programa sin enlazado (no crea ejecutable) solo crea el programa objeto.

Compilado de los módulos


a) g++ -c calculadora.c++ -o calculadora.o
b) g++ -c calculadora_main.c++ -o calcuadora_main.o

c) Luego enlazamos los programas objetos


g++ calculadora.o calculadora_main.o -o calculadora.exe

Con esto logramos compilar por separado los módulos y luego crear el ejecutable empleando los
programas objetos.

Notar que el ejemplo original contiene todo el código en un solo archivo.

El lenguaje C++ dispone de funciones propias predefinidas como sizeof, además posee otras las cuales forman
parte de las librerías estándar de c++, entre estas podemos enumerar:
De la librería cmath
pow : doble pow (doble base, doble exponente);
Devuelve la base elevada al exponente de potencia

Ejercicio.

1) Crear programa en C++ para evaluar la expresión (𝑎𝑥 + 𝑏𝑦)𝑛 . Para esto, tener en cuenta
las siguientes expresiones:

𝑛
𝑛
𝑛 ( ) (𝑎𝑥)𝑛−𝑘 (𝑏𝑦)𝑘
(𝑎𝑥 + 𝑏𝑦) = ∑ 𝑘
𝑘=0

𝑁 𝑛!
( ) = 𝑘!(𝑛−𝑘)!
𝐾

𝑛! = 𝑛 ∗ (𝑛 − 1) ∗ (𝑛 − 2) ∗ … ∗ 2 ∗ 1
a) Escribir función cuyo prototipo sea:

long facurial(int n);

La función factorial recibe como parámetro un entero y devuelve el factorial del


mismo.

b) Escribir una función con el prototipo:


long combinaciones(int n, int k);

la función combinaciones recibe como parámetro dos enteros n y k, y devuelve como


𝑛
resultado el valor de ( ).
𝑘

c) Escribir una función que contenga el prototipo:


double potencia(double base, int exponente);

d) la función potencia recibe como parámetro dos enteros, base y exponente y


devuelve como resultado el valor de 𝑏𝑎𝑠𝑒 𝑒𝑥𝑝𝑜𝑛𝑒𝑛𝑡𝑒 .

e) La función main leerá os valores de a, b, n, x e y, y utilizando las anteriores


mostrará como resultado el valor de (𝑎𝑥 + 𝑏𝑦)𝑛

2) Escribir nuevamente el ejercicio de registro de productos (ejercicio colocado en la unidad


anterior, estructuras) empleando funciones que permitan:
a. Registrar
b. Modificar
c. Eliminar
d. Listar
Los productos.

Nota:
El programa debe cumplir con los mismos requisitos colocados originalmente.