CONJUNTO ORDENADO DE VARIABLES (ARRAYS)
Los arreglos ó conjuntos de datos ordenados (arrays) recolectan variables del
MISMO tipo , guardandolas en forma secuencial en la memoria . La cantidad máxima
de variables que pueden albergar está sólo limitada por la cantidad de memoria
disponible . El tipo de las variables involucradas puede ser cualquiera de los
ya vistos, con la única restricción de que todos los componentes de un array
deben ser del mismo tipo .
La declaración de un array se realiza según la siguiente sintaxis :
tipo de las variables nombre[ cantidad de elementos] ; Por ejemplo : int var1[10] ; // declara las variables var[0], var[1],var[2], var[3]...var[9] char nombre[50] ; float numeros[200] ; long double cantidades[25] ;
Si tomamos el primer caso , estamos declarando un array de 10 variables enteras , cada una de ellas quedará individualizada por el subíndice que sigue al nombre del mismo es decir :
var1[0] , var1[1] , etc , hasta var1[9] .
Nótese que la CANTIDAD de elementos es 10 , pero su
numeración vá de 0 a 9 , y nó de 1 a 10 . En resumen un array de N elementos
tiene subíndices válidos entre 0 y
N - 1 . Cualquier otro número usado como subíndice , traerá datos de otras
zonas de memoria , cuyo contenido es impredictible .
Se puede referenciar a cada elemento , en forma individual , tal como se ha
hecho con las variables anteriormente , por ejemplo :
var1[5] = 40 ; contador = var1[3] + 7 ; if(var1[0] >>= 37) ..................
Tambien es posible utilizar como subíndice expresiones aritméticas , valores enteros retornados por funciones , etc . Así podríamos escribir :
printf(" %d " , var1[ ++i] ) ; var1[8] = var1[ i + j ] ; ............................... int una_funcion(void) ; var1[0] = var1[ una_funcion() ] * 15 ;
Por supuesto los subíndices resultantes de las
operaciones tienen que estar acotados a aquellos para los que el array fué
declarado y ser enteros .
La inicialización de los arrays sigue las mismas reglas que vimos para los
otros tipos de variables , es decir : Si se declaran como globales ( afuera del
cuerpo de todas las funciones ) cada uno de sus elementos será automaticamente
inicializado a cero . Si en cambio , su declaracion es local a una función , no
se realiza ninguna inicialización , quedando a cargo del programa cargar los
valores de inicio .
La inicialización de un array local , puede realizarse en su declaración ,
dando una lista de valores iniciales:
int numero[8] = { 4 , 7 , 0 , 0 , 0 , 9 , 8 , 7 } ;
Obsérvese que la lista está delimitada por llaves . Otra posibilidad , sólo válida cuando se inicializan todos los elementos del array , es escribir :
int numero[] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 } ;
donde se obvia la declaración de la cantidad de elementos
, ya que está implícita en la lista de valores constantes .
También se puede inicializar parcialmente un array , por ejemplo :
int numero[10] = { 1 , 1 , 1 } ;
en éste caso los tres primeros elementos del mismo valdran 1 , y los restantes cero en el caso que la declaración sea global , ó cualquier valor impredecible en el caso de que sea local .
CONJUNTO ORDENADO DE CARACTERES (STRINGS)
Los strings son simplementes arrays de caracteres , tal como
los vimos hasta ahora , con el agregado de un último elemento constante : el
caracter NULL ( ASCII == 0 , simbolizado por la secuencia de escape \0 ) . Este
agregado permite a las funciones que procesan a los mismos , determinar
facilmente la finalización de los datos .
Podemos generar un string , declarando :
char car_str[] = { 'A' , 'B' , 'C' , 'D' , 0 } ; char car_str[] = { 'A' , 'B' , 'C' , 'D' , '\0' } ;
Ambas maneras son equivalentes. Sin embargo hay , en el lenguaje C , una forma más compacta de declararlos :
char car_str[] = "ABCD" ; char car_str[5] = "ABCD" ; int texto[] = "renglon 1 \n renglon 2 \n " ; /* ERROR */ unsigned char texto[] = "renglon 1 \n renglon 2 \n " ;
Simplemente en la declaración del mismo se encierran los
caracteres que lo componen entre comillas . Obsérvese que en la segunda
declaración , se ha explicitado ( no es necesario ) , la cantidad de elementos
que tiene el string , y és uno más que la cantidad de caracteres con que se lo
inicializa , para dejar lugar al NULL . Todas éstas declaraciones agregan automáticamente
el NULL como último elemento del array .
Un caso interesante es él de la tercer línea ( comentada como ERROR ) , con el
fín de poder albergar al caracter "\n"20( ASCII 179 ) se intentó
asignar el string a un array de enteros , Esto no es permitido por el compilador
, que lo rechaza como una asignación inválida . La razón de ello se verá más
adelante cuando analicemos punteros , ya que el string constante usado como
rvalue es un puntero a char , y no a int . La solución mas común para este
caso es , declarar el array como unsigned char , con lo que llevamos el alcance
de sus elementos a 255 . Si tuvieramos el caso de tener que albergar en un
string el caracter EOF ( -1 ) y al mismo tiempo caracteres con ASCII mayor que
127 ,se podría definir el array como int , pero su inicialización se tendrá
que hacer obligatoriamente usando llaves , como vimos anteriormente .
Se deduce entonces , de lo antedicho que un string sigue
siendo un array de caracteres , con la salvedad del agregado de un terminador ,
por lo que las propiedades que veremos a continuacion , se aplicaran
indistintamente a ambos .
ARRAYS Y STRINGS COMO ARGUMENTOS DE FUNCIONES
Los arrays , como todos los otros tipos de variables , pueden
ser pasados como argumentos a las funciones . Veamos esquematicamente como sería
la sintaxis :
double funcion_1( float numeros[10] , char palabra[] ) ; /*linea 1*/ ....................................................... main() /*linea 2*/ { float numeros[10] = { 1.1 , 2.2 , 3.0 } ; /*linea 3*/ char palabra[] = " Lenguaje C " ; /*linea 4*/ double c ; /*linea 5*/ ........................................................ c = funcion_1( numeros , palabra ) /*linea 6*/ ........................................................ } double funcion_1( float numeros[10] , char palabra[] ) /*linea 7*/ { ........................................................ }
Es necesario analizar con mucho detenimiento , este último ejemplo . En la primer línea declaramos el prototipo de funcion_1() que recibe como argumentos dos arrays , uno de 10 elementos del tipo float , y otro de caracteres de longitud indeterminada . En el primer caso la función necesitará saber de alguna manera cual es la longitud del array numérico recibido, mientras que en el segundo , no hace falta , ya que la función puede ser construída para que , por sí misma , detecte la finalización del string por la presencia del caracter NULL . Se podría generalizar más el programa declarando :
double funcion_1( double numeros[] , int longitud_array , char palabra[] ) ;
en donde , en la variable longitud_array se enviaría la
cantidad de elementos de numero[] .
En la tercer línea se declara el array numérico , inicializandose sólo los
tres primeros elementos , y en la cuarta línea se declara el string .
En la séptima línea se dá la definición de la función , de acuerdo al
prototipo escrito anteriormente .
Si miramos con detenimiento la sexta línea , el llamado a la función , vemos
que los argumentos pasados sólo tienen el NOMBRE de ambos arrays . Esta es la
diferencia más importante entre este tipo de estructura de datos y las
variables simples vistas anteriormente , ya que los arrays son pasados a las
funciones por DIRECCION y nó por valor .
En el lenguaje C se prefiere , para evitar el uso abusivo del stack , cuando hay
que enviar a una función una larga estructura de datos , en lugar de copiar a
todos ellos , cargar el stack sólo con la dirección de la posición de memoria
donde está ubicado el primero de los mismos.
El nombre de un array equivale sintácticamente a la direccion del elemento cero
así será :
numero == dirección de numero[0] palabra == direccion de palabra[0]
Esto habilita a las funciones a que puedan acceder a los
arrays directamente , allí donde el programa los ha ubicado en la memoria , por
lo que pueden MODIFICARLOS EN FORMA PERMANENTE aunque no hayan sido declarados
como locales a la función misma ní globales al programa .
Es muy importante recordar este último concepto , a fín de evitar errores muy
comunes , en los primeros intentos de programación en C .
Otra característica importante de los arrays es que , su nombre ( ó dirección
del primer elemento ) es una CONSTANTE y nó una variable . El nombre de los
arrays implican para el compilador el lugar de memoria donde empieza la
estructura de datos por lo que , intentar cambiar su valor es tomado como un
error , asI si escribieramos por ejemplo :
char titulo[] = "Primer titulo" ; .................................... titulo = "subtitulo" ;
La primer sentencia es correcta , ya que estamos incializando al string , pero la segunda produciría un error del tipo " LVALUE REQUERIDO " , es decir que el compilador espera ver , del lado izquierdo de una expresión , a una variable y en cambio se ha encontrado con una constante titulo (ó sea la dirección de memoria donde está almacenada la P de "Primer título") . Esto al compilador le suena similar a una expresión de la clase : 124 = j y se niega rotundamente a compilarla .
ARRAYS MULTIDIMENSIONALES.
Las estructuras de datos del tipo array pueden tener más de
una dimensión , es bastante común el uso de arrays "planos" ó
matriciales de dos dimensiones , por ejemplo :
int matriz[ número total de filas ] [ número total de columnas ] ;
Si declaramos :
int matriz[3][4] ;
esquematicamente la disposicion "espacial" de los elementos seria:
columnas: 0 1 2 3 filas 0 [0][0] [0][1] [0][2] [0][3] matriz[0][] 1 [1][0] [1][1] [1][2] [1][3] matriz[1][] 2 [2][0] [2][1] [2][2] [2][3] matriz[2][]
Por supuesto , aunque menos usados , se pueden generar
arrays de cualquier número de dimensiones .
Para inicializar arrays multidimensionales , se aplica una técnica muy similar
a la ya vista , por ejemplo para dar valores iniciales a un array de caracteres
de dos dimensiones , se escribirá :
char dia_de_la_semana[7][8] = { "lunes" , "martes" , " miercoles" , "jueves" , "viernes" , "sábado" , "domingo" } ;
Acá el elemento [0][0] será la "l" de lunes ,
el [2][3] la "r" de miercoles , el [5][2] la "b" de sabado,
etc. Nótese que los elementos [0][5] , [1][6] ,etc estan inicializados con el
caracter NULL y demas [0][6] y [0][7], etc no han sido inicializados. Si le
parece que en este párrafo se nos escapó un error , está equivocado , lo que
ocurre es que se olvidó de contar los índices desde 0.
Este último ejemplo también podría verse como un array unidimensional de
strings.