Lab_08 Arreglos N Dimensiones
-
Upload
nestor-vasquez -
Category
Documents
-
view
30 -
download
1
Transcript of Lab_08 Arreglos N Dimensiones
1
UNIVERSIDAD DE EL SALVADOR
FACULTAD DE INGENIERÍA Y ARQUITECTURA
ESCUELA DE INGENIERÍA ELÉCTRICA
INTRODUCCIÓN A LA INFORMÁTICA
GUIA # 7:
PUNTEROS Y ARREGLOS.
Alumnos:
José Edgardo Escobar Hernández EH10002
David Alexander Portillo Rodríguez PR10059
Jonathan Amílcar Mendoza Ortiz MO10019
Néstor Gabriel Vásquez López VL10008
Grupo de Laboratorio: 05
Instructor: Br. Raúl Alvarenga
Ciudad Universitaria, Junio 2012.
2
ÍNDICE Pág.
Introducción……………………………………………………………………….03
Objetivos…………………………………………………………………………..04
Marco Teórico.........................................................................................................05
Asignación..............................................................................................................12
Conclusiones……………………………………………………………………..32
Bibliografía………………………………………………………………………33
3
INTRODUCCIÓN
Habiendo trabajado con matrices en una dimensión, también llamados vectores, en esta
ocasión nos centralizamos en estudiar las matrices que pueden ser de N dimensiones y con
las cuales podemos adentrarnos a un mundo de posibilidades para resolver todo tipo de
problemas con un alto número de herramientas que se podrán utilizar a la hora de declarar
programas de este tipo.
Se verán las formas que toman estos procesos y para los cuales se necesitan procedimientos
más completos y que son proporcionales al número de dimensiones que se quieran trabajar,
es decir, que el grado de dificultad para escribir un programa en dos dimensiones es menor
que la que se utiliza para uno de cinco dimensiones por decir un ejemplo.
No obstante se procurará en este laboratorio simplificar las dudas que se tengan y se
trabajarán unos programas con los cuales se demuestra lo que hemos planteado en esta
ocasión. Se verá como a medida se incrementan las dimensiones, se ve afectado hasta el
tamaño de los programas en cierto modo, y la comprensión que llevan.
A pesar de los beneficios de este tipo de datos, se sigue con la limitante que solo se puede
utilizar un único tipo de dato.
4
OBJETIVOS
General:
Utilizar arreglos de dos o más dimensiones en la solución de problemas complejos
que requieren del uso de este tipo de datos con los cuales se pueden resolver una
infinidad de estos.
Específicos:
Conocer las formas de acceder a los elementos de las matrices de N dimensiones,
tanto para extraer su información así como para modificarlos.
Analizar la estructura de las matrices y comprender que estas ocupan un lugar
especifico de memoria, conociendo la forma en que se guardan los datos y los
números que utilizan las mismas.
Observar que para recorrer los datos de las matrices se usan estructuras repetitivas
anidadas que usan tantas como dimensiones se tengan.
5
Marco Teórico
El lenguaje C permite el uso de matrices, es decir, se puede extrapolar a arrays incluso n-dimensionales (o tensores), aunque en la práctica el uso de arrays con más de 2 dimensiones no es muy común. La declaración de una matriz o array bidimensional es:
tipo variable_matriz[N][M];
Donde N y M son el número de filas y de columnas respectivamente (la dimensión de la matriz). Se ha escrito la dimensión con letras mayúsculas, ya que deben ser constantes, y al igual que con vectores se suelen definir con constantes, por ejemplo:
#define N 4 //número de filas de las matrices que voy a declarar #define M 5 //número de columnas de las matrices que voy a declarar main() { double matriz1[N][M], matriz2[N][M]; int matriz_entera[N][M]; ... }
Al igual que con vectores, las matrices se numeran empezando por el índice 0, con lo cual el elemento superior izquierdo es el [0][0] y el inferior derecho es el [N-1][M-1]. En la siguiente tabla se muestra cual sería la forma y los elementos de una matriz a[4][5], de tamaño 4×5. Véase que el primer elemento del array bidimensional es el a[0][0], el siguiente sería el a[0][1], y así, hasta llegar al elemento a[3][4].
Por otra parte, en lenguaje C las matrices se almacenan en memoria "por filas", es decir, los elementos de la fila primera (de índice 0) van consecutivos y cuando acaba el último de la primera fila empieza a almacenarse el primero de la segunda, y así sucesivamente hasta llegar a la última fila. La Figura 4.4 muestra como estaría almacenada en la memoria la matriz anterior a[4][5]. Hay que aclarar que algunos lenguajes de programación (FORTRAN) utilizan el convenio contrario, es decir, almacenan las matrices por columnas.
Distribución de una matriz bidimensional en memoria
Fila 0 1 2 3
En memoria
a[0][0]
a[0][1]
a[0][2]
a[0][3]
a[1][0]
a[1][1]
a[1][2]
a[1][3]
a[2][0]
a[2][1]
a[2][2]
a[2][3]
a[3][0]
a[3][1]
a[3][2]
a[3][3]
Aunque se puede trabajar con elementos por separado de una matriz, lo habitual es hacer operaciones matriciales con todos los elementos en conjunto. Al igual que no existen operadores vectoriales en lenguaje C, no existen tampoco operadores matriciales, de manera que tales operaciones hay que realizarlas con bucles. Como ya vimos, el anidamiento de bucles permite el recorrido completo de un array bidimensional o matriz, y tal recorrido puede hacerse por filas o por columnas. A continuación se muestra un sencillo
6
algoritmo que nos permite recorrer por filas completamente matrices (ya que el bucle interno actúa sobre los índices de las columnas); en primer lugar se valoran las matrices b y c con ciertos números y seguidamente se realiza la operación matricial suma (en matemáticas se define un operador suma matricial con el mismo símbolo '+', de forma que este algoritmo sería equivalente a la suma de matrices A=B+C).
int a[4][5], b[4][5], c[4][5], i, j; // se valoran las matrices origen A y B con ciertos números. for (i = 0 ; i < 4 ; i++) for (j = 0 ; j < 5 ; j++) { b[i][j] = i; c[i][j] = (i * j) + j; } // A continuación se realiza la suma matricial for (i = 0 ; i < 4 ; i++) for (j = 0 ; j < 5 ; j++) { a[i][j] = b[i][j] + c[i][j] ; }
Además del recorrido simple por filas o por columnas, pueden existir otros bucles anidados que mezclen recorridos por filas con recorridos con columnas. Esto ocurre, por ejemplo, en el producto matricial (en matemáticas A=B×C), ya que en tal operación es necesario que una fila de B se multiplique por una columna de C.
Hay que tener en cuenta que, por un lado, la declaración de matrices implica una reserva o consumo de memoria que puede ser importante, y por otro, que el uso posterior de las mismas en bucles anidados puede suponer un tiempo de ejecución considerable. Por ejemplo si es pretende trabajar con una matriz donde se almacena un elemento tipo double por cada píxel de la pantalla de alta resolución, esto supone declarar una matriz de dimensión [1024][768], es decir, un consumo de 8×1024×768=6,291,456 bytes, o sea, más de 6 MB. Posteriormente cada vez que se ejecute un bucle anidado para recorrer tal matriz, ¡se ejecutarán 1024×768=786,432 iteraciones!
Por último y como explicamos antes, se puede extrapolar todo lo anterior a arrays n-dimensionales. Así, el siguiente ejemplo declara un tensor de dimensión 4×5×6 e inicializa todos sus elementos a cero usando tres bucles anidados:
double tensor[4][5][6]; int i, j, k; for (i = 0 ; i < 4 ; i++) for (j = 0 ; j < 5 ; j++) for (k = 0 ; k < 6 ; k++) tensor[i][j][k] = 0;
7
La utilización de punteros en C es básica para la programación avanzada y más eficiente.
Algunos de sus usos son:
Que una función modifique o escriba en los parámetros que se le envía. Pasar a una función un vector (o matriz) completo como parámetro.
Para entender el concepto de puntero, hay que recordar un poco algunos conceptos de los lenguajes de bajo nivel; los que se usan en los procesadores (CPU). Recordemos que precisamente el lenguaje C es un lenguaje de alto nivel con algunos aspectos de bajo nivel, de ahí su potencia y popularidad. En concreto, hay que repasar el concepto de dirección y dato de memoria que estudiamos en el capítulo 2. Recordemos que, al final, tras todas las traducciones necesarias para que nuestro código C se ejecute, la CPU no va a trabajar con variables abstractas, sino con direcciones de acceso a memoria (en cada una de las cuales habrá un dato). Así la siguiente asignación:
a = b ;
se traducirá en código máquina, por algo que ejecutará algo como las dos siguientes instrucciones máquina:
CARGA REGISTRO, DIR(variable b) ESCRIBE DIR(variable a), REGISTRO
Es decir, se accede primero a memoria para leer el dato de la dirección de la variable b, que se almacena en un registro de la CPU, y después se accede a memoria para escribir el dato de tal registro en la dirección de la variable a.
Este sencillo ejemplo es extensible para todas las variables que hemos estado usando, ya sean double, vectores, matrices, cadenas, etc. Por eso, en general todas las variables no son más que direcciones de memoria, y es el compilador el que realiza el "trabajo sucio" de asignar a cada variable la dirección de memoria que considere más oportuna. Pues bien, un puntero es una variable cuyo valor es precisamente una dirección de memoria. Esa es precisamente la clave por la cual un programa escrito usando punteros suele ser más rápido (eficiente) que otro que no los usa: usar punteros, es decir, direcciones, evita muchas instrucciones que el compilador necesita introducir a veces para calcular las direcciones de las variables.
Pero, claro está, si se inventaron los compiladores para evitar el arduo trabajo de conocer o traducir las variables a sus direcciones, no tendría sentido tener que trabajar en un lenguaje de alto nivel como C con direcciones. Por eso, en realidad, las direcciones que van a contener los punteros se van a usar, pero no tienen por qué conocerse nunca. Eso sí, el puntero debe tener una dirección útil, es decir, la dirección de otra variable de memoria. Para comprender mejor este concepto, en la siguiente Figura se han representado los bytes de memoria, cuyas direcciones son la 0xF000F100, 0xF000F101, etc.(es decir, direcciones de 32 bits, como es habitual en los ordenadores actuales). Supongamos que una variable a tipo char (es decir, ocupa un byte) contiene la letra 'W', y va a ser colocada por el compilador en la dirección 0xF000F107. Por otro lado, una variable puntero p es colocada por el compilador en la dirección 0xF000F100. Si queremos que el puntero contenga la dirección de a, entonces a partir del byte de la dirección 0xF000F100 debe estar escrito el valor de la dirección de a: 0xF000F107. Este valor ocupa 4 bytes, que serán: 0xF0, 0x00, 0xF1, 0x07, los cuales se observan en la Figura
8
Mapa de memoria
Dirección de memoria
Dato en memoria
Variable abstracta
0xF000F100 0xF0
puntero p; apunta a la variable a
0xF000F101 0x00
0xF000F102 0xF1
0xF000F103 0x07
0xF000F104 irrelevante irrelevante
0xF000F105 irrelevante irrelevante
0xF000F106 irrelevante irrelevante
0xF000F107 'W' a
0xF000F108 irrelevante irrelevante
0xF000F109 irrelevante irrelevante
Se dice que el valor de un puntero "apunta" a una variable (en el ejemplo anterior, "p apunta a a", de ahí que la flecha de la figura salga de p y apunte a la variable a). También se suele expresar como que "el contenido de p es a". Notemos finalmente que el tamaño de un puntero depende del tamaño de una dirección, en nuestro caso 32 bits, pero no de lo que apunta el puntero. Es decir, si la variable puntero p apuntase a otra variable x tipo double, aunque x tendría un tamaño de 8 bytes, el puntero seguiría ocupando 4 bytes, es decir 32 bits de dirección.
La manera formal de declarar en C un puntero es la siguiente:
tipo *nombre_variable
Donde tipo es cualquier tipo válido de C y nombre_variable es el nombre de la variable puntero. El símbolo asterisco indicará al compilador que dicha variable es un puntero.
Existen dos operadores especiales unarios (sólo necesitan un operando) para el manejo de los punteros: & y *. El operador & devuelve la dirección de memoria de una variable, de ahí que se llame operador de dirección. El operador *, llamado por el contrario de "indirección", devuelve el valor de la variable a la que apunta. En el ejemplo anterior si se quiere que "p apunte a a", ha de hacerse: p=&a, es decir, la dirección de a la introducimos en p. Tras esto, si se quiere saber "el contenido de p" puede recurrirse a: *p, que nos daría el valor de a, o sea 'W'.
Sigamos viendo con otro ejemplo cual sería, intuitivamente, el funcionamiento de estos operadores. Supongamos que tenemos cuatro variables definidas, las dos primeras como variables enteras, a las cuales llamaremos a y b, y las otras, punteros a variable entera: p1 y p2. En general, es una buena costumbre para empezar a trabajar con punteros, nombrarlos empezando por la letra 'p', para distinguir claramente las variables que son punteros de las que son datos. La manera de declarar estas variables en C sería:
int a, b; int *p1, *p2;
9
O bien, todo junto:
int a, b, *p1, *p2;
Supongamos que el compilador ha ubicado las variables en memoria como muestra la Figura 4.6 (tenga en cuenta que un entero ocupa 32 bits, 4 bytes, lo mismo que un puntero, supuestas las direcciones de 32 bits). Tras la declaración de las cuatro variables, ninguna tiene un valor definido: a y b contendrán un valor indefinido y los punteros p1 y p2, no apuntarán a ningún sitio, como se muestra en esta tabla:
Mapa de memoria
Dirección de memoria Dato en memoria Variable abstracta
0xF000F100 Valor indefinido a
0xF000F104 Valor indefinido b
0xF000F108 Dirección indefinida p1
0xF000F10B Dirección indefinida p2
Para empezar a trabajar con las variables, asignamos un valor a cada una:
a = 3; p1 = &a; p2 = &b; b = (*p1) + 5;
El resultado que obtendremos es el que muestra en esta tabla:
Mapa de memoria
Dirección de memoria Dato en memoria Variable abstracta
0xF000F100 3 a
0xF000F104 8 b
0xF000F108 0xF000F100 p1
0xF000F10B 0xF000F104 p2
¿Qué ha ocurrido? Evidentemente, a contiene un 3, p1 contiene la dirección de a (0xF000F100), y p2 la de b (0xF000F104), debido a las tres primeras líneas de código. Pero la cuarta línea ha valorado la variable b con "lo que apunta p1" más 5. Como "lo que apunta p1" es a, y a vale 3, el resultado es que b tendrá un 8.
Obsérvese que, aunque en el ejemplo se muestran las direcciones para aclarar los conceptos, éstas no van a aparecer nunca en el código C, tal como dijimos antes. Por otro lado, conviene resaltar para fijar ideas que la dirección de cualquier variable es siempre constante (es decir, &a , &b son constantes), mientras que el contenido de un puntero (*p1, *p2) es variable.
10
Pero ¿y si hacemos después lo siguiente:?
(*p2) = (*p1) + 100;
Aquí hemos valorado de forma "indirecta" a la variable b, puesto que p2 apunta a b, y la expresión (*p2) a la izquierda de la asignación, conduce a asignar un valor a b. Por tanto, lo anterior es lo mismo que escribir:
b = a + 100;
Naturalmente la última línea que hemos escrito es más sencilla y comprensible que la penúltima, y por tanto, esta forma de usar no es realmente útil, pero es la mejor manera de comprender los punteros, para usarlos luego cuando realmente sea interesante. A partir de ahora evitaremos el uso de paréntesis en los accesos a los contenidos de los punteros como *p2, puesto que el operador de indirección * tiene una prioridad muy alta (ver apartado 4.2) y no se requieren ni son usuales tales paréntesis.
Aclaremos a continuación que varios punteros pueden apuntar a la misma variable, de forma que la modificación del "contenido de uno de los punteros", implicaría la modificación automática del contenido del resto de punteros (y, por supuesto, de la variable). Esta característica es la que nos permitirá poder modificar los parámetros de llamada de una función, y que esta modificación permanezca cuando salgamos de dicha función. Fijémonos en el siguiente trozo de código:
int a=130; int *p1, *p2, *p3; p1 = &a; p2 = p1; //p2=&a haría lo mismo p3 = p2; //p3=&a haría lo mismo
Las siguiente Figura 4.8 muestra la memoria, suponiendo que las variables empiezan a colocarse a partir de la dirección de memoria 0xF000F100.
Mapa de memoria
Dirección de memoria Dato en memoria Variable abstracta
0xF000F100 130 a
0xF000F104 0xF000F100 p1
0xF000F108 0xF000F100 p2
0xF000F10B 0xF000F100 p3
Por lo tanto, cualquier cambio en el valor de a, o del contenido de los punteros, implicaría un cambio en todas las variables. Así, *p3 = 50, es idéntico que a=50 o *p1=50, y en cualquier caso automáticamente tendremos que a valdrá igual que *p1, *p2 o *p3, o sea, 50.
11
Por último en este apartado hemos de explicar un "puntero especial" que siempre existe cuando se declara un vector (o matriz): se trata del propio nombre del vector. En efecto, la declaración de un vector como:
double vect[100];
implica que, mientras que cada una de las componentes vect[0], vect[1], etc. es un double, la expresión vect sería un puntero a double, que, como es evidente apunta al propio vector. En concreto la expresión vect no es una variable, sino simplemente la dirección (una constante) del vector. De esa forma, tiene sentido modificar un puntero declarado como tal pero no intentar modificar la dirección de vect:
double vect[100], d; double *pd1, *pd2; pd1 = vect; // pd1 también apunta ahora al vector pd2 = &d; // modificación del puntero pd2 vect = pd2; // error de compilación, vect es una constante
El uso del nombre de un vector como dirección o puntero constante, es vital para el paso de vectores como parámetros.
12
Asignación
1. Investigar las siguientes funciones de ANSI C:
GETS:
char *gets(char *cadena);
Esta función lee caracteres desde el stream apuntado por stream stdin, en el array apuntado
por cadena, hasta que se encuentre un final de fichero (EOF) o un carácter de línea nueva
es leído. Cualquier carácter de línea nueva es descartado, y un carácter nulo es escrito
inmediatamente después del último carácter leído en el array.
Valor de retorno:
La función gets retorna cadena si es realizada con éxito. Si un final de fichero (EOF) es
encontrado y ningún carácter ha sido leído en el array, entonces el contenido del array
permanece invariable y un puntero nulo es retornado. Si ocurre un error de lectura durante
el proceso, el contenido del array es indeterminado y un puntero nulo es retornado.
Ejemplo:
#include <stdio.h>
int main()
{
char oracion[81];
printf( "Escribe una oracion:\n");
printf( "\nHas escrito: \"%s\"\n", gets(oracion) );
return 0;
}
13
RANDOM:
int rand(void);
La función rand calcula una secuencia de números enteros pseudo-aleatorios en el intervalo
de 0 á RAND_MAX.
Valor de retorno
La función rand retorna un entero pseudo-aleatorio.
Ejemplo:
#include <stdio.h>
#include <stdlib.h>
int main(){
unsigned int i=1;
printf( "30 numeros generados aleatoriamente: \n\n" );
for( i=1; i<30; i++ )
printf( "%d, ", rand() );
printf( "%d\n", rand() );
return 0;
}
14
2. Escribir un programa que multiplique dos matrices:
//PROGRAMA QUE MULTIPLICA DOS MATRICES
#include <stdlib.h>
#include <stdio.h>
#define SIZE 100
int main(int main, char *argv[])
{
float matriz1[SIZE][SIZE], matriz2[SIZE][SIZE];
float solucion[SIZE][SIZE], suma;
int filas1,filas2, col1, col2, i, j, k;
printf("\nEste programa realiza el producto de dos
matrices.\n");
printf("ADVERTENCIA: las matrices no pueden tener dimensiones
de filas o columnas mayores que 100.\n");
do{
printf("\nIntroduzca la dimension de las
matrices:\n");
printf("\nfilas matriz1: ");
scanf("%d", &filas1);
printf("columnas matriz1: ");
scanf("%d", &col1);
printf("\nfilas matriz2: ");
scanf("%d", &filas2);
printf("columnas matriz1: ");
scanf("%d", &col2);
if ( (filas1 >100 || col2 > 100) || (filas2 > 100 ||
col2 > 100) )
printf("\nLa matrices no pueden tener
dimensiones de filas y columnas mayores q 100.\n\n");
}while( (filas1 >100 || col2 > 100) || (filas2 > 100 || col2 >
100) );
if ( col1 != filas2)
{
printf("\n\nEl numero de columnas de matriz 1");
printf(" debe ser igual al numero de filas de matriz
2\n\n");
}
if (col1 == filas2)
{
printf("\nIntroduzca los elementos de la primera
matriz:\n");
for (i = 0; i < filas1; i++) {
for (j = 0; j < col1; j++) {
printf("a(%d,%d): ", i + 1, j + 1);
scanf("%f", &matriz1[i][j] );
}
}
printf("\n");
printf("\nIntroduzca los elementos de la segunda
matriz:\n");
15
for (i = 0; i < filas2; i++)
{
for (j = 0; j < col2; j++)
{
printf("b(%d,%d): ", i + 1, j + 1);
scanf("%f", &matriz2[i][j] );
}
}
printf("\n");
for (i = 0; i < col1; i++)
{
for (j = 0; j < col2; j++)
{
suma = 0.0;
for (k = 0; k < col1; k++)
suma += matriz1[i][k] *
matriz2[k][j];
solucion[i][j] = suma;
}
}
printf("\nSolucion:\n\n\n");
for (i = 0; i < filas1; i++)
{
for (j = 0; j < col2; j++)
printf("%8.2f ", solucion[i][j]);
printf("\n");
}
}
printf("\n");
system("pause");
return 0;
}
16
17
3. Hacer los cambios necesarios al código anterior para que también se pueda
encontrar la matriz inversa del producto efectuado.
#include <stdlib.h>
#include <stdio.h>
#define col 100
#define fil 100
int main(){
int filaA, columnaA, filaB, fA, cB, columnaB, i, j, k;
float mat[fil][col];
float matb[fil][col];
float matc[fil][col];
float mati[fil][col];
printf("\nINGRESE DIMENSIONES DE LAS MATRICES, FILAS POR
COLUMNAS, PRIMERO DE A Y LUEGO DE B\n");
printf("\n");
//RESTRICCIONES MATRICES A INGRESAR, NUMEROS NO NEGATIVOS Y
MENORES QUE 100, COLUMNA A IGUAL A FILA MATRIZ B
do{
do{
printf("Numero de filas Matriz A: "); scanf("%d", &filaA);
printf("Numero de columnas Matriz A: "); scanf("%d", &columnaA);
printf("Numero de filas Matriz B: "); scanf("%d", &filaB);
printf("Numero de columnas Matriz B: "); scanf("%d", &columnaB);
if ((filaA <= 0 || filaA >100) || (columnaA <= 0 || columnaA >=
100) || (filaB<= 0 || filaB >100) || (columnaB <= 0 || columnaB
>= 100) )
printf("\nNUMERO MAXIMO PARA FILAS Y/O COLUMNAS: %d\n", fil);
}while ((filaA <= 0 || filaA >=100) || (columnaA <= 0 ||
columnaA >= 100 )||(filaB<= 0 || filaB >100)||(columnaB <= 0 ||
columnaB >= 100));
if (columnaA != filaB)
printf("¡¡¡EL NUMERO DE COLUMNAS DE LA MATRIZ A TIENE QUE SER
IGUAL AL NUMERO DE COLUMNAS DE LA MATRIZ B!!! \n");
}while (columnaA != filaB);
// LEER MATRIZ A
printf("Ingrese matriz A\n");
for (i = 0; i < filaA; i++)
for ( j = 0; j < columnaA ; j++)
{
printf("Elemento[%d, %d] = ", i+1, j+1); scanf("%f",
&mat[i][j]);
}
printf("\n");
//LEER MATRIZ B
18
printf("Ingrese matriz B\n");
for (i = 0; i < filaB; i++)
for ( j = 0; j < columnaB ; j++)
{
printf("Elemento[%d, %d] = ", i+1, j+1);
scanf("%f", &matb[i][j]);
}
printf("\nLA MATRIZ MULTIPLICACIÓN ES: \n");
//MULTIPLICAR MATRIZ A Y B
for ( i = 0; i < filaA; i++)
{
for (j = 0; j < columnaB; j++)
for(k = 0; k < columnaA; k++)
{
matc[i][j]=(matc[i][j] +(mat[i][k]*matb[k][j]));
}}
//MOSTRAR RESULTADO DE MULTILPLICACION EN MATRIZ C
for ( i = 0; i < filaA; i++)
{
for (j = 0; j < columnaB; j++)
printf("%.2f\t", matc[i][j]);
printf("\n");
}
//MATRIZ INVERSA
filaA = columnaB;
filaB = columnaA;
for (i= 0; i < filaA; i++)
for (j = 0; j < columnaB; j++)
{
mati[j][i] = matc[i][j];
}
printf("\nLA MATRIZ INVERSA DE LA MATRIZ MULTIPLICACION ES:\n");
//IMPRIMIR INVERSA
for (i = 0; i < filaA; i++)
{
for (j = 0; j < columnaA; j++)
printf("%.2f\t", mati[i][j]);
printf("\n");
}
system("pause");
return 0;
}
19
20
4. Si una matriz se declara como double A [ 4 ][ 5 ], responder las siguientes
preguntas:
¿Cuántos Bytes utiliza?
Ya que un dato tipo double ocupa 8 bytes de memoria y son 20 elementos de la matriz,
entonces se utilizan 8 x 20 = 160 Bytes.
¿Cómo se inicializa?
Double [ ] [ ] {
{elemento, elementoN}
{elemento, elementoN}
}
Se puede inicializar con los valores, de la forma mostrada anteriormente, o también desde
línea de comando. También otra alternativa es ingresarlos con dos ciclos repetitivos, con
dos for, por ejemplo, uno que lleve la cuenta de las filas y otras que lleve el número de
columnas.
Si la dirección de memoria del elemento A[ 2 ][ 3 ] es 0XFEABACA
1. ¿Cuál es la dirección del elemento A[ 0 ][ 0 ]?
Nos auxiliaremos de la siguiente tabla para ubicarnos en los espacios de memoria con los
que cuenta la matriz, esta tabla presenta las posiciones:
0 1 2 3 4
0 A[0][0] A[0][1] A[0][2] A[0][3] A[0][4]
1 A[1][0] A[1][1] A[1][2] A[1][3] A[1][4]
2 A[2][0] A[2][1] A[2][2] A[2][3] A[2][4]
3 A[3][0] A[3][1] A[3][2] A[3][3] A[3][4]
Debido a que las ubicaciones de memoria se cuentan en bytes, y se van llenando primero
las filas y luego las columnas, entonces podemos hacer una tabla que represente las
posiciones de memoria partiendo de la que nos da el problema.
Lo que se hace en el primer caso es que se multiplica el número de espacios que hay antes
de 0xFEABACA por 8 bytes que ocupa cada uno, son 13 espacios antes por 8 bytes cada
uno por ser double, esto hace un total de 104 bytes, convertido a hexadecimal este número
es: 0x68
21
Entonces la posición de la memoria es:
0xFEABACA – 0x68 = 0xFEABA62
2. ¿Cuál es la dirección de A[ 3 ][ 4 ]?
Ahora lo que se hace es sumar a la posición que nos dan los espacios que hay desde ella
hasta el elemento que se nos pide, son 6 espacios por 8 = 48 (0x30) Bytes de memoria,
entonces la posición es:
0xFEABACA + 0x30 = 0xFEABAFA
Así se indica en la siguiente representación de matriz:
0 1 2 3 4
0 0xFEABA62
1
2 0xFEABACA
3 0xFEABAFA
Mostrar todos las formas posibles de acceder a los elementos de A
- La forma más fácil es indicar la manera general de ingresar, que es poniendo
el nombre de la matriz y entre corchetes el elemento al cual se quiere
ingresar:
A[ 2 ][ 4 ] = 20
Con este ejemplo modificamos el valor que tenía la posición por 20.
- También se puede hacer por medio de un puntero:
*(b + fila) + columna, donde no se necesita que elemento se llame por su posición, sino
que el puntero mismo se mueve por todas las direcciones y devuelve el valor que está en
ella.
- Si se quiere recorrer elemento por elemento se hace con dos ciclos anidados
los cuales ingresan a las posiciones primero en fila y luego por columnas:
for (f = 0; f < filas; f++ )
for (c = 0; f < columnas; c++){
sentencias
}
22
Mostrar todas las formas para trabajar con las direcciones de los elementos
de A
- Se puede efectuar con punteros:
int *punt;
int matriz[ 4 ];
printf(“La dirección del elemento es %p”, &matriz[ 3 ]);
¿Cuáles son los índices de inicio y final de la matriz A?
A [0] [0] para primer elemento.
A [3] [4] para el último elemento.
¿Cuántos y cuáles punteros están en juego?
Si no se declaran más punteros que la misma matriz sólo existe ese, es decir, el mismo
puntero es una matriz y sólo existe ese.
Hacer un dibujo que incluya todos los punteros que están presentes, incluir
también la matriz.
A[0][0] A[0][1] A[0][2] A[0][3] A[0][4]
A[1][0] A[1][1] A[1][2] A[1][3] A[1][4]
A[2][0] A[2][1] A[2][2] A[2][3] A[2][4]
A[3][0] A[3][1] A[3][2] A[3][3] A[3][4]
El puntero declarado cambiará su dirección dependiendo del ciclo a usar, y se moverá por
todos los elementos.
*(punt + fila) + columna = 0XFEABA62
23
5. Hacer un programa en ANSI C que pueda indicar si una matriz es identidad
o no.
#include <stdlib.h>
#include<stdio.h>
#define SIZE 100
int main (int argc, char *argv[])
{
int matrizA[SIZE][SIZE];
int matrizB[SIZE][SIZE];
int af1, filas, col, i, j;
printf("\nEste programa identifica una matriz
identidad.\n");
printf("ADVERTENCIA: La matriz no puede tene dimensiones
de filas y columnas mayores q 100.\n");
do
{
printf("\nIntroduzca la dimension de las
matrices:\n");
printf("\nfilas matriz: ");
scanf("%d", &filas);
printf("columnas matriz: ");
scanf("%d", &col);
if( (filas >100 || col > 100) )
printf("\nLa matriz no puede tener
dimensiones de filas y columnas mayores q 100.\n\n");
}while( (filas >100 || col > 100) );
if ( filas != col )
printf("\n\nEl numero de filas debe ser igual al
numero de columnas\n\n\n");
if ( filas == col )
{
printf("\nIntroduzca los elementos de la
matriz:\n");
for (i = 0; i < filas; i++) {
for (j = 0; j < col; j++) {
printf("a(%d,%d): ", i + 1, j +
1);
scanf("%d", &matrizA[i][j] );
}
}
for (i = 0; i < filas; i++)
{
for (j = 0; j < col; j++)
matrizB[i][j] = 0;
}
24
for (i = 0; i < filas; i++)
{
for (j = 0; j < col; j++)
{
if ( i == j)
matrizB[i][j] = 1;
}
}
for (i = 0; i < filas; i++)
{
for (j = 0; j < col; j++)
{
if ( matrizA[i][j] ==
matrizB[i][j])
af1 = 1;
else
af1 = 0;
}
}
if ( af1 == 1)
{
printf("\nLA MATRIZ ES IDENTIDAD\n.");
printf("\nLa matriz introducida
es:\n\n");
for (i = 0; i < filas; i++)
{
for (j = 0; j < col; j++)
printf("%d\t",
matrizA[i][j]);
printf("\n\n");
}
}
else
{
printf("\nLA MATRIZ NO ES
IDENTIDAD\n.");
printf("\nLa matriz introducida
es:\n\n");
for (i = 0; i < filas; i++)
{
for (j = 0; j < col; j++)
printf("%d ",
matrizA[i][j]);
printf("\n\n");
}
printf("\nPara ser matriz identidad debe
ser de la siguiente manera:\n\n");
for (i = 0; i < filas; i++)
{
for (j = 0; j < col; j++)
25
printf("%d ",
matrizB[i][j]);
printf("\n\n");
}
}
}
system("pause");
return 0;
}
26
6. Hacer un programa en ANSI C que pueda resolver un sistema de ecuaciones
por el método de Gauss Jordan.
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#define EPS 1.e-12
void main(void)
{
int i, j, n, vRet;
double **a, *x, *b;
double **crearMatriz(int m, int n);
int triang(int n, double **a, double *b);
int pivot(int n, int k, double **a);
void sust(int n, double **a, double *b, double *x);
void escribir(int n, double *x);
/* Lectura de datos y creaci¢n dinamica de matrices y
vectores */
printf("\nNumero de ecuaciones: ");
scanf("%d", &n);
a=crearMatriz(n, n);
printf("\nMatriz del sistema:\n");
for (i=0; i<n; i++)
for(j=0; j<n; j++)
{
printf("a(%d, %d): ", i+1, j+1);
scanf(" %lf", &a[i][j]);
}
b=calloc(n, sizeof(double));
x=calloc(n, sizeof(double));
printf("\nTermino independiente:\n");
for (i=0; i<n; i++)
{
printf("b(%d): ", i+1);
scanf(" %lf", &b[i]);
}
/* Triangularizacion de la matriz */
vRet=triang(n, a, b);
if (vRet!=1)
{
printf("Ha aparecido un pivot nulo o muy
pequeño. Agur! \n");
exit(1);
}
/* Vuelta atras */
sust(n, a, b, x);
/* Escritura de resultados */
escribir(n, x);
system("pause");
} /* Fin de main() */
27
/* Funcion para hallar el pivot de la columna k */
int pivot(int n, int k, double **a)
{
int i, imaximo;
double maximo=0.0;
double va(double);
for (i=k; i<n; i++)
{
if (maximo<va(a[i][k]))
{
maximo=va(a[i][k]);
imaximo=i;
}
}
return imaximo;
}
/* Funcion para realizar la triangularizacion de una matriz */
int triang(int nec, double **a, double *b)
{
int i, j, k, ipivot, error;
double fac, *temp;
double va( double );
for (k=0; k<nec-1; k++)
{
ipivot=pivot(nec, k, a);
/* intercambio de los punteros a las filas k e
imaximo */
temp=a[k];
a[k]=a[ipivot];
a[ipivot]=temp;
fac=b[k];
b[k]=b[ipivot];
b[ipivot]=fac;
for (i=k+1; i<nec; i++)
{
if (va(a[k][k]) < EPS)
return error=-1;
fac=-a[i][k]/a[k][k];
for (j=k; j<nec; j++)
a[i][j]+=a[k][j]*fac;
b[i]+=b[k]*fac;
}
}
return error=1;
}
void sust(int n, double **a, double *b, double *sol)
{
int i, k;
double sum;
for (k=n-1; k>=0; k--)
{
sum=0.0;
for (i=k+1; i<n; i++)
sum+=a[k][i]*sol[i];
28
sol[k]=(b[k]-sum)/a[k][k];
}
}
double **crearMatriz(int m, int n)
{
double **matriz, *mat;
int i;
matriz=calloc(m, sizeof(double *));
matriz[0]=mat=calloc(m*n, sizeof(double));
for(i=1; i<m; i++)
matriz[i]=mat+n*i;
return matriz;
}
void escribir(int n, double *solucion)
{
int i;
printf("\nEl vector solucion es: \n");
for (i=0; i<n; i++)
printf("solucion(%d)= %.4lf\n", i, solucion[i]);
printf("\n");
}
double va(double u)
{
if (u<0.0)
return -u;
else
return u;
}
29
30
7. En una matriz de n x n escribir las tablas correspondientes, por ejemplo para
n = 4:
El último dato es 16 ya que nxn = 16. Considerar un máximo de n = 20 y este
dato (el valor de n) debe ser introducido desde la línea de comandos, se
recomienda utilizar la función atoi( ) que se encuentra en stdlib.h
#include <stdio.h>
#include <stdlib.h>
#define SIZE 20
int main(int main, char *argv[])
{
int matriz[SIZE][SIZE];
int i, j, n, k = 1;
n = atoi(argv[1]);
if ( n > 20)
{
printf("ADVERTENCIA: ");
printf("las matrices no pueden tener dimensiones
de filas o columnas mayores q 20.\n\n");
return 0;
}
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
matriz[i][j] = k * (j +1);
}
k++;
}
printf("\nTablas:\n\n");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf("%3d ", matriz[i][j]);
printf("\n\n");
}
printf("\n");
system("pause");
return 0;
}
31
32
CONCLUSIONES
Con el presente trabajo hemos desarrollado los conceptos investigados y los usos
de Arreglos de n-dimensiones implementando el manejo de cadenas de caracteres,
el uso de punteros; con los cuales ya no solo le demos un tamaño de memoria sino
que además podemos ahora evaluar a través de un puntero y asignar las variables
que deseamos y el tipo, y cantidad de memoria a asignarle.
Se ha observado que para ingresar a los datos alojados en las matrices o arreglos
que estamos trabajando se pueden hacer de muchas formas, desde una declaración
simple hasta el uso de ciclos repetitivos utilizando punteros. No hay una única
solución para implementarlo, sino que se las maneras de procesar los datos son
diversas y estos nos ayudan a optimizar los programas y a recortar los recursos a
utilizar en la memoria.
Cada dato almacenado está ocupando una dirección específica de memoria con la
cual se identifica el dato, y se debe tener cuidado que el puntero contiene la
dirección de memoria, no su contenido, por ello el programa se hace más ágil y
mejor optimizado en cuanto a recursos.
33
BIBLIOGRAFÍA
Luis Joyanes Aguilar. Programación en C
Guías de Laboratorio IIE-115