Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

5.-Arreglos (Arrays) y Punteros

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 31

5.

-ARREGLOS(ARRAYS) Y PUNTEROS

1.-Declaración e inicialización de Arreglos


Declaración de Arreglos
La declaración de un arreglo en C se realiza especificando el tipo de datos de sus elementos,
seguido por el nombre del arreglo y el número de elementos que puede contener, encerrado entre
corchetes. La sintaxis general es:
tipo nombre_del_arreglo[tamaño];

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.

int numeros[5] = {1, 2, 3, 4, 5};

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 ).

int numeros[5] = {1, 2}; // Los elementos restantes se inicializan a 0


int numeros[5] {1, 2}; // Los elementos restantes se inicializan a 0

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.

int numeros[] = {1, 2, 3, 4, 5}; // El tamaño es 5, determinado por el número de inicial


izadores

Acceso a los Elementos del Arreglo


El acceso a los elementos de un arreglo en C se realiza mediante el uso de índices. Un índice de un
arreglo es un entero que indica la posición de un elemento dentro del arreglo. La sintaxis para
acceder a un elemento es simple: se escribe el nombre del arreglo seguido por el índice del
elemento que deseas acceder, encerrado entre corchetes. Es importante notar que los índices en C
comienzan en 0, lo que significa que el primer elemento del arreglo está en el índice 0, el segundo
en el índice 1, y así sucesivamente. Esta convención se usa para facilitar ciertas operaciones
aritméticas con los índices.
Sintaxis:
nombre_del_arreglo: Es el nombre que le has dado al arreglo cuando lo declaraste.
indice: Es la posición del elemento al que deseas acceder, empezando desde 0.
Ejemplo de Acceso a Elementos de un Arreglo:
Considera el siguiente arreglo de enteros:

int numeros[5] = {10, 20, 30, 40, 50};


Para acceder al primer elemento (10), usarías:

int primer_elemento = numeros[0];

Para modificar el tercer elemento (30) a otro valor, digamos 300, usarías:

numeros[2] = 300;

Después de esta asignación, el arreglo numeros se vería así:

{10, 20, 300, 40, 50}

Acceso Mediante Bucle:


A menudo, querrás acceder a cada elemento de un arreglo secuencialmente. Esto se puede hacer
fácilmente con un bucle, como un bucle for . Por ejemplo, si quisieras imprimir cada elemento del
arreglo numeros , podrías hacer lo siguiente:

for(int i = 0; i < 5; i++) { printf("%d\n", numeros[i]); }

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:

char saludo[] = "Hola mundo!";

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:

char saludo[12] = "Hola mundo!";

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.

Declaración de un Arreglo Bidimensional


Para declarar un arreglo bidimensional, especificas el tipo de elementos que almacenará, seguido
Para declarar un arreglo bidimensional, especificas el tipo de elementos que almacenará, seguido
de dos pares de corchetes, cada uno conteniendo un número que representa la cantidad de filas y
la cantidad de columnas, respectivamente. La sintaxis es la siguiente:

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];

Inicialización de un Arreglo Bidimensional


Puedes inicializar un arreglo bidimensional en el momento de la declaración especificando los
valores de los elementos en una lista de inicializadores anidados, que reflejan la estructura de filas y
columnas del arreglo. Por ejemplo:

int matriz[2][3] = { {1, 2, 3}, {4, 5, 6} };

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 .

Acceso a los Elementos de un Arreglo Bidimensional


Para acceder a un elemento específico en un arreglo bidimensional, especificas los índices de la fila
y la columna dentro de corchetes después del nombre del arreglo. Los índices en C comienzan en
0, por lo que para acceder al primer elemento de la primera fila de la matriz anterior, usarías:
0, por lo que para acceder al primer elemento de la primera fila de la matriz anterior, usarías:

int valor = matriz[0][0];

Y para modificar el valor del tercer elemento de la segunda fila:

matriz[1][2] = 10;

Después de esta asignación, el elemento matriz[1][2] ahora contiene el valor 10 .

Ejemplo de Uso de Arreglos Bidimensionales


Aquí tienes un ejemplo de cómo podrías utilizar un arreglo bidimensional en C, incluyendo la
declaración, inicialización y un bucle para imprimir todos sus elementos:

#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.

Declaración de un Arreglo Multidimensional


Para declarar un arreglo multidimensional, defines el tipo de los elementos que almacenará seguido
de múltiples pares de corchetes, cada uno especificando el tamaño de una dimensión. La sintaxis
general es:

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];

Inicialización de un Arreglo Multidimensional


Puedes inicializar un arreglo multidimensional al momento de la declaración, proporcionando los
valores para sus elementos en una lista de inicializadores anidados que reflejan su estructura. Por
ejemplo:

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} } };

