Empezamos los 101, una serie de “glosarios” o cursos donde intento resumir de manera concisa y detallada muchos detalles de algunos lenguajes de programación o algunos lenguajes que si no están relacionados directamente con la programación, pueden servir para su desarollo.

Este curso será de C, a partir de la experiencia que he adquirido en mi primer año de universidad, con el fin de que sea útil a cualquiera. De todas maneras, toda esta información gratuita sobre el tema se puede encontrar en internet, siendo la madre de las soluciones de las dudas de la programación Stack Exchange y más en concreto, SuperUser.

— EN CONSTRUCCIÓN —
— EN CONSTRUCCIÓN —

Aviso: Aunque la mayoría de los ejemplos son de creación propia, algunos están inspirados en mis clases de universidad, así que puede haber algún gazapo que se me haya escapado, sobre todo en los ejemplos más grandes. Además, otros ejemplos o explicaciones están inspiradas en los artículos de tutorialspoint, con muy buenas explicaciones en C.

Vamos a organizarlo en unos puntos principales:

Variables

Sentencias y bucles

Funciones

Tablas, o arrays

Punteros

Estructuras

Strings

Memoria dinámica de memoria

Archivos de texto

Miscelánea

¡Y empezamos!

Variables

Empezemos por lo básico, por donde comenzaremos a usar el mundo de la programación: las variables. Éstas consisten en los datos que iremos manejando en nuestras aplicaciones. En C tienen la siguiente estructura: primero se declara el tipo de dato (lo vemos en el siguiente punto), después el nombre de nuestra variable, y de forma opcional se puede declarar directamente el valor asociado a la variable.

Vamos a ver un ejemplo rápido:

int numero = 5;
char palabra = "Hola";

// cómo añadir un valor a una variable vacía

int numero;
numero = 5; // int numero = 5;

