Conceptos de Vector y Array (PDF)
Document Details
Uploaded by Deleted User
Tags
Summary
This document provides information about vectors and arrays, common data structures in programming. It explains how to declare and access elements, perform various operations, as well as the use of arrays and matrices in programming and how to initialize and traverse them.
Full Transcript
CONCEPTO DE VECTOR O ARRAY Concepto Vector, arreglo o array: Objeto que permite definir un almacenamiento (con un nombre común) en memoria principal de una colección de datos: del mismo tipo ordenados de manera secuencial (en posiciones contigüas de memoria), de manera que cada dato ocupa una po...
CONCEPTO DE VECTOR O ARRAY Concepto Vector, arreglo o array: Objeto que permite definir un almacenamiento (con un nombre común) en memoria principal de una colección de datos: del mismo tipo ordenados de manera secuencial (en posiciones contigüas de memoria), de manera que cada dato ocupa una posición determinada (0, 1, 2 …) denominada índice. La forma de acceder a un dato determinado es indicar su número de posición. Declaración tipo_dato identificadorV[dimensión] El tipo indica al compilador cuantos bytes se necesitan para cada elemento y la dimensión cuantos elementos. Es típico definir la dimensión con #define dimensión N - Se puede inicializar un vector al declarlo - Solo se deberían declarar sin dimensión los vectores de caracteres inicializados: char vector[]=”hola” Acceso identificadorV[indice] Donde indice es una variable int o char (numérica) - Un array indexado se utiliza exactamente igual a como se utilizaría una variable del tipo base del vector (asignarle un valor,usarla en expresiones) - No se comprueba que el índice está dentro de los límites (‘0’ y ‘dimensión-1’): es responsabilidad del programador evitar escribir o leer fuera de los limites. - Escribir fuera de los límites de un array es especialmente pernicioso: se altera el contenido de posiciones de memoria que pueden contener cualquier cosa (otras variables, código máquina, etc). ARRAYS Y FUNCIONES - Un array declarado como variable local de una función se usa igual que cualquier otra variable - Un array que aparezca como parámetro de una función presenta algunas particularidades: 1-Llamada a una función que tenga como parámetro un array: se escribe el nombre del array sin corchetes 2-Declaración como parámetro formal en la cabecera de una función: se escribe tipo nombreVector [] (sin escribir un tamaño, aunque no sería incorrecto). 3- Los vectores no se pueden devolver en una función (return), por lo que si se desea que una función modifique un array en principio tendríamos que pasarlos como parámetro por referencia. Esto no es necesario ya que los vectores se pasan siempre por referencia (son en realidad punteros). Por tanto cualquier cambio que haga una función en un array pasado como parámetro será un cambio permanente. Un error típico del programador en C novato es confiar que un array pasado como parámetro actual a una función no se modificará. Tampoco es necesario utilizar el operador & delante del nombre del array. El nombre de un array equivale sintácticamente a la dirección del elemento cero (es decir array == &array). MANIPULACIONES BÁSICAS DE UN VECTOR O ARRAY Inicialización Si el tamaño del array no es muy grande, y sabemos de antemano los valores que debe tener Recorrido Recorrido secuencial del array: Siempre se debe utilizar un bucle para desde 0 hasta el tamaño - 1. Por ejemplo, recorrer el array para inicializarlo con 0 (tarea que, en principio, no tiene mucho sentido) Imprimir el contenido de un vector o array Lectura de los datos en un vector o array Ejemplo de trabajo con vectores o arrays MATRICES: ARRAYS MULTIDIMENSIONALES Concepto Los arrays pueden tener 1 dimensión (los estudiados antes) o 2 (arrays bidimensionales o matrices), 3 o mas. En una matriz: - Se distribuye la información en forma de tabla por filas y columnas. - Las filas y columnas se indexan desde 0 en adelante. - En la intersección de una fila y una columna hay un solo dato (identificado por num_fila y num_ columna) Las matrices suelen utilizarse para relacionar dos magnitudes, como por ejemplo, la nota de un determinado alumno en una determinada asignatura. Declaración tipo_dato identificadorM[dimensiónF][dimensiónC] Acceso identificador [fila] [columna] Para acceder a una posición de una matriz deberemos usar dos variables enteras ‘fila’, ‘columna’ que deberían tomar valores desde 0 hasta dimensiónF-1 y respectivamente. MATRICES Y FUNCIONES - Una matriz declarada como variable local de una función se usa igual que cualquier otra variable - Una matriz que aparezca como parámetro de una función presenta algunas particularidades: 1-Llamada a una función que tenga como parámetro una matriz: escribir nombre de matriz sin corchetes ni & 2-Declaración como parámetro formal en la cabecera de una función se escribe sin tamaño para las filas y a continuación [dimensión] para las columnas: tipo nombreMatriz [][Columnas] En general, si el argumento es una matriz n dimensional es obligatorio especificar todas las dimensiones menos la primera Tipo nombreMatriz [][DIM2][DIM3] La razón es que una matriz se almacena realmente en memoria “por filas”. MANIPULACIONES BÁSICAS DE UNA MATRIZ Inicialización Si el tamaño del array no es muy grande, y sabemos de antemano los valores que queremos almacenar en él, podemos inicializarlo en el momento de su declaración, igualándolo a la lista de valores separados por comas y encerrados entre llaves { }. Recorrido de una matriz Para recorrer una matriz secuencialmente: dos bucles para anidados desde 0 hasta la dimensión menos 1. Por ejemplo, si queremos inicializar todas las casillas a por ejemplo, a cero (tarea en principio absurda); Imprimir el contenido de una matriz Lectura de los datos de una matriz OPERACIONES CON PUNTEROS ASIGNACION A PUNTERO Mediante una sentencia de asignación, para darle: - una dirección con el operador &, o - el valor de otro puntero, del mismo tipo base ARITMÉTICA DE PUNTEROS - Un puntero contiene una dirección de memoria (un número): podemos sumar o restar una cantidad a un puntero, haciendo que el puntero apunte a otra dirección de memoria. - Todos los operadores aritméticos de suma y resta son aplicables a punteros (+ , - , += , -= , ++,--) - Semántica: Sumar (restar) la cantidad de 1 a un puntero, hace que el puntero se incremente (decremente) no en 1, si no en una cantidad igual al número de bytes del tipo base; es decir, se hace que el puntero apunte al siguiente dato en memoria de ese tipo. - Si se restan dos punteros que apuntan a dos posiciones de un array, se obtiene el número de elementos que hay almacenados entre ambos punteros. COMPARACION DE PUNTEROS Mediante una expresión relacional. Deben realizarse solo cuando las direcciones estén próximas (si no, pueden producir resultados erróneos). Normalmente la comparación de punteros se realiza cuando apuntan a un objeto común (p ej., a distintas posiciones en un array). PUNTEROS Y VECTORES Los operadores de punteros y vectores son intercambiables: se puede trabajar con los elementos de un array mediante operaciones de punteros y se pueden manipular punteros con el operador de indexación [] de arrays. En realidad el identificador de un vector es un puntero a su primer elemento. Desplazamiento sobre un array Los elementos del array se almacenan en posiciones consecutivas de memoria, así que para referirse a un elemento del array basta con desplazar el puntero a la posición deseada Modificación del contenido de una posición Al tratarse de un puntero, al identificador de un vector se le puede aplicar el operador * para obtener el contenido de la dirección a la que apunta: De igual manera para acceder a los elementos de una matriz m bidimensional con aritmética de punteros podemos usar: *(*(m + i) +j) CADENAS DE CARACTERES En C una cadena se define como un array de caracteres de cualquier longitud que termina en un carácter nulo. El carácter nulo se especifica como ‘\0’, cuyo valor ASCII es 0, es decir, el número es 0. Para declarar un array de caracteres es necesario que sean de un carácter más que la cadena más larga que pueda contener. char nif; //10 ==8 dígitos+1 letra + ‘\0’ Una constante literal cadena es una lista de caracteres encerrada en dobles comillas (El compilador se encarga de añadir el carácter nulo). Recordar que en C los caracteres se representan mediante un byte en el que se almacena el código ASCII que codifica el carácter. Pero ese byte, también se puede interpretar como un número; por ejemplo ‘\n’ es el 13, ‘A’ es 65 y ‘a’ es el 97. Por ello es válido escribir expresiones como ‘b’-‘a’, que da como resultado el número 1, ya que el código ASCII de la ‘b’ es el siguiente de la ‘a’. La mejor forma de pasar una cadena a una función es declarar el parámetro formal como un puntero o como un vector sin especificar tamaño. El parámetro actual puede ser un puntero o un vector. Para que una función pueda devolver una cadena de caracteres se declara de tipo ‘char *’ Para asignar el resultado de una función a una variable cadena, no podemos utilizar la asignación, como hasta ahora hemos realizado. Tenemos que utilizar obligatoriamente la función strcpy. FUNCIONES DE MANEJO DE CADENAS Estas funciones, que están en la librería ‘string.h ’ : - No realizan reserva dinámica de memoria: Si una función necesita escribir una cadena en un parámetro, ese parámetro debe ser o un vector o un puntero a char inicializado con malloc o realloc. - No comprueban los límites de los arrays, asi que es responsabilidad del programador asegurar que los arrays o punteros que se le pasan a estas funciones son lo suficientemente grandes Las funciones más utilizadas son las siguientes: char * strcat( char * cadena1, char * cadena2) Concatena (añade al final) una copia de cadena2 en cadena1 y añade al final de cadena1 el carácter nulo ‘\0’. El carácter nulo que originalmente tenía cadena1 se sustituye por el primer carácter de cadena2. La cadena2 no se modifica en esta operación. La función devuelve la (dirección de) cadena1 modificada por la concatenación. int strcmp (const char*cad1, const char*cad2) Compara lexicográficamente, distinguiendo entre minúsculas y mayúsculas, dos cadenas que finalizan con el carácter nulo y devuelve un entero. Las funciones stricmp y strcmpi: ignoran la diferencia entre minúsculas y mayúsculas. char *strcpy (char *cad1, const char *cad2) Copia el contenido de cad2 en cad1, devolviendo cad1. Es lo mas parecido que tiene C a la operación de asignación de una cadena a una variable. int strlen (const char *cad1) Devuelve el número de caracteres de una cadena sin contar el ‘\0’ final. char *strlwr (char *cad1) Convierte cad1 a minúculas. char *strncat char*cad2,int n) (char*cad1,const Añade no más de n caracteres (un carácter nulo y los demás caracteres siguientes no son añadidos) de la cadena apuntada por cad2 al final de la cadena apuntada por cad1. El carácter inicial de cad2 sobrescribe el carácter nulo al final de cad1. El carácter nulo siempre es añadido al resultado. char* strncpy(char char*orig,int n) *dest, const Copia no más de n caracteres (caracteres posteriores al carácter nulo no son copiados) de la cadena ‘orig’ a la cadena ‘dest’. char *strupr (char* cad) Convierte ‘cad’ a mayúsculas FUNCIONES DE MANEJO DE CARACTERES Estas funciones se aplican solo sobre caracteres y estan incluidas en las librerías ‘ctype.h ’y ‘stdlib.h’. (atof, atoi, atol, itoa) double atof(const char *cad) Convierte una cadena en un double. Devuelve el double correspondiente y 0 si la cadena no es un numero. int atoi(char *cad) Convierte una cadena en un entero. Devuelve el entero correspondiente y 0 si la cadena no es un entero. long int atol(const char *cad) Convierte una cadena en un entero largo. Devuelve el entero correspondiente y 0 si la cadena no es un entero. int isalpha(int car) Devuelve un valor distinto de 0 (verdadero) si el carácter es una letra del alfabeto y 0 (falso) en otro caso. int isdigit(int car) Devuelve un valor distinto de 0 (verdadero) si el carácter es un dígito y 0 (falso) en otro caso. 0 int isalnum(int car) Devuelve un valor distinto de 0 (verdadero) si el carácter es una letra o un dígito y 0 (falso) en otro caso. 0 int islower(int car) Devuelve un valor distinto de 0 (verdadero) si el carácter es una letra en minúscula y 0 (falso) en otro caso. 0 int ispunct(int car) Devuelve un valor distinto de 0 (verdadero) si el carácter es un símbolo (‘;’,’@, …) y 0 (falso) en otro caso. int isspace(int car) Devuelve un valor distinto de 0 (verdadero) si el carácter es un espacio (‘ ‘,tabulación, intro,…) y 0 (falso) en cualquier otro caso. int isupper(int car) Devuelve un valor distinto de 0 (verdadero) si el carácter es una letra en mayúsculas y 0 (falso) en cualquier otro caso. int itoa(int entero,char *cad,int base) Convierte un entero a una cadena. La base especifica la base que debe ser usada en la conversión. Debe estar entre 2 y 36. Si el entero es negativo y la base es 10 el primer carácter de la cadena será el signo menos (-). int tolower(int car) Devuelve el equivalente en minúscula del carácter. Si el carácter no es una letra del alfabeto no sufre ningún cambio. int toupper(int car) Devuelve el equivalente en mayúsculas del carácter. Si el carácter no es una letra del alfabeto no sufre ningún cambio. TIPOS DE DATOS DEFINIDOS POR EL USUARIO El lenguaje C permite al programador crear tipos de datos propios mediante los siguientes recursos: - registro o estructura: agrupación de varias variables (de igual o distinto tipo) bajo un mismo nombre - campo de bits: acceso a memoria a nivel de bits. No los estudiaremos - unión: permite que la misma porción de memoria sea compartida por dos o más variables de diferente tipo. - enum: creación de un tipo enumerado. - typedef: nuevo nombre para un tipo ya existente REGISTROS O ESTRUCTURAS Concepto Registro: Objeto que permite agrupar varias variables de igual o distinto tipo bajo un mismo nombre. Las variables que forman el registro se llaman campos del registro. Un registro es una especie de ‘”ficha” referente a un tema concreto y dividida en distintos apartados. Declaración 1º. De un tipo de registro Para declarar una variable de tipo registro struct nombreTipoRegistro variable; 2º. De varias variables de tipo registro struct nombreTipoRegistro{ tipo campo1; … tipo campoUltimo; } variable1,variable2, …, variableN; 3º. De una variable de tipo registro struct{ tipo campo1; … tipo campoUltimo; } variable1; Acceso 1º. Operador ‘.’ (punto) Para acceder a un campo de una variable de tipo registro se aplica el operador punto ‘.’: variableRegistro.variableCampo Se utiliza exactamente igual a como se utilizaría una variable del mismo tipo que ese campo. Operador ‘=’ (asignación) El operador de asignación está definido para el tipo registro; es decir se puede asignar a una variable de un tipo registro el valor de otra variable registro del mismo tipo registro: Operador ‘->’ Si una variable es de tipo “puntero a registro”, se puede acceder al valor de un campo del registro apuntado por ese puntero con el operador ‘->’. Uso de registros VECTORES DE REGISTROS Es frecuente que se necesite mantener una lista de registros (por ejemplo la lista de registos de empleados de una empresa). La manera mas simple de implementar esa lista es mediante un vector, del tamaño adecuado, y cuyo tipo base es de tipo registro. REGISTROS CON CAMPOS DE TIPO VECTOR Un campo de un registro puede ser un vector o una matriz. Se accede primero al campo (operador ‘.’) y luego se indexa (operador ‘[]’). REGISTROS CON CAMPOS DE TIPO REGISTRO Un registro puede tener campos que sean de tipo registro. Se accede primero al campo de tipo registro (operador ‘.’) y luego se accede a alguno de los campos de ese registro (operador ‘.’) Registros y Funciones Paso de parámetros − El tipo registro debe ser global (fuera de cualquier función) − En la cabecera de la función se especifica el tipo de registro y el nombre del parámetro formal − En la invocación de la función se escribe el nombre del parámetro actual Devolución de un registro Se puede definir una función de tipo registro. Si el registro es muy grande (ocupa muchos bytes) es aconsejable pasarlo como parámetro simulando el paso por referencia y trabajar con él a través de un puntero. Si no se hace así, cada vez que se devuelva un registro (return) se debe hacer una copia de todos los bytes del registro y el programa es mas lento. UNIONES Una unión es un registro en el que todos los campos se almacenan en la misma región de memoria (se reserva memoria para el campo de mayor tamaño). El acceso >’,’=’) también es similar (operadores ‘.’,’ Una unión reserva espacio suficiente para almacenar el tipo de datos más grande que la compone; de esta forma todos los elementos que la forman tienen sitio suficiente para almacenar valores en ella, siempre y cuando lo hagan de uno en uno. Es decir, una unión no permite que haya dos elementos con valores válidos al mismo tiempo. ENUMERACIONES Concepto Definir un tipo enumerado consiste en indicar explícitamete los valores que puede tomar una variable de ese tipo. En C una enumeración es un conjunto de constantes enteras con nombre que especifica todos los valores del tipo. Declaración − Cada elemento Ei es un identificador cuyo valor es un número entero. − Por defecto el valor del primer elemento es 0 y el valor del siguiente es el valor del anterior +1 − Se puede asignar un valor entero concreto a un elemento escribiendo ‘Ei=cteLiteralEntera’ TYPEDEF Concepto Permite crear alias de tipos ya definidos; es decir se puede definir otro nombre para un tipo ya existente. La razón para hacer esto es conseguir que el programa sea más legible. typedef tipoAntiguo aliasNuevo: typedef struct{...} tipoEstructura; Permite darle un nombre corto a una larga definicion de estructura o registro. typedef tipobase tipoVector[DIM] Evitar tener que escribir en todas las declaraciones de variables los parámetros de la(s) dimensión(es) FICHEROS Para el lenguaje C un flujo de datos es una corriente o flujo de bytes que puede hacerse corresponder con un fichero físico en disco. Para utilizar ficheros en primer lugar se debe declarar un flujo de datos como FILE * siendo el tipo FILE una estructura definida en la cabecera (así como el resto de funciones para manejar ficheros). Los ficheros pueden ser: Ficheros de Texto: si están divididos en líneas que contienen caracteres y que acaban en un salto de línea. Ficheros binarios: Si tienen cualquier otra estructura. Antes de trabajar con un fichero debemos abrirlo. Esta operación conecta el flujo con el fichero físico en disco. A partir de ese momento trabajaremos con el fichero a través del flujo con el que está conectado. En la operación de apertura es dónde se decide de qué tipo será el fichero (binario o texto). Una vez que se acabe de trabajar con un fichero este debe cerrarse, desconectando así el flujo del fichero físico. Declaración de un flujo FILE * id_var_fichero; Apertura y cierre de un fichero Antes de poder leer o escribir datos en un fichero hay que abrirlo mediante la función fopen() definida del siguiente modo: FILE * fopen (const char *nombre_fichero, const char *modo_apertura) Como parámetros recibe dos cadenas: Nombre_fichero: es el nombre o ruta de acceso del fichero físico que deseamos abrir. Modo_apertura: especifica el modo de apertura, indica el tipo de fichero (texto o binario) y el uso que se va a hacer de él lectura, escritura, añadir datos al final, etc. Los modos disponibles son: Modo Descripción r abre un fichero para lectura. Si el fichero no existe devuelve error. w abre un fichero para escritura. Si el fichero no existe se crea, si el fichero existe se destruye y se crea uno nuevo. a abre un fichero para añadir datos al final del mismo. Si no existe se crea. + símbolo utilizado para abrir el fichero para lectura y escritura. b el fichero es de tipo binario. t el fichero es de tipo texto. Si no se pone ni b ni t el fichero es de texto. Los modos anteriores se combinan para conseguir abrir el fichero en el modo adecuado. Por ejemplo, para abrir un fichero binario ya existente para lectura y escritura el modo será "rb+ "; si el fichero no existe, o aun existiendo se desea crear, el modo será " wb+". Si deseamos añadir datos al final de un fichero de texto bastará con poner "a", etc. La función fopen() devuelve un flujo que usaremos para trabajar con el fichero, a través del resto de funciones de manejo de ficheros, o NULL en caso de que el fichero no se haya podido abrir. int fclose( FILE* id_var_fichero); Devuelve 0 si el cierre se ha realizado con éxito y la macro EOF en caso contrario. Funciones de lectura Lectura de caracteres de un fichero: int fgetc (FILE *f); Devuelve el carácter del fichero situado en la posición actual o EOF si es final del fichero. Avanza el indicador a la siguiente posición. Lectura de cadenas de un fichero: char * fgets( char * s, int tam, FILE * f); Lee una cadena de caracteres del fichero y la copia en la cadena apuntada por s, el tamaño de la cadena leida vendrá determinado por el entero tam, por el carácter fin de línea o por el carácter de fin de fichero EOF. Devuelve un puntero a la misma cadena. Lectura formateada de un fichero: int fscanf(FILE * f, const char * formato, …); Funciona exactamente igual que scanf pero leyendo del fichero en vez de de la entrada estándar (flujo stdin). Lectura de bloques de un fichero: size_t fread (void * p, size_t tam, size_t n, FILE * f); Lee tantos datos como indique n del fichero, colocando los datos leídos a partir de la dirección p. Los datos tienen que tener tantos bytes como especifique tam. La función fread devuelve el número de elementos leídos, y el valor devuelto debe coincidir con n. Funciones de escritura Escritura de caracteres en un fichero: int fputc (int c, FILE *f); Escribe el carácter c en la posición actual del fichero. Devuelve el carácter escrito o EOF en caso de error. Avanza el indicador a la siguiente posición. Escritura de cadenas en un fichero: int fputs( const char * s, FILE * f); Escribe la cadena de caracteres apuntada por s en el fichero. Devuelve EOF si se produce un error. Escritura formateada en un fichero: int fprintf(FILE * f, const char * formato, …); Funciona exactamente igual que printf pero escribiendo en el fichero en vez de de la salida estándar (flujo stdout). Escritura de bloques en un fichero: size_t fwrite (const void * p, size_t tam, size_t n, FILE * f); Escribe tantos datos como indique n el fichero, tomando los datos a partir de p. Cada dato leido tiene que tener tantos bytes como especifique tam. La función fwrite devuelve el número de elementos escritos, este valor debe coincidir con n. Las funciones de lectura y escritura de bloques suelen usarse con ficheros binarios, para permitir leer o escribir datos de cualquier tipo: números, caracteres, o incluso estructuras completas: es decir que nos permitan leer o escribir zonas de memoria que puedan contener cualquier tipo de datos. Son funciones muy potentes, con una sola llamada podemos leer o escribir un vector, una estructura o incluso un vector de estructuras. Funciones de posicionamiento Conocer la posición del indicador: long int ftell (FILE *f); Para ficheros binarios devuelve la posición del indicador en número de bytes medida desde el principio. Posicionamiento directo: int fseek (FILE * f, long int adonde, int desdedonde); Posiciona el indicador en la posición adonde, medida en bytes desde donde indique el argumento desdedonde. Teniendo en cuenta las siguientes constantes de desplazamiento: SEEK_SET desde el principio SEEK_CUR desde donde estamos SEEL_END desde el final Rebobinamiento: void rewind (FILE *f); Posiciona el indicador al principio del fichero. ASIGNACIÓN DINÁMICA DE MEMORIA Los vectores se declaran con un cierto tamaño (número de casillas) máximo: - Durante una ejecución del programa es posible que algunas posiciones nunca se utilicen: se reserva una parte de la memoria que no se usa - Por el contrario, puede que el tamaño que estimó resulte demasiado pequeño y falten posiciones La gran ventaja de trabajar con punteros es que podemos utilizarlos como arrays dinámicos, es decir, no se declara su longitud a priori en tiempo de compilación, sino que se decide el tamaño que tendrán en tiempo de ejecución. A esto se le llama reserva o asignación dinámica de memoria Se puede realizar la reserva dinámica de memoria siguiendo dos enfoques: 1. Averiguar en tiempo de ejecución el número de casillas que necesitan y reservar (en tiempo de ejecución) esa cantidad de memoria. 2. Ir reservando casillas conforme vayan haciendo falta durante la ejecución del programa. RESERVA DINÁMICA DE UN BLOQUE DE MEMORIA