diff --git a/fundamentos-programacion/PEC3/README.md b/fundamentos-programacion/PEC3/README.md index ae4eea8..94d5d99 100644 --- a/fundamentos-programacion/PEC3/README.md +++ b/fundamentos-programacion/PEC3/README.md @@ -17,9 +17,8 @@ - [7.2. Cambio explícito de tipos de datos](#72-cambio-explícito-de-tipos-de-datos) - [8. Cadenas de carácteres en C](#8-cadenas-de-carácteres-en-c) - [8.1. Cadenas de caracteres o strings](#81-cadenas-de-caracteres-o-strings) - - [8.1.1. Definición](#811-definición) - - [8.1.2. Asignación](#812-asignación) - - [8.1.3. Comparación](#813-comparación) + - [8.1.1. Asignación](#811-asignación) + - [8.1.2. Comparación](#812-comparación) - [8.2. Programación segura](#82-programación-segura) @@ -557,34 +556,266 @@ int main(int argc, char** argv) { ``` - - - ## 7. Cambio de tipos de datos en C +En la mayoría de casos, el compilador de C es capaz de realizar conversiones implícitas de tipos de datos, automáticas y transparentes. Sin embargo, en ocasiones es necesario realizar conversiones explícitas. El estándar del lenguaje C ([ISO/IEC 9899](http://www.open-std.org/JTC1/SC22/WG14/www/standards.html#9899)) define las reglas utilizadas para hacer estos cambios. + + ### 7.1. Reglas de coneversión +Lista simplificada de las normas del estándar: + +1. Si una operación involucra dos operandos y uno de ellos es de tipo `long double`, el otro se convierte en `long double`. +2. Si una operación involucra dos operandos y uno de ellos es de tipo `double`, el otro se convierte en `double`. +3. Si una operación involucra dos operandos y uno de ellos es de tipo `float`, el otro se convierte en `float`. +4. En la mayoría de los casos, los valores de tipo `char` y `short int` se convierten en `int` inmediatamente. +5. Si una operación involucra dos operandos y uno de ellos es de tipo `long int`, el otro se convierte en `long int`. +6. Si una expresión involucra tanto enteros con signo como sin signo, la situación se complica. Si el operando sin signo tiene una capacidad de representación menor (por ejemplo, tenemos un `unsigned int` y un `long int`) por lo que el tipo con signo tiene capacidad para representar todos los valores del tipo sin signo, el valor sin signo se convierte en el tipo con signo y el resultado adopta este tipo con signo. En caso contrario (es decir, si el tipo con signo no puede representar todos los valores del tipo sin signo, como por ejemplo `unsigned short int` y un `int`), ambos valores se convierten en un tipo sin signo común y el resultado tiene este tipo sin signo. +7. Finalmente, cuando se asigna un valor a una variable mediante el operador de asignación, este se convierte automáticamente al tipo de la variable, si tanto el valor como la variable tienen tipo aritmético (es decir, número entero o punto flotante). ### 7.2. Cambio explícito de tipos de datos +En casos no cubiertos por estas reglas o si nos interesa que no se apliquen, habrá que hacer el cambio de datos de forma explícita. En la regla 6 es aconsejable hacer la conversión explícita para evitar resultados inesperados. + +En los casos que nos interese hacer un cambio de tipo de datos o `cast`, lo haremos poniendo el tipo destino entre paréntesis, actuando como un operador unario, lo que significa que afectará a lo que tenga justo a su derecha. + +```c +#include + +int main(int argc, char** argv) { + int a = 1; + int b = 2; + + float r; + r = a / b; + + printf("%f", r); + + return 0 +} +``` + +Esperaríamos que la variable `r` tuviera el valor 0.5, pero realmente tendrá el valor 0. La expresión `a/b`, tanto `a` como `b` son enteros y, por tanto, la operación es una división entera que tiene como resultado 0. Después tenemos una asignación a una variable real que, siguiendo la regla 7, se convierte en real y se guarda en la variable `r`. + +Si queremos que la división no sea entera deberemos hacer un cambio explícito: + +```c +#include + +int main(int argc, char** argv) { + int a = 1; + int b = 2; + + float r; + r = (float)a / (float)b; + + printf("%f", r); + + return 0; +} +``` + +Teniendo en cuenta la regla 3, solo con que cambiemos uno de los tipos ya sería suficiente. + +```c +#include + +int main(int argc, char** argv) { + int a = 1; + int b = 2; + + float r; + r = (float)a / b; +} +``` ## 8. Cadenas de carácteres en C ### 8.1. Cadenas de caracteres o strings -#### 8.1.1. Definición +Una cadena de caracteres o `string`, es una secuencia de caracteres finalizada por el carácter `'\0'`. Esta secuencia de caracteres se almacena dentro de un vector de caracteres. Por tanto, hay que diferenciar entre el tamaño del vector y el tamaño de la cadena de caracteres. + +Para saber la longitud de un string, tenemos el método `strlen`. + +Necesitamos la librería `string.h`. + +```c +#include +#include + +int main(int argc, char **argv) { + char c[3]="ab"; + int len; + + /* Get the string length */ + len=strlen(c); + + /* Show the length */ + printf("The length is %d\n",len); + + return 0; +} +``` +#### 8.1.1. Asignación -#### 8.1.2. Asignación +Cuando trabajamos con vectores y las cadenas de caracteres no son una excepción, no podemos hacer una asignación directa. + +```c +#include + +int main(int argc, char **argv) { + char s1[3]="ab"; + char s2[3]; + + /* BAD assignation */ + s2=s1; + + /* Show the values */ + printf("s1=%s, s2=%s\n", s1, s2); + + /* Change the first char in s2 */ + s2[0]='k'; + + /* Show the values */ + printf("s1=%s, s2=%s\n", s1, s2); + + return 0; +} +``` + +Pueden pasar dos cosas. +1. Que el compilador detecte esta situación y nos dé un error de compilación. +2. Que queden asignadas a la misma zona de memoria, por lo que el segundo `printf` nos mostrará dos cadenas de caracteres con los valores `kb`, ya que al modificar `s2` también habremos modificado `s1`. (Como un puntero) + +No se puede hacer una asignación directa entre vectores ni cadenas de caracteres. + +Con el método `strcpy` se puede copiar el contenido de una `string` a otra. + +```c +#include +#include + +int main(int argc, char **argv) { + char s1[3]="ab"; + char s2[3]; + + /* *** Correct assignation *** */ + strcpy(s2,s1); + + /* Show the values */ + printf("s1=%s, s2=%s\n", s1, s2); + + /* Change the first char in s2 */ + s2[0]='k'; + + /* Show the values */ + printf("s1=%s, s2=%s\n", s1, s2); + + return 0; +} +``` + +Los vectores no deben tener obligadamente la misma longitud, pero el vector `s2` debe tener suficiente espacio para `s1`. +#### 8.1.2. Comparación -#### 8.1.3. Comparación +De la misma manera que en el caso de la asignación, la comparación entre dos vectores no se puede hacer con +los operadores habituales (==, <, >, ...). Siempre diría que no son iguales aunque tengan las mismas cadenas de caracteres. +```c +#include +int main(int argc, char **argv) { + char s1[3]="ab"; + char s2[3]="ab"; + + /* Compare the two strings (ERROR)*/ + if (s1 == s2) { + printf("EQUALS\n"); + } else { + printf("NOT EQUALS\n"); + } + + return 0; +} +``` + +Para comparar cadenas de caracteres, tenemos el método `strcmp(s1, s2)`. Recibe como parámetros las dos cadenas de caracteres y nos devuelve un valor entero: +- `0` si `s1` y `s2` son iguales, +- menor a `0` si `s1 < s2` (en orden alfabético), +- mayor a `0` si `s1 > s2` (en orden alfabético). ### 8.2. Programación segura + +No debemos pasarnos nunca del espacio disponible en el vector. + + +```c +#include +#include + +int main(int argc, char **argv) { + int a=3; + char b='a'; + char c[3]="ab"; + int d=3; + + /* Show the initial values */ + printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d); + + /* Assign all the space. No '\0' is stored */ + strcpy(c,"abc"); + printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d); + + /* Assign more space. Memory of other variables is used */ + strcpy(c,"abcd"); + printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d); + return 0; +} + +``` + +El resultado es: +```c +a=3, b=a, c=ab, d=3 +a=3, b=a, c=abc, d=3 +a=3, b=a, c=abcd, d=0 +``` + +Se ha modificado el valor de la variable d. + +Para evitar este tipo de situaciones, existen versiones de la mayoría de métodos que tratan con cadenas de caracteres y que permiten limitar la longitud a copiar. Estos métodos suelen añadir una «n» al nombre de la función. El siguiente código es robusto a este tipo de problemas, utiliza el método `strncpy` que le indica el tamaño del vector de destino: + +```c +#include +#include + +int main(int argc, char **argv) { + int a=3; + char b='a'; + char c[3]="ab"; + int d=3; + + /* Show the initial values */ + printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d); + + /* Assign all the space. No '\0' is stored */ + strncpy(c,"abc",3); + printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d); + + /* Assign more space. Memory of other variables is NOT used */ + strncpy(c,"abcd",3); + + printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d); + + return 0; +} +``` + +Siempre se deben tomar precauciones, pero en especial cuando las cadenas de caracteres se crean dentro del programa, por ejemplo concatenando valores o distintas cadenas de caracteres, o cuando las introduce el usuario.