Recordatorio básico

  • Punto y coma (semicolon, ; ) = En C, el punto y coma es final de línea. Recuerda ponerlo siempre, porque si no C entenderá que sigues en la misma línea.
  • Comentarios (/* */, // ) = El primero pone como comentario todo el texto entre los 2, inclutyendo saltos de linea, y el segundo solamente se pone al principio y pone el frase a partir de ahí en comentario.
  • Identificadores de variable: son sensibles a mayúsculas y minúsculas (hola y Hola son 2 variables diferentes), y no se puede usar “, $ ni % en los identificadores.
  • Palabras clave o keywords: palabras que no se pueden usar como nombre en constantes, variables u otro tipo de identificador. Aquí tienes una lista.

Tipos de dato

En C, existe un concepto muy importante llamado tipo de dato. Esto significa que en función del valor de la información el tipo de dato correspondiente es uno u otro, y en C es MUY IMPORTANTE especificar el tipo correcto de tipo de dato, porque esto tiene 2 consecuencias:

  • El tipo de dato influye directamente en la cantidad de memoria que consume el programa, y por lo tanto directamente en su optimización, y como es lo mejor para el programa ser lo más eficiente posible es necesario que el tipo de dato sea el correcto.
  • Lo veremos más adelante cuando hablemos de las funciones, pero es importante ser certero a la hora de especificar el tipo de dato para no crear discrepancias y errores con las funciones que usen estas variables que estamos creando. No os preocupéis, hablaremos de eso en su momento.

Por lo tanto, tenemos varios tipos de dato. En este PDF podéis encontrar la explicación completa sobre los distintos tipos de dato.

Básicamente los tipos de dato fundamentales son:

  • int (%d): números enteros hasta 2^16.
  • long (%li): números enteros hasta 2^32.
  • float (%f): números reales.
  • double (%lf): números reales de más capacidad.
  • char (%c): caracteres.

Lo que aparece al lado es el formato de escritura, es decir, cómo se escribe el tipo de dato para imprimirlo en pantalla. Ahora, cuando avancemos un poco, veremos ejemplos prácticos de esto.
Aquí hay otro PDF donde se explica más detalladamente los formatos de escritura.
También, cuando estamos escribiendo el formato de escritura, nos pueden aparecer las siguientes opciones:

  • %d => normal
  • %4d => número apareciendo a la derecha 4 espacios (para alinear)
  • %.4d => número con hasta 4 decimales
  • \n => salto de línea

Asignaciones

Aquí trataremos de tratar el tema de las asignaciones, es decir, cuando asignamos el valor a una variable. El problema en C es que hay que tener en cuenta que hay que jugar con la memoria con el fin de entender como realmente afectan nuestras acciones al programa.

Como hemos visto, la manera de asignar un valor a una variable es:

int prueba = hola;
int prueba;
prueba = hola;

Sin embargo, veremos que hay algunos factores que pueden complicar esta tarea.
Por ejemplo, vamos a ver el caso de asignar una letra, y el caso de asignar una palabra.

char letra = "h";
char palabra [5] = {'H', 'e', 'l', 'l', 'o', '\0'};
char palabra2 [] = "Hello";

A medida que vayamos avanzando en el artículo, veremos las particularidades de cada una de las asginaciones.

Operaciones

Como en la aritmética matemática, tenemos la suma (+), la resta (-), la multiplicación (*) y la división (/).

Otro operador importante es el %; que indica el resto de la operación de dividir el número anterior por el posterior (útil para ver si hay números divisibles entre otros y ese tipo de cosas)

Un detalle a tener en cuenta, es que la información se guarda en función del tipo de dato en el que estén trabajando.

Hay varios tipos de operadores en C: aritméticos, relacionales, lógicos, bit-a-bit, de asignación y otros. Mi mejor consejo es que consultéis aquí el artículo de tutorialspoint donde explican mediante el uso de tablas los distintos operadores y su función. No os preocupéis, los veremos en acción.

Nota: con el propósito de mostrar un buen ejemplo, hacemos uso de una parte de C de la que no hemos hablado: las funciones, concretamente la función printf. Lo que nos tenemos que quedar es que es una herramienta a la que introducimos un texto o una variable de cierta manera y esa información se exporta a la consola. Más adelante veremos cómo funciona en detalle la sintaxis de las funciones.

Ejemplo

int a = 5;
double b = 5;
a = a / 3;
b = b / 3;
printf(a); // OUTPUT: 1
printf(b); // OUTPUT: 1,6666

Como podemos ver, cuando el tipo de dato no puede contener decimales (como un int), el resultado de la división/multiplicación se trunca: es decir, se corta hasta el máximo que se pueda almacenar.

Macros

Aparte de definir variables dentro de nuestra aplicación, o dentro de nuestras funciones, podemos usar una “variable global” o macro. Lo que sirve es para crear una variable con un cierto valor que se pueda acceder en cualquier función del mismo documento.

Sintaxis

#define + nombre de la variable + valor de la variale
const + tipo de variable = valor de la variable

Ejemplo

#define PI 3.14
int main () {
  const int NUMERO = 2;
  printf (NUMERO + PI); // OUTPUT 5.14
  return 0;
}

Sentencias y bucles

Vamos a comenzar una etapa bastante importante de la programación en C: el uso lógico para la toma de decisiones. Esto es la base de la programación, ya que es lo que vamos a “programar”, es decir, el objetivo principal de nuestros programas son que analicen una condición y que realizen una condición o otra en función de como esté programado.

Podemos encontrar en C los siguientes tipos de sentencias de lógica de decisión:

  • Sentencias if
  • Sentencias if.. else
  • Sentencias if anidado
  • Sentencias switch
  • Sentencias switch anidado

Lógica de decisión

Podemos encontrar algo claro en la lógica de decisión en C: se parte de una condición. A partir de ahí se analiza la condición: si se cumple, se ejecuta el código de la condición y se sigue con el código siguiente, y si no se cumple (es decir, la condición es falsa), no se ejecuta el código de la condición y se va directamente al código siguiente.

En la programación asumimos que cualquier valor distintos de cero y distinto de nulo es verdadero (TRUE), y si es cero o nulo, entonces es falso (FALSE).

Sentencias if-else

Sentencia if
Una sentencia if consiste de una expresión boleana seguida de una o más sentencias.

Sintaxis

if(boolean_expression) {
   /* statement(s) will execute if the boolean expression is true */
}

Si la expresión boleana es verdadera, entonces el código dentro del if se ejecuta. Si la expresión boleana es falsa, entonces el código al final del if se ejecuta. Sigue existiendo la lógica de decisión.

Ejemplo

#include <stdio.h>
 
int main () {

   /* local variable definition */
   int a = 10;
 
   /* check the boolean condition using if statement */
	
   if( a < 20 ) {
      /* if condition is true then print the following */
      printf("a is less than 20\n" ); // se ejecuta
   }
   
   printf("value of a is : %d\n", a); // value of a is : 10
 
   return 0;
}x

Sentencia if… else
Una sentencia if puede ser seguida por una sentencia opcional else, que se ejecuta cuando la expresión boleana era falsa.

Sintaxis

if(boolean_expression) {
   /* statement(s) will execute if the boolean expression is true */
}
else {
   /* statement(s) will execute if the boolean expression is false */
}

Si la expresión boleana se evalúa como cierta, el código dentro del if se ejecutará; de otro modo, el bloque de código else se ejecutará.

Ejemplo

#include <stdio.h>
 
int main () {

   /* local variable definition */
   int a = 100;
 
   /* check the boolean condition */
   if( a < 20 ) {
      /* if condition is true then print the following */
      printf("a is less than 20\n" );
   }
   else {
      /* if condition is false then print the following */
      printf("a is not less than 20\n" ); // se ejecuta
   }
   
   printf("value of a is : %d\n", a); // value of a is: 100
 
   return 0;
}

Se pueden stackear los “else if” para hacer los else que se necesiten.

Ejemplo

#include <stdio.h>
 
int main () {

   /* local variable definition */
   int a = 100;
 
   /* check the boolean condition */
   if( a == 10 ) {
      /* if condition is true then print the following */
      printf("Value of a is 10\n" );
   }
   else if( a == 20 ) {
      /* if else if condition is true */
      printf("Value of a is 20\n" );
   }
   else if( a == 30 ) {
      /* if else if condition is true  */
      printf("Value of a is 30\n" );
   }
   else {
      /* if none of the conditions is true */
      printf("None of the values is matching\n" ); // se ejecuta
   }
   
   printf("Exact value of a is: %d\n", a ); // Exact value of a is: 100
 
   return 0;
}

Sentencia switch

Visto el if-else, vamos a ver una sentencia un poco más elegante que un if.. else: switch.

Sintaxis de switch

switch(expression) {

   case constant-expression1  :
      statement(s);
      break; /* optional */
	
   case constant-expression2  :
      statement(s);
      break; /* optional */
  
   /* you can have any number of case statements */
   default : /* Optional */
   statement(s);
}

Aquí tenemos que analizar varias cosas:

  • 1º: escoges una variable que tiene un cierto valor
  • 2º: vas poniendo casos de cada variable: si la variable vale 1, se va al caso 1; si la variable vale 2, se va al caso 2, etc.
  • 3º: si el valor de la variable no está definido por un case, no hará nada excepto que haya un caso default, que corresponde al resto de casos, siendo este default opcional.
  • 4º: originalmente cuando ejecutes un caso ejecutará TODOS los case que tenga por debajo excepto el default. Si queremos evitar eso hace falta poner un break; al final de cada case, que indica al programa que queremos terminar el switch al final de ese caso.

El diagrama de flujo es bastante poco explicativo en este caso, ya que no explica los break, pero algo es algo:

Diagrama de flujo en los switch

Tutorialspoint / Switch

Vamos a ver un ejemplo:

#include <stdio.h>
 
int main () {

   /* local variable definition */
   char grade = 'B';

   switch(grade) {
      case 'A' :
         printf("Excellent!\n" );
         break;
      case 'B' :
      case 'C' :
         printf("Well done\n" );
         break;
      case 'D' :
         printf("You passed\n" );
         break;
      case 'F' :
         printf("Better try again\n" );
         break;
      default :
         printf("Invalid grade\n" );
   }
   
   printf("Your grade is  %c\n", grade );
 
   return 0;
}

Este código devolverá “Well done / Your grade is B”;

El operador ?:

El operador ?: puede usarse para reemplazar sentencias if… else. Tienen la forma general:

Sintaxis de ?:

Exp1 ? Exp2: Exp3;

donde Exp1, Exp2 y Exp3 son expresiones. Notar el uso y la posición del colon o punto y coma (;).

Se determina de la siguiente forma:

  • Se evalúa la expresión Exp1. Si es verdad, entonces Exp2 se evalúa y se convierte el valor de la expresión ?.
  • Si Exp1 es falso, entonces Exp3 es evaluado y su valor se convierte en el valor de la expresión.

Bucles while y do-while

Ya vistas las sentencias de comparación básicas (if…else) vamos a algo relativamente más complicado, pero que agilizará muchas de nuestras tareas de manera masiva: los bucles. Como su misma definición dice, son unas sentencias que se van ejecutando siempre y cuando una condición inicial se cumpla (hasta aquí igual que if… else) pero lo interesante de esto es que mientras la condición inicial no se cumpla, podemos hacer que la ejecución de las sentencias de dentro del bucle se repitan más de una vez, al contrario que en las sentencias básicas!

Sintaxis de while

while(condición) {
   sentencia(s);
}

Como va esto es sencillo: mientras que la condición de entrada no se cumpla, la sentencia se va a ejecutar una y otra vez. Obviamente vemos que es muy sencillo crear un bucle infinito, y seguramente más de una vez os habrá explotado el ordenador porque aunque no consuma memoria per sé, el ordenador se queda haciendo las sentencias indefinidamente lo cual deja la maquina bastante bloqueada. ¿Cómo solucionamos esto? Hay muchas maneras de salir de un bucle, ya las iréis viendo mientras trabajéis más C y veáis ejemplos (que si llegar al final de un array de letras, también llamado string; que si un puntero apunte a NULL, etc) pero la más común es que se unan los bucles con las sentencias antes trabajadas y activemos en el caso de un resultado positivo el flag o la variable que nos permita salir del bucle.
Vamos a ver un ejemplo rápido de esto:

Ejemplo de flag en bucles

int i = 0;
int flag = 0;
while(flag == 0) {
    i++;
    if (i == 5)
       flag = 1;
}

Vale que es un ejemplo bastante rebuscado (en vez de usar un flag podía hacer directamente while(i<=5)), pero os muestra la esencia del flag: una variable que usamos para decirle al bucle que la condición que queríamos que se cumpliese se ha cumplido. Como veréis en ejemplos posteriores, el flag más usado por lo general suele ser un i; una variable int que va subiendo o bajando su valor gradualmente, dependiendo de como queramos.
Entonces, ¿para qué sirven los bucles? Como alguno ya habrá visto, son bastante útiles porque nos sirven para ejecutar un mismo código una cantidad de veces limitada que podemos controlar, ya sea comparando una variable, asignando un flag…

Diagrama de flujo en los while

Flux Diagram / While

Y vamos a ver por fin un ejemplo práctico: imprimir por pantalla los números del 10 al 19. ¿10 printf? ¡No, por favor! No nos pagan por trabajar de más:

#include <stdio.h>
 
int main () {

   /* local variable definition */
   int a = 10;

   /* while loop execution */
   while( a < 20 ) {
      printf("value of a: %d\n", a);
      a++;
   }
 
   return 0;
}

Como imaginaréis, imprime por pantalla “value of a: 10”, “value of a: 11”, y así hasta el 20. ¿Útil, verdad?

Vamos a ver una versión un poco más avanzada de esto:

Sintaxis de do… while

do {
   statement(s);
} while( condition );

Ok, ¿qué es esto?. Vamos a ver un momento el diagrama de flujo de este bucle:

Diagrama de flujo en los do… while

xd

Es un poco complicado de comprender qué hace o para qué sirve si no lo has usado nunca, pero es muy transparente una vez hacemos una comparación:

  • En el bucle while, primero se comprueba la condición y después se ejecuta el bloque de código. 1º condición, 2º código.
  • En el bucle do.. while, sin embargo, primero se ejecuta el bloque de código y después se comprueba la condición. 1º código, 2º condición.

¿Para qué sirve esto, entonces? El mejor ejemplo que se me ocurre ahora mismo es un menú de una aplicación. Imaginad que tenéis una aplicación con varias funciones, pero no te quieres limitar a cada vez que quieras usar una funcionalidad del programa, lo tengas que cargar de nuevo, ¿verdad?

Así que lo que puedes hacer es usar do… while haciendo que muestre por pantalla el menú principal (es decir, que vuelva a él una vez haya terminado la funcionalidad, y por lo tanto haya terminado el bucle) mientras el usuario no le dé explicitamente a la opción de salir de la aplicación.

Os pongo 2 ejemplos, uno de mi idea y otro más general sobre el do.. while:

Ejemplo 1

int opcion; 
int salir == 0; // Aquí inicializo salir a 0 para asegurar que es algo distinto de 1
do {
   printf("Menú principal: Escoja una función 1-3 o salga del programa con 4.\n");
   scanf("%d", &opcion);
   switch(opcion) {
       case 1 : 
         función1;
         break;
       case 2 :
         función2;
         break;
       case 3 :
         función3;
         break;
       case 4 :
         printf("Saliendo del programa...\n");
         salir = 1;
         break;
       case default :
         printf("Por favor, introduzca una opción válida.\n");
} while (break /= 1);

Aquí como véis, mientras el usuario no le de explícitamente a ir al case 4, el programa no saldrá del bucle principal.

Ejemplo 2

#include <stdio.h>
 
int main () {

   /* local variable definition */
   int a = 10;

   /* do loop execution */
   do {
      printf("value of a: %d\n", a);
      a = a + 1;
   }while( a < 20 );
 
   return 0;
}

¡Esto devuelve lo mismo que el ejemplo anterior, con el while! Como podéis ver, se puede usar como si fuera un while, pero obliga a que haya al menos una iteración del bucle (una iteración es cada vez que el bloque de código de dentro del bloque se ejecuta).

Bucles for

Ok, este es EL BUCLE. El más complejo pero sin duda el más potente. Vamos a ver la sintaxis:

for ( init; condition; increment ) {
   statement(s);
}

Vamos a explicar esto un poco:

  • 1º Primero se hará una asignación, que corresponde al init. Aquí se suele usar el valor que va a cambiar durante nuestro bucle, nuestro flag.
  • 2º Después se realizará una condición (que no tiene que corresponder con la asignación anterior) y mientras esa condición sea cierta, el bucle se seguirá ejecutando.
  • 3º Por último, se realizará una linea de código que se suele llamar incremento, ya que este tipo de bucles se pueden usar para eso.

Vamos a ver el diagrama de flujo:

Diagrama de flujo en los for

Diagrama de Flujo / For

Esto se explica casi mejor con un ejemplo:

Ejemplo

#include <stdio.h>
 
int main () {

   int a;
	
   /* for loop execution */
   for( a = 10; a < 20; a = a + 1 ){
      printf("value of a: %d\n", a);
   }
 
   return 0;
}

Y esto devolverá nuestro clásico “value de a: 10, 11…”

¿Qué vemos aquí? Está claro que en vez de hacer la asignación de la variable fuera, la hacemos dentro del bucle for (a = 10), después asignamos la condición, que te puedes fijar que es la misma que la del while y do.. while (a < 20) y por último hacemos el incremento para poder avanzar por el bucle (a++, a = a+1).

Y si es lo mismo que un while, ¿porqué no usamos while? La respuesta es que es más compacto y concentrado, y recuerda que se pueden hacer varias comprobaciones simultáneamente, podemos inicializar varios valores a la vez por lo que permite hacer que pase por muchas funciones de manera muy rápida, aparte de que es más bonito a la vista. Además, hay cosas que son bastante más fáciles de hacer con un for, creéme.

Funciones

Una función es un grupo de sentencias que hacen una tarea. Como te puedes haber percatado, todas las funciones de C tienen una función mínima obligatoria, main(), y en esta se ejecutarán el resto de funciones que hagamos fuera del main. Un buen detalle de diseño a la hora de crear una aplicación es ir asignando diferentes funciones a diferentes tareas, así quedando más organizado el código y hacer que se puedan hacer modificaciones o pruebas a una parte concreta de la aplicación sea más sencillo.

Hay 2 cosas a tener en cuenta de las funciones:

  • Las declaraciones: se refiere a la primera línea de la función, que dice el tipo de función, lo que devuelve la función (el tipo de return) y sus parámetros.
  • Las definiciones: comentan el “cuerpo” de la función, además de incluir la declaración.

Como habrás podido intuir, ¡ya hemos usado infinitas funciones durante este artículo! Desde nuestro colega printf(), scanf() o funciones algo más específicas como strcat() o memcpy().
En C no hay verdadera distinción entre función, método, sub-rutina o procedimiento, ya que no existen los objetos en C.

Sintaxis básica

Vimos antes que tenemos declaraciones y funciones, ¿verdad?

Vamos a ver primero la definición de una función:

Sintaxis

return_type function_name( parameter list ) {
   body of the function
}

Una definición de una función consiste de una cabecera de la función (header) y el cuerpo de la función (body).
Analizemos sus partes:

  • El tipo de return es el tipo de dato que devuelve la función a través de un return. Si no se devuelve nada, usamos el keyword void.
  • El nombre de la función es… su nombre. No hay que ser muy avispao pa darse cuenta -_-
  • Los parámetros son moldes que le explican a la función que le van a llegar unos ciertos datos, con un tipo, orden y número determinado. Se pueden hacer funciones sin parámetros, independientes.
  • El cuerpo de la función es el conjunto de sentencias que definen qué hace la función.

Pongamos un ejemplo: la función max() coge dos parámetros num1 y num2 y devuelve con return el mayor de los 2:

Ejemplo

/* function returning the max between two numbers */
int max(int num1, int num2) {

   /* local variable declaration */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

Miremos ahora una declaración:

return_type function_name( parameter list );

En el ejemplo de arriba, cualquiera de las 2 siguientes lineas son correctas:

int max(int num1, int num2);
int max(int, int);

Esto ocurre porque para la declaración, no le importa cómo se llamen las funciones, solamente le importa saber que le van a llegar 2 variables de tipo int como argumentos.

Ahora que hemos visto su estructura, ¿para qué queremos solo las declaraciones? Lo veremos más en conciencia más adelante, pero cuando tenemos una aplicación con varios archivos y queremos usar funciones de un archivo a otro, tenemos que hacer referencia a su cabecera para que sepa con qué tiene que trabajar la función. Esto se utiliza con los ficheros .h, que vienen de header, que indica que son ficheros que contienen las cabeceras del archivo .c asociado con el fin de que se pueda referenciar en otro archivo y entonces el compilador entiende qué le estás pidiendo.

Bien, hemos visto lo aburrido de las funciones. Ahora, ¿cómo se usan? Hace falta hacer una llamada a la función para que haga la tarea que queremos. Cuando llamamos a una función, el control del programa pasa a suchodicha función, haciendo una tarea determinada hasta que llega al final de la función o se encuentra un return. Para llamarla explícitamente, solamente es necesario los parámetros necesarios junto con el nombre de la función. Si la función devuelve un valor, puedes hacer una asignación con la función para dar el resultado de la función a una variable de la función padre.

Ejemplo

#include <stdio.h>
 
/* function declaration */
int max(int num1, int num2);
 
int main () {

   /* local variable definition */
   int a = 100;
   int b = 200;
   int ret;
 
   /* calling a function to get max value */
   ret = max(a, b);
 
   printf( "Max value is : %d\n", ret );
 
   return 0;
}
 
/* function returning the max between two numbers */
int max(int num1, int num2) {

   /* local variable declaration */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

Como resultado, da “Max Value: 200”.

Argumentos de la función

Según cómo le pasemos los argumentos a la función, hay dos tipos de funciones: funciones que hacen una llamada por valor, y una llamada por referencia.

La llamada por valor consiste en los tipos de funciones que hemos estado viendo hasta ahora: cogen de una variable el valor asignado a ella y lo utilizan, pero no pueden modificarlo. Para devolver un valor, hace falta devolverlo mediante returns.

Ejemplo

void swap(int x, int y) { // Esto sería la definición de la función.

   int temp;

   temp = x; /* save the value of x */
   x = y;    /* put y into x */
   y = temp; /* put temp into y */
  
   return;
}

--

#include <stdio.h> 
 
/* function declaration */
void swap(int x, int y); // Suponiendo que swap está en otro archivo, haría falta declararlo
 
int main () {

   /* local variable definition */
   int a = 100;
   int b = 200;
 
   printf("Before swap, value of a : %d\n", a );
   printf("Before swap, value of b : %d\n", b );
 
   /* calling a function to swap the values */
   swap(a, b);
 
   printf("After swap, value of a : %d\n", a );
   printf("After swap, value of b : %d\n", b );
 
   return 0;
}

/*
RESULTADO
Before swap, value of a :100
Before swap, value of b :200
After swap, value of a :100
After swap, value of b :200
*/

Aquí se ve que aunque los valores a y b se han alterado dentro de la función swap, no se ha alterado en la función principal, ya que pasando a la función los argumentos de esa manera (“prestando” los valores, pero no dando la función de verdad) no se puede modificar los valores de la función principal.

Es como si la función cuando se le llama de esta forma, “leyera” las variables y creara una copia dentro de la función, que se corresponde a los nombres que le damos a los argumentos en la definición de la variable, que aunque se llamen igual son distintas. Por lo tanto, aunque modifiquemos los valores de las variables “copiadas” dentro de la función, al ser distintas, no se van a modificar las variables originales que han sido pasadas como argumentos a la función.

¿Qué es la llamada por referencia, entonces? Necesitamos punteros, así que si no sabéis de qué estoy hablando, bajad un poco para leeros la zona de punteros y volved aquí un poco más adelante.

Ya avisados, ¡continuamos! Aquí en vez de pasarse el valor de la variable como argumento, se pasa la dirección de la variable como argumento (con nuestro amigo el &). Dentro de la función, el argumento se usa para acceder al valor que está en la dirección de memoria proporcionada. Por lo tanto, los cambios que se hagan a los argumentos, al hacerse con la misma variable, se conservarán fuera de la función. Por lo tanto, para pasar un valor como referencia, necesitamos argumentos puntero y llamarlos con la memoria. Recordad: & => DATO => *:

/* function definition to swap the values */
void swap(int *x, int *y) {

   int temp;
   temp = *x;    /* save the value at address x */
   *x = *y;      /* put y into x */
   *y = temp;    /* put temp into y */
  
   return;
}
#include <stdio.h>
 
/* function declaration */
void swap(int *x, int *y);
 
int main () {

   /* local variable definition */
   int a = 100;
   int b = 200;
 
   printf("Before swap, value of a : %d\n", a );
   printf("Before swap, value of b : %d\n", b );
 
   /* calling a function to swap the values.
      * &a indicates pointer to a ie. address of variable a and 
      * &b indicates pointer to b ie. address of variable b.
   */
   swap(&a, &b);
 
   printf("After swap, value of a : %d\n", a );
   printf("After swap, value of b : %d\n", b );
 
   return 0;
}
/*
RESULTADO
Before swap, value of a :100
Before swap, value of b :200
After swap, value of a :100
After swap, value of b :200
*/

Como véis en el ejemplo, siendo más o menos la misma idea de código, usando llamadas memoria y argumentos puntero podemos modificar el valor de una variable de la función padre en una función hija. Esto es fundamental para el desarollo de programas más complejos.

Alcance de una función: el scope

El alcance o scope en cualquier lenguaje de programación es una región del programa donde una variable puede tener existencia y fuera de esa región no puede ser accedida. Hay 3 lugares donde se pueden declarar variables en C:

  • Variables locales: dentro de funciones o bloques
  • Variables globales: fuera de cualquier función
  • Variables formales: declaradas en la definición de funciones

Las variables locales, como hemos dicho antes, se encuentran dentro de bloques y solamente pueden ser usadas por sentencias que están dentro de estos bloques. Las funciones fuera de este bloque no conocen ni siquiera la existencia de esta variable.

Ejemplo

;
#include <stdio.h>
int main () {

  /* local variable declaration */
  int a, b;
  int c;
 
  /* actual initialization */
  a = 10;
  b = 20;
  c = a + b;
 
  printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
 
  return 0;
}

Como se puede apreciar en el código de arriba, las variables a,b y c son locales para la función main().

Las variables globales se suelen definir fuera de las funciones, al principio del programa. ¿Recordáis cuando os hablé de las macros? Puede parecer lo mismo, pero al contrario de lo primero, las variables globales sí que se modifican durante el tiempo de ejecución del programa, mientras como hemos dicho las macros son siempre constantes.

Ejemplo

#include <stdio.h>
 
/* global variable declaration */
int g;
 
int main () {

  /* local variable declaration */
  int a, b;
 
  /* actual initialization */
  a = 10;
  b = 20;
  g = a + b;
 
  printf ("value of a = %d, b = %d and g = %d\n", a, b, g);
 
  return 0;
}

También podemos combinar variables globales y locales, pero en el caso de tener 2 con el mismo nombre se le dará prioridad a la variable local.

Por último tenemos las variables formales, que funcionan como variables locales, y por lo tanto también toman preferencia contra las variables globales.

Ejemplo

#include <stdio.h>
 
/* global variable declaration */
int a = 20;
 
int main () {

  /* local variable declaration in main function */
  int a = 10;
  int b = 20;
  int c = 0;

  printf ("value of a in main() = %d\n",  a);
  c = sum( a, b);
  printf ("value of c in main() = %d\n",  c);

  return 0;
}

/* function to add two integers */
int sum(int a, int b) {

   printf ("value of a in sum() = %d\n",  a);
   printf ("value of b in sum() = %d\n",  b);

   return a + b;
}
/*
value of a in main() = 10
value of a in sum() = 10
value of b in sum() = 20
value of c in main() = 30
*/

Un último detalle, mientras que las variables locales no se inicializan, las variables globales siempre se inicializan automáticamente con FALSE, dependiendo del formato con 0, NULL o ‘\0’.

Tablas, o arrays

Un array es un tipo de estructura de dato que almacena una cierta cantidad de elementos del mismo tipo. Aunque se usa para almacenamiento de datos, es mejor pensarlo como almacenamiento de variables del mismo tipo. En vez de declarar N variables como tabla1, tabla2, declaramos un array llamado tabla y usamos tabla[1], tabla[2], etc para representar las variables indiviudales. Cada elemento del array se accede por un índice (el número dentro del corchete).

Algo que será útil más adelante: todos los arrays consisten de direcciones de memoria contiguas, siendo la dirección de memoria más pequeña el primer elemento y la mayor el último.

Para declarar un array, la sintaxis es la siguiente:

type arrayNombre [tamañoArray];

Esto es el array básico, o un array unidimensional, siendo tamañoArray un entero mayor que 0 y type cualquier tipo de dato válido en C.

Ejemplo: array de 10 elementos

double balance[10];

Los arrays se inicializan de manera algo distinta:

  • Puedes declarar primero el array e inicializar elemento a elemento (bastante tedioso, seamos honestos)
  • O bien hacer la inicialización junto a la declaración de la siguiente manera:
  • double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
    

    Aquí podéis observar algunas cosas:

  • El número de valores entre llaves no puede ser mayor que el número de elementos que declaramos para el array entre corchetes.
  • Puedes omitir el tamaño del array (el 5 en el caso de arriba) si haces la inicialización justo al lado, ya que C es capaz de darse cuenta de que hay 5 elementos y por tanto asignará 5 variables al array almacenando los elementos.
  • Para asignar elemento a elemento es exactamente igual que si fuera una variable independiente, solamente que tenemos que indicar a qué elemento del array nos referimos, y por lo tanto lo tenenemos que referenciar mediante su índice
  • Ejemplo: asignación de elementos de un array

    balance[4] = 50.0;
    

    ¡Cuidado aquí! Como puedes haber visto, he puesto el índice 4 pero estoy escribiendo en el quinto elemento del array. Eso es porque en C los índices comienzan con 0 como el 1er índice, así que el 1er elemento será el elemento de índice 0, el 2º elemento el elemento de índice 1, etc.

    Aparte de esto, existen varias cosas relacionadas con los arrays:

  • Arrays multidimensionales, en el que especificas 2 “dimensiones” del array creando una tabla de filas y columnas en el caso de 2 dimensiones y más si pasas a 3 o a N dimensiones
  • Puedes tanto pasar arrays a funciones usando un puntero (soonTM) pasando el nombre del array entero como variable, como devolver arrays en funciones.
  • Por lógica con lo de arriba, también existe por sí mismo un puntero a un array.
  • — EN CONSTRUCCIÓN —

    Nada que ver por aquí
    Nada que ver por aquí
    Nada que ver por aquí

    — EN CONSTRUCCIÓN —

    Código ASCII

    Un pequeño detalle de C, es que es compatible con el código ASCII, así que puedes imprimir cualquier caracter soportado por este convenio. Aquí se encuentra una tabla de todos los caracteres admitidos en C.

    Ejemplo

    #include <stdio.h>
    int main() {
      printf ("\u00f1o\n"); // OUTPUT: año
      return 0;
    }
    

    Como puedes ser, la ñ es \u00f1 y para poder escribirla es escribirle dentro del mismo printf el código ASCII.

    Anuncios

    Responder

    Introduce tus datos o haz clic en un icono para iniciar sesión:

    Logo de WordPress.com

    Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

    Imagen de Twitter

    Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

    Foto de Facebook

    Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

    Google+ photo

    Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

    Conectando a %s