3: C en C++

Tabla de contenidos

3.1. Creación de funciones
3.2. Control de flujo
3.3. Introducción a los operadores
3.4. Introducción a los tipos de datos
3.5. Alcance
3.6. Especificar la ubicación del espacio de almacenamiento
3.7. Los operadores y su uso
3.8. Creación de tipos compuestos
3.9. Consejos para depuración
3.10. Direcciones de función
3.11. Make: cómo hacer compilación separada
3.12. Resumen
3.13. Ejercicios

Como C++ está basado en C, debería estar familiarizado con la sintaxis de C para poder programar en C++, del mismo modo que debería tener una fluidez razonable en álgebra para poder hacer cálculos.

Si nunca antes ha visto C, este capítulo le dará una buena base sobre el estilo de C usado en C++. Si está familiarizado con el estilo de C descrito en la primera edición de Kernighan & Ritchie (también llamado K&R) encontrará algunas características nuevas o diferentes tanto en C++ como en el estándar C. Si está familiarizado con el estándar C debería echar un vistazo al capítulo en busca de las características particulares de C++. Note que hay algunas características fundamentales de C++ que se introducen aquí, que son ideas básicas parecidas a características de C o a menudo modificaciones en el modo en que C hace las cosas. Las características más sofisticadas de C++ se explicarán en capítulos posteriores

Este capítulo trata por encima las construcciones de C e introduce algunas construcciones básicas de C++, suponiendo que tiene alguna experiencia programando en otro lenguaje. En el CD-ROM que acompaña a este libro hay una introducción más suave a C, titulada Thinking in C: Foundations for Java & C++ de Chuck Alison (publicada por MidView, Inc. y disponible también en www.MindView.net). Se trata de un seminario en CD-ROM cuyo objetivo es guiarle cuidadosamente a través de los fundamentos del lenguaje C. Se concentra en el conceptos necesarios para permitirle pasarse a C++ o a Java, en lugar de intentar convertirle en un experto en todos los oscuros recovecos de C (una de las razones para usar un lenguaje de alto nivel como C++ o Java es precisamente evitar muchos de estos recovecos). También contiene ejercicios y soluciones guiadas. Tenga presente que este capítulo va después del CD Thinking in C, el CD no reemplaza a este capítulo, sino que debería tomarse como una preparación para este capítulo y para el libro.

3.1. Creación de funciones

En el antiguo C (previo al estándar), se podía invocar una función con cualquier número y tipo de argumentos sin que el compilador se quejase. Todo parecía ir bien hasta que ejecutabas el programa. El programa acababa con resultados misteriosos (o peor, el programa fallaba) sin ninguna pista del motivo. La falta de ayuda acerca del paso de argumentos y los enigmáticos bugs que resultaban es, probablemente, la causa de que C se considerase «un lenguaje ensamblador de alto nivel». Los programadores de pre-Estándar C simplemente se adaptaron.

C y C++ Estándar usan una característica llamada prototipado de funciones. Con esta herramienta se han de describir los tipos de argumentos al declarar y definir una función. Esta descripción es el «prototipo». Cuando la función es llamada, el compilador usa el prototipo para asegurar que los argumentos pasados son los apropiados, y que el valor retornado es tratado correctamente. Si el programador comete un error al llamar a la función, el compilador detecta el error.

Esencialmente, aprendió sobre prototipado de funciones (sin llamarlas de ese modo) en el capítulo previo, ya que la forma de declararlas en C++ requiere de un prototipado apropiado. En un prototipo de función, la lista de argumentos contiene los tipos de argumentos que se deben pasar a la función y (opcionalmente para la declaración), identificadores para los argumentos. El orden y tipo de los argumentos debe coincidir en la declaración, definición y llamada a la función. A continuación se muestra un ejemplo de un prototipo de función en una declaración:

int translate(float x, float y, float z);

No se puede usar la misma sintaxis para declarar los argumentos en el prototipo de una función que en las definiciones ordinarias de variables. Esto significa que no se puede escribir: float x, y, z. Se debe indicar el tipo de cada argumento. En una declaración de función, lo siguiente también es correcto:

int translate(float, float, float);

Ya que el compilador no hace más que chequear los tipos cuando se invoca la función, los identificadores se incluyen solamente para mejorar la claridad del código cuando alguien lo está leyendo.

En la definición de la función, los nombres son necesarios ya que los argumentos son referenciados dentro de la función:

int translate(float x, float  y, float z) {
    x = y = z;
    // ...
}

Esta regla sólo se aplica a C. En C++, un argumento puede no tener nombrado en la lista de argumentos de la definición de la función. Como no tiene nombre, no se puede utilizar en el cuerpo de la función, por supuesto. Los argumentos sin nombre se permiten para dar al programador una manera de «reservar espacio en la lista de argumentos». De cualquier modo, la persona que crea la función aún así debe llamar a la función con los parámetros apropiados. Sin embargo, la persona que crea la función puede utilizar el argumento en el futuro sin forzar una modificación en el código que llama a la función. Esta opción de ignorar un argumento en la lista también es posible si se indica el nombre, pero siempre aparecería un molesto mensaje de advertencia, informando que el valor no se utiliza, cada vez que se compila la función. La advertencia desaparece si se quita el nombre del argumento.

C y C++ tienen otras dos maneras de declarar una lista de argumentos. Si se tiene una lista de argumentos vacía, se puede declarar esta como func() en C++, lo que indica al compilador que hay exactamente cero argumentos. Hay que tener en cuenta que esto sólo significa una lista de argumentos vacía en C++. En C significa «un número indeterminado de argumentos» (lo que es un «agujero» en C ya que deshabilita la comprobación de tipos en ese caso). En ambos, C y C++, la declaración func(void); significa una lista de argumentos vacía. La palabra clave void significa «nada» en este caso (también puede significar «sin tipo» en el caso de los punteros, como se verá mas adelante en este capítulo).