Esta declaración e inicialización configuran los valores de un arreglo tridimensional de 2x2x2.

Acceso a los Elementos de un Arreglo Multidimensional


El acceso a un elemento específico en un arreglo multidimensional se realiza especificando los
índices para cada dimensión dentro de corchetes después del nombre del arreglo. Por ejemplo,
para acceder al valor 6 en el arreglo matriz3D declarado previamente, usarías:

int valor = matriz3D[1][0][1];

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.

Cómo Funciona la Búsqueda Lineal


Para realizar una búsqueda lineal en un arreglo en C, sigues estos pasos:
Empieza desde el primer elemento del arreglo.
Compara el elemento actual con el valor que estás buscando.
Si el elemento actual es igual al valor buscado, has encontrado el elemento y puedes terminar la
búsqueda.
Si el elemento actual no es el que buscas, pasa al siguiente elemento.
Repite los pasos 2 a 4 hasta que encuentres el elemento o hasta que hayas revisado todos los
elementos del arreglo.
Si has revisado todos los elementos y no has encontrado el valor buscado, entonces el elemento no
está en el arreglo.

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.

Pasos del Algoritmo


Comparar el primer elemento con el segundo elemento del arreglo.
Si el primer elemento es mayor que el segundo, intercambiarlos.
Moverse un elemento hacia adelante y repetir el proceso de comparación e intercambio si es
necesario, haciendo esto para cada par de elementos adyacentes en el arreglo.
Una vez que se llega al final del arreglo, repetir todo el proceso desde el principio.
Continuar repitiendo el proceso hasta que se pueda pasar por todo el arreglo sin hacer ningún
intercambio.

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"); }

Este código implementa el algoritmo de ordenamiento burbuja y lo aplica a un arreglo de enteros.


La función ordenamientoBurbuja toma el arreglo y su tamaño como argumentos, realiza el
ordenamiento, y luego el arreglo ordenado se imprime en la función main .

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.

9.-Direcciones de memoria y sus operadores


Direcciones de memoria
En C, cada variable se almacena en una ubicación única de la memoria. La dirección de memoria es
básicamente la posición exacta en la memoria donde se almacena una variable. Estas direcciones se
pueden utilizar para acceder y manipular los datos almacenados en la memoria. Las direcciones de
pueden utilizar para acceder y manipular los datos almacenados en la memoria. Las direcciones de
memoria son particularmente útiles para la programación a bajo nivel, donde el control preciso
sobre la ubicación de los datos en la memoria puede ser crucial para el rendimiento y la eficiencia
del programa.

Operadores relacionados con las direcciones de memoria


Los principales operadores en C que se relacionan con las direcciones de memoria son el operador
de dirección & y el operador de indirección * (también conocido como operador de
desreferenciación).

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;

tipo_de_dato: Es el tipo de la variable a la que el puntero puede apuntar. Esto es importante


porque el tipo de datos determina el tamaño de la memoria que el puntero debe interpretar.
nombre_del_puntero: Es el identificador del puntero.

Ejemplos de Declaraciones de Punteros

int *p; // p es un puntero a un entero. char *c; // c es un puntero a un carácter. float


*f; // f es un puntero a un flotante.

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 x = 10; int *p = &x; // p ahora contiene la dirección de memoria de x.


Punteros no Inicializados
Si se declara un puntero sin inicializarlo, su contenido es indeterminado (puede contener una
dirección de memoria aleatoria). Acceder a la dirección de memoria a través de un puntero no
inicializado puede llevar a comportamientos indefinidos. Por seguridad, es una buena práctica
inicializar los punteros a NULL si no se les asigna una dirección específica inmediatamente después
de su declaración:

int *p = NULL;

Punteros y el Operador de Indirección


Una vez que un puntero está inicializado con una dirección, se puede acceder al valor almacenado
en esa dirección utilizando el operador de indirección * (también llamado operador de
desreferenciación) como se mencionó anteriormente. Por ejemplo:

int x = 10; int *p = &x; printf("%d", *p); // Imprime el valor de x, que es 10.

11.-Acceso directo e indirecto con punteros


Acceso Directo
El acceso directo se refiere a la interacción con una variable directamente por su nombre. Cuando
se accede a una variable de esta manera, estamos trabajando con el valor almacenado en la
ubicación de memoria asignada a esa variable. Por ejemplo, si tenemos una variable entera llamada
x y le asignamos un valor directamente, como en x = 5; , estamos accediendo a x directamente
para establecer su valor.

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.

Importancia del Acceso Indirecto


El acceso indirecto es especialmente útil en varias situaciones, como cuando se trabaja con arrays,
estructuras de datos dinámicas (como listas enlazadas), o al pasar grandes estructuras de datos a
funciones. Permite a los programas modificar los datos sin necesidad de copiarlos o pasarlos
directamente, lo que puede llevar a una mayor eficiencia en términos de uso de la memoria y
rendimiento del programa.

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.

