Esta sección es la primera parte de la introducción a C++. Aquí nos enfocamos en el lenguaje C, del cuál C++ fué adoptado. C++ extiende el lenguaje de programación C con una fuerte tipificación, algunas características y - más importante aún - conceptos de orientación a objetos.
Desarrollado en en la última parte de los 1970s, C se constituyó en un enorme éxito debido al desarrollo de UNIX que fue escrito casi por completo en este lenguaje [4]. En contraste con otros lenguajes de alto nivel, C fué escrito por programadores para programadores. De tal manera que algunas veces permite, por decirlo así, cosas extrañas que en otros lenguajes como Pascal están prohibidas debido a su mala influencia sobre el estilo de programación. De todas maneras, cuando se usa con alguna disciplina, C es un lenguaje tan bueno como cualquier otro.
Los comentarios en C se encierran en /* ... */. Y no se pueden anidar .
La Tabla 7.1 describe los tipos de datos integrados de C. El Size (tamaño) especificado se mide en bytes en una PC 386 corriendo Linux 1.2.13. El Domain (dominio) provisto se basa en el valor del Size (tamaño). Puedes obtener información acerca del tamaño de los tipos de datos por medio del operador sizeof.
Las variables de estos tipos se definen simplemente precediendo el nombre con el tipo:
int an_int; float a_float; long long a_very_long_integer;
Por medio de struct puedes combinar varios tipos dirferentes en forma conjunta. En otros lenguajes, ésto es llamado algunas veces record (registro):
struct date_s { int day, month, year; } aDate;
La definición de arriba de aDate es también la declaración de una estructura llamada date_s. Podemos definir otras variables de este tipo referenciando la estructura por el nombre:
struct date_s anotherDate;
No es obligatorio poner nombre a las estructuras. Si omitimos el nombre, simplemente no podemos reutilizarlas. Sin embargo, si damos nombre a una estructura, podemos simplemente declararla sin necesidad de definir una variable:
struct time_s { int hour, minute, second; };
Podemos usar esta estructura como se mostró para anotherDate. Esto es muy similar al tipo de definición conocido en otros lenguajes, donde un tipo es declarado previamente a la definición de una variable de ese tipo.
Las variables deben ser definidas antes de usarse. Estas definiciones deben ocurrir antes de cualquier instrucción, así, constituyen al parte inicial o superior dentro de un bloque de instrucciones.
C define todos las instrucciones comunes para control de flujo. Las instrucciones son terminadas por un punto y coma " ;". Podemos agrupar instrucciones múltiples dentro de bloques, encerrando aquéllas dentro de llaves. Dentro de cada bloque, podemos definir nuevas variables :
{ int i; /* Define una variable i global */ i = 1; /* Asigna a i el valor 0 */ { /* Inicio de un nuevo bloque */ int i; /* Define una variable i local */ i = 2; /* Pone su valor en 2 */ } /* Fin del bloque */ /* Aquí i es otra vez 1 en el bloque externo */ }
La Tabla 7.2 muestra todas las instrucciones de control de flujo:
La instrucción for es la única instrucción que realmente difiere de las instrucciones for conocidas de otros lenguajes. Todas las otras instrucciones más o menos difieren solamente en la sintaxis. Lo que sigue son dos bloques exactamente equivalentes en su funcionalidad. Uno usa el bucle while mientras que el otro usa la variante for:
{ int ix, sum; sum = 0; ix = 0; /* inicialización */ while (ix < 10) { /* condición */ sum = sum + 1; ix = ix + 1; /* incremento */ } } { int ix, sum; sum = 0; for (ix = 0; ix < 10; ix = ix + 1) sum = sum + 1; }
Para entender ésto, hay que saber que una asignación es una expresión.
En C casi todo es una expresión. Por ejemplo, la instrucción de asignación "=" regresa el valor del operando derecho. Como un "efecto secundario", también establece el valor del operando izquierdo. Así,
ix = 12;
le da a ix el valor de 12 (asumiendo que ix es del tipo apropiado). Como la asignación también es una expresión, podemos combinar varias de ellas ; por ejemplo :
kx = jx = ix = 12;
¿Qué sucede ? La primera asignación le da a kx el valor de su derecha. Este es el valor de la asignación a jx. Pero ésta última es el valor de la asignación a ix. El valor de ix es 12, el cuál es regresado a jx que a su vez es regresado a kx. Así, hemos expresado
ix = 12; jx = 12; kx = 12;
en una línea.
Verdadero en C se define como sigue. El valor 0 (cero) significa FALSE (falso). Cualquier otro valor es TRUE (verdadero). Por ejemplo, la función estándar strcmp() lleva dos strings como argumentos y regresa -1 si el primero es inferior al segundo, 0 si son iguales y 1 si el primero es mayor que el segundo. Para comparar si dos strings str1 y str2 son iguales, seguido ves la siguiente construcción if:
if (!strcmp(str1, str2)) { /* str1 es igual a str2 */ } else { /* str1 no es igual a str2 */ }
El signo de admiración indica el NOT booleano. Así, la expresión evalúa TRUE solamente si strcmp() regresa 0.
Las expresiones son combinaciones de ambos términos y operadores. Los primeros podrían ser constantes, variables o expresiones. De los segundos, C ofrece todos los operadores conocidos de otros lenguajes. Sin embargo, ofrece algunos operadores que podrían ser vistos como abreviaciones a combinaciones de otros operadores. La Tabla 7.3 muestra los operadores disponibles. La segunda columna muestra su prioridad, donde los números más chicos indican prioridad más alta y números iguales, prioridad igual. La última columna enlista el orden de evaluación.
La mayoría de estos operadores ya son conocidos para ti. Sin embargo, algunos necesitan alguna descripción adicional. Primero que nada, nótese que los operadores booleanos binarios &, ^ and | son de menor prioidad que los operadores de igualdad == and !=. Consecuentemente, si quieres checar patrones de bits como en
if ((pattern & MASK) == MASK) { ... }
debes encerrar la operación binaria entre paréntesis .
Los operadores de incrementeo ++ y pueden ser explicados por medio del siguiente ejemplo. Si tienes la siguiente secuencia de instrucciones
a = a + 1; b = a;
puedes usar el operador de preincremento
b = ++a;
En forma similar, si tienes el siguiente orden de instrucciones :
b = a; a = a + 1;
puedes usar el operador de postincremento
b = a++;
De esta manera, el operador de preincremento primero incrementa su variable asociada y entonces, regresa el nuevo valor ; mientras que el operador de postincremento primero regresa el valor y después incrementa su variable. Se aplican las mismas reglas para los operadores de pre- y postdecremento .
Las llamadas a funciones, las asignaciones anidadas y los operadores de incremento/decremento provocan efectos secundarios cuando se aplican. Esto puede introducir dependencias del compilador debido a que el orden de evaluación en algunas situaciones es dependiente del compilador. Considera el siguiente ejemplo que demuestra esto:
a[i] = i++;
La pregunta es, si el nuevo o el viejo valor de i se usa como subíndice dentro del arreglo a depende del orden que use el compilador para evaluar la asignación.
El operador condicional ?: es una abreviatura para una instrucción if de uso común. Por ejemplo para asignarle a max el valor máximos de entre a y b, podemos usar la siguiente instrucción if:
if (a > b) max = a; else max = b;
Estos tipos de instrucciones if pueden ser más cortas si se escriben así
max = (a > b) ? a : b;
El siguiente operador poco común es el operador de asignación. Nosotros seguido estamos usando asignaciones de la forma siguiente
expr1 = (expr1) op (expr2)
por ejemplo
i = i * (j + 1);
En estas asignaciones, el valor de la izquierda también aparece en el lado derecho. Por medio de lenguaje informal, podríamos expresar esto como "poner al valor de i i el valor actual de i multiplicado por la suma del valor de j más 1''. Usando una manera más natural, diríamos más bien "Multiplicar i por la suma del valor de j más 1''. C nos permite abreviar estos tipos de asignaciones a
i *= j + 1;
Nosotros podemos hacer éso con casi todos los operadores binarios. Nótese que el operador de asignación de arriba realmente implementa la forma larga "j + 1'' no esté entre paréntesis.
El último operador poco común es el operador coma (,). Se explica mejor por medio de un ejemplo:
i = 0; j = (i += 1, i += 2, i + 3);
Este operador toma sus argumentos y los evalúa de izquierda a derecha y regresa el valor de la expresión de la derecha. Así, en el ejemplo de arriba, el operador primero evalúa "i += 1" lo cuál, como un efecto secundario, incrementa el valor de i. Después, es evaluada la siguiente expresión "i += 2" la cuál añade 2 a i conduciendo al valor de 3. La tercera expresión es evaluada y su valor es regresado como el resultado del operador. Así, se le asigna 6 a j.
El operador coma presenta una particular posiblidad de cometer el siguiente error cuando se usan arreglos de n-dimensiones siendo . Un error frecuente es usar una lista de índices separados por comas para tratar de accesar un elemento:
int matrix[10][5]; // matriz de 2-dimensiones int i; ... i = matrix[1,2]; // ¡¡NO FUNCIONARA ! ! i = matrix[1][2]; // OK
Lo que actualmente sucede en el primer caso es que la lista separada por comas es interpretada como un operador coma. Por consecuencia, el resultado es 2, lo que conduce a una asignación de la dirección a los terceros cinco elementos de la matriz.
Algunos de ustedes se podrían preguntar que es lo que C hace con valores que no son usados. Por ejemplo en las instrucciones de asignación que hemos visto anteriormente,
ix = 12; jx = 12; kx = 12;
tenemos tres líneas cada una de las cuáles regresa 12. La respuesta es que C ignora los valores que no se usan. Esto lleva a algunas cosas extrañas. Por ejemplo, tú podrías escribir algo como ésto :
ix = 1; 4711; jx = 2;
Olvidemos estas cosas extrañas y regresemos a algo de más utilidad. Hablemos de funciones.
Debido a que C es un lenguaje procedimental, permite la definición de funciones. Los procedimientos son "simulados" por funciones que no regresan "ningún valor". Este valor es un tipo especial llamado void.
Las funciones se declaran en forma similar a las variables, pero aquéllas encierran sus argumentos entre paréntesis (aún si no llevan argumentos, los paréntesis deben ser especificados):
int sum(int to); /* Declaración de sum con un argumento */ int bar(); /* Declaración de bar sin argumentos */ void foo(int ix, int jx); /* Declaración de foo con dos argumentos */
Para definir efectivamente una función, simplemente añade su cuerpo :
int sum(int to) { int ix, ret; ret = 0; for (ix = 0; ix < to; ix = ix + 1) ret = ret + ix; return ret; /* regresa el valor de la función */ } /* sum */
C solamente permite que pases los argumentos de las funciones por valor. Por consecuencia, no puedes cambiar el valor de un argumento dentro de la función. Si tu debes pasar un argumento por referencia, debes programarlo por ti mismo(a). Para tal efecto usas apuntadores.
Uno de los problemas más comunes al programar en C (y algunas veces en C++) es la comprensión de apuntadores y arreglos. En C (C++) ambos están altamente relacionados ; con algunas pequeñas pero esenciales diferencias. Tú declaras un apuntador poniendo un asterisco entre el tipo de datos y el nombre de la variable o función :
char *strp; /* strp es un "apuntador a char" */
Se accesa el contenido de un apuntador derreferenciándolo por medio de -otra vez- el asterisco :
*strp = 'a'; /* Un carácter simple */
Al igual que en otros lenguajes, se debe proveer algún espacio para el valor al cuál el apuntador está apuntando. Un apuntador a caracteres puede ser usado para apuntar a una secuencia de caracteres : el string. Los strings en C están terminados por un carácter especial NUL (0 o como char ''). Así, puedes tener strings de cualquier longitud. Los strings se encierran en dobles comillas:
strp = "hello";
En este caso, el compilador automáticamente agrega el carácter de terminación NUL. De este modostrp apunta a una secuencia de 6 caracteres. El primer carácter es 'h', el segundo es 'e' y así sucesivamente. Podemos accesar estos caracteres por medio de un índice en strp:
strp[0] /* h */ strp[1] /* e */ strp[2] /* l */ strp[3] /* l */ strp[4] /* o */ strp[5] /* \0 */
El primer carácter también es igual a "*strp" que puede ser escrito como "*(strp + 0)". Esto nos lleva a algo llamado aritmética de apuntadores lo cuál constituye una de las poderosas cararacterísticas de C. Así, tenemos las siguientes ecuaciones :
*strp == *(strp + 0) == strp[0] *(strp + 1) == strp[1] *(strp + 2) == strp[2] ...
Nótese que estas ecuaciones son verdaderas para cualquier tipo de datos. La suma no está orientada a bytes, está orientada al tamaño del correspondiente tipo del apuntador.
El apuntador strp puede ser puesto en otras localidades. Su destino puede variar. En contraste con eso, los arreglos son apuntadores fijos. Apuntan a una predefinida área de memoria, la cuál es especificada por corchetes :
char str[6];
Puedes ver a str como un apuntador constante apuntando a una área de 6 caracteres. No podemos usar ésto del siguiente modo:
str = "hallo"; /* ERROR */
debido a que esto significaría cambiar el apuntador para que apuntara a 'h'. Debemos copiar el string al área de memoria provista. Por lo tanto, usamos una función llamada strcpy() la cuál es parte de la biblioteca estándar de C.
strcpy(str, "hallo"); /* Ok */
Nota sin embargo, que podemos usar str en cualquier caso donde un apuntador a carácter es utilizado, debido a que es un apuntador (si bien fijo).
Presentamos aquí el primer programa tab frecuentemente usado : un programa que despliega "Hello, world !" en tu pantalla :
#include <stdio.h> /* Aquí deberían ir las variables globales */ /* Las definiciones de funciones deberián ir aquí */ int main() { puts("Hello, world!"); return 0; } /* main */
La primera línea se ve algo extraña. Su explicación requiere alguna información acerca de como los programas en C (y C++) son manejados por el compilador. La compilación se divide gruesamente en dos pasos. El primer paso es llamado "preprocesamiento" y se usa para preparar código C "en bruto". En este caso, este paso toma la primera línea como un argumento para incluir un archivo llamado stdio.h dentro del código fuente. Los corchetes angulares simplemente indican que el archivo debe ser buscado en la trayectoria de búsqueda estándar configurada para tu compilador. El archivo en sí mismo provee algunas declaraciones y definiciones para input/output estándar. Por ejemplo, declara la función llamada put(). Este paso del preprocesador también borra todos los comentarios.
En el segundo paso, el "código en bruto" de C generado se compila como un ejecutable. Cada ejecutable debe definir una función llamada main(). Es esta función la que es llamada cuando el programa es arrancado. Esta función regresa un entero que significa el estatus de salida del programa.
La función main() puede llevar argumentos que vienen a representar los parámetos de la línea de comandos. Los presentamos aquí solamentes pero no damos ninguna explicación ulterior :
#include <stdio.h> int main(int argc, char *argv[]) { int ix; for (ix = 0; ix < argc; ix++) printf("My %d. argument is %s\n", ix, argv[ix]); return 0; } /* main */
El primer argumento argc regresa simplemente el número de argumentos dados en la línea de comandos. El segundo argumento argv es un arreglo de strings. (Recuerda que todos los strings se representan por apuntadores a caracteres. Así, argv es un arreglo de apuntadores a caracteres.)
Esta sección está lejos de ser completa. Solamente queremos darte una expresión de lo que es C. También queremos presentar algunos conceptos básicos que usaremos en la siguiente sección. Algunos conceptos de C son mejorados en C++. Por ejemplo, C++ introduce el concepto de referencias lo cuál permite algo similar a llamar por referencia en las llamadas a funciones.
Sugerimos que tomes tu compilador local y empieces a escribir unos pocos programas (si no estás familiarizado con C, por supuesto). Un problema para los principiantes es que frecuentemente las funciones de biblioteca no son conocidas. Si tines un sistema UNIX trata de usar el comando man para obtener algunas descripciones. Especialmente podrías intentar :
man gets man printf man puts man scanf man strcpy
También sugerimos que te consigas un buen libro sobre C(o que encuentres uno de los tutoriales en-línea). Tratamos de explicar todo lo que presentamos en las siguientes secciones. Sin embargo, no hay nada de malo en tener alguna referencia a la mano.