La otra opción para las listas de argumentos se produce cuando no se sabe cuantos argumentos o qué tipos tendrán los argumentos; esto se conoce como lista de argumentos variable. Esta «lista incierta de argumentos» se representada con puntos suspensivos (...). Definir una función con una lista de argumentos variable es significativamente más complicado que definir una función normal. Se puede utilizar una lista de argumentos variable para una función que tiene un grupo de argumentos fijos si (por alguna razón) se quiere deshabilitar la comprobación del prototipo de función. Por eso, se debe restringir el uso de listas de argumentos variables en C y evitarlas en C++ (en el cual, como aprenderá, hay alternativas mucho mejores). El manejo de listas de argumentos variables se describe en la sección de librerías de la documentación de su entorno C particular.

3.1.1. Valores de retorno de las funciones

Un prototipo de función C++ debe especificar el tipo de valor devuelto de la función (en C, si no se especifica será por defecto un int). La especificación del tipo de retorno precede al nombre de la función. Para especificar que no se devolverá valor alguno, se utiliza la palabra reservada void. Esto provocará un error si se intenta devolver un valor desde la función. A continuación hay algunos prototipos completos de funciones:

int f1(void); // Devuelve un entero, no tiene argumentos
int f2(); //  igual que f1() en C++ pero no en C Stantard
float f3(float, int, char, double); // Devuelve un float
void f4(void); // No toma argumentos, no devuelve nada

Para devolver un valor desde una función, se utiliza la sentencia return. Esta sentencia termina la función y salta hasta la sentencia que se halla justo después de la llamada a la función. Si return tiene un argumento, se convierte en el valor de retorno de la función. Si una función indica que retornara un tipo en particular, entonces cada sentencia return debe retornar un valor de ese tipo. Puede haber más de una sentencia return en una definición de función:

//: C03:Return.cpp
// Use of "return"
#include <iostream>
using namespace std;

char cfunc(int i) {
  if(i == 0)
    return 'a';
  if(i == 1)
    return 'g';
  if(i == 5)
    return 'z';
  return 'c';
}

int main() {
  cout << "type an integer: ";
  int val;
  cin >> val;
  cout << cfunc(val) << endl;
} ///:~

Listado 3.1. C03/Return.cpp


En cfunc(), el primer if que comprueba que la condición sea true sale de la función con la sentencia return. Fíjese que la declaración de la función no es necesaria puesto que la definición aparece antes de ser utilizada en main(), de modo que el compilador sabe de su existencia desde dicha definición.

3.1.2. Uso de funciones de librerías C

Todas las funciones en la librería local de funciones de C están disponibles cuando se programa en C++. Se debería buscar bien en la librería de funciones antes de definir una propia - hay muchas probabilidades de que alguien haya resuelto el problema antes, y probablemente haya dedicado más tiempo pensando y depurando.

Una advertencia, del mismo modo: muchos compiladores incluyen muchas funciones extra que hacen la vida mucho mas fácil y resultan tentadoras, pero no son parte de la Librería C Estándar. Si está seguro de que jamás deseará portar la aplicación a otra plataforma (¿y quién está seguro de eso?), adelante -utilice esas funciones y haga su vida más fácil. Si desea que la aplicación pueda ser portada, debería ceñirse únicamente al uso de funciones de la Librería Estándar. Si debe realizar actividades específicas de la plataforma, debería intentar aislar este código de tal modo que pueda cambiarse fácilmente al migrarlo a otra plataforma. En C++, las actividades de una plataforma específica a menudo se encapsulan en una clase, que es la solución ideal.

La fórmula para usar una librería de funciones es la siguiente: primero, encontrar la función en la referencia de programación (muchas referencias de programación ordenan las funciones por categoría además de alfabéticamente). La descripción de la función debería incluir una sección que demuestre la sintaxis del código. La parte superior de esta sección tiene al menos una línea #include, mostrando el fichero principal que contiene el prototipo de función. Debe copiar este #include en su fichero para que la función esté correctamente declarada. Ahora puede llamar la función de la misma manera que aparece en la sección de sintaxis. Si comete un error, el compilador lo descubrirá comparando la llamada a la función con el prototipo de la cabecera e informará de dicho error. El enlazador busca en la Librería Estándar por defecto, de modo que lo único que hay que hacer es: incluir el fichero de cabecera y llamar a la función.

3.1.3. Creación de librerías propias

Puede reunir funciones propias juntas en una librería. La mayoría de paquetes de programación vienen con un FIXME:bibliotecario que maneja grupos de módulos objeto. Cada FIXME:bibliotecario tiene sus propios comandos, pero la idea general es la siguiente: si se desea crear una librería, se debe hacer un fichero cabecera que contenga prototipos de todas las funciones de la librería. Hay que ubicar este fichero de cabecera en alguna parte de la ruta de búsqueda del preprocesador, ya sea en el directorio local (de modo que se podrá encontrar mediante #include "header") o bien en el directorio include (por lo que se podrá encontrar mediante #include <header>). Luego se han de juntar todos los módulos objeto y pasarlos al FIXME:bibliotecario junto con un nombre para la librería recién construida (la mayoría de los bibliotecarios requieren una extensión común, como por ejemplo .lib o .a). Se ha de ubicar la librería completa donde residan todas las demás, de manera que el enlazador sabrá buscar esas funciones en dicha librería al ser invocadas. Pueden encontrar todos los detalles en su documentación particular, ya que pueden variar de un sistema a otro.