Adición ( p + n ) y sustracción ( p - n ): Mueven el puntero hacia adelante o hacia atrás en n


elementos.

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).

Ejemplo de Aritmética de Punteros


Consideremos el siguiente ejemplo que ilustra la aritmética de punteros con un array de enteros:
#include <stdio.h> void app_main() { int arr[] = {10, 20, 30, 40, 50}; int *p = arr; //
Inicialización del puntero al primer elemento del array printf("El primer elemento es %d
\n", *p); // Acceso directo al primer elemento p++; // Mueve el puntero al segundo eleme
nto printf("El segundo elemento es %d\n", *p); // Acceso directo al segundo elemento pri
ntf("El tercer elemento usando aritmética de punteros es %d\n", *(p + 1)); // Acceso ind
irecto al tercer elemento }

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 ).

Importancia de la Aritmética de Punteros


La aritmética de punteros es una herramienta poderosa en C, ya que proporciona una forma
eficiente de recorrer arrays y estructuras de datos similares. Es especialmente útil en la
implementación de algoritmos donde la manipulación directa de la memoria y el acceso rápido a
los elementos de datos son cruciales. Sin embargo, también requiere un manejo cuidadoso para
evitar errores, como acceder fuera de los límites de un array, que puede llevar a comportamientos
indefinidos.

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]; .

Acceso a Elementos del Arreglo mediante Punteros


Una vez que se tiene un puntero al primer elemento de un arreglo, se puede acceder a cualquier
elemento del arreglo mediante aritmética de punteros. Por ejemplo, si p es un puntero al primer
elemento de un arreglo, *(p + i) da acceso al elemento en el índice i del arreglo (equivalente a
arreglo[i] ).

Ejemplo de Punteros a Arreglos


Consideremos un ejemplo concreto que ilustra el uso de punteros para acceder y modificar
elementos de un arreglo:

#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.

Importancia de Punteros a Arreglos


La capacidad de utilizar punteros para manipular arreglos es fundamental en C por varias razones:
Eficiencia: El acceso y manipulación de arreglos mediante punteros puede ser más eficiente que
usar índices de arreglo, especialmente en contextos donde la aritmética de punteros es optimizada
por el compilador.
Flexibilidad: Los punteros ofrecen una forma más flexible de trabajar con datos secuenciales,
permitiendo, por ejemplo, pasar subarreglos a funciones sin copiar datos.
Interoperabilidad con Funciones: Los punteros son esenciales para pasar arreglos a funciones y
para retornar arreglos desde funciones, ya que C no permite pasar o retornar arreglos
directamente.
Entender la relación entre punteros y arreglos abre la puerta a técnicas de programación más
avanzadas y eficientes en C, especialmente en lo que respecta al manejo de datos y la
implementación de estructuras de datos complejas.

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.

Sintaxis de Declaración de un Puntero a Función


La sintaxis para declarar un puntero a función puede parecer compleja al principio, pero sigue un
La sintaxis para declarar un puntero a función puede parecer compleja al principio, pero sigue un
patrón lógico:

tipo_retorno (*nombre_puntero_funcion)(tipo_param1, tipo_param2, ..., tipo_paramN);

tipo_retorno: Es el tipo de dato que la función apuntada retornará.


nombre_puntero_funcion: Es el nombre del puntero a la función.
tipo_paramX: Son los tipos de los parámetros que la función apuntada espera recibir.

Ejemplo de Uso de Punteros a Funciones


Consideremos un ejemplo donde definimos un puntero a una función que toma dos enteros como
argumentos y retorna un entero:

#include <stdio.h> // Prototipo de la función int suma(int a, int b) { return a + b; } v


oid app_main() { // Declaración de un puntero a función que toma dos enteros y retorna u
n entero int (*punteroFuncion)(int, int); // Asignando la dirección de la función suma a
l puntero punteroFuncion = suma; // Llamando a la función a través del puntero int resul
tado = punteroFuncion(5, 3); printf("El resultado es: %d\n", resultado); }

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 .

Usos de Punteros a Funciones


Los punteros a funciones son útiles en varios contextos, incluyendo:
Implementación de callbacks: Permiten que una función llame a otra función pasada como
argumento sin conocer su nombre específico en tiempo de compilación.
Tablas de funciones: Son útiles para implementar tablas de funciones, donde diferentes
operaciones se pueden ejecutar dependiendo de la entrada, sin usar múltiples instrucciones
condicionales.
Interfaces de programación genérica: Facilitan la escritura de código genérico que puede operar
con diferentes funciones específicas pasadas como parámetros.

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:

char *cadena = "Hola Mundo";

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:

char *c = "Hola Mundo"; while (*c != '\0') { putchar(*c); c++; }

Este código imprime cada carácter de la cadena "Hola Mundo" recorriéndola caracter por caracter
usando un puntero.

Ventajas de Usar Punteros a Cadenas


Eficiencia: La manipulación de cadenas usando punteros puede ser más eficiente que usar índices
de arreglo, especialmente para operaciones que requieren recorrer o modificar la cadena.
Flexibilidad: Los punteros ofrecen maneras más flexibles de trabajar con subcadenas o de pasar
cadenas a funciones sin copiar toda la cadena.
Compatibilidad con Funciones Estándar: Muchas funciones de la biblioteca estándar de C para
manipulación de cadenas ( strcpy , strcat , strlen , etc.) requieren punteros a cadenas como
argumentos.

Ejemplo de Manipulación de Cadenas

#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.

16.-Paso de parámetros por referencia a una


función
El paso de parámetros por referencia implica pasar la dirección de memoria de una variable (es
decir, un puntero a la variable) en lugar de pasar una copia del valor de la variable. Esto significa
que cualquier modificación de la variable dentro de la función afectará a la variable original en el
contexto de llamada, ya que la función operará directamente sobre la dirección de memoria de la
variable.

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.

Ejemplo de Paso por Referencia


Consideremos un ejemplo donde queremos escribir una función que incremente el valor de una
variable entera pasada a ella:

#include <stdio.h> // Definición de la función que incrementa el valor de la variable pa


sada por referencia void incrementar(int *valor) { (*valor)++; // Incrementa el valor de
la variable apuntada por 'valor' } void app_main() { int num = 10; printf("Antes de incr
ementar: %d\n", num); incrementar(&num); // Paso por referencia usando el operador '&' p
rintf("Después de incrementar: %d\n", num); }

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 .

Ventajas del Paso por Referencia


Eficiencia: Es más eficiente para estructuras de datos grandes, ya que evita la copia innecesaria de
datos.
Flexibilidad: Permite a las funciones modificar los valores de las variables pasadas, lo que es útil
para retornar múltiples valores de una función o actualizar los estados de las variables pasadas.

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

#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include


"driver/gpio.h" void operacion1(void); void operacion2(void); void operacion3(void);
void operacion4(void); void on_led(int); int leds[5] = {18,17,4,2,18}; void app_main() {
int bt1,bt2; int s = 0; bt1 = 14; bt2 = 13; void (*pfun[4])(void) =
{operacion1,operacion2,operacion3,operacion4};
gpio_set_direction(leds[0],GPIO_MODE_OUTPUT);
gpio_set_direction(leds[1],GPIO_MODE_OUTPUT);
gpio_set_direction(leds[2],GPIO_MODE_OUTPUT);
gpio_set_direction(leds[3],GPIO_MODE_OUTPUT); gpio_set_direction(bt1,GPIO_MODE_INPUT);
gpio_set_direction(bt2,GPIO_MODE_INPUT); gpio_pullup_en(bt1); gpio_pullup_en(bt2);
gpio_set_level(leds[0],0); gpio_set_level(leds[1],0); gpio_set_level(leds[2],0);
gpio_set_level(leds[3],0); printf("Hello, Wokwi!\n"); on_led(s); while (1) {
vTaskDelay(500 / portTICK_PERIOD_MS); if (!gpio_get_level(bt1)){ s++; on_led(s); if (s
== 4){ s = 0; } } while (!gpio_get_level(bt2)){ pfun[s](); } } } void operacion1(void){
gpio_set_level(leds[0],0); vTaskDelay(100 / portTICK_PERIOD_MS);
gpio_set_level(leds[0],1); vTaskDelay(100 / portTICK_PERIOD_MS); } void operacion2(void)
{ gpio_set_level(leds[1],0); vTaskDelay(100 / portTICK_PERIOD_MS);
gpio_set_level(leds[1],1); vTaskDelay(100 / portTICK_PERIOD_MS); } void operacion3(void)
{ gpio_set_level(leds[2],0); vTaskDelay(100 / portTICK_PERIOD_MS);
{ gpio_set_level(leds[2],0); vTaskDelay(100 / portTICK_PERIOD_MS);
gpio_set_level(leds[2],1); vTaskDelay(100 / portTICK_PERIOD_MS); } void operacion4(void)
{ gpio_set_level(leds[3],0); vTaskDelay(100 / portTICK_PERIOD_MS);
gpio_set_level(leds[3],1); vTaskDelay(100 / portTICK_PERIOD_MS); } void on_led(int num){
// gpio_set_level(num,1); gpio_set_level(leds[0],0); gpio_set_level(leds[1],0);
gpio_set_level(leds[2],0); gpio_set_level(leds[3],0); gpio_set_level(leds[num],1); }

También podría gustarte