Update PEC3

This commit is contained in:
Manuel Vergara 2024-03-24 11:47:08 +01:00
parent bcb7577058
commit de6fb8fa4d

View File

@ -17,9 +17,8 @@
- [7.2. Cambio explícito de tipos de datos](#72-cambio-explícito-de-tipos-de-datos) - [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. 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. Cadenas de caracteres o strings](#81-cadenas-de-caracteres-o-strings)
- [8.1.1. Definición](#811-definición) - [8.1.1. Asignación](#811-asignación)
- [8.1.2. Asignación](#812-asignación) - [8.1.2. Comparación](#812-comparación)
- [8.1.3. Comparación](#813-comparación)
- [8.2. Programación segura](#82-programación-segura) - [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 ## 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 ### 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 ### 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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
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. Cadenas de carácteres en C
### 8.1. Cadenas de caracteres o strings ### 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 <stdio.h>
#include <string.h>
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 <stdio.h>
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 <stdio.h>
#include <string.h>
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 <stdio.h>
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 ### 8.2. Programación segura
No debemos pasarnos nunca del espacio disponible en el vector.
```c
#include <stdio.h>
#include <string.h>
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 <stdio.h>
#include <string.h>
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.