5.-Arreglos (Arrays) y Punteros
5.-Arreglos (Arrays) y Punteros
5.-Arreglos (Arrays) y Punteros
-ARREGLOS(ARRAYS) Y PUNTEROS
tipo: Es el tipo de dato de cada elemento del arreglo (por ejemplo, int , float , char , etc.).
nombre_del_arreglo: Es el identificador dado al arreglo.
tamaño: Es el número de elementos que el arreglo puede almacenar. Debe ser un valor entero.
Por ejemplo, para declarar un arreglo de 10 enteros, se haría de la siguiente manera:
int numeros[10];
Inicialización de Arreglos
La inicialización de un arreglo implica asignar valores específicos a sus elementos en el momento
de la declaración. Esto se puede hacer de varias maneras:
Inicialización explícita de todos los elementos: Se especifican los valores de todos los elementos
del arreglo entre llaves {} y separados por comas.
Inicialización parcial: Si solo se proporcionan algunos valores, los elementos restantes se inicializan
automáticamente a 0 (para tipos numéricos) o a \0 (para char ).
Omitir el tamaño del arreglo: Se puede omitir el tamaño del arreglo en la declaración si se
inicializa en el mismo momento, ya que el compilador puede contar los elementos inicializadores.
Para modificar el tercer elemento (30) a otro valor, digamos 300, usarías:
numeros[2] = 300;
Este bucle recorre el arreglo desde numeros[0] hasta numeros[4] (el último elemento),
imprimiendo cada valor en la consola. La condición i < 5 asegura que el bucle se ejecute solo
para los índices válidos dentro del arreglo, lo que es crucial para evitar el acceso a memoria fuera
de los límites del arreglo.
2.-Variables String
En el lenguaje de programación C, los strings (o cadenas de caracteres) se tratan como arreglos de
caracteres terminados en un carácter nulo ( \0 ). Este carácter nulo es crucial, ya que marca el final
de la cadena y permite que las funciones de manejo de strings en la biblioteca estándar de C
determinen la longitud de una cadena de forma segura y eficiente.
Declaración de Strings
Un string en C puede ser declarado de varias maneras, todas las cuales implican definir un arreglo
de caracteres:
Declaración y inicialización con una cadena de caracteres:
En este caso, el tamaño del arreglo se determina automáticamente contando los caracteres de la
cadena literal, incluyendo el carácter nulo final.
Especificando el tamaño del arreglo:
Aquí, se especifica que el arreglo saludo tiene espacio para 12 caracteres, incluyendo los 11
Aquí, se especifica que el arreglo saludo tiene espacio para 12 caracteres, incluyendo los 11
caracteres de "Hola mundo!" más el carácter nulo \0 .
Inicialización carácter por carácter:
char saludo[] = {'H', 'o', 'l', 'a', ' ', 'm', 'u', 'n', 'd', 'o', '!', '\0'};
Esta forma detallada permite especificar cada carácter individualmente, incluido el carácter nulo al
final.
Importante
Recuerda que los strings en C deben terminar siempre con un carácter nulo ( \0 ) para su correcto
manejo. Acceder a un índice fuera de los límites de la cadena puede provocar errores de
segmentación y comportamientos indefinidos, poniendo en riesgo la seguridad y estabilidad del
programa. Siempre asegúrate de que los arreglos de destino en operaciones como la copia y la
concatenación sean lo suficientemente grandes para contener el resultado esperado, incluyendo el
carácter nulo final.
3.-Arreglos Bidimensionales
Los arreglos bidimensionales en C, a menudo referidos como matrices, son esencialmente arreglos
de arreglos. Puedes pensar en un arreglo bidimensional como una tabla con filas y columnas,
donde cada elemento del arreglo se identifica por dos índices: uno para la fila y otro para la
columna.
tipo nombre_del_arreglo[numero_de_filas][numero_de_columnas];
Por ejemplo, para declarar una matriz de enteros de 3 filas y 4 columnas, lo harías así:
int matriz[3][4];
Esta declaración e inicialización crean una matriz de 2 filas y 3 columnas, donde, por ejemplo, el
elemento matriz[0][2] es 3 .
matriz[1][2] = 10;
#include <stdio.h> void app_main() { int matriz[2][3] = {{1, 2, 3}, {4, 5, 6}}; for(int
i = 0; i < 2; i++) { for(int j = 0; j < 3; j++) { printf("matriz[%d][%d] = %d\n", i, j,
matriz[i][j]); } } }
Este código imprimirá los valores de todos los elementos de la matriz, junto con sus índices.
Los arreglos bidimensionales son una herramienta poderosa en C para trabajar con datos en una
estructura tabular, útil en aplicaciones que van desde la manipulación de matrices matemáticas
hasta la representación de tableros de juegos y mucho más.
4.-Arreglos Multidimensionales
4.-Arreglos Multidimensionales
Los arreglos multidimensionales en C extienden el concepto de arreglos bidimensionales,
permitiendo estructuras de datos más complejas con tres, cuatro o más dimensiones. Puedes
pensar en un arreglo multidimensional como un arreglo de arreglos de arreglos (y así
sucesivamente), lo que facilita representar y manejar datos en múltiples dimensiones.
tipo nombre_del_arreglo[dim1][dim2][...][dimN];
Por ejemplo, un arreglo tridimensional de enteros que representa una matriz de 2x3x4 (2 bloques,
cada uno con 3 filas y 4 columnas) se declara así:
int matriz3D[2][3][4];
int matriz3D[2][2][2] { { {1 2} {3 4} } { {5 6} {7 8} } }
int matriz3D[2][2][2] = { { {1, 2}, {3, 4} }, { {5, 6}, {7, 8} } };
Este índice indica que estamos accediendo al bloque 2 ( [1] porque los índices comienzan en 0),
fila 1, columna 2.
Ejemplo Práctico
Imagina que quieres almacenar y acceder a datos que representan las temperaturas en diferentes
puntos de una región a lo largo del tiempo. Un arreglo tridimensional podría usarse para este
propósito, con una dimensión para el tiempo (días), otra para la ubicación en el eje X y otra para la
ubicación en el eje Y.
Los arreglos multidimensionales son especialmente útiles en aplicaciones de ciencia de datos,
gráficos por computadora, simulaciones físicas y el manejo de datos que naturalmente se organizan
en más de dos dimensiones. Su uso, sin embargo, debe ser equilibrado con consideraciones de
claridad y eficiencia de la memoria, especialmente en sistemas embebidos o con recursos limitados.
5.-Búsqueda en arreglos
La búsqueda lineal, también conocida como búsqueda secuencial, es uno de los algoritmos más
básicos para encontrar un elemento específico dentro de un arreglo. Este método implica revisar
cada elemento del arreglo, uno por uno, hasta encontrar el elemento que estás buscando o hasta
que hayas revisado todos los elementos sin encontrarlo. La búsqueda lineal se puede aplicar a
arreglos ordenados o desordenados, lo que la hace versátil pero no necesariamente la más eficiente
en todos los casos, especialmente para arreglos grandes.
Ejemplo de Código en C
Aquí tienes un ejemplo de cómo implementar una búsqueda lineal en un arreglo de enteros en C:
#include <stdio.h> // Función para realizar la búsqueda lineal int busquedaLineal(int ar
reglo[], int tamaño, int objetivo) { for (int i = 0; i < tamaño; i++) { if (arreglo[i] =
= objetivo) { return i; // Elemento encontrado, devuelve la posición } } return -1; // E
lemento no encontrado, devuelve -1 } void app_main() { int arreglo[] = {2, 4, 6, 8, 10,
12}; int tamaño = sizeof(arreglo) / sizeof(arreglo[0]); int objetivo = 10; int resultado
= busquedaLineal(arreglo, tamaño, objetivo); if (resultado != -1) { printf("Elemento enc
ontrado en el índice: %d\n", resultado); } else { printf("Elemento no encontrado en el a
rreglo.\n"); } }
En este ejemplo, la función busquedaLineal toma tres parámetros: el arreglo en el cual buscar, el
tamaño de ese arreglo, y el valor objetivo que estás buscando. La función devuelve el índice del
elemento si lo encuentra, o -1 si el elemento no está presente en el arreglo.
Consideraciones
Eficiencia: La búsqueda lineal tiene una complejidad temporal de O(n), lo que significa que el
tiempo necesario para encontrar un elemento crece linealmente con el tamaño del arreglo. Por lo
tanto, para arreglos grandes, este método puede no ser el más eficiente.
Simplicidad: A pesar de su eficiencia relativamente baja para arreglos grandes, la búsqueda lineal
es fácil de implementar y entender, lo que la hace útil para arreglos pequeños o cuando la
eficiencia no es crítica.
Versatilidad: Funciona tanto en arreglos ordenados como desordenados, a diferencia de algoritmos
más rápidos pero más restrictivos como la búsqueda binaria, que requiere que el arreglo esté
ordenado.
8.-Ordenamiento de arreglos
El algoritmo de ordenamiento burbuja es uno de los métodos de ordenamiento más simples y
El algoritmo de ordenamiento burbuja es uno de los métodos de ordenamiento más simples y
conocidos. A pesar de su simplicidad, no es el más eficiente para conjuntos de datos grandes, pero
su concepto fácil de entender lo hace popular para introducir los conceptos básicos del
ordenamiento en la computación. El algoritmo recibe este nombre porque los elementos más
pequeños "burbujean" hacia el principio de la lista (o los elementos más grandes hacia el final,
dependiendo de cómo lo implementes) a través de una serie de intercambios entre elementos
adyacentes.
Cómo Funciona
El algoritmo de ordenamiento burbuja compara pares de elementos adyacentes en el arreglo y los
intercambia si están en el orden incorrecto. Este proceso se repite para cada par de elementos
adyacentes, comenzando desde el principio del arreglo hasta el final. Luego, el proceso se repite
para todo el arreglo varias veces hasta que no se necesiten más intercambios, lo que significa que
el arreglo está ordenado.
Ejemplo de Código en C
#include <stdio.h> void ordenamientoBurbuja(int arr[], int n) { int i, j, temp; for (i =
0; i < n-1; i++) { // Pasada a través del arreglo for (j = 0; j < n-i-1; j++) { if (arr
[j] > arr[j+1]) { // Intercambio de elementos temp = arr[j]; arr[j] = arr[j+1]; arr[j+1]
= temp; } } } } void app_main() { int arreglo[] = {64, 34, 25, 12, 22, 11, 90}; int n =
sizeof(arreglo)/sizeof(arreglo[0]); ordenamientoBurbuja(arreglo, n); printf("Arreglo ord
enado: \n"); for (int i = 0; i < n; i++) { printf("%d ", arreglo[i]); } printf("\n"); }
Consideraciones
Eficiencia: El ordenamiento burbuja tiene una complejidad temporal de O(n^2) en el peor caso y
en el caso promedio, lo que significa que el tiempo necesario para ordenar el arreglo aumenta
cuadráticamente con el número de elementos. Esto lo hace ineficiente para arreglos grandes.
Ventajas: Es uno de los algoritmos de ordenamiento más fáciles de entender e implementar.
Desventajas: Su baja eficiencia lo hace poco práctico para la mayoría de los conjuntos de datos de
tamaño real, especialmente cuando se comparan con algoritmos más eficientes como quicksort,
mergesort o heapsort.
Operador de dirección & : Este operador se utiliza para obtener la dirección de memoria de una
variable. Por ejemplo, si tenemos una variable int x = 10; , escribir &x devolverá la dirección de
variable. Por ejemplo, si tenemos una variable int x = 10; , escribir &x devolverá la dirección de
memoria donde se almacena x . Esto es útil cuando queremos pasar la dirección de una variable a
un puntero.
Operador de indirección : Este operador se utiliza para acceder al valor almacenado en la
dirección de memoria a la que apunta un puntero. Por ejemplo, si tenemos un puntero int *p =
&x; , donde x es una variable entera, p nos permitirá acceder al valor de x a través del puntero
p . Esencialmente, p "sigue" la dirección almacenada en p para llegar al valor real almacenado
allí.
Conceptos clave
Puntero: Un puntero en C es una variable que almacena la dirección de memoria de otra variable.
Los punteros son poderosos porque permiten manipular directamente la memoria y pasar
eficientemente grandes cantidades de datos a funciones, entre otros usos.
Tipo de un puntero: Todos los punteros tienen un tipo que corresponde al tipo de datos de la
variable a la que apuntan. Esto es crucial porque el tipo determina cuánta memoria debe leerse o
escribirse al acceder a la variable a través del puntero.
Ejemplo básico
Para ilustrar cómo se usan estos conceptos y operadores, aquí hay un ejemplo simple:
#include <stdio.h> void app_main() { int x = 10; // Una variable entera. int *p = &x; //
Un puntero a un entero, inicializado con la dirección de x. printf("La dirección de x es
: %p\n", (void*)&x); printf("El valor de x es: %d\n", x); printf("p contiene la direcció
n: %p\n", (void*)p); printf("El valor al que apunta p es: %d\n", *p); }
10.-Declaración de variables apuntadores
Declaración de un Puntero
La declaración de un puntero se realiza especificando primero el tipo de datos al que apunta el
puntero, seguido de un asterisco * , y luego el nombre del puntero. La sintaxis general es:
tipo_de_dato *nombre_del_puntero;
Inicialización de un Puntero
Después de declarar un puntero, se puede inicializar con la dirección de una variable existente
usando el operador de dirección & . Por ejemplo:
int *p = NULL;
int x = 10; int *p = &x; printf("%d", *p); // Imprime el valor de x, que es 10.
Acceso Indirecto
El acceso indirecto, en contraste, implica utilizar un puntero para trabajar con la variable a la que
apunta el puntero, en lugar de trabajar con la variable directamente. Este tipo de acceso se realiza a
través de la dirección de memoria almacenada en el puntero. Utilizando el operador de indirección
* (también conocido como operador de desreferenciación), se puede acceder al valor de la
variable a la que apunta el puntero. Por ejemplo, si p es un puntero a una variable entera x , se
puede modificar el valor de x indirectamente haciendo *p = 5; .
Ejemplo Ilustrativo
Para ilustrar mejor el acceso directo e indirecto, consideremos el siguiente ejemplo en C:
#include <stdio.h> void app_main() { int x = 20; // Declaración y acceso directo a 'x' i
nt *p = &x; // Declaración de un puntero 'p' que almacena la dirección de 'x' // Acceso
directo printf("Acceso directo: x = %d\n", x); // Acceso indirecto a través de 'p' *p =
30; // Cambia el valor de 'x' indirectamente printf("Acceso indirecto: x = %d\n", x); }
En este ejemplo, x es una variable entera a la cual se accede directamente para establecer su valor
inicial a 20. Luego, se declara un puntero p que apunta a x , y se modifica el valor de x
indirectamente a través de p cambiándolo a 30. Este código ilustra cómo un puntero permite
acceder y modificar datos de manera indirecta.
12.-Aritmética de punteros
La aritmética de punteros se basa en algunos conceptos clave:
Tipos de datos y tamaño: Cuando se realiza aritmética con punteros, el tipo de datos al que apunta
el puntero es crucial. El incremento o decremento de un puntero se hace en términos del tamaño
del tipo de datos al que apunta. Por ejemplo, si un puntero apunta a un entero ( int ), que
usualmente ocupa 4 bytes (esto puede variar según la arquitectura de la computadora),
incrementar el puntero ( p++ ) lo avanzará 4 bytes en la memoria.
Operaciones permitidas: Las operaciones de aritmética de punteros incluyen:
Incremento ( p++ o ++p ) y decremento ( p-- o -p ): Mueven el puntero hacia adelante o hacia
atrás en la memoria, respectivamente, según el tamaño del tipo de datos al que apunta.
Diferencia ( p1 - p2 ): Calcula la distancia entre dos punteros (es decir, cuántos elementos de ese
tipo de datos hay entre dos direcciones de memoria).
En este ejemplo, p se inicializa al principio del array arr . Luego, se utiliza la aritmética de
punteros para acceder a diferentes elementos del array. Incrementar p ( p++ ) avanza el puntero al
siguiente elemento del array debido a que el incremento tiene en cuenta el tamaño de los
elementos del array (en este caso, int ).
13.-Punteros a arreglos
Un puntero en C puede apuntar al inicio de un arreglo, lo que permite iterar sobre el arreglo y
acceder a sus elementos utilizando aritmética de punteros. Esto es posible porque el nombre de un
arreglo en C actúa como un puntero constante al primer elemento del arreglo.
Declaración y Uso
Declaración y Uso
Un puntero a un arreglo se declara de la misma manera que cualquier otro puntero, especificando
el tipo de los elementos del arreglo seguido por un asterisco * , y luego el nombre del puntero.
Por ejemplo, para un arreglo de enteros, un puntero se declara como int *p; .
Para hacer que un puntero apunte al primer elemento de un arreglo, simplemente se asigna la
dirección del primer elemento del arreglo al puntero, como en p = arreglo; o p = &arreglo[0]; .
#include <stdio.h> void app_main() { int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; // Inic
ializa el puntero al primer elemento del arreglo // Accede a elementos del arreglo media
nte aritmética de punteros for(int i = 0; i < 5; i++) { printf("%d ", *(p + i)); } print
f("\n"); // Modifica elementos del arreglo mediante aritmética de punteros for(int i =
0; i < 5; i++) { *(p + i) = *(p + i) * 2; // Duplica el valor de cada elemento } // Mues
tra el arreglo modificado for(int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf
("\n"); }
En este ejemplo, un puntero p se inicializa para apuntar al primer elemento de un arreglo arr .
Luego, se utiliza aritmética de punteros para acceder y modificar cada elemento del arreglo. Este
Luego, se utiliza aritmética de punteros para acceder y modificar cada elemento del arreglo. Este
código demuestra cómo los punteros pueden ser utilizados para leer y escribir en un arreglo de
manera eficiente.
14.-Punteros a funciones
Un puntero a función es, simplemente, un puntero que almacena la dirección de memoria de una
función. Esto permite llamar a la función apuntada indirectamente a través del puntero. La
capacidad de almacenar y manipular direcciones de funciones hace que los punteros a funciones
sean una herramienta extremadamente versátil en C.
En este ejemplo, punteroFuncion es un puntero a una función que acepta dos int como
parámetros y retorna un int . Se asigna la dirección de la función suma a punteroFuncion , y luego
se llama a la función suma indirectamente a través de punteroFuncion .
15.-Punteros a cadenas
Un "puntero a cadena" en C es esencialmente un puntero que apunta a un carácter ( char * ), pero
se utiliza para apuntar al primer carácter de una cadena de caracteres. Al igual que con los punteros
a arreglos, la aritmética de punteros permite navegar por la cadena y acceder o modificar sus
caracteres.
Declaración y Uso
Un puntero a una cadena se puede declarar e inicializar de varias maneras. Por ejemplo, se puede
asignar directamente la dirección de una cadena literal a un puntero:
Aquí, cadena es un puntero al primer carácter de la cadena literal "Hola Mundo" . Es importante
recordar que las cadenas literales son inmutables, por lo que intentar modificar la cadena a través
del puntero resultaría en un comportamiento indefinido.
Manipulación de Cadenas Usando Punteros
Los punteros a cadenas permiten recorrer y manipular cadenas con gran flexibilidad. Por ejemplo,
se puede utilizar un bucle para imprimir cada carácter de una cadena:
Este código imprime cada carácter de la cadena "Hola Mundo" recorriéndola caracter por caracter
usando un puntero.
#include <stdio.h> void app_main() { char *mensaje = "Hola"; printf("Mensaje: %s\n", men
saje); // Modificar una cadena a través de un puntero (solo si no es una literal) char s
aludo[20] = "Buenas"; char *p = saludo; *(p + 6) = 'T'; // Añadir 'T' al final de "Buena
s", formando "BuenasT" printf("Saludo modificado: %s\n", saludo); }
En este ejemplo, se muestra cómo se pueden imprimir y modificar cadenas utilizando punteros. Sin
embargo, la modificación se realiza en un arreglo de caracteres, no en una cadena literal, para
evitar comportamiento indefinido.
Implementación en C
Para pasar un parámetro por referencia en C, se siguen dos pasos básicos:
En la llamada a la función: se pasa la dirección de la variable usando el operador & .
En la definición de la función: se declara el parámetro como un puntero del tipo adecuado para
almacenar la dirección de la variable pasada.
En este ejemplo, la función incrementar recibe un puntero a entero int *valor como su
parámetro, lo que le permite modificar el valor de la variable original num pasada a la función.
Dentro de incrementar , el valor de num se incrementa usando el operador de indirección * para
modificar el valor de la variable apuntada por valor .
Consideraciones
Seguridad: Al pasar direcciones de memoria, se debe tener cuidado de no acceder a memoria no
asignada o fuera de los límites de una estructura de datos, lo que podría llevar a comportamientos
indefinidos.
Inicialización: Asegúrate de que las variables pasadas por referencia estén inicializadas antes de
pasarlas a funciones que las modifiquen.
PRÁCTICA D EAPLICACIÓN
MENU DE DOS PULSADORES Y 4 OPERACIONES
Realizar un programa que con 2 pulsadores, que seleccionen 4 operaciones, de la siguiente manera:
Pulsador 1 se utiliza para cambiar entre opciones
Pulsador 2 se utiliza para activar la operación de la opción actual
Utilizar 4 leds para indicar en que opción se encuentra el menu
Codificar cada operación en una función
Utilizar punteros a funciones