Tipos de datos:
Estructuras de control:
Son tanto validaciones como bucles, son órdenes que dictan la ejecución del programa.
-
if: Condición simple para ejecutar un bloque de código en caso de que se cumpla.
Si hay una sola instrucción dentro, la llave es opcional.
if(condición){}
-
while: Bucle que repite el bloque de código indefinidamente mientras la condición se siga cumpliendo
while(condición){}
-
do while: Como el while, pero ignora la condición en su primera ejecución
do{}while(condición);
-
for: Bucle iterativo en el que se establece un inicio, un fin (tanto por iterador como por otras condiciones), y un incremento de iterador(variable que hace que el bloque vaya avanzando)
Tanto el inicio como el incremento son opcionales, y si pones dentro una sola instrución las llaves son opcionales
for(iterador=0;iterador<=10;i++){}
-
switch: Orden que actúa como un conjunto de ifs. Según el valor de una variable, se activa uno de sus múltiples casos, delimitados por break
switch(variable){ case 1: printf("meow"); break; case 2: printf(":D"); break; }
Funciones:
Las funciones son conjuntos de instrucciones, sirven para organizar el programa y ahorrar líneas de código. Con definir una vez la función, puedes llamarla cuanto necesites sin escribir de nuevo todas las instrucciones que contiene. Siempre acaban con punto y coma.
int i=2; printf("Numero: %d",i);
Aquí, definimos el entero i con valor 2 y a continuación está la función printf();, la cual
visualiza lo que se ponga dentro de sus paréntesis. Es parte de la biblioteca estándar stdio.h, por
lo que no se puede ver su definición.
Para incluir una variable hay que escribir su especificador de formato, que consiste en un porcentaje seguido del tipo de variable que es:
%d es para numeros enteros con signo por ejemplo.
%c es para caracteres.
%s es para cadenas de caracteres. etc etc...
Y después de las comillas, una coma y seguirlo, en orden (si hay múltiples variables), de las variables
que se van a visualizar. Abajo un ejemplo de una función personalizada:
#include <stdio.h> void mostrarMensaje() { printf("Buenas tardes"); } main(){ mostrarMensaje(); }
Declaramos la función de tipo void, que significa que no devuelve nada. Las funciones
también tienen tipos como las variables según lo que devuelvan. Le sigue el nombre de la función
sin espacios y siguiendo la nomenclatura típica: la primera letra es siempre minúscula y solo se
pone mayúscula para indicar otra palabra.
Luego van los parámetros, y por último las llaves que delimitan el bloque de código.
Es una buena práctica declarar el prototipo de función, que define su tipo, retorno y parámetros
Parámetros:
Los parámetros son las variables externas que va a necesitar la función para ejecutarse. No todas las funciones los necesitan, como en la anterior, ya que o tienen sus propias variables, o no tienen ninguna. Abajo un ejemplo:
#include <stdio.h> // Variables globales int vg1, vg2, vg3; // Prototipo de funcion int comparacion(int vl1, int vl2, int vl3); int main() { printf("Primer numero: "); scanf("%d", &vg1); printf("Segundo numero: "); scanf("%d", &vg2); printf("Tercer numero: "); scanf("%d", &vg3); int max = comparacion(vg1, vg2, vg3); printf("El valor mas grande es: %d\n", max); return 0; } int comparacion(int vl1, int vl2, int vl3) { if (vl1 >= vl2 && vl1 >= vl3) return vl1; if (vl2 >= vl1 && vl2 >= vl3) return vl2; return vl3; }
En este programa, comparación es una función para comparar los valores que utiliza parámetros.
Los nombres de los variables de la función no tienen que ser los mismos que los del resto del programa.
A la hora de llamar la función, los parámetros tienen que ser las variables ajenas, es decir, las que están
fuera de la función. Siempre tienen que tener el tipo de dato, el nombre y separación por comas.
Arrays:
Un array es una estructura interna de datos con un conjunto de elementos homogéneos, del mismo tipo, numérico o alfanumérico, reconocidos por un nombre en común que residen en la memoria del ordenador. A cada elemento se accede por la posición que ocupa dentro del conjunto de datos. Hay dos tipos:
Unidimensionales
Los arrays unidimensionales son arrays con una única lista de valores. Abajo un ejemplo:
#include <stdio.h> //variables globales int t1[5], t2[5], t3[10], i, k=0; main(){ printf("De dos tablas saldra una tercera con los valores de las dos previas intercaladas\n"); printf("\nTabla 1: \n"); for (i=0; i<=4; i++){ scanf("%d", &t1[i]); } printf("\nTabla 2: \n"); for (i=0; i<=4; i++){ scanf("%d", &t2[i]); } printf("\nTabla 3: "); for (i=0; i<=9; i=i+2){ t3[i]=t1[k]; t3[i+1]=t2[k]; k=k+1; printf("%d ", t3[i]); printf("%d ", t3[i+1]); } }
Para declarar un array le ponemos los corchetes y dentro indicamos el número de elementos
del array. Poniéndolos así todos están vacíos, porque así es este programa. Para insertar
valores hay que recorrer el array con un bucle, preferiblemente for, yendo índice a índice
empezando por 0 (en la mayoría de lenguajes se empieza por 0) hasta el fin del array.
También se pueden insertar valores al inicio, así:
int array[]{1,2,3,4,5};
Debido a que estás directamente indicando el valor de los elementos, no hace falta escribir cuantos elementos tiene el array, en los corchetes.
Bidimensionales
Los arrays bidimensionales son arrays con múltiples listas de valores. Abajo un ejemplo:
#include <stdio.h> //variables globales int t[3][3], k, i, j; main(){ printf("Ejercicio Array bidimensional con valor constante, introduzcalo\n\n"); printf("Valor: "); scanf("%d", &k); for (i=0; i<=2; i++){ for(j=0; j<=2; j++){ t[i][j]=k; } printf("\n"); } for (i=0; i<=2; i++){ for(j=0; j<=2; j++){ printf("%d ", t[i][j]); } printf("\n"); } }
Para declarar un bidimensional, hay que poner dos corchetes más indicando el número de filas de listas que tendrá la variable, que en este caso se llama t. Como hay más de una lista, hay que hacer dos bucles para recorrer variable a variable, lista por lista.
Arrays dinámicos
Los arrays dinámicos son arrays los cuales no tienen un tamaño fijo asignado, tienen un máximo, pero no tienen siempre que usarlo.
int *matriz = malloc(filas * columnas * sizeof(int)); //Ejemplo de como sería en uno bidimensional
Métodos de ordenación
Los métodos de ordenación son algoritmos diseñados para ordenar los datos de un array. Hay varios tipos:
- Método Burbuja:
El método burbuja consiste en llevar el valor máximo a la última posición.
#include <stdio.h> void burbuja(int arr[], int n) { int i, j, temp; for(i = 0; i < n - 1; i++) { // número de pasadas for(j = 0; j < n - i - 1; j++) { // elementos a comparar if(arr[j] > arr[j + 1]) { // Intercambio si están en orden incorrecto temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } void visualizar(int arr[], int n) { for(int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int datos[] = {5, 2, 9, 1, 5, 6}; int n = sizeof(datos)/sizeof(datos[0]); printf("Antes de ordenar:\n"); visualizar(datos, n); burbuja(datos, n); printf("Después de ordenar:\n"); visualizar(datos, n); return 0; }
- Método Switch:
Es una variante del método burbuja pero con la diferencia de que cuenta con un interruptor que
detecta si la lista está o no está ordenada, y en caso de no estarlo, resetea la búsqueda
y vuelve a comprobar valores desde el principio.
#include <stdio.h> #define N 6 //constante N como 6 int lista[N] = {4, 3, 6, 9, 0, 1}, sw, i, j, aux; int main() { // Imprimir los elementos de la lista antes de ordenar for (i = 0; i < N; i++) { printf("%d ", lista[i]); } printf("\n"); sw = 1; while (sw) { sw = 0; for (i = 0; i < N - 1; i++) { if (lista[i] > lista[i + 1]) { aux = lista[i]; lista[i] = lista[i + 1]; lista[i + 1] = aux; sw = 1; } } } // Imprimir los elementos de la lista tras ordenar for (i = 0; i < N; i++) { printf("%d ", lista[i]); } printf("\n"); return 0; }
- Método Burbuja-Switch:
Combinación del método burbuja y el switch. No hace comparaciones innecesarias hasta el final o si
ya había una sección ordenada. Abajo un ejemplo de burbuja-switch con introducción automática con función random,
arrays dinámicos y funciones personalizadas:
#include <stdio.h> #include <stdlib.h> #include <time.h> //Variables globales int tam, *cachuela;//cachuela es el array dinamico //Prototipos de funcion void creacion_array(); void ordenacion(); void visualizacion(); int main(){ printf(" ARRAY DINAMICO ORDENADO CON BURBUJA-SWITCH\n\n"); creacion_array(); printf("\nDESORDENADO\n"); visualizacion(); printf("\n\n"); ordenacion(); printf("ORDENADO\n"); visualizacion(); free(cachuela); return 0; } void creacion_array(){ printf("Introduzca el tamanio del array: "); scanf("%d", &tam); srand(time(NULL)); cachuela=(int*)malloc(tam*sizeof(int)); for(int i=0;i<=tam;i++) cachuela[i] = rand()%(9 - 0 + 1); } void visualizacion(){ printf("\n"); for(int i=0;i<=tam;i++){ printf("cachuela[%d]: %d ",i,cachuela[i]); printf("\n"); } } void ordenacion(){ int sw=1; for(int i=0;i<=tam && sw!=0;i++){ sw=0; for(int j=0;j<=tam-i;j++){ if(cachuela[j]>cachuela[j+1]){ int aux=cachuela[j]; cachuela[j]=cachuela[j+1]; cachuela[j+1]=aux; sw=1; } } } }
- Por el Mínimo:
Consiste en llevar el valor más pequeño al principio.
#include <stdio.h> //Variables globales int t[10], aux, i, j, posicionminima; int main() { printf("Metodo de ordenacion por el minimo PASO A PASO\n"); for(i=0; i<10; i++) { printf("t[%d] = ",i); scanf("%d",&t[i]); } for(i=0;i<9;i++) { posicionminima=i; printf("Se empieza en t[%d] = %d",i,t[i]); printf("\n"); for(j=i+1;j<10;j++) { printf("t[%d] = %d",j, t[j]); printf("\n"); if(t[j]<t[posicionminima]) { posicionminima=j; printf("Ahora el minimo es %d en la posicion t[%d]",t[j], posicionminima); printf("\n"); } } aux=t[posicionminima]; t[posicionminima]=t[i]; t[i]=aux; printf("\n"); printf("Ahora el valor de t[%d] se intercambia con [%d] y queda que t[%d] es %d\n",posicionminima,i,i,t[i]); } for(i=0;i<10;i++) { printf("t[%d] = %d",i, t[i]); printf("\n"); } return 0; }
- Por inserción:
Compara los elementos más recientes con los anteriores, y si es menor
lo inserta en la posición anterior hasta llegar al final del array.
#include <stdio.h> void insercion(int arr[], int n) { int i, key, j; for (i = 1; i < n; i++) { key = arr[i]; // Elemento a insertar j = i - 1; // Mover los elementos mayores que 'key' una posición adelante while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; j--; } // Insertar 'key' en la posición correcta arr[j + 1] = key; } } void visualizacion(int arr[], int n) { for (int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); } int main() { int datos[] = {9, 5, 1, 4, 3}; int n = sizeof(datos) / sizeof(datos[0]); printf("array original: "); visualizacion(datos, n); insercion(datos, n); printf("array ordenado: "); visualizacion(datos, n); return 0; }
- Método Shell:
El método shell es una versión mejorada del algoritmo de ordenación por inserción.
Su objetivo es ordenar más rápidamente listas grandes, comparando y ordenando elementos
que están más separados entre sí, y reduciendo ese espacio gradualmente hasta que estén uno al lado del otro.
#include <stdio.h> void shell(int arr[], int n) { // Comenzamos con una gran separación y la vamos reduciendo for (int gap = n / 2; gap > 0; gap /= 2) { // Hacemos una especie de insertion sort para cada grupo for (int i = gap; i < n; i++) { int temp = arr[i]; int j; // Desplazamos los elementos mayores que temp for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) { arr[j] = arr[j - gap]; } // Colocamos temp en su lugar correcto arr[j] = temp; } } } void visualizar(int arr[], int n) { for (int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); } int main() { int datos[] = {23, 12, 1, 8, 34, 54, 2, 3}; int n = sizeof(datos) / sizeof(datos[0]); printf("Arreglo original:\n"); visualizar(datos, n); shell(datos, n); printf("Arreglo ordenado con Shell Sort:\n"); visualizar(datos, n); return 0; }
- Por mezclas:
Consiste en ordenar los valores de dos listas en una tercera, ordenada.
#include <stdio.h> // Función para fusionar dos mitades ordenadas void merge(int arr[], int izquierda, int medio, int derecha) { int n1 = medio - izquierda + 1; int n2 = derecha - medio; int L[n1], R[n2]; // Copiar datos a arrays temporales for (int i = 0; i < n1; i++) L[i] = arr[izquierda + i]; for (int j = 0; j < n2; j++) R[j] = arr[medio + 1 + j]; // Mezclar los arrays temporales int i = 0, j = 0, k = izquierda; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k++] = L[i++]; } else { arr[k++] = R[j++]; } } // Copiar los elementos restantes while (i < n1) arr[k++] = L[i++]; while (j < n2) arr[k++] = R[j++]; } // Función principal de Merge Sort void mergeSort(int arr[], int izquierda, int derecha) { if (izquierda < derecha) { int medio = izquierda + (derecha - izquierda) / 2; // Ordenar primera y segunda mitad mergeSort(arr, izquierda, medio); mergeSort(arr, medio + 1, derecha); // Mezclar las mitades ordenadas merge(arr, izquierda, medio, derecha); } } // Imprimir array void imprimirArray(int arr[], int size) { for (int i = 0; i < size; i++) printf("%d ", arr[i]); printf("\n"); } // Programa principal int main() { int datos[] = {12, 11, 13, 5, 6, 7}; int n = sizeof(datos) / sizeof(datos[0]); printf("Array original:\n"); imprimirArray(datos, n); mergeSort(datos, 0, n - 1); printf("Array ordenado con Merge Sort:\n"); imprimirArray(datos, n); return 0; }
Métodos de búsqueda
Los métodos de búsqueda son algoritmos diseñados para encontrar datos concretos en un array.
- Búsqueda secuencial o lineal:
Consiste en buscar el valor de una sola pasada desde el principio hasta el final.
#include <stdio.h> int busquedaSecuencial(int arr[], int n, int clave) { for (int i = 0; i < n; i++) { if (arr[i] == clave) { return i; // Devuelve el índice donde se encontró } } return -1; // No se encontró } int main() { int datos[] = {4, 8, 15, 16, 23, 42}; int clave = 23; int n = sizeof(datos) / sizeof(datos[0]); int resultado = busquedaSecuencial(datos, n, clave); if (resultado != -1) printf("Elemento encontrado en la posición: %d\n", resultado); else printf("Elemento no encontrado\n"); return 0; }
- Búsqueda dicotómica o binaria:
Suponiendo que el array ya está ordenado, busca a partir de la mitad en donde está el valor y descarta la otra.
#include <stdio.h> int busquedaBinaria(int a[], int dato, int inicio, int fin); #define consdato 5 //constante del dato que queremos buscar int main() { int a[] = {1,2,3,4,5,6,7,8,9,10}; int tam = sizeof(a)/sizeof(a[0]); int pos = busquedaBinaria(a,consdato,0,tam-1); if(pos != -1){ printf("El dato se encuentra en la posicion %d \n",pos); }else{ printf("El dato no se encuentra en el arreglo\n"); } return 0; } int busquedaBinaria(int a[], int dato, int inicio, int fin){ int mid; while(inicio <= fin){ mid = (inicio + fin) / 2; if(a[mid] == dato){ return mid+1; }else if(dato < a[mid]){ fin = mid -1; inicio = mid + 1; } } }
Cadenas de caracteres:
Las cadenas de caracteres son cadenas de caracteres
- Para escritura:
scanf. Permite introducir caracteres no separados por espacios. Sintaxis: scanf(“%s”, &cadena); Librería: #include<stdio.h>
[^\n]. Permite introducir caracteres (también espacios) hasta que se pulse “enter”. Sintaxis: scanf(“%[^\n]”,cadena); Librería: #include<stdio.h>
gets. Permite introducir caracteres con espacios. Sintaxis: char *gets(char *cadena); Librería: #include<stdio.h> Ejemplo gets(nombre);
- Para lectura:
printf. Permite visualizar cadenas. Sintaxis: printf(“%s”, cadena); Librería: #include<stdio.h>
puts. Visualiza todos los caracteres introducido. Normalmente se utiliza junto al gets. Sintaxis: int puts(const char *s); Librería: #include<stdio.h>
- Para hacer una asignación:
strcpy: copia la cadena2, incluyendo el carácter de terminación nulo (\0), en la cadena1 y devuelve un puntero a cadena1. Sintaxis: char *strcpy(char *cadena2, const char *cadena1); Librería: #include<string.h>
- Para comparar dos cadenas:
strcmp: compara dos cadenas de caracteres lexicográficamente y devuelve un valor que puede ser: - > 0 si s1 > s2. - = 0 si s1= = s2. - < 0, si s1 < s2. Sintaxis: int strcmp(const char *s1, const char *s2); Librería: #include<string.h>
strcmpi: compara dos cadenas de caracteres lexicográficamente, sin diferenciar mayúsculas de minúsculas. Devuelve un valor que puede ser: - > 0 si s1 > s2. - = 0 si s1= = s2. - < 0, si s1 < s2. Sintaxis: int strcmpi(const char *s1, const char *s2); Librería: #include<string.h>
- Para leer un caracter:
getch: lee un carácter del teclado, sin visualizarlo en el monitor (sin eco Sintaxis: int getch(void); Librería: #include<conio.h>
getchar. Se utiliza para leer un carácter de entrada estándar (stdin). Cada vez que se ejecute esta función se leerá el siguiente carácter al último leído. Suponiendo que el buffer de entrada está limpio, cuando se ejecute la sentencia anterior, la ejecución del programa se detendrá hasta que introduzcamos un carácter y pulsemos la tecla “ENTER”. Esta sentencia equivale a – scanf(“%c”, &car); Sintaxis: int getchar(void); Librería: #include<stdio.h>
getche. Lee un carácter de teclado haciendo eco del carácter en la pantalla. Sintaxis: int getche(void); Librería: #include <conio.h>
- Para escribir un caracter:
putchar: Visualiza un carácter en la posición actual del cursor. Sintaxis: int putchar(void); #include <stdio.h>
- Para saber el tamaño:
strlen. Devuelve la longitud en bytes de cadena, no incluyendo el carácter de terminación nulo. El tipo size_t es sinónimo de unsigned int. Sintaxis: size_t strlen(char *cadena); Librería: #include<string.h>
- Para concatenar:
strcat. Añade una cadena en otra. Sintasis: char *strcat(char *dest, const char *src); Librería: #include <string.h>
Tipo de dato estructural:
La finalidad de una estructura es agrupara una o más variables generalmente de diferentes tipos, bajo un mismo nombre para hacer más fácil su manejo.
Crear Una Estructura
La sintaxis es la siguiente:
struct tipo_estructura
{
/*declaración de los miembros*/
};
Después de definir un tipo estructura, podemos declarar una variable de ese tipo así
struct tipo estructura [variable[, variable]...]
EJEMPLOS struct ficha { char nombre [40]; char dirección [40]; long telefono; }; struct ficha var1, var2; // Declaración en ANSI C.
Observemos que en la declaración var1 y var2 se ha especificado la palabra struct cuando parece lógico escribir:
ficha var1, var2; // Declaración en C++.
Esto no es posible en ANSI C, pero si es permitido en C++. No obstante utilizando typedef, podemos conseguir la forma de declaración anterior
struct ficha { char nombre [40]; char dirección [40]; long telefono; }; typedef struct ficha ficha2; ficha2 var1, var2;
La declaración typedef anterior declara un sinónimo ficha2 de struct ficha. Esto también puede hacerse de la forma siguiente:
struct ficha { char nombre [40]; char dirección [40]; long telefono; } ficha2; ficha2 var1, var2;
La definición de las estructuras var1 y var2, puede realizarse también justamente a continuación de la declaración del nuevo tipo, así:
struct ficha { char nombre [40]; char dirección [40]; long telefono; } var1, var2;
O también así, como se indica a continuación, sin dejar constancia del nuevo tipo declarado. Esta forma no es aconsejable, puesto que posteriormente no podríamos definir otras variables de este tipo.
struct{ char nombre [40]; char dirección [40]; long telefono; } var1, var2;
Esta es la forma menos aconsejada.
Miembros Que Son Estructuras.
Para declarar un miembro como una estructura, es necesario haber declarado
previamente este tipo estructuras.
EJEMPLO
struct fecha { int dia, mes, anyo; }; struct ficha { char nombre [40]; char dirección [40]; long telefono; struct fecha fecha-nacimiento; } ; ficha persona;
Para acceder al elemento anyo: persona.fecha-nacimiento.anyo.
Por último, si se utiliza la palabra reservada typedef, debemos utilizar el siguiente
formato:
typedef struct fic{ char nombre [40]; char dirección [40]; long telefono; struct fecha fecha-nacimiento; }ficha ;
Array De Estructura
Cuando los elementos de un array son de tipo estructura, el array recibe el nombre de array de estructuras o array de registros.
typedef struct ficha { char nombre [40]; char dirección [40]; long telefono; } ; ficha alumnos [40];
Recursividad:
Recursividad es el proceso por el que algo se define en términos de sí mismo. Cuando se aplica a lenguajes de computadora, recursividad significa que una función se puede llamar a sí misma. No todos los lenguajes de computadora soportan funciones recursivas, pero C lo hace. En este programa se muestra un ejemplo sencillo de recursividad:
#include <stdio.h> #include <conio.h> void recursiva(int i); void main(void) { recursiva(0); getch(); } void recursiva(int i) { if(i<3){ recursiva(i+1); printf("%d ", i); } }
Veamos este ejemplo sin recursividad quedaría:
#include <stdio.h> #include <conio.h> void rec1(int i); void rec2(int i); void rec3(int i); void main(void) { rec1(0); getch(); } void rec1(int i) { if(i<3){ rec2(i+1); printf("%d ", i); } } void rec2(int i) { if(i<3){ rec3(i+1); printf("%d ", i); } } void rec3(int i) { if(i<3){ printf("%d ", i); } }
Es posible tener un programa en el que dos o más funciones sean mutuamente recursivas. La recursividad mutua se produce cuando una función llama a otra, que a su vez llama a la primera. Por ejemplo, estudiemos este breve programa:
#include <stdio.h> #include <conio.h> void f2(int b); void f1(int a); void main(void) { f1(20); getch(); } void f1(int a) { if(a) f2(a-1); printf("%d ", a); } void f2(int b) { printf ("*"); if(b) f1(b-1); }
Este programa muestra en la pantalla ************** 0 2 4 6 8 10 12 14 16 18 20 El ejemplo más típico de recursividad es el cálculo de factorial.
#include <stdio.h> #include <conio.h> //Declaración de prototipos long factorial (long ); main() { long n, resultado; printf ("Introduzca un número: "); scanf ("%d", &n); resultado = factorial (n); printf ("El resultado del factorial es: %ld", resultado); getch(); } long factorial (long n) { if (n == 0) return 1; return n * factorial (n-1); }
Punteros:
Un puntero en C es una variable que almacena la dirección de memoria de otra variable.
En lugar de contener un valor directamente (como un int o char), un puntero contiene la ubicación
en memoria donde está almacenado ese valor, ya sea una variable singular, array o función.
Abajo una demostración de como se puede usar:
#include <stdio.h> // Función que se usará con puntero a función int multiplicar(int a, int b) { return a * b; } int main() { // --- 1. Puntero a variable --- int numero = 10; int *ptrNumero = № printf("Valor original de numero: %d\n", *ptrNumero); *ptrNumero = 25; printf("Valor modificado de numero: %d\n\n", numero); // --- 2. Puntero a array --- int array[] = {5, 10, 15, 20}; int *ptrArray = array; printf("Elementos del array usando puntero:\n"); for (int i = 0; i < 4; i++) { printf("array[%d] = %d\n", i, *(ptrArreglo + i)); } printf("\n"); // --- 3. Puntero a función --- int (*ptrFuncion)(int, int) = multiplicar; int resultado = ptrFuncion(6, 7); printf("Resultado de multiplicar(6, 7) usando puntero a función: %d\n", resultado); return 0; }
Ficheros:
1.- Introducción.
Muchas aplicaciones requieren escribir o leer información de un dispositivo de almacenamiento auxiliar. Tal información se almacena en el dispositivo de almacenamiento en la forma de un archivo de datos. Por tanto, los archivos de datos nos permiten almacenar información de modo permanente y acceder y alterar la misma cuando sea necesario.
En C existe un conjunto extenso de funciones de biblioteca para crear y procesar archivos de datos. A diferencia de otros lenguajes de programación, en C no se distingue entre archivos secuenciales y de acceso directo (acceso aleatorio). Pero existen dos tipos diferentes de archivos de datos, llamados archivos secuenciales de datos (o estándar) y archivos orientados al sistema (o de bajo nivel).
Generalmente es más fácil trabajar con archivos de datos secuenciales que con los orientados al sistema, y por tanto son los más usados. Los archivos de datos secuenciales se pueden dividir en dos categorías:
En la primera categoría están los archivos de texto, que contienen
caracteres consecutivos. Estos caracteres pueden interpretarse como datos individuales,
como componentes de una cadena de caracteres o como números.
La segunda categoría de archivos de datos secuenciales, a menudo
llamados archivos sin formato, organiza los datos en bloques de bytes contiguos de
información. Estos bloques representan estructuras de datos más complejas, como
formaciones o estructuras.
2.- Conceptos Iniciales.
sizeof. Da como resultado el tamaño en bytes de su operando. Ejemplo:
#include <stdio.h> main ( ) { int a=0, tam=0; tam = sizeof a; printf ("El tamaño es: %d", t); printf ("El tamño es: %d", sizeof (float)); }
Los paréntesis son opcionales, excepto cuando el operando se corresponde con un tipo de datos. sizeof se puede aplicar a cualquier objeto de un tipo fundamental o de un tipo definido por el usuario, excepto el tipo void, un array de dimensión no especificada, a un campo de bits o a una función. Asignación Dinámica De Memoria. La asignación dinámica de memoria consiste en asignar la cantidad de memoria necesaria para almacenar un objeto durante la ejecución del programa, en vez de hacerlo en el momento de la compilación del mismo. Cuando se asigna memoria para un objeto de un tipo cualquiera, se devuelve un puntero a la zona de memoria asignada. malloc. Permite asignar un bloque de memoria de nbytes bytes para almacenar uno o más objetos de un tipo cualquiera. Esta función devuelve un puntero a void que referencia al espacio asignado.
Ejemplo:
#include <stdio.h> #include <stdlib.h> main ( ) { int *pa =NULL; int nbytes =100 * sizeof (int); pa = malloc (nbytes); if (pa==NULL) printf ("insuficiente espacio de memoria"); else printf ("Se ha asignado %d bytes de memoria\n", nbytes); }
free. free libera un bloque de memoria asignado por las funciones malloc, calloc o realloc. Si la memoria liberada por free no ha si previamente asignada con malloc se pueden producir errores durante la ejecución del programa.
#include <stdio.h> #include <stdlib.h> main ( ) { int *pa =NULL; int nbytes =100 * sizeof (int); pa = malloc (nbytes); if (pa==NULL) printf ("insuficiente espacio de memoria"); else printf ("Se ha asignado %d bytes de memoria\n", nbytes); /* OPERACIONES*/ free (pa); }
C++ utiliza para asignar y liberar memoria las funciones new y delete. new. Asigna un bloque de memoria que es el tamaño del tipo de datos. El dato u objeto dato puede ser un int, flota, una estructura o cualquier otro tipo de datos. El operador new devuelve un puntero que es la dirección del bloque asignado de memoria. El puntero se utiliza para referenciar al bloque de memoria. Su sintaxis es:
Puntero = new tipo_datos (inicializador opcional)
Ejemplo
int *pent; ... pent = new (int)
Para un array de 10 elementos
int *parray;
parray = new int[10];
O bien de modo más conciso
int *parray = new int[10];
El almacenamiento libre no es una fuente inagotable de memoria. Si el operador new se ejecuta
con falta de memoria, se devuelve un puntero NULL. Es responsabilidad del programador comprobar
siempre el puntero para asegurar que es válido antes de que se asigne un valor al puntero.
delete. Cuando se ha terminado de utilizar un bloque de memoria previamente asignado con
new, se puede liberar el espacio de memoria y dejarlo disponible para otros usos, mediante el operador
delete. El bloque de memoria suprimido se devuelve al espacio de almacenamiento libre, de modo que
habrá más memoria disponible para asignar otros bloques de memoria. El formato es
delete puntero.
Ejemplo
int *pent; pent = new (int) delete pent Para un array de 10 elementos int *parray; parray = new int[10]; delete parray
3.-Apertura Y Cierre De Un Archivo.
fopen La función fopen abre el fichero especificado por nombre de fichero . El argumento
modo especifica cómo es abierto el fichero. Después de abrir un fichero, la posición de L/E está al
principio del fichero, excepto para el modo añadir que está al final.
Sintaxis:
#include <stdio.h> FILE *fopen (const char *nombre de fichero, const char *modo);
TIPO Y DESCRIPCIÓN
- r --> Abrir un fichero para leer. Si el fichero no existe o no se encuentra, se obtiene un error.
- w --> Abrir un fichero para escribir. Si el fichero no existe, se crea; y si existe, su contenido se destruye para ser creado de nuevo.
- a --> Abrir un fichero para añadir información al final del mismo. Si el fichero no existe, se crea.
- r+ --> Abrir un fichero para leer y escribir. El fichero debe existir.
- w+ --> Abrir un fichero para leer y escribir. Si el fichero no existe, se crea; y si existe, su contenido se destruye para ser creado de nuevo.
- a+ --> Abrir un fichero para leer y añadir. Si el fichero no existe, se crea.
Ejemplo:
#include <stdio.h> FILE *pf; pf=fopen (“datos.dat”, “w”); if (pf == NULL) printf (“Error: el fichero no se puede abrir\n”);
fclose. La función fclose cierra el stream pf, y por lo tanto cierra el fichero asociado con pf Cualquier dato en el buffer asociado, se escribe en el fichero antes de cerrarlo.
#include <stdio.h> int fclose (FILE *pf);
Ejemplo:
fclose (pf);
4.- Detección De Errores.
Cuando en una operación sobre un fichero ocurre un error, éste puede ser detectado por la función ferror. Cuando ocurre un error, el indicador de error permanece activado hasta que el fichero se cierra, a no ser que utilicemos la función clearerr o rewind para desactivarlo explícitamente.
ferror. La función ferror verifica si ha ocurrido un error en una operación con ficheros.
Cuando ocurre un error, el indicador de error para, ese fichero se pone activo y permanece en este estado,
hasta que sea ejecutada la función clearerr.
#include <stdio.h> int ferror (FILE *p[);
La función ferror devuelve un cero si no ha ocurrido un error y un valor distinto de cero en caso contrario.
clearerr. La función clearerr desactiva el indicador de error y el indicador de fin de fichero para un determinado fichero, poniéndolos a valor O.
#include <stdio.h> void clearerr (FILE *pf);
El siguiente ejemplo, muestra cómo utilizar algunas de las funciones explicadas hasta ahora. Lo que intenta hacer el ejemplo es abrir en el directorio actual un fichero llamado datos y escribir en él una cadena de caracteres. Cuando el programa finaliza, el fichero se cierra.
/* ferror.c */ #include <stdio.h> #include <stdlib.h> void main( ) FILE *pf; char *cadena = “Esta cadena nunca será escrita”; pf == fopen (”datos”, “r”); if (pf == NULL) { printf (”Error: no se puede abrir el fichero\n”); exit (1); } fprintf (pf, “%s\n‟, cadena); if ( ferror(pf) ) { printf (“Error al escribir en el fichero\n”); clearerr(pf); } fclose (pf);
En el ejemplo anterior, cuando se invoca a la función fopen para abrir el fichero “datos” para leer (r) se verifica si la operación ha sido satisfactoria y en caso afirmativo se continúa. Para abrir un fichero para leer, el fichero debe existir. Si el fichero datos existe, se intenta escribir en él una cadena de caracteres y se verifica si la operación se ha efectuado correctamente. Para ello, la función ferror interroga los flags de error asociados con el fichero, detectando en este caso que ocurrió un error en la última operación de escritura. La función ferror manda un mensaje por la consola y la función clearerr desactiva el indicador de error para ese fichero. Este error se debe a que el fichero estaba abierto para leer, no para escribir. Si no hubiéramos hecho esta verificación, no nos hubiéramos enterado del error ya que el sistema no envía ningún mensaje.
feof. Cuando en una operación de lectura sobre un fichero se intenta leer más allá de la marca
fin de fichero, automáticamente el sistema activa el indicador de fin de fichero asociado con él. Para saber
el estado de este indicador para un determinado fichero, hay que invocar a la función feof. La marca fin
de fichero es añadida automáticamente por el sistema cuando crea el fichero.
Sintaxis:
#include <stdio.h> int feof (FILE *pf)
La función feof devuelve un valor distinto de 0 cuando se intenta leer un elemento del fichero y nos encontramos con un eof (end of file- fin de fichero). En caso contrario devuelve un 0.
5.- Entrada/Salida De Cadenas De Caracteres
Los datos pueden ser escritos en un fichero y leídos de un fichero como cadenas de caracteres con las funciones fputs y fgets.
fputs. La función fputs copia la cadena de caracteres almacenada en cadena, en el fichero
apuntado por pf. La terminación „\0‟ con la que finaliza toda cadena C, no se copia.
Sintaxis:
*include <stdio.h> int fputs (const char *cadena, FILE *pf);
La función fputs devuelve un valor 0 si se ejecuta satisfactoriamente. En caso contrario,
devuelve un valor distinto de 0.
Para recuperar de una forma sencilla la información escrita en el fichero, es aconsejable copiar el
carácter \n después de cada cadena escrita sobre el fichero.
Por ejemplo,
while (gets (cadena) != NULL) { fputs(cadena, pf); /* escribir la cadena en el fichero */ fputc („\n‟, pf); /* escribir a continuación, el carácter \n */ }
fgets La función fgets lee una cadena de caracteres del fichero apuntado por pf y la almacena en cadena. Se entiende por cadena la serie de caracteres que va desde la posición actual dentro del fichero, hasta el primer carácter nueva línea („\n‟) incluido éste, o hasta el final del fichero, o hasta que el número de caracteres sea igual a n-1. La terminación „\0‟ es añadida automáticamente a la cadena leída y el carácter „\n‟, si lo hay, no es eliminado.
#include <stdio.h> char *fgets (char *cadena, int n, FILE *pf);
6.- Entrada/Salida Con Formato.
Los datos pueden ser escritos con formato en un fichero y leídos con formato de un fichero
utilizando las funciones fprintf y fscanf.
fprintf. La función fprintf escribe sus argumentos, arg, en el fichero apuntado por pf con el
formato especificado. La descripción de formato, es la misma que se especificó para printf.
#include <stdio.h> int fprintf (FILE *pf, const char *formato[, arg]...);
La función fprintf devuelve el número de caracteres escritos o un valor negativo si ocurre un
error.
Ejemplo:
fprintf (punt, "%ld\n", personal.dni); fprintf (punt, " %s\n", personal.nombre);
fscanf. La función fscanf lee sus argumentos, arg, del fichero apuntado por pf, con el formato especificado. La descripción de formato es la misma que se especificó para scanf. Cada argumento arg, debe ser un puntero a la variable en la que queremos almacenar el valor leído. El tipo de cada una de estas variables debe corresponderse con la especificación de formato indicada para cada una de ellas.
#include <stdio.h> int fscanf (FILE *pf, const char *formato[, arg]...);
La función fscanf devuelve el número de argumentos que han sido leídos y asignados. Si el valor devuelto es un 0, significa que no se han asignado valores; y si es un EOF, significa que se ha detectado el final del fichero. Ejemplo:
fscanf (punt, "%ld", &personal.dni); fscanf (punt, "%s\n", &personal.nombre);
7.- E/S Utilizando Registros.
Los datos pueden ser escritos y leídos como registros o bloques con las funciones fwrite y fread.
fwrite. Permite escribir c elementos de longitud n bytes almacenados en el buffer
especificado, en el fichero apuntado por pf.
size_t fwrite (const void * buffer, size_t n, size_t c, FILE *pf);
La función write devuelve el número de elementos actualmente escritos. Si este número es
menor que c, entonces es que ha ocurrido un error.
El total de bytes escritos es (n * size_t).
Ejemplo:
fwrite (®istro, tamreg, 1,archivo);
fread. Permite leer c elementos de longitud n bytes del fichero apuntado por pf y los almacena en el buffer especificado.
size_t fread (const void * buffer, size_t n, size_t c, FILE *pf);
La función read devuelve el número de elementos actualmente leídos, que puede ser menor que c
si ocurre un error.
El total de bytes leídos es (n * size_t).
Se deben utilizar las funciones feof o ferror para detectar si se ha detectado el final del fichero o
si ha ocurrido un error.
Ejemplo:
fread (®istro, tamreg, 1, archivo);
8.- Acceso Aleatorio A Un Fichero.
fseek. Mueve el puntero de L/E asociado con el fichero apuntado por pf, a una nueva localización desplazada desp bytes de la posición dada por el argumento pos. El desplazamiento puede ser positivo o negativo.
int fseek (FILE *pf, long desp, int pos);
El argumento pos puede tomar alguno de los valores siguientes:
SEEK_SET: Principio del fichero. SEEK_CUR: Posición actual delpuntero L/E. SEEK_END: Final del fichero.
fseek puede usarse, entre otras cosas para:
1. Calcular el número de registros de un fichero.
fseek (pt, 0L, SEEK_END);
totalreg = ftell (pt/bytesreg);
2. Calcular el desplazamiento.
desp= (nreg -1) * bytesreg;
fseek (pt, desp, SEEK_SET);
3. Posicionarse sobre un registro y escribir.
fseek (pt, desp, SEEK_CUR);
ftell. Da como resultado la posición actual del puntero de L/E, dentro del fichero. Si ocurre un error devuelve el valor -1L. Su sintaxis:
long ftell (FILE *pt);
Ejemplo:
long pos; pos = ftell (pf); //posición en bits pos = ftell(pf)/tamreg; //posición del registro
rewind. Pone el puntero de L/E del fichero apuntado por pf, al comienzo del mismo.
void rewind (FILE *pf);
Una llamada a esta función equivale a:
(void) fseek (pf, 0L,SEEK_SET);
con la excepción de que rewind desactiva los indicadores de error y fin de fichero , y fseek no.
Ejemplo que creo bastante explicativo de la función fread y fwrite sacado de C++ con clase, en el que se aprecia claramente el significado del tercer parámetros de estas instrucciones, aquel parámetro que normalmente se pone a 1.
#include <stdio.h> int main() { FILE *fichero; char nombre[11] = "datos5.dat"; unsigned int dinero[10] = { 23, 12, 45, 345, 512, 345, 654, 287, 567, 124 }; unsigned int leer[10], i; fichero = fopen( nombre, "w+" ); printf( "Fichero: %s -> ", nombre ); if( fichero ) printf( "creado (ABIERTO)\n" ); else { printf( "Error (NO ABIERTO)\n" ); return 1; } printf( "Escribiendo cantidades:\n\n" ); for( i=0; i<10; i++ ) printf( "%d\t", dinero[i] ); fwrite( dinero, sizeof(unsigned int), 10, fichero ); printf( "\nLeyendo los datos del fichero \"%s\":\n", nombre ); rewind( fichero ); fread( leer, sizeof(unsigned int), 10, fichero ); for( i=0; i<10; i++ ) printf( "%d\t", leer[i] ); getch(); if( !fclose(fichero) ) printf( "\nFichero cerrado\n" ); else { printf( "\nError: fichero NO CERRADO\n" ); return 1; } return 0;