==== Lenguaje C: Datos ==== * [[pub:c_data#tipos_basicos_y_modificadores|Tipos básicos y modificadores]] * [[pub:c_data#constantes|Constantes]] * [[pub:c_data#variables|Variables]] * [[pub:c_data#otros_modificadores_de_datos|Otros modificadores de tipos de datos]] * [[pub:c_data#conversion_de_tipos|Conversión de tipos]] * [[pub:c_data#vectores_y_cadenas|Vectores y cadenas]] * [[pub:c_data#datos_definibles|Tipos de datos definibles]] * [[pub:c_data#punteros|Punteros]] * [[pub:c_data#asignacion_dinamica_de_memoria|Asignación dinámica de memoria]] ---- Los **datos** son el __objeto de procesamiento__ en los programas de ordenador, en lenguajes avanzados se habla de **objetos**, como denominación más genérica. En lenguaje C las **variables** y las **constantes** deben declararse antes de ser utilizadas. La **declaración de un dato** requiere expresar: * El **tipo de dato** * El **modificador** (opcional) * El **identificador** modificador tipodato identificador; === Tipos básicos y modificadores === Los tipos de datos establecen la diferencia entre los objetos que se van a procesar, en cuanto a: * **Memoria** que ocupan * **Rango** o valores que se pueden almacenar * **Modo** en el que van a ser tratados La cantidad de **memoria** necesaria para el almacenamiento de datos, así como el margen de variación (**rango**) de dichos datos depende: * Del compilador * Del sistema operativo * De la máquina Las palabras reservadas en lenguaje C para los **tipos de datos básicos** son: * ''char'' Carácter * ''int'' Número entero * ''float'' Número real * ''double'' Número real de doble precisión * ''void'' Tipo que no existe * ''enum'' Tipo enumeración, lista de valores enteros Los **modificadores** que se pueden aplicar a los tipos de datos básicos son: * ''signed'' Con signo * ''unsigned'' Sin signo * ''long'' Largo, de mayor tamaño de almacenamiento * ''short'' Corto, de menor tamaño de almacenamiento Los datos fundamentales utilizados en lenguaje C se obtienen de las combinaciones permitidas de tipos básicos y modificadores. == Enteros == Los tipos de datos enteros permiten representar cantidades numéricas enteras * ''char'' (''signed char'') Tipo carácter * Normalmente ocupa 1 byte (permite almacenar un símbolo ASCII) * ''int'' (''signed int'') Tipo entero con signo * Normalmente ocupa 4 bytes * ''short'' (''signed short int'') Tipo entero corto * Normalmente ocupa 2 bytes * ''long'' (''signed long int'') Tipo entero en formato largo * En máquinas de 32 bits: 4 bytes; en 64 bits: 8 bytes * ''enum'' Tipo enumerado * Declara una variable que puede tomar como valores una lista de símbolos arbitrarios La relación entre tamaños que se cumple siempre es: short ≤ int ≤ long __Representación interna de números enteros:__ * Números **sin** signo: aritmética binaria de módulo //2n// siendo //n// el número de bits empleados * Números **con** signo: Complemento a 2 con el bit de mayor peso como bit de signo. Ejemplos: * Variable letra de tipo carácter: char letra; * Variable cantidad de tipo entero: int cantidad; * Variable edad de tipo entero corto: short edad; * Variable memoria de tipo largo: long memoria; * Definición y utilización de un tipo de enumeración: enum semana = {lunes, martes, miercoles, jueves, viernes, sabado, domingo}; enum semana hoy; hoy = martes; * * //semana// es un tipo de enumeración * //hoy// es una variable de tipo enumerado que se ha cargado con el valor //martes//, que si se imprime, mostraría un «''1''». * Si //hoy// se inicializase con el valor //domingo//, al imprimirse, mostraría un «''6''» (//lunes// equivale a «''0''») == Reales == Los tipos de datos reales permiten representar cantidades numéricas en notación científica y de mayor rango. Se almacenan en memoria en un formato normalizado, ya explicado, en el que se distinguen tres campos: * El **signo** del número * La **mantisa** * El **exponente** (en representación sesgada) __Tipos__: * ''float'' Tipo real de simple precisión * Hasta 7 dígitos significativos * ''double'' Tipo real de doble precisión * Hasta 16 dígitos significativos * ''long double'' Tipo real de doble precisión con formato largo * Puede llegar a tener hasta 19 dígitos significativos == Otros tipos de datos == * **Indefinido** * El tipo ''void'' indica un dato de tipo indefinido * Se usa como genérico para instanciarlo más adelante * **Derivados** * Son datos complejos que se obtienen a partir de los datos fundamentales * **Arrays**, **funciones**, **punteros**, **estructuras** y **uniones** * **Definidos** * Son tipos creados por el usuario, con un nombre y definición propios typedef tipodato nuevonombre; * * * Facilitan la lectura y escritura de programas * Ejemplo: typedef unsigned long int mitipo; /* Se ha creado un nuevo tipo de dato: mitipo */ === Constantes === Las **constantes** son valores fijos que no pueden ser alterados por el programa. Pueden ser de cualquiera de los tipos de datos posibles en lenguaje C: * Constantes **enteras** * Constantes **reales** * Constantes **de caracteres** * Constantes **simbólicas** == Enteras == Para su almacenamiento el compilador escoge el tipo de dato más pequeño compatible con esa constante Pueden expresarse: * En **decimal**: La opción por omisión * El dígito de mayor peso no puede ser un «0» * Sólo son válidos los caracteres numéricos entre el 0 y el 9 * En **octal** * El dígito de mayor peso es siempre un «0» * Sólo son válidos los caracteres numéricos entre el 0 y el 7 * En **hexadecimal** * Van precedidas por los símbolos «0x» * Son válidos los caracteres numéricos del 0 al 9 y las letras A, B, C, D, E y F tanto mayúsculas como minúsculas * Al escribirlas, se distinguirán los siguientes //campos//: * El **prefijo** para las hexadecimales o el carácter «0» para las octales. * El **signo** (opcional en el caso de números positivos) * El **valor numérico** * Un **sufijo opcional** que permite modificar el //tamaño// que el compilador debe asignarle: * ''U'' para indicar unsigned * ''L'' para indicar long * ''UL'' para indicar unsigned long * Ejemplos: -23L /* el número -23 almacenado como long */ 010 /* el octal 10 que equivale al 8 en decimal*/ 0xF /* el 0F hexadecimal que es el 15 decimal */ == Reales == * En la asignación o definición, el compilador las crea siempre de tipo ''double'' * Al escribirlas, se distinguirán los siguientes campos: * El **signo** (opcional en el caso de números positivos) * Una **parte entera** precediendo al punto decimal «.» * La **parte fraccionaria** a la derecha del punto decimal * Se permite también la notación científica con «e» o «E» * Un **sufijo opcional** que permite modificar el tamaño que el compilador debe asignarle: * ''F'' para indicar ''float'' * ''L'' para indicar ''long double'' * Ejemplos: 35.78 /* constante real de tipo double */ 1.25E-12 /* constante real de tipo double */ 45F /* constante real de tipo float */ 33L /* constante real de tipo long double */ == Caracteres == * Las constantes de **un solo carácter** son de tipo ''char'' y se expresan poniendo el carácter entre comillas simples: ''%%'A'%%'' * Las constantes de //barra invertida// o //caracteres de escape// * Permiten representar códigos ASCII sin símbolo * Se expresan mediante el valor numérico de su código ASCII precedidos de la barra invertida y entre comillas: ''%%'\código'%%'' * El código puede representarse * En decimal, con hasta tres dígitos: ''%%'\ddd'%%'' * En octal, con dos dígitos: ''%%'\0oo'%%'' * En hexadecimal, con dos dígitos: ''%%'\0xhh'%%'' * Ejemplos: '6' /* Carácter 6, código ASCII 0x36 */ '\12' /* Código ASCII 12 (Salto de línea) */ '\0x20' /* Códgio ASCII 32 (Espacio) */ * Las constantes de **cadena** * No son un tipo de dato * Definen un conjunto de caracteres almacenados de forma consecutiva cada uno en un byte * Se representan entre comillas dobles * ''%%"Esto es una cadena de caracteres"%%'' * El compilador almacena la lista de caracteres y un carácter terminador para representar el final de la cadena: el carácter nulo «''%%'\0'%%''» == Simbólicas == * Se definen mediante la directiva: #define NOMBRECONSTANTE Equivalencia * La directiva **NO** es una sentencia de lenguaje C * ''NOMBRECONSTANTE'' es el identificador de la constante simbólica (recomendado en mayúsculas) * ''Equivalencia'' representa los símbolos que va a representar ''NOMBRECONSTANTE'' * Siempre que en el programa aparezca ''NOMBRECONSTANTE'' será sustituido antes de compilar por Equivalencia * Ejemplo: #define MAXIMO 100 /* MAXIMO toma el valor 100 */ #define FRASE "Pulsa una tecla" === Variables === == Declaración de variables == Todas las variables deben //declararse// antes de ser utilizadas para que el compilador les asigne la memoria necesaria * La //declaración// de una variable es una sentencia * Consiste en escribir el nombre de la variable precedida por el tipo de dato: tipodedato nombrevariable; * * ''tipodedato'' representa la palabra o palabras que definen el tipo de dato * ''nombrevariable'' es el identificador de la variable * Ejemplos: char letra; /* variable tipo carácter */ int actual, mayor, menor; /* variables enteras */ float resultado; /* variable real */ Según el punto del programa donde se declaran, las variables pueden ser **locales**, **globales** o **parámetros** formales. * **Variables locales**, variables **dinámicas** o variables **automáticas** (''auto'') * Se declaran dentro de una bloque de código (función) * La declaración debe situarse al comienzo de la función o bloque de código, antes de realizar cualquier otra operación * Sólo son válidas dentro de ese bloque de código * Desaparecen cuando se finaliza la ejecución de ese bloque de código * Si el bloque de código se ejecuta varias veces, en cada ocasión la variable es creada al inicio y destruida al finalizar * Hasta que se inicializan, contienen valores "basura" * Se almacenan en una zona de memoria que funciona como memoria pila (LIFO-Last Input First Output; último en entrar, primero en salir) * **Variables globales** * Se declaran fuera de cualquier función * Permanecen activas durante todo el programa * Se almacenan en una zona fija de memoria establecida por el compilador * Pueden ser utilizadas en cualquier función a partir del punto de definición. Cualquier sentencia de tales funciones puede operar con ellas sin restricciones * Pueden estar definidas en otro fichero, en cuyo caso deben definirse con el modificador ''extern'' en el fichero en que se utilicen * Al definirse, el compilador las inicia a cero * No se aconseja su uso, salvo cuando sea imprescindible ya que: * Hacen las funciones menos portátiles * Ocupan la memoria permanentemente * Provocan fácilmente errores * **Parámetros formales** * Son variables que reciben los valores que se pasan a la función * Son siempre locales a la propia función * Se declaran en la línea de nombre de la función * Ejemplo: long int Mifuncion(int base, int exponente) { /* Cuerpo de la función */ } == Inicialización de variables == La inicialización de variables sirve para asignar el primer valor * Por omisión: * Las variables globales se inicializan a 0 * Las variables locales adquieren el valor de lo que haya en la memoria donde se almacenan (puede ser basura) * Puede realizarse en la misma declaración y se realiza mediante un operador de asignación: tipodato nombrevariable = valorinicial; * Ejemplo: unsigned int edad = 25; === Otros modificadores de datos === * Modificadores de **tipo de acceso**: Complementan la declaración de una variable para cambiar la forma en la que se acceden o modifican las variables * ''const'' * Define una variable como constante, que no podrá ser modificada durante la ejecución del programa. * ''volatile'' * Crea una variable cuyo contenido puede cambiar, incluso por medios ajenos al programa * Ejemplo: unsigned int const year = 2018; * Modificadores de **tipo de almacenamiento**: Permiten indicar al compilador el modo de almacenamiento de la variable * ''extern'' * Declara una variable que ha sido definida en un archivo diferente al de la función (ya tienen memoria asignada) * ''static'' (dentro de una función) * Declara una variable local que mantiene su valor entre llamadas. * ''static'' (fuera de una función) * Declara una variable global privada del fichero en que se define * ''register'' * Indica al compilador que la variable debe ser almacenada en un lugar en el que se optimice el tiempo de acceso a ella (preferiblemente en un registro de la CPU) * ''auto'' * Declara una variable local a una función o a un bloque de código (es la opción por omisión) === Conversión de tipos === En las expresiones, los operandos cambian de tipo **automáticamente** * Si intervienen operandos reales, se unifican los tipos al de mayor precisión * Las constantes reales son tipo ''double'' por omisión * Los ''char'' y ''short'' se convierten a ''int'' si el ''int'' puede representar todos los valores del tipo original, o a ''unsigned int'' en caso contrario * Si intervienen operando enteros, se unifican los tipos al de mayor longitud * Ejemplo: long a char b; int c, f; float d; f = a + b * c / d; * * ''b'' se convierte al tipo de ''c'' (''int'') y se realiza ''b*c''.Se obtiene un ''int'' * El ''int b*c'' se convierte a ''float'' y se divide entre ''d''. Se obtiene un ''float'' * ''a'' se convierte a ''float'' y se suma a ''b*c/d''. Se obtiene un ''float'' * El ''float'' resultante de ''a+b*c/d'' se convierte a ''int'' (eliminando la parte fraccionaria) y se guarda en la variable entera ''f'' **Conversión explícita: operador ''cast'' ** * Consiste en convertir el tipo de dato de una variable o de una expresión * Sólo sirve para la evaluación de la expresión donde se realiza la conversión * Sintaxis: (tiponuevo)expresion; * * ''tiponuevo'' es el tipo de dato al que se convertirá ''expresion'' * Ejemplo: * La expresión ''7/2'' da como resultado ''3'', sin embargo la expresión ''(float)7/2'' convierte el ''7'' en real y el resultado será un número real: ''3.5'' ==== Vectores y cadenas ==== === Vectores === Un **vector** es un tipo de variable especial que permite almacenar un conjunto de datos del mismo tipo * Los elementos de un vector pueden ser referenciados de forma independiente mediante el nombre del vector seguido por los índices necesarios entre corchetes «[]» * El elemento que ocupa la primera posición tiene como índice cero. * El índice puede representarse con cualquier expresión cuyo resultado sea un número entero positivo * Pueden ser: * Unidimensionales * Multidimensionales * Los elementos de un vector se almacenan en posiciones consecutivas de memoria (el primer elemento en la dirección más baja) * En lenguaje C, los límites en la comprobación del tamaño y dimensiones de los vectores es responsabilidad del programador == Declaración de vectores == La declaración de un vector supone **reservar memoria para sus elementos** * Declaración de vectores **unidimensionales** tipodato nombrearray[tamaño]; * * ''tipodato'' representa el tipo de datos de los elementos del vector (cualquiera excepto ''void'') * ''nombrearray'' es el identificador del vector y de sus elementos * ''tamaño'' representa un valor entero y constante que indica el número de elementos del vector * Si el número de elementos es //n//, el primer elemento es el ''nombrearray[0]'' y el último es ''nombrearray[n-1]'' * Puede no indicarse el tamaño si se inicializan los valores en la declaración o si está declarado ya en otro punto del programa (parámetros de una función) * La cantidad de memoria que se asigna a un vector es: num_bytes = tamaño * sizeof(tipodato) * Declaración de un vector **multidimensional** tipodato nombrearray[tamaño1][tamaño2]...[tamañoN]; * * ''tamaño1'', ''tamaño2'', ''tamañoN'' son expresiones constantes enteras * El número de ellas determina el número de dimensiones del vector * Cada una determinan el tamaño de cada dimensión * Cada dimensión necesita un índice para hacer referencia a una posición * Los elementos del vector se almacenan de forma consecutiva, siendo el índice más a la derecha el que más rápido cambia * La cantidad de memoria que se reserva para el vector es: num_bytes = tamaño1 * tamaño2 * ... * tamañoN * sizeof(tipodato) * Los vectores **bidimensionales** se llaman también **tablas** y sus dos dimenisones se llaman **filas** y **columnas**: tipodato nombrearray[numfilas][numcolumnas]; * * ''numfilas'' indica el número de filas * ''numcolumnas'' indica el número de columnas * Los elementos de una tabla se almacenan en memoria de forma consecutiva por filas. * Ejemplos: int lista[10]; /* Vector de 10 enteros */ char vocales[5]; /* Vector de 5 letras */ float matriz[6][4]; /* Tabla de 6 filas y 5 columnas de */ /* números reales */ == Inicialización de vectores == Cuando se declara un vector sólo se inicializa con cero si se trata de una variable global. En caso contrario su contenido inicial será basura * Forma general de inicialización de un vector en su declaración: tipodato nombrearray[tam1]...[tamN]={listavalores}; * * ''listavalores'' es una relación de constantes del tipo declarado para el vector entre llaves «{}» y separadas por comas * Si un vector se inicializa en su declaración, se permite omitir el tamaño de su primera dimensión (la más a la izquierda: ''tam1''): * En los **unidimensionales**: tipodato nombrearray[] = {listavalores}; * En los **multidimensionales**: tipodato nombrearray[][tam2]..[tamN]={listavalores}; * Al inicializar un vector en su declaración debe recordarse que el índice que cambia más rápido es el de la derecha * Cuando se inicializa un vector en su declaración, se ha de inicializar completo: int digit [10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int impares[] = {1, 3, 5, 7, 9}; int matriz[3][4] = { s00, 01, 02, 03, 10, 11, 12, 13, 20, 21, 22, 23}; char letras[][5] = { ‘a’, ’b’, ’c’, ’d’, ’e’, ’f’, ’g’, ’h’, ’i’, ’j’, ’k’, ’l’, ’m’, ’n’, ’o’, ’p’, ’q’, ’r’, ’s’, ’t’, ’u’, ’v’, ’x’, ’y’, ’z’}; * La inicialización de un vector después de su declaración (en tiempo de ejecución) requiere la programación de un bucle por cada una de las dimensiones del vector. * Para inicializar una matriz de ''FILxCOL'' enteros el código necesario será similar la siguiente: int matriz[FIL][COL], f, c; /* Declaraciones */ for(f=0 ; f === Cadenas === Una //cadena de caracteres//, //string// o **cadena** es un **vector unidimensional** en el que todos sus elementos son de tipo ''char'' y el último elemento es el carácter nulo «''('0')''» char nombrecadena[longcad]; * ''nombrecadena'' es un identificador válido para la cadena completa * ''longcad'' es una constante entera que indica el número de elementos de la cadena incluido el nulo final. **Inicializaciones en la declaración** char nombrecadena[longcad]= "cadena"; char nombrecadena[longcad]= {listacaracteres}; * ''nombrecadena'' es el identificador de la cadena * ''longcad'' es una constante entera que determina el número de caracteres de la cadena incluido el carácter nulo (si es menor producirá error y si es mayor se rellenarán con "nulos") * ''listacaracteres'' representa un conjunto de constantes de carácter (entre comillas simples «' '»y separados por comas) que deberá incluir el nulo '''0''' al final * El carácter nulo final es un limitador acordado * Los caracteres de una cadena pueden ser accedidos como elementos de un vector ordinario * __Ejemplos__ de inicialización en la declaración: char nombre[6]= "Pedro"; char apellido[]={'R', 'u', 'a', 'n', 'o', ''}; * Las cadenas **no son un tipo de dato** * Su inicialización __fuera de la declaración__ requeriría un __bucle__ * Existen muchas __funciones__ que facilitan el trabajo con las cadenas o "strings" * La mayoría de las funciones que facilitan las operaciones con cadenas se encuentran declaradas en: * ''stdio.h'' * ''stdlib.h'' * ''string.h'' == Funciones que utilizan las cadenas de caracteres == * **''scanf()'' ** permite leer una cadena de caracteres desde el teclado, con las siguientes condiciones: * ''scanf("%[^n]s",cadena);'' nos permite leer una cadena completa hasta pulsar return ''('n')'' * ''cadena'', sin corchetes y sin que vaya precedido por el operador «''&''», es el identificador de la cadena * Es conveniente utilizar ''fflush(stdin)'' previamente para limpiar el buffer de entrada para eliminar los caracteres no recogidos * **''printf()'' ** permite, utilizando el especificador de formato ''%s'', imprimir cadenas de caracteres * **''gets()'' ** lee una cadena completa y sustituye el return por el caracter nulo al almacenarla * **''puts()'' ** escribe una cadena completa, sustituyendo previamente el caracter nulo por el return * **''strcat(cadena1,cadena2)'' ** concatena ''cadena2'' al final de ''cadena1'' * **''strcpy(cadena1,cadena2);'' ** copia ''cadena2'' en ''cadena1'' * **''strcmp(cadena1,cadena2);'' ** compara ''cadena1'' y ''cadena2'' * **''strlen(cadena);'' ** devuelve la longitud de la cadena * **''strlwr(cadena);'' ** convierte los caracteres de cadena a minúsculas * **''strupr(cadena);'' ** convierte los caracteres de cadena a mayúsculas * **''atof(cadena);'' ** convierte la cadena a un decimal en doble precisión equivalente al representado por los dígitos que cadena contiene * **''atoi(cadena);'' ** convierte la cadena a un entero * **''atol(cadena);'' ** convierte la cadena a un entero largo * **''fcvt();'' ** convierte un número real a una cadena de caracteres. == Vectores de cadenas de caracteres == Un **vector de cadenas** es un vector bidimensional en el que el índice izquierdo señala el número de cadena y el índice derecho la longitud máxima de las cadenas char nombrearray[numcad][longcad]; * Ejemplo: char frases[3][80]= {"Error de lectura", "Error de escritura", "Error de acceso" }; /* frases[0] representa la cadena "Error de lectura" y puede mostrarse en pantalla escribiendo puts(frases[0]); */ /* La longitud de 80 caracteres nos asegura que quepan todas las frases, aunque en algunas se desaproveche la memoria */ ==== Datos definibles ==== === Estructuras === Una estructura es un tipo de variable especial que permite __almacenar datos de tipos diferentes con un identificador común__ * Las variables que forman parte de la estructura se llaman **elementos** de la estructura * **Definir una estructura** consiste en crear el tipo de estructura: struct nombretipoestructura { tipodato1 nombrelemento1; tipodato2 nombrelemento2; ... tipodatoN nombrelementoN; }; * La definición de un tipo de estructura no crea ninguna variable ni ocupa memoria * **Declarar una estructura** consiste en crear una variable de un tipo determinado de estructura: struct nombretipoestructura variablestructura; * Se pueden declarar variables de estructura en la misma sentencia que la declaración: struct nombretipoestructura { tipodato1 nombrelemento1; tipodato2 nombrelemento2; ... tipodatoN nombrelementoN; } listavariablestructura; * * ''nombretipoestructura'' es el identificador del tipo de estructura que se está definiendo * ''tipodatoX'' representa al diferente tipo de dato de cada elemento * ''nombrelementoX'' son los identificadores propios de los elementos de la estructura * ''listavariablestructura'' representa al identificador (o una lista de estos) de la variable de estructura que se va a crear * La definición de las estructuras se suele hacer fuera de la función ''main()'' en los archivos cabecera ''.h'' * Los elementos de una estructura se almacenan en posiciones consecutivas de memoria (¡no del todo cierto!) * La cantidad de memoria ocupada por una variable estructura (más o menos las suma de la ocupada por sus elementos), puede obtenerse con el operador ''sizeof()'' * Ejemplos: struct Militar /* Tipo de estructura */ { char nombre[40]; char apellidos[80]; unsigned edad; float estatura; unsigned long telefono; } cabo, sargento, teniente; struct Militar capitan; /* cabo, sargento, teniente y */ /* capitan son variables */ /* estructuras del tipo militar */ * Salvo la copia de una variable estructura en otra del mismo tipo (mediante el operador de asignación) **no se pueden realizar operaciones con estructuras**, deben realizarse con sus elementos y por separado * La referencia a un elemento de una estructura se hace mediante las etiquetas de la estructura y del elemento unidas por el operador punto «''.''» variablestructura.nombrelemento * * ''variablestructura'' es el nombre de la variable de estructura en la que se encuentra el elemento que se quiere referenciar * ''nombrelemento'' es el nombre del elemento de la estructura * El operador punto «''.''» une los dos identificadores en uno solo * En caso de anidación de estructuras, el operador punto aparecerá entre identificadores sucesivos * La dirección en memoria de un elemento de una estructura se obtiene aplicando el operador de dirección a la referencia a ese elemento &variablestructura.nombrelemento * Una variable de estructura se asimila a una ficha de una base de datos; los elementos de la estructura son los campos de la ficha. * Ejemplos: /* Inicialización de algunos campos de la variable cabo de tipo estructura militar */ gets(cabo.nombre); cabo.telefono = 916830106; scanf("%f", &cabo.estatura); === Uniones === Constituyen una porción de memoria compartida por variables de diferentes tipos * Es una forma de interpretar los mismos datos de diferente manera * Se //definen// y //declaran//**igual**** que las estructuras** cambiando la palabra reservada struct por union * La referencia a los elementos de una unión se hace con el operador punto «''.''», del mismo modo que en las estructuras * Definición de un tipo unión: union nombretipounion { tipodato1 nombrelemento1; tipodato2 nombrelemento2; ... tipodatoN nombrelementoN; }; * Creación de una variable unión de un tipo previamente definido: union nombretipounion variableunion; * La cantidad de memoria necesaria para almacenar una unión es la misma que la que ocupa el elemento de mayor tamaño * Es responsabilidad del programador conocer el dato almacenado en la variable de tipo unión * Ejemplos: union Talla { int numero; /* 38, 40, 42 */ char letra; /* P, M, G */ char letras[4]; /* L, XL, XXL */ } camiseta, camisa, jersey; camiseta.numero = 44; scanf("%c",&camiseta.letra); gets(camiseta.letras); /* Primero se almacena el entero 44, después se almacena la letra leída con scanf() y al final se guarda una cadena de hasta 3 caracteres (más el nulo). Los anteriores se pierden */ /* Podría utilizarse para guardar la talla en un formato diferente en cada variable */ === Campos de bits === Un campo de bits es un tipo especial de elemento de una estructura en el que se puede definir su tamaño en bits * Definición de un campo de bits tipodato nombrecampo:longitud; * * ''tipodato'' es el tipo del dato, que sólo puede ser entero (''int'', ''signed'', ''unsigned'', ''char'', ''short'', ''long'', …) * ''nombrecampo'' representa el nombre del elemento que va a ser un campo de bits * ''longitud'' representa a un entero positivo que indica el número de bits de ese campo * En una estructura se pueden declarar elementos ordinarios o campos de bits, indistintamente * Restricciones en los campos de bits: * Su almacenamiento en memoria, depende de la máquina y del compilador * No se puede obtener la dirección en memoria de un campo de bits * Su tamaño no debe exceder del tamaño de un entero * Ejemplo: struct Campobit{ int entero; unsigned sietebits:7; char letra } trescampos; trescampos.sietebits = dato; * Los campos de bits: * Facilitan las operaciones a nivel de bits * Facilitan el almacenamiento de variables lógicas (tipo boolean) * Aumenta el número de operaciones de la CPU * No suponen un ahorro de memoria importante === Definición de tipos con typedef === La expresión ''typedef'' permite dar un nombre particular a cualquier tipo de dato válido * Sintaxis: typedef tipodatovalido nuevonombre; * * ''tipodatovalido'' representa un tipo de dato válido * ''nuevonombre'' es el nuevo identificador para ese tipo de dato * Ejemplo: typedef struct Militar { ... } Midato; Midato soldado, cabo, sargento; ==== Punteros ==== Un **puntero** es una //variable// que contiene la //dirección de memoria// de otra variable * Por eso se dice que un puntero //apunta// a una variable * Se trata de una //indirección//: se puede acceder a la variable //indirectamente// Los punteros constituyen una importante herramienta en lenguaje C * Proporcionan acceso rápido y eficiente a los vectores * Facilitan el trabajo con listas enlazadas * Facilitan el intercambio de información entre funciones * Son imprescindibles para: * Asignar memoria dinámicamente * El trabajo con archivos * En el manejo de los punteros hay que extremar las precauciones para evitar errores de muy difícil localización * Ejemplo: | Memoria de datos ||| ^ Etiqueta ^ Dirección ^ Contenido ^ |dato1 | 0x00000F00 | 1234| |dato2 | 0x00000F04 | 6789| |puntdat1| 0x00000F08 | 4321| |puntdat2| 0x00000F0C | 7500| === Declaración e inicialización === * La **declaración** de una variable puntero asigna la memoria necesaria para almacenar una dirección: tipodato *nombrepuntero; * * ''tipodato'' es el tipo de dato de la variable a la que apuntará el puntero. * ''nombrepuntero'' es la etiqueta de la zona de memoria que contendrá la dirección de una variable * ''*nombrepuntero'' hace referencia al contenido de la variable apuntada por el puntero * No se crea ni se reserva memoria para la variable a la que va a apuntar * El tamaño de la memoria necesaria para almacenar una dirección es siempre el mismo, independientemente del tipo de dato al que corresponda esa dirección * La **Inicialización** de un puntero consiste en hacerle apuntar a una variable válida * La variable debe existir antes de la inicialización * Que la variable exista no significa que deba contener un dato válido tipodato *punt; /* Declaración del puntero*/ tipodato var; /* Declaración de la variable */ punt = &var; /* Inicialización del puntero */ /* La variable var no contiene todavía ningún dato válido */ * Ejemplos: float *punt; /* Puntero */ float var; /* Variable */ /* La variable y el puntero deben ser el mismo tipo de dato */ punt = &var; /* Inicialización del puntero */ *punt = 7.98; /* Inicialización de la variable */ /* Equivalente a lo anterior: var = 7.98; */ * * Una declaración de la forma ''int *punt=25;'' hace que el puntero apunte a la dirección ''25'' (y no sabemos qué contiene) == Operadores de memoria == Los **operadores de punteros** son «''*''» y «''&''», se asocian //de derecha a izquierda// y tienen //mayor precedencia// que todas las operaciones aritméticas y lógicas * Operador de **dirección** «''&''» * Aplicado a un //identificador// (a su derecha) obtiene la //**dirección** de memoria// de su operando o variable correspondiente * Sólo puede aplicarse a identificadores de variables y elementos de vectores, no es válida sobre expresiones * Operador de **indirección** «''*''» * Cuando precede a un //identificador// convierte al identificador en una dirección de memoria y el conjunto ''*identificador'' hace referencia al //**dato**// contenido por la dirección ''identificador'' * Sólo tiene sentido sobre variables que contienen una dirección de memoria válida (punteros) * Aplicado a una variable puntero permite utilizar esa expresión como una variable cualquiera, sin limitaciones * Operador/funcion «''sizeof''» * Aplicado a un operando nos devuelve el número de bytes que el operando ocupa en memoria === Operaciones === * Las **operaciones con punteros** son operaciones con //direcciones de memoria// * Sólo tienen sentido la suma, la resta, incrementos y decrementos * Operaciones de **asignación** * Es posible asignar el contenido de un puntero a otro puntero * Lo que se consigue es hacer que ambos punteros apunten a la misma posición de memoria * Deben ser del mismo tipo * Ejemplo: int dato, *punt1, *punt2; /* Declaraciones */ punt1 = &dato; /* Inicialización de punt1 */ punt2 = punt1; /* asignación del contenido de punt1 a punt2. Ahora punt2 apunta a la variable dato */ == Aritmética con punteros == * La **suma** de un número entero //n// a un puntero hace que este apunte al //n-ésimo// elemento del mismo tipo a partir del originalmente apuntado. * La **resta** de un número entero //n// a un puntero hace que este apunte al elemento //n-veces// anterior al que apuntaba * Independientemente de que el dato al que se traslada el puntero exista o no. * Los **incrementos** o **decrementos** de un puntero hacen que este apunte al elemento siguiente o anterior del mismo tipo. * En valor numérico absoluto, el incremento o decremento de un puntero cambia su valor un número de unidades que depende del tipo de dato al que apunta * La aritmética de punteros sólo coincide con la aritmética ordinaria cuando se opera con punteros que apunten a elementos de tipo byte * Son posibles las **comparaciones** y las **operaciones lógicas** con punteros siempre que se realicen entre punteros del mismo tipo de dato == Tipos de punteros == * Puntero **genérico** es aquel que no apunta a ningún tipo de dato void *nombrepuntero; * * Se declaran así para que posteriormente se les pueda hacer __apuntar a cualquier tipo de dato__ * Puntero **nulo** es aquel que apunta a la dirección ''NULL'' (= 0) tipodato *nombrepuntero = NULL; * * ''NULL'' es una constante definida en ''stdio.h'' * Se utiliza porque la dirección ''0'' nunca es válida * Puntero **constante** es aquel que se declara como tal y, por tanto, __siempre apunta a la misma posición__ tipodato *const nombrepuntero; * * El contenido, el dato apuntado, si puede cambiar * Si es __el dato__ lo que se declara como constante se escribe: const tipodato *nombrepuntero; * * Si __el puntero y el dato__ al que apunta se declaran constantes: const tipodato *const nombrepuntero; === Relación entre punteros y vectores === En general, todo lo que puede hacerse con vectores puede hacerse también con punteros, si bien, las versiones con punteros son más rápidas y más utilizadas * El identificador de un **vector** (sin índice) es un puntero constante al primer elemento del vector * En un vector de //N// elementos, se puede acceder al elemento //M// (tal que //0≤M /* Mediante vectores: */ elementoM = nombrearray[M]; /* Mediante punteros: */ elementoM = *(nombrearray+M); * Un **puntero a un vector de caracteres** (o **cadena**) es un puntero al carácter primero de la cadena * Se puede inicializar en la declaración: char *nombrepuntero = "cadena"; * * ''nombrepuntero'' es un puntero a carácter: contiene la dirección del primer elemento de la cadena * Si en el transcurso del programa, se le hace apuntar a otro sitio, ya no será posible volver a apuntar a la posición original * ''cadena'' es una cadena constante que se almacena en una tabla de constantes. Finaliza con el carácter nulo '''0''' * Cuando a una función se le pasa como argumento una constante de cadena (cadena de caracteres entre comillas dobles), en realidad se le pasa un puntero al primer elemento de esa cadena char *mensaje = "Error de lectura"; puts(mensaje); Un **vector de punteros** contiene elementos que son direcciones a elementos un tipo determinado * Se declara del siguiente modo: tipodato *nombrevariable[tamaño]; * * ''tipodato'' es el tipo de datos de los elementos que serán apuntados * ''nombrevariable'' es el nombre del vector de punteros * ''tamaño'' indica el número de punteros que contendrá el vector * El vector de punteros contiene elementos que son direcciones a elementos de tipo tipodato * Cada dirección apuntará a un dato del tipo declarado * Es preciso inicializar todos los punteros del vector (hacerles apuntar a un tipo de dato válido) * Un vector de punteros a carácter es similar a un vector bidimensional o a un vector de cadenas {{:pub:c_data1.png?500}} * Ejemplo de un vector bidimensional de caracteres: char mens[3][80] = {"Inicial", "Central", "Último"}; /* Vector de 3 cadenas. Se reserva más memoria de la necesaria para que quepan todos los mensajes */ puts(mens[1]); /* Saca el mensaje "Central" */ * Ejemplo de vector de punteros a carácter: char *mens[3]; /* Vector de 3 punteros a carácter */ mens[0]= "Inicial"; /* Inicialización de punteros */ mens[1]= "Central"; mens[2]= "Último"; puts(mens[1]); /* Saca el mensaje "Central" */ === Puntero a un puntero === Un **puntero a puntero** representa una //indirección múltiple//: el primer puntero contiene la dirección del segundo puntero, el cual apunta al dato * Se declara del siguiente modo: tipodato **nombrepuntero; * * ''nombrepuntero'' contiene la dirección de ''*nombrepuntero'' que, a su vez, contiene la dirección de ''**nombrepuntero'' * '' **nombrepuntero'' es el identificador del dato * Un puntero a puntero es //equivalente// al nombre de un vector bidimensional * Si ''datos[M][N]'' es un vector bidimensional, el elemento ''datos[i][j]'' puede accederse también mediante la aritmética de punteros: ''*(*(datos+i)+j)'' __false__ === Declaraciones complejas === La combinación de: * El operador puntero a «''*''» * Los corchetes indicadores de vector «''[]''» * Los paréntesis «''()''» que agrupan operaciones o los indicadores de función dan lugar a declaraciones complejas y difíciles de descifrar Para **interpretar correctamente** las declaraciones: - Se debe comenzar con el **identificador**, mirando a su __derecha__ * Los **paréntesis** indicarán que es una __función__ * Los **corchetes** indicarán que es un __vector__ - Mirar si a la __izquierda__ hay un **asterisco**, lo que indicará que es un __puntero__ - Aplicar las reglas anteriores a __cada nivel de paréntesis__ y __de dentro hacia fuera__ * Ejemplos int (*lista)[20]; /* lista es puntero a vector de 20 enteros */ char *datos[20]; /* datos es vector de 20 punteros a carácter */ void (*busc)(); /* busc es puntero a función que no devuelve nada */ char (*(*Func())[])(); /* Func es función que devuelve un puntero a vector de punteros a funciones que devuelven un carácter */ int (*(*tim[5])())[3]; /* tim es vector de 5 punteros a funciones que devuelven cada una un puntero a un vector de 3 enteros */ ==== Asignación dinámica de memoria ==== En la declaración de variables, el compilador de C reserva memoria para cada variable: * Si son **globales**, en la __zona de datos__ asociada al propio programa * Si son **locales**, en la __zona de pila__ (//stack//) La **asignación dinámica de memoria** consiste en la reserva de memoria para los datos __en tiempo de ejecución__, es decir, tomándola de la que el sistema tiene libre en ese momento. Un //programa en ejecución// es un **proceso activo** que tiene a su disposición: * El **tiempo** de utilización del procesador * La **memoria** que le asignó el sistema operativo: * Segmento de código * Segmento de datos (variables globales) * Segmento de pila * Variables locales * Direcciones de regreso de las llamadas a funciones {{ :pub:c_data2.png?300 }} === Asignación y liberación === * En //tiempo de ejecución// es posible **pedir memoria** al sistema operativo: void *malloc(unsigned tamanio); * * ''tamanio'' es un entero sin signo que indica el número de bytes que se solicitan al sistema operativo * La función devuelve un puntero genérico que apunta a la dirección de comienzo del bloque de memoria asignado. En caso de error devuelve un puntero nulo (''NULL'') * * En el prototipo se indica que devuelve un puntero de tipo ''void'' que puede apuntar a cualquier tipo válido * Está declarada en ''stdlib.h'' * Tras utilizar la memoria asignada dinámicamente, hay que **devolverla** al sistema operativo, //liberándola// de nuestro programa: void free(void *nombrepuntero); * * ''nombrepuntero'' es el identificador del puntero (de cualquier tipo) que apunta al bloque de memoria que se desea liberar * La función no devuelve nada * Está declarada en ''stdlib.h'' * Ejemplo de asignación dinámica de memoria para un entero y liberación posterior: int *dato; /* Puntero a int */ dato = (int *)malloc(sizeof(int)); if (dato==NULL) printf("Error en la asignación"); /* ... Operaciones de utilización de la memoria asignada ... */ free(dato); /* Liberación de la memoria asignada dinámicamente */ === Vectores dinámicos === Los **vectores dinámicos** son aquellos cuyo tamaño se determina en un proceso de asignación dinámica de memoria * **Vectores dinámicos unidimensionales**: void * calloc(numelementos, tamañoelemento); * * Devuelve un puntero convertible a cualquier tipo de dato que apunta a la primera posición del bloque de memoria asignado (o puntero nulo en caso de error) * ''numelementos'' es un entero sin signo que representa el número de elementos del vector * ''tamañoelemento'' es un entero sin signo que representa el tamaño de un elemento del vector * Está declarada en ''stdlib.h'' * Ejemplo de reserva de memoria para un vector de 10 elementos de tipo ''double'': punt = (double*)calloc(10, sizeof(double)); * **Vectores dinámicos bidimensionales**: - Debemos crear un puntero a puntero al tipo de datos del vector bidimensional - Debe asignarse dinámicamente un vector de punteros - Debe asignarse dinámicamente un vector para datos a cada uno de los punteros del vector anterior - Tras su utilización, debe liberarse la memoria en orden inverso al orden en el que fue asignada: * Primero, mediante un bucle, se libera cada uno de los vectores unidimensionales del tipo de dato utilizado * Después, mediante otro bucle, se libera el vector de punteros {{ :pub:c_data3.png?600 }} * Ejemplo de asignación dinámica de memoria para un vector unidimensional de N enteros y liberación posterior: int *lista; /* Puntero a int */ lista = (int *)calloc(N, sizeof(int)); if (lista==NULL) printf("Error en la asignación"); /* ... Operaciones de utilización de la memoria asignada ... */ free(lista); /* Liberación de la memoria asignada dinámicamente */ * Ejemplo de reserva dinámica de memoria para un vector bidimensional de números reales (matriz de ''NFIL'' filas y ''NCOL'' columnas) y su liberación posterior: float **punt; /* Puntero a puntero a float */ int i,j; punt = (float **)calloc(NFIL , sizeof(float *)) /* Vector de NFIL punteros a float */ for (i=0 ; i === Reasignación de bloques de memoria === Es posible reasignar un bloque de memoria previamente asignado dinámicamente (para redimensionar ese bloque) void * realloc(void *puntbloc, numbytes); * * ''puntbloc'' es un puntero que apunta al bloque de memoria que se quiere reasignar * ''numbytes'' es un número entero sin signo que indica el nuevo tamaño en bytes que deberá tener el bloque de memoria * Devuelve un puntero genérico al bloque de memoria asignado, cuya dirección de comienzo puede ser diferente de la dirección inicial. En caso de error devuelve un puntero nulo (''NULL'') * No se pierden los datos almacenados en el bloque de partida, se trasladan su ubicación * La función está definida en ''stdlib.h'' ---- \\