jueves, 7 de julio de 2011

ANSI C tarea: Usando ciclos, apuntadores, arreglos y subrutinas

Existen tres tipos de ciclo en C: while, do..while y for. En el post ANSI C extra: while, do while y for pueden ser equivalentes se demostró su uso y además se compararon estos ciclos utilizando un pequeño ejemplo.

En esta ocasión, utilizaré otro ejemplo para ilustrar el uso que se puede da a apuntadores, arreglos y subrutinas en C.

En C, un apuntador es una variable "especial" que contiene la dirección de memoria de algún elemento que estamos utilizando en nuestro programa. Este elemento puede ser otra variable, un tipo definido por el programador e inclusive, otro apuntador.

Los arreglos utilizan apuntadores para navegar a través de los elementos que contienen. Así, cuando se crea un arreglo, el nombre del arreglo indica un apuntador al elemento 0 (primer elemento del arreglo) y a través de ciclos, podemos realizar alguna operación o simplemente conocer a los elementos que integran al arreglo. Un arreglo puede contener dentro de sí mismo a otros arreglos, lo que llamamos "Arreglo multidimensional", el cual es muy útil para representar por ejemplo, matrices en matemáticas.

Otro aspecto del lenguaje C que se ejemplifica en este post es el uso de subrutinas. Las subrutinas son métodos definidos cada uno en su propio archivo. De esta manera, a través del uso de subrutinas, el programa que creamos se vuelve más simple de manejar y adquiere un aspecto llamado "modularidad", esto quiere decir que dividimos las distintas funciones que queremos que realice en porciones o módulos que, debido a que se encuentran definidos cada uno en un archivo por separado, es posible reutilizarlos más adelante sin tener que volver a teclear el mismo código o pegarlo dentro de otro archivo.

El ejemplo que estoy presentando es la generación del Triángulo de Pascal. Este triángulo es una herramienta que nos permite conocer los coeficientes del polinomio que resulta al elevar un binomio a cierta potencia. El número de renglón corresponde a la potencia a la que se elevó el binomio. Por ejemplo, si elevamos el binomio (x + 1) al cuadrado obtendremos x² + 2x + 1. Luego, El triángulo de Pascal tiene la siguiente forma:

1

1 2 1

1 3 3 1

.

.

.

La segunda fila (1 2 1) corresponde a nuestro resultado de elevar el binomio al cuadrado: 1 es el coeficiente de x², 2 es el coeficiente de 2x y 1 es el coeficiente de 1².

Cada elemento después del primer 1 se obtiene sumando los dos elementos que se encuentran en la fila anterior y hacia la derecha del valor que deseamos obtener, así, en la fila 3 por ejemplo el 3 se forma de la suma de 1 y 2.

La implementación del ejemplo la dividí en varios módulos, cada uno en su propio archivo. Para poder compilarlos es necesario crear un encabezado donde se colocan las librerías a utilizar, las definiciones tanto de variables como de estructuras y los nombres y parámetros de los distintos métodos seguidos de punto y coma sin nada de su implementación.

El encabezado (al que llamé trianguloHeader.h) de este ejemplo es el siguiente:

#include 
#include 
#include 
#include 
#include 

#define TRUE 1
#define FALSE 0
typedef short boolean;

#define SIN_DEFINIR -1
#define LARGO_MAXIMO 12
#define MAX_DIMENSION 30

typedef struct una_matriz{
  int filas;
  int columnas;
  int** elementos;
}matriz;

char pide_opcion(char* permitidos);
int pide_numero(int minimo, int maximo);
void vacia(matriz* m);
void llena(matriz* m);
void imprime(matriz m);

Después, en otro archivo reutilicé el método para pedir un valor numérico al usuario. A este archivo lo llamé pedirNumero.c. Su contenido es el siguiente:

#include "trianguloHeader.h"

int pide_numero(int minimo, int maximo){
  int numero = SIN_DEFINIR;
  char* entrada = NULL;
  char actual;
  int posicion;
  double temporal;

  entrada = 
    (char*)malloc(sizeof(char)*LARGO_MAXIMO + 1);
  if(entrada == NULL){
    return SIN_DEFINIR;
  }
  do{
    printf("Dame un valor entre %d y %d: ", 
    minimo, maximo);
    posicion = 0;
    do{
      actual = getchar();
      if(posicion < LARGO_MAXIMO){
 if(isdigit(actual)){
   entrada[posicion++] = actual;
 }
      }
    }while(actual != '\n');
    entrada[posicion] = '\0';
    temporal = atof(entrada);
    if(temporal > INT_MAX - 1){
      continue;
    }
    numero = atoi(entrada);
  }while(numero < minimo || numero > maximo);
  free(entrada);
  return numero;
}

