Curso de programación en C desde cero

Topic created · 17 Mensajes · 4661 Visitas
  • Post #10

    Antes de continuar veremos como enlazar condiciones. Para ello veamos un ejemplo.

    Ejemplo 9: diseña un programa que pida al usuario un número. Si el número es mayor que 22 que muestre el número por pantalla, si es negativo que muestre por pantalla la cadena "Muy mal." y en cualquier otro caso que no muestre nada por pantalla y termine.

    #include 
    
    int main()  {
        int num1;
        printf("Introduce un numero: ");
        scanf("%i",&num1);
        if(num1 
    

    Este código es completamente válido, aunque algo confuso, por tener ifs encadenados. Podemos simplificarlo usando "else if". Un else if siempre va después de un if o después de un else if. else if es como else pero permite añadir una condición, es decir, si la condición anterior es falsa pero se cumple otra condición, se realiza una acción.

    if(condición) {
         acción
    }
    else if(otra_condición) {
         acción
    }
    else if(otra_condición_mas) {
        acción
    }
    //Además podemos añadir después un else
    else {
         acción
    }
    

    Usando else if el programa anterior quedaría:

    #include 
    
    int main()  {
        int num1;
        printf("Introduce un numero: ");
        scanf("%i",&num1);
        if(num1 < 0) {
             printf("Muy mal.\n");
        }
        else if(num1 > 22) {
             printf("Tu numero es el %i.\n",num1);
        }
        system("pause > null");
        return 0;
    }
    

    Bien, ahora veamos que es un array. Un array es como una lista de valores en la que se distinguen unos elementos de otros por su posición. Hasta ahora cada variable tenía su nombre, en un array tenemos un nombre para el array que contiene varios valores. Podemos distinguir unos de otros por su posición en el array. La posición se indica entre corchetes tras el nombre del array. Una vez vista la teoría vamos a la práctica, que es como mejor se entiende. Para declarar un array se hace lo mismo que para declarar una variable. Se pone el tipo de datos, y el nombre del array, pero ahora tenemos que poner después del nombre el número de elementos que contendrá entre corchetes. Vamos a declarar un array de 5 enteros:

    int mi_array[5];
    

    Y, ya está, tenemos un array que podrá contener 5 números enteros. Ahora vamos a darle algún valor. Hay dos formas de hacerlo, una es poner todos los valores entre llaves separados por comas. Vamos a ello:

    mi_array = { 0, 1, 2, 3, 4 };
    

    Sin embargo, si tenemos que hacer esto cada vez que queramos cambiar el valor de un array no tendría sentido usar un array. Los arrays son útiles porque podemos referirnos a cualquier elemento de la lista. Aquí viene algo un poco difícil de pillar. Si tenemos un array de 5 elementos las posiciones a las que podemos referirnos son la 0, la 1, la 2, la 3 y la 4, pero no la 5. Si tenemos un array de N elementos podremos usar desde la posición 0 a la N-1. Vamos a rellenar el array con valores de esta forma.

    mi_array[0] = 0;
    mi_array[1] = 1;
    mi_array[2] = 2;
    mi_array[3] = 3;
    mi_array[4] = 4;
    

    Ya lo hemos rellenado con los cinco valores que queríamos. Muy largo, ¿no? Por suerte hay algo que nos puede facilitar el trabajo: los bucles. Vamos a ver lo mismo de antes con un bucle for. Suponiendo que ya tenemos la variable i creada:

    for(i = 0; i < 5; i++) {
         mi_array[i] = i;
    }
    Como ya dijimos antes, una variable puede usarse como el valor que contiene, por eso podemos poner la i entre los corchetes. Sin embargo, [color]en C no podemos usar una variable para definir de qué tamaño va a ser un array al declararlo[/color]. Es decir, este código es incorrecto, y por tanto no funciona en un compliador de C:
    [code]int variable = 10, array[variable];[/code]A la hora de crear un array de un tamaño tenemos que poner ese número a mano.
    
    Ahora vienen los ejercicios:
    
    [u]Ejercicio 21 (M): Diseña un programa que pida al usuario dos números. Si su diferencia es menor que el primer número nos saludará por pantalla, si su diferencia no es menor que el primer número pero es menor que el segundo se despedirá. Si su diferencia es mayor que el primer número y que el segundo, mostrará la suma de los dos números por pantalla.[/u]
    
    [u]Ejercicio 22 (B): Crea un programa que cree un array de 7 enteros, pida al usuario que rellene el array con valores y luego los muestre todos por pantalla. Usa bucles para resolver este ejercicio.[/u]
    
    [u]Ejercicio 23(M): Diseña un programa que rellene un array con 6 valores de tu elección. Los valores que estén en una posición par del array serán multiplicados por dos, y los que estén en una posición impar serán divididos entre dos. Finalmente se mostrará el array entero. Usa bucles para resolver este ejercicio.[/u][/i]
  • Post #12

    Bien, antes que nada empezaré explicándoos qué son los prototipos, cuáles son sus ventajas y como se usan. Sabemos que podemos declarar (crear) primero una variable y después darle un valor. Pues bien, con las funciones es lo mismo, podemos primero declararlas y luego inicializarlas (decir lo que van a hacer). Para declarar una función sin inicializarla se usan los prototipos. Una de las ventajas de los prototipos es que el código queda más organizado y puedes ver más rápidamente qué funciones tiene tu programa. Además si estás haciendo un programa junto con más gente y tú por ejemplo te encargas de hacer la función main y otro se encarga de hacer la función x() si tú pones un prototipo de esa función y después la usas dentro de main aunque todavía no esté inicializada y el programa no sabe qué tiene que hacer, no se producirá ningún error de compilación, por supuesto después al intentar ejecutarlo sí, pero al compilarlo no.

    Para hacer el prototipo de una función primero se pone el tipo de datos que devuelve, después entre paréntesis y separados por comas los tipos de datos que va a recibir esta función, después del tipo de datos podemos opcionalmente poner un nombre a ese tipo de datos que recibe, que puede coincidir o no con el nombre que le pondremos después en la implementación. Por último se escribe punto y coma (;) después de los paréntesis. Esto parece muy difícil pero con un ejemplo se ve mucho más claro:

    #include 
    //Esto es el prototipo  de la función mifuncion. Gracias
    //al prototipo podemos declararla primero y después de
    //la función main implementarla, cosa que antes no se
    //podía, había que implementarla antes de main
    void mifuncion(char c, int i, int i2, float f);
    //Otras posibles implementaciones COMPLETAMENTE VÁLIDAS
    //void mifuncion(char w, int j, int a, float asdfasdas);
    //void mifuncion(char,int,int,float);
    
    int main() {
        mifuncion('@',25,40,1.25);
        return 0;
    }
    //Ahora podemos dejar encima de main todos los prototipos
    //e implementar todas las funciones debajo de main
    void mifuncion(char c, int i, int i2, float f) {
         //operaciones de la función
    }
    

    Los prototipos podéis usarlos (e incluso os recomiendo que lo hagáis) en todas vuestras funciones. Si no los usáis no pasa nada.

    Ahora vamos a ver una forma de interrumpir los bucles. Sirve tanto para el bucle while como para el for. Si en algún momento queremos interrumpir un bucle y salir de él sin ejecutar nada más que haya dentro del bucle usaremos la palabra clave break. Es bastante fácil, vamos a ver un ejemplo con un bucle while, aunque funciona exactamente igual con un bucle for. Creo que no está de más aclarar que si hay un bucle dentro de otro break sólo saldrá del interior, no de los dos.

    int num;
    while(1){//como 1 significa verdadero siempre se cumplirá
        num++;
        if(num == 12) {//Si el número es 12...
            break;//...salimos del bucle
        }
        printf("Este mensaje ya se ha mostrado %d veces",num);
    }
    //gracias al break el programa puede salir del bucle
    

    Antes de empezar con los punteros he visto que había gente que tenía dudas con algunos signos. Aquí tenéis casi todos los que vais a usar (hay algunos más como operadores de bits, pero no son muy usados):
    Signo de suma: +
    Signo de resta: -
    Signo de multiplicación: *
    Signo de división: /
    Signo de módulo*: %
    (*) El módulo de a y b (a % b) es el resto de hacer la división entre a y b. Por ejemplo 3 % 2 da 1, que es el resto de dividir 3 entre 2.
    No debéis intentar usar el símbolo ^ para las potencias, en otros programas sirven para eso pero en programación es uno de los operadores de bits menconado antes que no tiene nada que ver con las potencias.

    Ahora ha llegado el momento de enseñaros qué son los punteros. Esto puede llegar a ser muy difícil pero estoy seguro de que lo vais a entender. Las variables que creamos en nuestros programas se guardan en la memoria RAM de nuestro ordenador. La memoria RAM es como una gran ciudad. Dentro de la ciudad hay muchas casas (que serían las variables) donde puede vivir gente (que serían los datos que almacena). Una misma casa puede ser vendida a otras personas y cambiar sus dueños, pero una vez construyes una casa siempre está en la misma dirección (por ejemplo C/San Benito 4). Obviamente las direcciones de nuestras variables no tienen nombres de calles sino que son números. Hay un tipo de variable especial para guardar direcciones de memoria, los punteros. Al igual que hay muchos tipos de casas hay muchos tipos de variables. Hay casas grandes como mansiones y casas pequeñas como chabolas, pues con las variables igual. Hay variables que pueden guardar muchos datos y ocupan mucha memoria y otras que guardan menos y ocupan menos. Por ejemplo las variables tipo float ocupan bastante más que las variables char, por eso hay punteros de diferentes tipos de datos. Hay punteros a int, punteros a char, punteros a float, y punteros a otros tipos de datos que no hemos visto. Para declarar un puntero lo hacermos como lo haríamos con una variable normal pero con un asterisco (*) delante. Por ejemplo:

    int *puntero;
    

    La variable puntero es un puntero a int. Puede guardar direcciones de memoria de variables de tipo int. ¿Cómo se hace eso? Es muy fácil, para obtener la dirección de memoria de una variable se pone el símbolo ampersand (&) delante. O sea, que cuando en scanf poníamos & delante del nombre de la variable era por que en realidad enviábamos a la variable la dirección de memoria de esa variable, más adelante veremos por qué. Para asignar una dirección de memoria a un puntero lo hacemos así:

    int var, *punt;
    punt = &var;
    

    Como podéis observar el símbolo * se pone delante al crearlo para indicar que es un puntero, pero no se hace al asignarle un valor (en este caso el valor es la dirección de memoria de var).

    Esto puede ser algo difícil de asimilar, así que pongo un ejercicio para aseguraros que lo entendéis. Si tenéis alguna duda preguntadla.

    Ejercicio 27 (M): Crea un programa que asigne el valor a una variable con scanf y luego muestre el valor, pero que a scanf le pase un puntero y no directamente la dirección de la variable con &.

    En el próximo post veremos más a fondo los punteros y habrá más ejercicios.

  • Post #13

    Antes de seguir leyendo este post asegúrate de que entiendes lo que es un puntero.

    Bien, una vez hemos creado un puntero, y le hemos asignado la dirección de una variable podemos cambiar su valor y cambiará el valor de la variable. Es decir que si tenemos una variable _numero _y un puntero con la dirección de numero, desde el puntero podemos utilizar y cambiar el valor de número. Para ello se usa lo que se conoce como operador de desreferencia. El operador de desreferencia lo que hace es acceder a una variable sabiendo su dirección de memoria. Cuando accedemos al valor de una variable mediante este método, no estamos accediendo a una copia del valor, sino al valor original, es decir que si una variable contiene 3 y mediante un puntero cambiamos el valor a 4, el valor de la variable cambiará a 4. Como hemos dicho antes un puntero guarda la dirección de memoria de algo, así que si ponemos simplemente el nombre del puntero, es como si pusieramos esa dirección de memoria, para referirnos a la variable que hay en esa dirección de memoria usamos * que es el operador de desreferencia del que habíamos hablado antes. Tómate unos segundos para asimilar todo esto y lee el código siguiente, te recomiendo que intentes predecir que saldrá con printf() antes de leer la solución abajo para comprobar que lo has entendido.

    #include 
    
    int main() {
        //Creo una variable normal y un puntero a int
        int var, *punt;
        //Ahora hago que el puntero "apunte" a la dirección de memoria de var
        punt = &var;
        printf("Introduce un numero: ");
        scanf("%d",&var);
        //Hasta aquí hemos asignado un valor a var, intenta predecir que pasará ahora
        *punt += 6;
        printf("El valor de var ahora es %i",var);
        system("pause > null");
        return 0;
    }
    

    Solución: var ahora vale 6 más que lo que valía antes.
    Fíjate que donde ponemos:

    printf("El valor de var ahora es %i",var);
    

    Perfectamente podríamos poner:

    printf("El valor de var ahora es %i",*punt);
    

    Porque estamos accediendo a lo que hay en la dirección de memoria de var. Por eso mismo a scanf pasamos la dirección de memoria de la variable a la cual queremos asignar una valor. Porque lo que queremos es cambiar el valor de esa variable, y para hacerlo en una función necesitamos desreferenciar su dirección de memoria para acceder a la variable. Vamos a ver esto con un ejemplo para entenderlo mejor. Compila y ejecuta este programa y comprueba como no cambia el valor de la variable:

    #include 
    
    void nocambia(int);
    
    int main() {
        int numero;
        printf("Introduce un numero: ");
        scanf("%i",&numero);
        nocambia(numero);
        printf("El valor ahora es %d.",numero);
        system("pause > null");
        return 0;
    }
    
    void nocambia(int a) {
         a = -6;
    }
    

    Ahora vamos a ver un ejemplo de una función que SÍ cambia el valor de la variable:

    #include 
    
    void sicambia(int *a);
    
    int main() {
        int numero;
        printf("Introduce un numero: ");
        scanf("%i",&numero);
        sicambia(&numero);
        printf("El valor ahora es %d.",numero);
        system("pause > null");
        return 0;
    }
    
    void sicambia(int *a) {
         //Los paréntesis nunca sobran, puedes poner los que quieras
         (*a) = -6;
    }
    

    Ya no estamos cambiando un valor, sino lo que hay en una dirección de memoria, así que estamos cambiando la propia variable.

    Ejercicio 28 (B): Diseña una función que reciba un puntero a float y un int, y que cambie el valor del número con decimales multiplicándolo por el entero. Crea también un programa que utilice tu función.

    Ejercicio 29 (M): Crea una función que reciba un array de enteros, un entero que contenga el número de elementos del array y un puntero a entero y que no devuelva nada. En la función modificarás el valor de la variable a la que apunta el puntero para que su valor sea la suma de todos los elementos del array. Haz también un programa que utilice esta función.

  • Post #14

    Antes de seguir con los punteros, vamos a hacer una pequeña pausa y os voy a hablar del ámbito de las variables. Según su ámbito las variables pueden ser locales o globales. Cuando declaráis una variable dentro de una función, sólo podéis acceder a esa variable desde esa función, pero también es posible hacer variables que sean accesibles desde muchas funciones. Para hacerlo las ponemos justo debajo de todos nuestros includes, antes de todas las funciones en las que vayamos a usar nuetras variables globales. Vamos a ver un ejemplo:

    #include 
    
    int global = 3;
    
    int mifuncion(void);
    
    int main() {
        int local1 = 2*global+1;
        printf("%d %d",local1,mifuncion());
        return 0;
    }
    
    int mifuncion() {
        int local2 = global+2;
        return local2;
    }
    

    En este programa, global sería una variable global, y como habréis visto, se puede acceder a ella tanto desde main como desde mifuncion, y no se produce ningún error. Sin embargo si dentro de main intentáramos poner por ejemplo int a = local2; se produciría un error (os animo a que lo comprobéis). Esto es porque local2 es una variable local, y solo se puede acceder a ella desde donde se creó. En este caso se creó en la función mifuncion, así que fuera de esa funcion no puedes acceder a esa variable. Sin embargo, se puede acceder a global desde todos lados, porque es una variable global. Esto también se cumple para las funciones como for, if, else, while, etc., si declaramos una variable dentro de alguna de esas funciones no la podremos usar fuera. Si por ejemplo tenemos un if y dentro de ese if creamos una variable no podremos usarla fuera de ese if, ni si quiera en otro if. Esto se debe a que cuando creas una variable dentro de una función, al salir de la función, esa variable se "destruye", y no puedes volver a usarla a no ser que la vuelvas a crear.

    Otra cosa que no hemos visto es cómo crear constantes. Una constante es como una variable pero con la diferencia de que no se puede cambiar su valor. Una vez se define el valor de una constante ya no se puede cambiar nunca, a no ser que cambies el código fuente donde creaste la constante. Si intentamos cambiar el valor de esa constante se produciría un error. Una forma de verlo más claro es con este ejemplo:

    3 = 7;
    

    ¿Imposible hacer eso, verdad? Exacto, porque 3 es una constante y no se puede cambiar su valor. Para crear una constante se pone al principio del programa "#define nombreconstante valor". No se pone punto y coma (;) ni igual (=). Por norma general, y para distinguirlas de las variables, las constantes suelen tener nombres que solo contengan letras mayúsculas, así cuando alguien vea por ejemplo MI_NUMERO sabrá que es una constante. No es obligatorio esto, el programa funciona aunque el nombre lleve minúsculas, pero no es nada recomendable hacerlo. Las constantes definidas así (hay otros modos) tienen ámbito global, se puede acceder a ellas desde todo el fichero entero. Vamos a ver un ejemplo:

    #include 
    #define PI 3.141592
    
    int main() {
        float radio;
        printf("Introduce el radio de una circunferencia para saber su longitud: ");
        scanf("%f",&radio);
        printf("La longitud de tu circunferencia es %f.\n",2*PI*radio);
        return 0;
    }
    

    Aquí PI es una constante, que podemos usar en todas nuestras funciones sabiendo siempre que no cambiará su valor.

    Otra cosa que puede ser útil a la hora de programar son diversas funciones matemáticas que están en la librería math.h. Para poder usarlas, es necesario poner al principio del fichero:

    #include 
    

    Algunas de estas funciones son:

    • cos(): recibe un float (que será un ángulo medido en radianes) y devuelve otro float (el coseno de ese ángulo).
    • sin(): recibe un float (que será un ángulo medido en radianes) y devuelve otro float (el seno de ese ángulo).
    • tan(): recibe un float (que será un ángulo medido en radianes) y devuelve otro float (la tangente de ese ángulo).
    • acos(): recibe un float (que será el coseno de un ángulo) y devuelve otro float (el ángulo en radianes cuyo coseno es el que ha recibido).
    • asin(): recibe un float (que será el seno de un ángulo) y devuelve otro float (el ángulo en radianes cuyo seno es el que ha recibido).
    • atan(): recibe un float (que será la tangente de un ángulo) y devuelve otro float (el ángulo en radianes cuyo coseno es el que ha recibido).
    • floor(): recibe un float y devuelve otro float que será la parte entera del primero (o lo que es lo mismo, el primero redondeando hacia abajo).
    • ceil(): recibe un float y devuelve otro float que será el primero redondeando hacia arriba.
    • fabs(): recibe un float y devuelve otro float que será el valor absoluto del primero (o lo que es lo mismo, el primero pero siempre con signo positivo).
    • log10(): recibe un float y devuelve otro float que será el logaritmo en base 10 del primero.
    • log(): recibe un float y devuelve otro float que será el logaritmo neperiano (en base e) del primero.
    • sqrt(): recibe un float, y devuelve otro float que será la raíz cuadrada del primero.
    • pow(): recibe dos floats, el primero la base y el segundo el exponente, y devuelve otro float que será el resultado de elevar el primer número al segundo.
      Ejercicio 30 (B): Diseña un programa similar al que pongo de ejemplo, pero que en lugar de calcular la longitud de la circunferencia, calcule el área del círculo. Evita multiplicar un número por sí mismo, en lugar de ello usa alguna de las funciones aprendidas.

    Ejercicio 31 (M): Crea un programa en el que salga un menú para que el usuario pueda elegir realizar 5 operaciones a tu gusto de entre las que has aprendido. Las opciones del menú serán constantes definidas al principio del programa.

  • Post #15

    Si habéis llegado hasta aquí deberíais entender perfectamente que son los punteros, y cómo hacer que una funcion modifique el valor de una variable local gracias a ellos.

    Ahora vamos a profundizar un poco más en los punteros. Los punteros pueden apuntar a una dirección de memoria, pero tienen la particularidad de que pueden también apuntar a la siguiente, y a la siguiente... Funcionando como un array. Supongamos que tenemos un puntero a entero (int). Normalmente los int ocupan 4 bytes. Entonces si está alojado en la dirección de memoria 122 (por poner un ejemplo) el int ocuparía los 4 siguientes bytes, hasta la posición 126. Esto no necesitamos decirselo, porque al hacer el puntero de tipo entero ya sabe cuántos bits tiene que leer. Pues lo que podemos hacer es después de ese entero poner otro, y decirle al puntero que avance (al decirle el tipo ya sabe cuántos bytes tiene que avanzar) y leemos el siguiente. Y así todo lo que queramos hasta que lleguemos a una zona de memoria prohibida. En realidad los arrays hacen esto, y siempre se cumple que los elementos de un array están en posiciones de memoria correlativas (es decir, si tenemos un array a[], en la memoria, a[1] estará justo después de a[0]). Además, un array apunta a la dirección de memoria del primer elemento, es decir, si tenemos el array miarray[], miarray sin corchetes es la dirección de memoria del primer elemento del array. Vamos a ver que esto es cierto con un ejemplo:

    #include 
    
    int main() {
        int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
        puntero = array;
        //acabamos de hacer que puntero apunte también
        //al primer elemento del array
        printf("primer elemento del array: %d\ncontenido del puntero: %d",array[0],*puntero);
        return 0;
    }
    

    Podéis compilarlo y ejecutarlo vosotros mismos para ver qué sale, intentad predecir primero lo que va a hacer el programa.

    Después de ver esto, podríais pensar "si el nombre del array sin corchetes guarda una dirección de memoria, ¿entonces podemos usar el operador de desreferencia (*) con el nombre del array igual que hacemos con los punteros?". Pues sí, si se puede hacer, podéis probarlo vosotros mismos. Este es el mismo ejemplo de antes pero usando el * también con el array:

    #include 
    
    int main() {
        int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
        puntero = array;
        printf("primer elemento del array: %d\ncontenido del puntero: %d",*array,*puntero);
        return 0;
    }
    

    Según lo que hemos aprendido antes, desde el puntero también podemos acceder a la segunda posición del array, y a la tercera, y a la cuarta... ¿Cómo se hace? solo hay que sumarle al array tantos elementos como queramos avanzar. Al principio estamos en la posición [0] del array, pero si queremos ir a la [1] habrá que sumarle 1. Eso nos dará la dirección de memoria del segundo elemento, el que está en la posición [1]. Ahora queremos desreferenciar esa dirección de memoria para obtener su valor, pero para que no se confunda el compilador lo hacemos entre paréntesis. Vamos a ver otro ejemplo:

    #include 
    
    int main() {
        int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
        puntero = array;
        printf("segundo elemento del array: %d\nahora lo mismo desde el puntero: %d",array[1],*(puntero+1));
        return 0;
    }
    

    Esta forma de hacerlo puede resultar algo liosa, pero era necesario que supiérais que se puede hacer así. A mi en lugar de eso me gusta tratar a los punteros como arrays. Si después de un puntero ponemos corchetes, nos muestra el contenido de la dirección de memoria del array más lo que haya dentro de los corchetes. Para entenderlo mejor, hace lo mismo que un array. Vamos a verlo en otro ejemplo:

    #include 
    
    int main() {
        int array[5] = { 1, 2, 3, 4, 5 }, *puntero;
        puntero = array;
        printf("tercer elemento del array: %d\nahora lo mismo desde el puntero: %d",array[2],puntero[2]);
        return 0;
    }
    

    A mi me parece más claro así, por lo que a partir de ahora yo usaré corchetes también con los punteros, pero vosotros podéis hacerlo de las dos formas.

    Lo que vamos a ver ahora es un poquito más complicado, así que aseguraros de entender todos los ejemplos anteriores antes de seguir.

    Hasta ahora siempre hemos usado punteros para que "apunten" a las direcciones de memoria de otras variables, pero también podemos nosotros reservar la memoria que queramos para ellos, y así podemos hacer arrays del tamaño que queramos (antes no podíamos definir el tamaño de un array con una variable, pero podemos hacerlo con punteros). Para hacer esto necesitamos unas funciones especiales que no están en , tenemos que incluir otra librería además de esa, . Las funcinoes que vamos a usar son: malloc() y calloc() (para reservar memoria), realloc() (para cambiar el tamaño de la memoria reservada) y free() (para liberar espacio). Para empezar vamos a reservar memoria para un solo elemento, pero antes vamos a ver de forma más detallada las funciones.

    • malloc() se encarga de reservar para nosotros la memoria que le pidamos (si intentamos acceder a memoria que no hemos reservado puede producirse un error). Devuelve un puntero a void que tenemos que transformar en el tipo de datos que usemos. Ese puntero "apunta" a la dirección de memoria tras la cual malloc ha reservado para nosotros tantos bytes como le hemos pedido, para que no se produzca ningún error al acceder ahí.

    • calloc() hace lo mismo que malloc pero nos rellena con ceros todos los bytes que reserva, es decir, nos garantiza que la memoria que nos ha reservado contendrá siempre 0 a no ser que nosotros cambiemos su contenido. Esta función recibe el número de elementos que queremos reservar y cuánta memoria ocupa CADA UNO (no en total).

    • realloc() sirve para cambiar cuánta memoria tenemos reservada. Veremos esta función con más detenimiento en el próximo post.

    • free() es muy importante, sirve para liberar la memoria que hemos reservado, recibe un puntero y hace que ya no tenga asignada memoria, con lo cual ya no se puede usar. Sirve o bien para borrar la memoria reservada en un puntero y reservar más o simplemente para no quedarse con memoria reservada al terminar el programa. Esto es importante porque si finalizamos un programa y teníamos memoria reservada es posible que la memoria siga estando reservada aunque ningún puntero apunte a ella ya. Lo que significa que eso es RAM que perdéis, hasta que reiniciéis el PC o la limpiéis con algún programa. Así que siempre, memoria que reserváis, memoria que liberáis luego. Que no se os olvide.Ahora vamos a ver cómo se usa malloc. Malloc viene de memory allocate que literalmente significa "asignar memoria". Hemos dicho que recibe el número de bytes que queremos reservar, pero ¿cómo sabemos cuantos bytes queremos reservar? Cada tipo de datos ocupa un número diferente de bytes, pero tranquilos, no tenéis que aprenderoslo de memoria. Para eso sirve sizeof(). Le pasamos una variable, un tipo de datos, una función o LO QUE SEA y nos dice cuantos bytes ocupa. Por ejemplo sizeof(int) nos devolverá lo que ocupa un int en bytes. Si queremos reservar 3 bytes pues ponemos sizeof(int)*3 (lo que ocupa un int * número de ints que queremos reservar). Además hemos dicho que devuelve un puntero a void, pero nosotros no queremos un puntero a void, sino uno a int (o a float o a lo que sea). Así que tenemos que transformarlo. Eso se hace simplemente poniendo antes el tipo de datos que queremos entre paréntesis. por ejemplo, al poner "(int *) malloc()" hemos transformado lo que devuelve malloc en un puntero a entero. Calloc funciona casi igual pero recibe el número de elementos a almacenar y cuánta memoria ocupa cada uno. Vamos a ver un ejemplo:

      #include
      #include //ademas de stdio tambien incluimos stdlib

      int main() {
      int *punt; //creamos el puntero
      punt = (int *) malloc(sizeof(int)); //le asignamos tamaño suficiente para guardar un int
      *punt = 10; //guardamos el valor 10 en la direccion que hemos reservado
      printf("%d",*punt); //ahora mostramos su valor
      free(punt); //y por ultimo liberamos espacio
      return 0;
      }

    Acordaos de incluir stdlib para usar malloc y compañía. Con calloc sería igual pero en lugar de poner

    punt = (int *) malloc(sizeof(int));
    

    pondríamos:

    punt = (int *) calloc(1,sizeof(int));
    

    y así nos aseguramos de que hasta que no le asignemos un nuevo valor el contenido de punt será cero. Seguirá mostrando 10 en el prinf porque le hemos cambiado el valor, pero si no lo hiciéramos saldría 0. Ponemos 1 porque solo vamos a guardar un elemento en el array, y cada uno ocupa el tamaño de un int (sizeof(int)). Vamos a ver el mismo ejemplo de antes pero guardando dos números:

    #include 
    #include  //ademas de stdio tambien incluimos stdlib
    
    int main() {
        int *punt; //creamos el puntero
        punt = (int *) malloc(sizeof(int)*2); //le asignamos tamaño suficiente para guardar dos ints
        punt[0] = 10; //guardamos el valor 10 en la posición 0
        punt[1] = 20; //guardamos el valor 20 en la posición 1
        printf("%d %d",*punt,*(punt+1)); //ahora mostramos su valor, lo hago de la otra forma para que veais que da igual
        free(punt); //y por ultimo liberamos espacio
        return 0;
    }
    

    Ejericio 32 (M): Crea un programa que le pregunte al usuario cuántos números quiere guardar, reserve suficiente memoria para guardarlos todos y luego se los pida al usuario y los almacene. Por último los mostrará por pantalla en orden.

    Ejericio 33 (M): Diseña un programa que pregunte al usuario un número N. El programa reservará memoria en dos punteros diferentes para almacenar N números en cada uno y luego le pedirá al usuario que los rellene con datos. Para terminar mostrará el producto de los números de ambos arrays en posición par y la suma de los números de ambos arrays en posición impar.

  • Post #16

    En este post vamos a aprender a cambiar la cantidad de memoria que tenemos reservada.
    Ya dijimos en el post anterior que con realloc podíamos cambiar la memoria que tenemos reservada. En este post vamos a aprender a usarla con más detenimiento. Realloc recibe dos argumentos (dos valores). El primero es el puntero cuya memoria reservada queremos cambiar. El segundo es la memoria total que tendrá reservada tras el cambio. Otra forma de liberar el espacio además de con free sería asignándole 0 bytes a nuestro puntero. Esto se haría así:

    realloc(puntero,0);
    

    Y listo, ya tenemos la memoria de ese puntero liberada, siempre que hayamos incluido la librería . Por supuesto podemos volver a darle memoria con malloc o calloc. Pero también podemos asignarle más o menos memoria de la que teníamos. Vamos a ver un ejemplo:
    Crea un programa que lea números de forma indefinida hasta que el número leído sea -1. Después mostrará todos los números, incluido el -1.

    #include 
    #include 
    
    int main() {
        int *numeros,contador=0 ,i;
        numeros = (int *) malloc(sizeof(int));
        while(1) {
            numeros = (int *) realloc(numeros,sizeof(int)*(contador+1));
            //Tambien podriamos haber puesto
            //numeros = (int *) realloc(numeros,sizeof(numeros)+sizeof(int));
            scanf("%d",numeros+contador);
            if(numeros[contador]==-1) {
                break;
            }
            contador++;
            //Tambien podriamos haber puesto:
            //scanf("%i",&numeros[i]);
        }
        printf("\nLos numeros son:\n");
        for(i = 0; i
  • Post #17

    En este post por fin vamos a aprender a trabajar con strings. La palabra string viene del inglés y significa cadena. Y una string es eso, una cadena de caracteres, es decir un conjunto de números, letras y caracteres especiales como '#' o '@', es decir, una string es un array de caracteres. Sin embargo no todos los arrays de caracteres son strings. La diferencia es que una cadena siempre termina con el carácter '\0' (contrabarra cero). Esto no significa que el último elemento del array tenga que ser \0 sino que todo lo que haya después de ese carácter se ignorará. Vamos a ver un pequeño ejemplo. En este ejemplo simplemente mostramos con printf una cadena. Para mostrar cadenas con printf se usa %s (igual que para enteros usábamos %i y para caracteres %c).

    #include 
    
    int main() {
        char string[6] = { 'H', 'o', 'l', 'a', '\0', '!' };
        printf("%s\n",string);
        return 0;
    }
    

    Como vemos el '!' no se muestra porque está después del '\0'. Escribir las cadenas así es demasiado trabajoso, así que podemos simplemente escribirlas entre comillas y automáticamente se le pone el \0 al final. Por ejemplo, vamos a ver otra vez el ejercicio anterior pero ahora de la nueva forma, con comillas.

    #include 
    
    int main() {
        char string[6] = "Hola";
        printf("%s\n",string);
        return 0;
    }
    

    como véis el array es de 6 caracteres, y la cadena mide 5 (4 más el '\0'). No pasa nada si hacemos un array de 200 caracteres y ocupamos solamente 1. Sin embargo si hacemos un array de 10 chars e intentamos guardar en él una cadena de 15 nos dará error. Este es uno de los posibles usos de realloc. Hacemos un puntero a char y según los caracteres que vayamos a guardar lo hacemos más grande o más pequeño.

    Hay una serie de funciones en la librería que nos pueden ser muy útiles a la hora de trabajar con cadenas. Podéis ver una lista de todas en http://cplusplus.com/reference/clibrary/cstring/. Pone cstring porque así es como se llama la librería en C++, pero las funciones hacen lo mismo. Ya sé que está en inglés pero están todas con ejemplos y muy bien explicadas. Os explicaré en español aquí las que más se usan:

    • strcpy(destino, origen): copia el contenido de origen en la cadena destino. Todo el contenido de la cadena destino se pierde. Es importante recordar que no podemos copiar cadenas con "=" porque estaríamos asignando la dirección de memoria y si cambiamos una cambia la otra.

    • strcat(destino, origen): añade el contenido de la cadena origen al final de la cadena destino, o lo que es lo mismo concatena origen al final destino. Es decir si la cadena origen contenía " Mundo" y la cadena destino contenía "Hola" después de usar esta función la cadena origen seguirá conteniendo lo mismo pero la cadena destino ahora contendrá "Hola Mundo". Además esta función devuelve una cadena exactamente igual que destino una vez añadido el nuevo contenido.

    • strcmp(cadena1, cadena2): comprueba si el contenido de la cadena1 es el mismo que el de la cadena2. Devuelve 0 si las cadenas son exactamente iguales. Devuelve un valor positivo si el primer carácter que no es igual en ambas cadenas es mayor en la primera y un valor negativo en cualquier otro caso. Recordad que no podemos usar el operador "==" para comparar el valor de dos cadenas porque esto lo que haría es ver si apuntan a la misma dirección de memoria.

    • strlen(cadena): devuelve la longitud de la cadena que recibe, sin contar el '\0' ni nada que haya después. Por ejemplo si cadena es "123" strlen devolverá 3.
      Aún faltan algunas funciones para leer y mostrar cadenas. Éstas no están en sino en y son las siguientes:

    • gets(cadena): lee por teclado hasta que pulses ENTER y guarda todo lo que el usuario ha escrito antes de pulsar ENTER. Tenéis que tener MUCHO cuidado al usar esta función, ya que no limita el máximo de caracteres a leer, así que nos arriesgamos a que intente meter más caracteres de los que caben a la cadena.

    • fgets(destino,N,flujo): lee un máximo de N-1 caracteres del flujo de datos flujo y lo introduce en la cadena destino. Para leer la entrada normal por teclado en flujo pondremos stdin (standard input), es decir, que quedaría por ejemplo puts. Si en algún momento llega a leer un carácter de nueva línea (\n) parará de leer, pero el carácter '\n' lo incluirá en la cadena (es decir, que para al pulsar ENTER pero lo incluye en la cadena). Después de leer los N-1 caracteres o cuando lee '\n' mete todos los caracteres en la cadena y al final el carácter '\0' para que sea una cadena válida.

    • scanf(): también se puede usar para leer cadenas, usando %s como identificador, sin embargo para de leer al encontrar un espacio en blanco (' '), un tabulador ('\t') o un carácter de nueva línea ('\n). No incluye ninguno de estos caracteres en la cadena, pero en su lugar pone un carácter '\0'.

    • puts(cad): muestra la cadena cad por pantalla, con un carácter de nueva línea al final. Es equivalente a hacer printf("%s\n",cad). Tened en cuenta que no hace falta que sea una variable lo que reciba, podéis poner directamente puts("HOLA") por ejemplo.
      Ejercicio 36 (B): Diseña un programa que pida dos cadenas y a la primera le concatene la segunda. Después mostrará la primera cadena por pantalla.

    Ejercicio 37 (B): Crea un programa que pida al usuario dos cadenas y diga si son iguales.

    Ejercicio 38 (B): Crea un programa que pida al usuario una cadena y muestre su longitud incluyendo el carácter '\0'.

    Ejercicio 39 (A): Diseña un programa que pida al usuario un número N. Después el programa reservará suficiente espacio como para guardar N caracteres, y leerá por teclado un máximo de N caracteres. Al final mostrará la cadena leida por pantalla, pero si la cadena acaba por '\n' no mostrará este carácter.