float temperatura[20];
anuncia que temperatura es un array de veinte miembros o “elementos”. El primer elemento es temperatura[0], el segundo elemento es temperatura[1], y así sucesivamente, hasta llegar a temperatura[19]. Obsérvese que la numeración de los elementos del array empieza con cero y no con 1, hasta llegar a 19. En virtud de que el array fue declarado de tipo float, se le puede asignar a cada elemento del array un número de tipo float, por ejemplo:
temperatura[5] = 27.5;
temperatura[17] = 8.4;
Un array puede ser de cualquier tipo de dato:
int manzanas[80]; /* un array de 80 enteros */
char alfa[25]; /* un array para 25 caracteres */
long vector[3]; /* un array para 3 enteros long */
Para C, los arrays consisten de localidades de memorias contiguas. El domicilio más bajo corresponde al primer elemento, y el domicilio más alto corresponde al último elemento. Para la creación de hileras (o cadenas), puesto que en C no se define para las hileras algún tipo de dato (como en BASIC agregando el símbolo $ a una variable W para definir una variable de hilera W$), lo que se usa son arrays de caracteres. Sin embargo, esto que parecería una omisión permite en C mayor poder y flexibilidad que el que hay en lenguajes que usan un tipo de dato para representar las hileras. La sintaxis de la forma general para la declaración de un array unidimensional (esto es, posee un solo índice) es la siguiente:
tipo nombre[tamaño];
en donde el tipo declara el tipo de base del array. El tipo de base determina el tipo de dato de cada elemento que forma parte del array. Para un cierto sistema computacional en el cual se conoce el número de bytes asignado a cada uno de los tipos de datos, es posible calcular el tamaño total consumido en la memoria de la computadora por un cierto array mediante una simple multiplicación:
bytes totales = tamaño del tipo de base * número de elementos
Los arrays son muy comunes en la programación porque nos permiten manejar fácilmente un número grande de variables relacionadas. A modo de ejemplo, el siguiente programa hace que sea fácil calcular el promedio de una lista de números, leyendo diez números enteros introducidos de uno en uno por el usuario y poniendo al final en la pantalla el resultado:
#include <stdio.h>
main()
{
int muestra[10], i, promedio;
for(i=0; i<10; i++) {
printf("dame el numero %d: ", i);
scanf("%d", &muestra[i]);
}
promedio = 0;
/* suma cumulativa y promediaje */
for(i=0; i<10; i++) promedio = promedio + muestra[i];
/* impresion resultado final */
printf("el promedio es %d\n", promedio/10);
}
Una cosa con la que hay que tener mucho cuidado es que C no lleva a cabo chequeo de límites (<i>bounds checking</i>), y no hay nada que detenga al programador de cometer el error de rebasar el tope de la capacidad de un array. Si esto ocurre al llevarse a cabo una operación de asignación, es posible que se termine asignando valores no deseados a los datos de otras variables, o inclusive que se pueda sobreescribir sobre el área asignada para contener código del programa, causando resultados erráticos o inclusive fatales que requieran apagar y encender la computadora. Poniéndolo de otra manera, un array de tamaño N puede ser indexado más allá de N sin que se generen mensajes de error ya sea en tiempo de compilación o en tiempo de ejecución aunque cuando ocurre tal cosa el programa seguramente se estrellará. Esto pone sobre los hombros del programador la responsabilidad de asegurarse de que todos los arrays que especifique en un programa serán lo suficientemente grandes para almacenar lo que el programa ponga en ellos, e inclusive proporcionar por cuenta propia chequeos de límites en donde tal cosa se requiera. A modo de ejemplo se proporciona el siguiente código no para ser compilado y ejecutado sino para que se pueda apreciar la manera en la cual se puede generar este tipo de error fatal:
/* NO SE COMPILE NI SE EJECUTE */
/* ESTA ES MUESTRA DE CODIGO CON UN ERROR FATAL */
main()
{
int error[5], i;
for(i = 0; i < 20; i++) error[i] = i;
}
En este código, el bucle será ejecutado veinte veces. Sin embargo, únicamente se le han asignado cinco elementos al array <b>error[]</b>. Esto ocasionará que al crecer el array más allá de su capacidad permisible se borre información importante causando el fracaso del programa.
¿Por qué razón el lenguaje C no proporciona chequeo de límites en los arrays? La respuesta es que C fue diseñado para ser usado por los programadores de sistemas, sobre todo por los diseñistas de sistemas operativos, su objetivo desde un principio fue el reemplazar el uso de los lenguajes assembler en la mayoría de las situaciones. Con este fin, no se incluyó virtualmente ningún tipo de verificación de límites en virtud de que tal cosa disminuye la velocidad de ejecución de un programa (con cierta frecuencia, la disminución en la ejecución resulta considerable). C espera que el programador de sistemas sea lo suficientemente cuidadoso de evitar exceder los límites de los arrays cuando estos tengan que ser usados.
Los arrays unidimensionales son básicamente listas de información del mismo tipo. A modo de ejemplo, después de que correrse el siguiente programa:
char hil[6];
main()
{
int i;
for(i = 0; i<6; i++)
hil[i] = 'B' + i;
}
el array tendrá el siguiente aspecto en la memoria de la computadora:
hil[0] | hil[1] | hil[2] | hil[3] | hil[4] | hil[5] |
B | C | D | E | F | G |
Seguramente el uso más frecuente de los arrays, más allá de la programación científica, es la creación de hileras de caracteres. En C una hilera se define como un array de caracteres terminado por un nulo el cual es especificado en un programa como '\0'. En virtud de que se requiere de un terminador nulo para declarar arrays de caracteres, es necesario declarar los arrays de caracteres para ser un caracter mayor que la hilera más grande que puedan contener. Si se quiere declarar un array hil[.] para poder contener una hilera de 16 caracteres, escribimos lo siguiente en el programa:
char hil[17];
con lo cual se hace espacio suficiente para el nulo puesto al final de la hilera.
Ya vimos anteriormente que aunque en C no hay ningún tipo de dato para la declaración de hileras, aún así permite la declaración de constantes de hileras. Una constante de hilera es una lista de caracteres encerrados entre comillas dobles, por ejemplo:
"Armando Martinez"
"Programa C para calculo de funciones Bessel"
En la declaración de constantes de hilera, no es necesario agregar manualmente un nulo al final de las constantes de hilera, el compilador se encarga de hacerlo automáticamente. Esto significa que la constante de hilera "Armando" será puesta en la memoria del modo siguiente:
A | r | m | a | n | d | o | '\0' |
Usando lo que ya tenemos disponible hasta este momento, podemos crear varias funciones para el manejo de hileras de texto, no se requiere de ninguna otra cosa más que lo que ya sabemos. Sin embargo, aunque puede ser divertido y hasta instructivo, este tipo de trabajo ya se ha llevado a cabo y se han definido varias funciones en C para el manejo, varias de las cuales son comunes a la gran mayoría de los compiladores C que hay en el mercado. Es usual encontrar las funciones definidas para el manejo de hileras en un archivo de cabecera titulado, apropiadamente, STRING.H. Dando por hecho de que el paquete de programación C que tengamos a la mano ya incluye este archivo de cabecera, podemos dar un repaso a varias de las funciones que usualmente se encuentran allí.
La primera función que veremos es gets(), usada para dar entrada a una hilera desde el teclado. La sintaxis de dicha función es la siguiente:
gets(nombre de la hilera);
Para leer una hilera, invocamos gets() con el nombre del array, sin usar ningún índice, como su argumento. Al ser ejecutada, el array contendrá la entrada de hilera que el usuario haya proporcionado desde el teclado. Al ser invocada, la función gets() empezará a leer lo que el usuario vaya escribiendo en el teclado, y dará por concluída su labor cuando el usuario golpee la tecla [Enter]. A modo de ejemplo, el siguiente programa repite (imprime) la hilera introducida por un usuario desde el teclado:
#include <stdio.h>
main()
{
char hilera[80];
printf("escribe alguna frase: ");
gets(hilera); /* leer una hilera del teclado */
printf("%s", hilera);
}
Obsérvese que hilera puede ser usada como un argumento para printf(). Obsérvese también que, una vez declarado, se emplea el nombre del array sin usar un índice. En el programa que se ha proporcionado, la función gets() no lleva a cabo ningún chequeo de límites. Se ha proporcionado una capacidad para 80 caracteres, pero si el usuario escribe una hilera de caracteres que sea mayor que esto, digamos de unos 100 caracteres, gets() rebasará el límite del array. Obsérvese también que no fue necesario especificar al principio la inclusión del archivo de cabecera STRING.H; algunos compiladores permiten la omisión pero otros no la permiten.
Otra función útil es la función strcpy() usada para el copiado de hileras. Dicha función toma la siguiente forma:
strcpy(desde, hacia);
Recuérdese que el array especificado por hacia debe ser lo suficientemente grande para poder almacenar la hilera contenida en desde, de lo contrario la capacidad del array receptor será rebasada con posible daño a la ejecución del programa. El siguiente programa copiará “Hola!” hacia la hilera hil (se ha especificado el archivo de cabecera STRING.H en virtud de que la función no es tan común como la que vimos arriba y algunos compiladores exigen la inclusión de STRING.H o de lo contrario producen un mensaje de error argumentando una funcion indefinida):
#include <stdio.h>
#include <string.h>
main()
{
char hil[80];
strcpy(hil, "Hola!");
printf("%s", hil);
}
Otra función para el manejo de hileras que puede ser útil es la función strcat() usada para la concatenación de hileras, la cual toma la siguiente forma:
strcat(hilera1, hilera2);
Esta función anexa la hilera hil2 a la hilera hil1 dejando hil2 sin cambio alguno. Ambas hileras tienen que estar terminadas con un nulo y el resultado también estará terminado con un nulo. El siguiente programa imprimirá “Hola, que gusto de verte por aqui!”:
#include <stdio.h>
#include <string.h>
main()
{
char h1[35], h2[35];
strcpy(h1, "Hola,");
strcpy(h2, " que gusto de verte por aqui!");
strcat(h1, h2);
printf("%s", h1);
}
Podemos comparar dos hileras haciendo uso de la función strcmp(), la cual toma la forma:
strcmp(hilera1, hilera2);
Esta función regresa un valor de cero (0) si ambas hileras son iguales, si hilera1 es lexicográficamente mayor que hilera2 (o sea mayor en términos del orden en el cual aparecen en un diccionario, o sea antes) entonces se regresa un valor positivo, y si el orden es al revés entonces se regresa un número negativo.
Una aplicación muy común usada en la comparación de hileras está en las contraseñas (password) o claves de acceso que se les pide a los usuarios de sitios Web o de programas después de que han proporcionado su nombre antes de darles acceso al sitio o al programa. La siguiente rutina puede ser usada para llevar a cabo una verificación de la clave de acceso, usaremos la palabra “clave” como lo que el usuario tiene que escribir para que se le confirme que su palabra es válida como contraseña:
#include <stdio.h>
#include <string.h>
int clave();
/* Declaracion de la funcion clave() */
/* Regresa verdadero si la clave es aceptada */
/* Regresa falso si la clave es rechazada */
int clave()
{
char s[80];
printf("dame la clave: ");
gets(s);
if(strcmp(s, "clave")) { /* hileras diferentes */
printf("clave no valida\n");
return 0;
}
/* hileras iguales */
printf("clave valida\n");
return 1;
}
/* Funcion principal main() */
main()
{
clave();
}
La clave en el uso de strcmp() es que regresa falso cuando las hileras coinciden. Por lo tanto, será necesario usar el operador de inversión lógica NOT cuando queramos que ocurra algo cuando las hileras son iguales. El siguiente programa continúa pidiendo entrada hasta que el usuario introduce la palabra quit:
#include <stdio.h>
#include <string.h>
main()
{
char s[80];
for(;;) {
printf("dame la orden de salida: ");
gets(s);
if(!strcmp("quit",s)) break;
}
}
Otra función útil para el manejo de hileras es la función strlen() usada para obtener la longitud (en caracteres) de una hilera. ¿Incluye el conteo de caracteres el nulo agregado al final de una hilera? No, el nulo es usado por la función para que la función sepa el punto exacto en el cual debe dejar de contar. Como una muestra del uso de esta función en acción, tenemos el siguiente programa que imprimirá al revés una hilera de texto proporcionada por el usuario:
'
#include <stdio.h>
#include <string.h>
main()
{
char hil[80];
int i;
printf("dame una hilera: ");
gets(hil);
for(i=strlen(hil)-1; i>=0; i--) printf("%c", hil[i]);
}
El siguiente programa utiliza funciones de hilera para pedirle al usuario dos hileras distintas, proporcionando la longitud de cada hilera y concatenando ambas:
#include <stdio.h>
#include <string.h>
main()
{
char h1[80], h2[80];
printf("dame dos hileras: ");
gets(h1); gets(h2);
printf("longitudes: %d %d\n", strlen(h1), strlen(h2));
if(!strcmp(h1,h2)) printf("las hileras son iguales\n");
strcat(h1, h2);
printf("%s\n", h1);
}
Si al ejecutarse el programa anterior se le proporcionan como dos hileras “armando” y “armando”, lo que veremos en una ventana de líneas de comandos:
dame dos hileras: armando
armando longitudes: 7 7 las hileras son iguales armandoarmando C:> |
En virtud de que la función strcmp() regresa falso si las dos hileras son iguales, se debe tener la precaución de usar el operador de inversión lógica NOT (!) para invertir la condición y hacer que la función regrese verdadero si las dos hileras son iguales. Esto es justo lo que se ha hecho en el programa dado arriba.
Puesto que todas las hileras son terminadas con un nulo, esto frecuentemente puede ser aprovechado juiciosamente para simplificar operaciones llevadas a cabo con hileras, como se muestra en el siguiente ejemplo en el cual se requiere de poco código para convertir todos los caracteres de una hilera de minúsculas a mayúsculas:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
main()
{
char hilera[80];
int i;
strcpy(hilera, "ejemplo de conversion a mayusculas");
for(i=0; hilera[i]; i++) hilera[i] = toupper(hilera[i]);
printf("%s", hilera);
}
El programa, que usa la función de biblioteca toupper(), imprimirá “EJEMPLO DE CONVERSION A MAYUSCULAS”, regresando el equivalente en mayúscula de cada argumento de caracter, convirtiendo a mayúscula cada caracter en la hilera. Obsérvese que el programa usa el archivo de cabecera CTYPE.H requerido para que el compilador encuentre a la función toupper(). Obsérvese con detenimiento que la condición de prueba usada en el bucle for es simplemente el array indexado por la variable de control. La razón por la cual el programa produce los efectos deseados es porque cualquier valor verdadero (no nulo) es cualquier valor diferente de cero. Por lo tanto, el bucle se repite hasta que se encuentra con el terminador nulo, el cual es cero. Puesto que el terminador nulo es lo que marca el final de una hilera, el bucle detiene su ejecución precisamente en el punto en donde debe detenerse.
Previamente, para poner en la pantalla una hilera contenida en un array de caracteres, hemos estado usando la función printf() con el siguiente formato básico:
printf("%s", <i>nombre_del_array");
Lo relevante aquí es que el primer argumento entregado a printf() es una hilera, y todos los caracteres que no son comandos de formato son impresos (puestos en la pantalla). Por lo tanto, si todo lo que queremos hacer es imprimir una hilera, podemos simplificar lo anterior escribiendo simplemente:
printf(nombre_del_array");
El siguiente programa imprime en la pantalla “Hola! Me llamo Armando”:
#include <stdio.h>
#include <string.h>
main()
{
char hilera[80];
strcpy(hilera, "Hola! Me llamo Armando");
printf(hilera);
}
Hasta este punto hemos estado hablando de arrays de una sola dimensión, unidimensionales. Pero también podemos definir arrays multidimensionales. La forma más sencilla de un array multidimensional es el array bi-dimensional. Esencialmente, un array bidimensional es una lista de arrays unidimensionales. Para declarar un array bidimensional de enteros llamado inventario de tamaño 30x50, escribimos lo siguiente:
int inventario[30][50];
Obsérvese la declaración con cuidado. A diferencia de lo que se acostumbra usar en otros lenguajes de computación, que usan comas para separar las dimensiones de un array bidimensional, C pone cada dimensión dentro de su propio conjunto de paréntesis rectangulares.
De modo semejante, para poder accesar el elemento 7,11 del array (identificado con los índices 7 y 11), usamos inventario[7][11]. En el siguiente cargamos un array bidimensional con los números del 1 al 12:
main()
{
int x, i tabla[3][4];
for(x=0; x<3; ++x)
for(i=0; i<4; ++i)
tabla[x][i] = (4*x) + i + 1;
}
En este ejemplo, tabla[0][0] contendrá el entero 1, tabla[0][1] el entero 2, tabla[0][2] contendrá el entero 3, y así sucesivamente. El entero almacenado en tabla[2][3] será 12.
Aunque los arrays bidimensionales son almacenados en la memoria RAM en forma lineal, poniendo un elemento tras otro desde el primero hasta el último, podemos conceptualizarlo imaginando que son almacenados en una matriz formada por renglones y columnas. Puesto que no es posible para la máquina el poder accesar todos los elementos al mismo tiempo (a menos de que sea una máquina construída específicamente para tal propósito), esto implica que el elemento con el índice situado más a la derecha (en el ejemplo dado, [i]) cambia con mayor rapidez que el elemento situado más a la izquierda (en el ejemplo dado, [x]) al accesar los elementos en el orden en el cual están almacenados en la memoria. Esto lo podemos ilustrar del siguiente modo:
Podemos imaginar que el índice que está más a la izquierda (el primer índice) apunta hacia el renglón correcto.
Algo importante a recordar es que el almacenamiento de todos los elementos de array globales son adjudicados durante el tiempo de compilación, lo cual a su vez significa que la sección de la memoria RAM que conserva un array global está apartada de antemano y no puede ser reclamada para otra cosa durante la ejecución del programa, podemos cambiar los contenidos del array pero el espacio adjudicado al array permanece inalterable. Podemos, desde luego, declarar un array localmente dentro de un programa (dentro de alguna función), pero al salir de la función los valores almacenados en tal array se dan por perdidos a menos de que algunos de ellos sean transferidos a variables globales.
Tratándose de un array bidimensional, el espacio requerido en bytes en la memoria RAM se puede calcular de la manera siguiente:
espacio en bytes = renglones * columnas * tamaño del tipo de dato
De este modo, un array usado para almacenar números enteros de dos bytes con una dimensión 12x28 consumirá un total de:
12 * 28 * 2 = 112 bytes
Estamos en condiciones de poder hablar acerca de los arrays de hileras, algo que surge con frecuencia en la programación cotidiana. Los arrays bidimensionales son esenciales para poder construír tablas, por ejemplo tablas con las cuales el procesador de comandos a una base de datos pueda verificar los comandos enviados por el usuario contra un array de hileras de comandos válidos. En el proceso de compilación requerido para convertir un programa elaborado en un lenguaje de alto nivel a las instrucciones requeridas al nivel del lenguaje de máquina, se requiere construír tablas (a veces muchas) para asignar variables a localidades en la memoria. De hecho, los constructores de compiladores requieren del uso de tablas como punto de partida. Para poder crear un array de hileras, recurrimos a un array bidimensional de caracteres con el tamaño del índice izquierdo especificando el número de hileras y el tamaño del índice derecho determinando la longitud máxima permisible en cada hilera. A modo de ejemplo, el siguiente enunciado declara un array de 50 hileras, pudiendo contener cada hilera un máximo de 100 caracteres:
char array_de_hileras[50][100];
Hecho esto, el acceso a cada hilera individual es un asunto sencillo, basta con especificar el primer índice. El siguiente enunciado invoca la función gets() con la cuarta hilera que está almacenada dentro del array:
gets(array_de_hileras[3]);
Esto es funcionalmente equivalente a:
gets(&array_de_hileras[3][0]);
aunque la forma previa es más común en programas C escritos profesionalmente. En la segunda forma, el símbolo ampersand (&) es requerido por cuestiones que tienen que ver cuando se trata el tema de los punteros en C.
Para poder entender mejor la manera en la cual trabajan los arrays de hileras, resulta instructivo analizar la manera en la cual funciona el siguiente programa que acepta líneas de texto que son ingresadas desde el teclado y que son puestas en la pantalla en cuanto se introduce una línea en blanco (o sea, en cuanto el usuario oprime en el teclado la tecla de espaciado, la tecla más larga en la mayoría de los teclados). Cabe agregar que en los sistemas operativos más recientes es posible que el programa aunque pueda ser compilado no funcione si hay una incomptabilidad en el emulador de la ventana de comandos de línea DOS del sistema operativo, en particular en el manejo de la pila (stack), y es posible que ni siquiera pueda ser compilado dentro de un entorno integrado de desarrollo IDE:
#include <stdio.h>
main()
{
register int t, i;
char texto[100][80];
for(t=0; t<100; t++) {
printf("%d: ", t);
gets(texto[t]);
if(!texto[t][0]) break; /* salir con espacio en blanco */
}
/* sacar fuera las hileras */
for(i=0; i<t; i++)
printf("%s\n", texto[i]);
}
En este programa se ha introducido una palabra reservada por C, la palabra register, la cual es usada para declarar variables de registro (register variables), la cual tradicionalmente es aplicada únicamente a variables de tipo int y char. En la versión original de C, el especificador register le pide al compilador que conserve el valor de las variables declaradas con este modificador en el registro de la unidad de procesamiento central CPU en lugar de ser mantenidas en la memoria, con la finalidad de que el programa se ejecute con mayor rapidez.
La razón por la cual el programa C dado arriba puede fallar no radica en el uso de la palabra register que ha sido introducida arriba, sino en la capacidad de la pila asignada a este tipo de programas DOS, pudiendo ocurrir un desborde de la pila. De cualquier modo, se puede experimentar usando arrays de hileras más pequeños. Tomando el programa anterior y modificándolo de este modo:
#include <stdio.h>
main()
{
register int t, i;
char texto[50][50];
for(t=0; t<50; t++) {
printf("%d: ", t);
gets(texto[t]);
if(!texto[t][0]) break; /* salir con espacio en blanco */
}
/* sacar fuera las hileras */
for(i=0; i<t; i++)
printf("%s\n", texto[i]);
}
De este modo, compilado en el IDE Borland C++ 4.0 usando un procesador AMD K6-2 con un sistema operativo Windows XP, la ejecución del programa anterior produce la siguiente ventana de resultados:
Este tipo de situaciones son precisamente las que ocasionan a los programadores profesionales dolores de cabeza, y nos lleva a preguntarnos seriamente: ¿realmente es portátil el código de un programa escrito en lenguaje C? Ello depende del código. Si se trata de algoritmos usados para evaluaciones de tipo científico (como las que usan los ingenieros), lo más probable es que el código elaborado en el milenio anterior podrá ser compilado y reciclado. Pero si se trata de código con el cual habrá entrada y salida de datos a través de una computadora funcionando bajo el control lde cierto sistema operativo, puede haber problemas si se trata de correr bajo algo como el sistema operativo Windows el código C elaborado para un programa que funciona bien en un sistema operativo de líneas de comandos como UNIX y DOS. Y no es posible tratar de correr código C elaborado inicialmente para un sistema operativo Windows de 64 bits en una computadora añeja como la IBM PC XT que se usaba cuando Windows aún no existía, el procesador CPU seguramente no reconocerá muchas de las instrucciones (puesto de otra manera, un procesador 80486 con un conjunto de instrucciones en lenguale de máquina relativamente amplio puede reconocer las instrucciones que el añejo procesador 8088 podía ejecutar, pero el procesador 8088 con un conjunto de instrucciones más reducido no es capaz de ejecutar un programa elaborado para el 80486). Se puede, desde luego, elaborar y compilar un programa C en una máquina antigua no para ejecutarlo en dicha máquina sino en una máquina más reciente, pero el entorno IDE debe estar preparado para procesar código cuyo objetivo final será no DOS sino algo como una máquina funcionando con Windows de 32 bits, o bien Windows de 64 bits, o quizá Windows de 128 bits, cada ambiente tiene sus propias peculiaridades y es posible que se tenga que reescribir un programa C para cada entorno diferente.
Y por cierto, si en el programa anterior se borra la palabra register, muy posiblemente el programa de cualquier modo se compilará y se ejecutará. La única diferencia, desde la perspectiva de la máquina, es que el programa se ejecutará con una menor rapidez al estar las variables t e i puestas en alguna parte de la memoria RAM y no dentro de un registro del CPU, aunque considerando las velocidades de los procesadores CPU contemporáneos al usuario le será imposible detectar diferencia alguna en la velocidad obtenida de ambas versiones (sin register y con register).
Así como podemos definir arrays bidimensionales, también podemos definir arrays multidimensionales. La sintaxis general para un array multidimensional es la siguiente:
tipo nombre[tamaño1][tamaño2]...[tamañoN]
A modo de ejemplo, lo siguiente crea un array de enteros 5x8x3 llamado vector:
int vector[5][8][3];
Exceptuando las super-computadoras, no es frecuente usar arrays de tres o más dimensiones por la cantidad de memoria que se requiere para almacenarlos, ya no se diga para procesarlos, tomando en cuenta que el almacenamiento de elementos de arrays globales es adjudicado de manera permanente durante la ejecución del programa, y este es un espacio de memoria que no le será regresado ni siquiera al sistema operativo de la máquina hasta en tanto el programa no haya terminado de ejecutar. Por ejemplo, un array de caracteres 6x4x9x10 requiere un total de 2,160 bytes. Si el array fuese para enteros de dos bytes, se requerirían 4320 bytes de espacio. Y si el array fuese para almacenar números de punto flotante con doble precisión, o sea double (requiriéndose por lo menos 8 bytes para ello, dependiendo del tipo de máquina), se requerirían 34,560 bytes. El almacenamiento requerido aumenta exponencialmente con el número de dimensiones, y un programa que use arrays de más de tres o cuatro dimensiones puede estar fuera de la capacidad de memoria de una computadora inclusive en estos tiempos en los que las memorias andan en los rangos de los gigabytes en lugar de los kilobytes cuando aparecieron las primeras computadoras caseras.
No hemos tocado el punto sobre cómo podemos inicializar un array. Al igual que otras cosas, C permite la inicialización de los arrays, la cual se lleva a cabo en una forma parecida a la manera en la cual se inicializan las variables, de la manera en que se indica a continuación:
tipo nombre[tamaño1]...[tamañoN] = {lista de valores};
La lista de valores es una lista de constantes separadas con comas, las cuales son compatibles en tipo con el tipo de base del array. La primera constante será puesta en la primera posición del array, la segunda constante en la segunda posición, y así sucesivamente. Obsérvese que hay que poner un semicolon al final del corchete derecho. En el siguiente ejemplo, un array de diez elementos enteros es inicializado con los números 1 al 10:
int i[10] = {1,2,3,4,5,6,7,8,9,10};
En este caso, i[0] contendrá el valor 1, e i[9] contendrá el valor 10.
En C, a los arrays de caracteres que contienen hileras se les permite una forma abreviada en su inicialización:
char nombre[tamaño] = "hilera";
El siguiente fragmento inicializa hilera a la palabra vidente:
char hilera[8] = "vidente";
Lo anterior es lo mismo que escribir:
char hilera[8] = {'v','i','d','e','n','t','e','\0'};
Puesto que todas las hileras tienen que terminar con un nulo, hay que asegurarse de que el array que se está declarando será lo suficientemente grande para poder contener las hileras que se le metan. Esta es la razón del por qué hilera tiene espacio para ocho caracteres pese a que la palabra “vidente” consta de solo siete caracteres. Cuando se usa una constante de caracter, el compilador proporcionará y agregará automáticamente al final de la hilera el terminador nulo.
Los arrays multidimensionales son inicializados del mismo modo en que se hace con los arrays unidimensionales. El siguiente array cuadrados es inicializado con los enteros que van del 1 al 8 así como los cuadrados de dichos enteros:
int cuadrados[8][2] = {
1,1,
2,4
3,9
4,16,
5,25,
6,36,
7,49,
8,64
};
Tratándose de arrays de hileras, todo lo anterior supone que el tamaño de las hileras que serán almacenadas será igual de una hilera a otra. Sin embargo, si hay hileras de tamaños distintos almacenadas en un array, resulta un despilfarro de memoria darles a todas ellas el mismo tamaño que es usado para almacenar la hilera máxima. Lo ideal en una situación así es darle a cada hilera justo el tamaño requerido. Un ejemplo de ello sería la construcción de una tabla para almacenar mensajes de error como la siguiente (obsérvese que el retorno de carro o caracter de nueva línea “\n” cuenta como un solo caracter):
char e1[23] = "no existe tal archivo\n";
char e2[19] = "asignacion ilegal\n";
char e3[26] = "seleccion fuera de rango\n";
Sin embargo, si se trata de una lista grande de mensajes de error, digamos unos 500 mensajes de error, se vuelve extremadamente tedioso el tener que ir contando manualmente los caracteres en cada hilera para poder determinar en forma correcta las dimensiones de cada array. Es por ello que en C se hizo posible el poder llevar a cabo un dimensionamiento automático en el caso de los arrays sin tamaño. Usando esta alternativa, la tabla de mensajes de error toma el siguiente aspecto:
char e1[] = "no existe tal archivo\n";
char e2[] = "asignacion ilegal\n";
char e3[] = "seleccion fuera de rango\n";
De este modo, no es necesario especificar de antemano el tamaño de un array al llevar a cabo la inicialización de un array, ya que el compilador se encargará de hacer un recuento y crear un array lo suficientemente grande para poder contener todos los inicializadores presentes.
Las inicializaciones de array sin especificación previa de tamaño son están restringidas a arrays unidimensionales. Para arrays multidimensionales, tenemos que especificar todas dimensiones excepto las que están más a la izquierda para permitirle a C llevar a cabo la indexación adecuada del array. De este modo, el array bidimensional cuadrados dado previamente se puede escribir de la siguiente manera:
int cuadrados[8][2] = {
1,1,
2,4
3,9
4,16,
5,25,
6,36,
7,49,
8,64
};
Esto ofrece la ligera ventaja sobre la declaración con tamaño de que la tabla puede ser agrandada o reducida sin tener que preocuparse por cambiar las dimensiones del array.
En la serie de entradas relativas al entorno Visual Basic, para las instrucciones de programación usadas en dicho lenguaje lo que determina el sentido y alcance de cada instrucción es el inicio de la misma a partir del extremo izquierdo en donde se empieza a escribir la instrucción, hasta el final de la línea en el extremo derecho antes de saltar a la línea que sigue abajo de ella. En efecto, el punto en el cual se oprime la tecla de entrada [Enter] en el teclado para pasar a la siguiente línea es lo que encapsula la instrucción completa. Y si se trata de una instrucción demasiado larga (compuesta, digamos, por unos 300 caracteres de texto alfanumérico), la forma para poder continuar la instrucción en la siguiente línea (o líneas) consiste en poner al final de cada punto de ruptura de la instrucción un espacio en blanco seguido de un guión bajo (subrayado):
Data1.RecordSource = _
"SELECT * FROM Titles, Publishers" _
& "WHERE Publishers.PubID = Titles.PubID _
& "AND Publishers.State = 'CA'"
El lenguaje C no presenta este tipo de dificultades en virtud de que, al efectuarse el proceso de compilación, el compilador se “come” los espacios en blanco y los saltos de línea, y podemos continuar una instrucción larga hacia otra línea (u otras líneas) como se juzgue conveniente. Esto significa que el siguiente programa:
#include <stdio.h>
main()
{
float temperatura_ambiente, humedad_relativa, presion_atmosferica, factor_X;
temperatura_ambiente = 25.0;
humedad_relativa = 65.5;
presion_atmosferica = 140.32;
factor_X = (temperatura_ambiente/0.364)*(humedad_relativa *presion_atmosferica);
printf("El factor relativo en la condicion atmosferica es %f", factor_X);
}
que hace que se imprima:
El factor relativo en la condicion atmosferica es 631247.312500
se puede reacomodar de la siguiente manera sin que haya problema alguno:
#include <stdio.h>
main()
{
float temperatura_ambiente,
humedad_relativa,
presion_atmosferica, factor_X;
temperatura_ambiente = 25.0;
humedad_relativa = 65.5;
presion_atmosferica = 140.32;
factor_X = (temperatura_ambiente/0.364)
*(humedad_relativa *presion_atmosferica);
printf("El factor relativo en la condicion atmosferica es %f",
factor_X);
}
Sin embargo, hay una excepción a la regla. Obsérvese que no se intentó subdividir en dos líneas la hilera que va puesta entre dobles comillas en printf(); ya que si se trata de hacer tal cosa de una manera como se muestra a continuación:
#include <stdio.h>
main()
{
float temperatura_ambiente,
humedad_relativa,
presion_atmosferica, factor_X;
temperatura_ambiente = 25.0;
humedad_relativa = 65.5;
presion_atmosferica = 140.32;
factor_X = (temperatura_ambiente/0.364)
*(humedad_relativa *presion_atmosferica);
printf("El factor relativo en la
condicion atmosferica es %f",
factor_X);
}
el compilador marcará mensajes de error y no llevará cabo ningún proceso de compilación. Puesto de otro modo, al usar la función printf() para dar salida en una pantalla de texto no se pueden intercalar entre las comillas dobles opresiones de la tecla de entrada [Enter], a menos de que vayan precedidas al final de cada terminación de línea por una diagonal inversa. Tomando en cuenta esto, el programa dado arriba se compilará sin problema alguno si se agrega una diagonal inversa “\” del modo en que se muestra:
#include <stdio.h>
main()
{
float temperatura_ambiente,
humedad_relativa,
presion_atmosferica, factor_X;
temperatura_ambiente = 25.0;
humedad_relativa = 65.5;
presion_atmosferica = 140.32;
factor_X = (temperatura_ambiente/0.364)
*(humedad_relativa *presion_atmosferica);
printf("El factor relativo en la \
condicion atmosferica es %f",
factor_X);
}
Obsérvese que en la línea que sigue (línea nueva) a la diagonal inversa “\” se tuvo que prescindir de la indentación; o sea que en vez de escribirse:
printf("El factor relativo en la \
condicion atmosferica es %f",
factor_X);
se tuvo que escribir:
printf("El factor relativo en la
condicion atmosferica es %f",
factor_X);
¿Por qué? Porque en la segunda línea la indentación requería de once espacios en blanco, y si se dejan tal cual entonces esos espacios en blanco serán tomados en cuenta en la ejecución de la impresión llevada a cabo por printf(), de modo tal que se aparecería en la pantalla lo siguiente:
El factor relativo en la es 631247.312500
Y eso no es lo que queremos, al menos no en aras de una buena legibilidad.