El siguiente archivo que creé contiene el método pedir_opcion, que también estoy reutilizando del material visto en clase. A este archivo lo nombré opcionSiNo.c. Este es su contenido:

#include "trianguloHeader.h"

char pide_opcion(char* permitidos){
  char actual;
  boolean listo = FALSE;

  while(TRUE){
    if(!listo){
      actual = tolower(getchar());
      if(strchr(permitidos, actual) != NULL){
 listo = TRUE;
      }
    } else{
      if(getchar() == '\n'){
 if(listo){
   break;
 }
      }
    }
  }
  return actual;
}

Después creé el archivo llenarTriangulo.c para almacenar la funcionalidad de llenado. La mayoría de este código también está basado en el material visto en clase:

#include "trianguloHeader.h"

void llena(matriz* m){
  int i, j, k;

  printf("Llenando el Triangulo de Pascal\n");
  printf("Cuantos renglones quieres que tenga?\n");
  m->filas = pide_numero(1, MAX_DIMENSION);
  
  m->elementos = (int**)malloc(sizeof(int*)*(m->filas));
  if(m->elementos == NULL){
    printf("No se pudo reservar memoria.\n");
    return;
  }
  for(i = 0; i < m->filas; i++){
    m->elementos[i] = 
      (int*)malloc(
     sizeof(int)*m->filas);
    for(j = 0; j < m->filas; j++){
      m->elementos[i][j] = 0;
    }
  }

  i = 0;
  j = 0;
  m->elementos[i][j] = 1;
   for(i = 1; i < m->filas; i++){
     j = 0;
     m->elementos[i][j] = 1;
     for(j = 1; j < m->filas;j++){
 m->elementos[i][j] = m->elementos[i-1][j-1]
     + m->elementos[i-1][j];
      }
   }
}

Otra funcionalidad que ocupé fue la de vaciar la matriz donde almacené los valores del triángulo de Pascal con el objetivo de liberar memoria. Este otro método también está basado en el material visto en clase. El archivo se llama vaciarTriangulo.c:

#include "trianguloHeader.h"

void vacia(matriz* m){
  int i;

  m->filas = SIN_DEFINIR;
  m->columnas = SIN_DEFINIR;

  for(i = 0; i < m->filas; i++){
    free(m->elementos[i]);
  }
  free(m->elementos);
  return;
}

La última funcionalidad que agregué es la de imprimir el triángulo reutilizando el método de imprimir una matriz bidimensional visto en la clase. El archivo se llama imprimeTriangulo.c:

#include "trianguloHeader.h"

void imprime(matriz m){
  int i, j, k;

  for(i = 0; i < m.filas; i++){
    for(j = 0; j <= i; j++){
      printf("%s%3d", (j == 0? "": "  "),
       m.elementos[i][j]);
    }
    printf("\n");
  }
  return;
}

Finalmente, el método principal, desde donde se mandan a llamar a casi todas mis funcionalidades se encuentra en el archivo trianguloPascal.c:

#include "trianguloHeader.h"

int main(int argc, char** args){

  boolean opcion = FALSE;
  do{
    matriz m;
    llena(&m);
    imprime(m);
    vacia(&m);

    printf("Deseas generar otro triangulo (s/n)?\n");
    opcion = (pide_opcion("sn") == 's');

  }while(opcion);
}

Es importante resaltar que todos los archivos, incluyendo el principal (trianguloPascal.c) deben incluir al encabezado (trianguloHeader.h) para poder compilarlos juntos. La forma de compilar y algunas salidas del programa se pueden observar en la siguiente imagen:

Referencias

"Pascal's Triangle". Wolfram MathWorld. 2011

Pointers.Te GNU C Programming Tutorial, Edition 4.1

Loops.Te GNU C Programming Tutorial, Edition 4.1

1 comentario:

  1. Excelente :) Un reto: hazlo con solamente dos arreglos unidimensionales de tamaño n/2 donde n es el número de la última fila a generar ;) Te pongo 15.

    ResponderEliminar