10 PR4
10.2 strncpy()
La acción strncpy(destino, origen, numero)
copia la cadena de caracteres de la variable origen
a la variable destino
. Solo copia los primeros n-caracteres, indicados por el valor numero
. Así, si origen = "Hello World!"
i numero = 5
, el contenido de la variable destino
será "Hello"
(limitado a los 5 primeros caracteres).
Ejemplo: imaginemos que nos piden que las entregas de las PEC tienen que seguir una nomenclatura concreta. El nombre del fichero tiene que estar compuesto por:
- La inicial del nombre.
- Las tres primeras letras del primer apellido.
- Las tres primeras letras del segundo apellido.
- El literal
_pecN.zip
, siendoN
el número de la PEC.
Por ejemplo, si el alumno se llama David Rives Ingles y quiere entregar la PEC6, el nombre del fichero será DRivIng_pec6.zip
.
Queremos un programa que muestre por pantalla el nombre el fichero a partir del nombre, los apellidos y el número de la PEC del alumno.
Una posible solución es la siguiente:
/* Ejemplo ES1101 */
#include <stdio.h>
#include <string.h>
#define MAX_NOMBRE 20+1
#define MAX_APELLIDO 15+1
#define MAX_PARTE_NOMBRE 1+1
#define MAX_PARTE_APELLIDO 3+1
#define CARACTERES_PARTE_NOMBRE 1
#define CARACTERES_PARTE_APELLIDO 3
int main(int argc, char **argv)
{
char nombre[MAX_NOMBRE];
char apellido1[MAX_APELLIDO];
char apellido2[MAX_APELLIDO];
int numPec;
char parte1[MAX_PARTE_NOMBRE];
char parte2[MAX_PARTE_APELLIDO];
char parte3[MAX_PARTE_APELLIDO];
printf("Nombre: ");
scanf("%s", nombre);
printf("Primer apellido: ");
scanf("%s", apellido1);
printf("Segundo apellido: ");
scanf("%s", apellido2);
printf("Número de PEC: ");
scanf("%d", &numPec);
strncpy(parte1, nombre, CARACTERES_PARTE_NOMBRE);
parte1[CARACTERES_PARTE_NOMBRE] = '\0';
strncpy(parte2, apellido1, CARACTERES_PARTE_APELLIDO);
parte2[CARACTERES_PARTE_APELLIDO] = '\0';
strncpy(parte3, apellido2, CARACTERES_PARTE_APELLIDO);
parte3[CARACTERES_PARTE_APELLIDO] = '\0';
printf("\n>> Nombre de la entrega: %s%s%s_pec%d.zip \n",
parte1, parte2, parte3, numPec);
return 0;
}
Un ejemplo de ejecución:
Nombre: David
Primer apellido: Rives
Segundo apellido: Ingles
Número de PEC: 6
>> Nombre de la entrega: DRivIng_pec6.zip
10.3 Ejemplo: tupla dentro de tupla
A veces puede costar ver cómo trabajar con atributos de una tupla que a su vez está dentro de otra tupla, si se accede por valor, por referencia (puntero), etc. Por este motivo, se adjunta el siguiente ejemplo inventado, en el que se han añadido comentarios para que se vea claramente cómo trabajar con atributos de una tupla contenida dentro de otra tupla:
/* Ejemplo ES1102 */
#include <stdio.h>
#include <string.h>
/* Un parking de un centro comercial nos ha
* encargado una app que facilite a sus
* clientes el localizar dónde ha aparcado su
* vehículo.
*
* El parking ya dispone de tres tipos de
* cámaras, independientes entre ellas:
* - de entrada: cámara posicionada en la entrada
* del parking que, además de leer la matrícula,
* permite saber el tipo de vehículo.
* - de acceso: cámaras que nos permiten
* saber si un vehículo ha subido o ha bajado
* una planta.
* - de planta: cámaras que nos permiten saber
* la fila y el número de plaza donde se ha
* aparcado un vehículo.
* El identificador de vehículo es su
* matrícula.
* A partir de la información obtenida por los
* tres conjuntos de cámaras, deberá indicarse
* al usuario dónde ha aparcado su vehículo.
*/
#define MAX_MATRICULA 7+1
typedef enum {COCHE, MOTO, FURGONETA} tTipo;
/* El lugar donde aparca un vehículo viene dado
* por tres valores: la planta, la fila y el
* número de la plaza.
*/
typedef struct {
int planta;
char fila;
int numero;
} tAparcamiento;
/* El tipo tVehiculo contiene su matrícula
* (identificador único), el tipo de vehículo
* detectado en la primera cámara (COCHE, MOTO,
* FURGONETA), y el lugar donde ha aparcado (tAparcamiento)
*/
typedef struct {
char matricula[MAX_MATRICULA];
tTipo tipo;
tAparcamiento aparcamiento;
} tVehiculo;
/* Predeclaración de las acciones */
void inicializar(tVehiculo *vehiculo, tTipo tipo, char *matricula);
void subirPlanta(tVehiculo *vehiculo);
void bajarPlanta(tVehiculo *vehiculo);
void aparcar(tVehiculo *vehiculo, char fila, int numero);
void obtenerPosicion(tVehiculo);
/* Programa principal */
int main(int argc, char **argv){
tVehiculo vehiculo;
/* Se lee la matrícula del vehículo con la
* cámara de entrada del parking y se asigna
* al elemento de tipo tVehiculo de nuestro
* parking
*/
printf("\n>> Entrada al parking \n");
inicializar(&vehiculo, COCHE, "7472GZZ");
printf("\n>> Bajar una planta\n");
bajarPlanta(&vehiculo);
printf("\n>> Bajar una planta\n");
bajarPlanta(&vehiculo);
printf("\n>> Subir una planta\n");
subirPlanta(&vehiculo);
printf("\n>> Bajar una planta\n");
bajarPlanta(&vehiculo);
printf("\n>> Bajar una planta\n");
bajarPlanta(&vehiculo);
printf("\n>> Aparcar\n");
aparcar(&vehiculo, 'C', 23);
printf("\nVamos de compras ...");
printf("\n... pasamos más de 3 horas ...");
printf("\n... ¡y nos olvidamos de dónde hemos aparcamos el vehículo! \n");
printf("\nSolución: consultamos la posición de nuestro vehículo");
printf("\nen la app del parking : \n");
obtenerPosicion(vehiculo);
return 0;
}
/* Como la posición viene dada por los atributos planta, fila y
* número de tAparcamiento, es muy importante inicializar
* los valores (sobre todo por la planta). La planta inicial del
* parking es la 0, y el resto de plantas son subterráneas
*/
void inicializar(tVehiculo *vehiculo, tTipo tipo, char *matricula) {
strcpy(vehiculo->matricula, matricula);
vehiculo->tipo = tipo;
/* El acceso al atributo aparcamiento (tupla) lo hacemos con '->'
* ya que se trata de un puntero, y accedemos a
* los atributos de la tupla aparcamiento con '.'
*/
vehiculo->aparcamiento.planta = 0;
vehiculo->aparcamiento.fila = '-';
vehiculo->aparcamiento.numero = 0;
}
/* Cuando subimos una planta, incrementamos en 1 el atributo
* planta de la tupla tAparcamiento que contiene la tupla tVehiculo
*/
void subirPlanta(tVehiculo *vehiculo) {
vehiculo->aparcamiento.planta = vehiculo->aparcamiento.planta + 1;
}
/* Cuando bajamos una planta, decrementamos en 1 el atributo
* planta de la tupla tAparcamiento que contiene la tupla tVehiculo
*/
void bajarPlanta(tVehiculo *vehiculo) {
vehiculo->aparcamiento.planta = vehiculo->aparcamiento.planta - 1;
}
/* Cuando aparcamos el vehículo, damos valor a los atributos
* fila y número de la tupla tAparcamiento que contiene la
* tupla tVehiculo
*/
void aparcar(tVehiculo *vehiculo, char fila, int numero) {
vehiculo->aparcamiento.fila = fila;
vehiculo->aparcamiento.numero = numero;
}
/* Mostramos por pantalla la posición del tVehiculo */
void obtenerPosicion(tVehiculo vehiculo) {
if (vehiculo.tipo == COCHE) {
printf("\nCoche %s : ", vehiculo.matricula);
} else {
if (vehiculo.tipo == MOTO) {
printf("\nMoto %s : ", vehiculo.matricula);
} else {
printf("\nFurgoneta %s : ", vehiculo.matricula);
}
}
/* En este caso el acceso al atributo aparcamiento (tupla)
* se hace con '.' ya que se ha pasado por valor (no es un puntero)
* y accedemos a los atributos de la tupla aparcamiento con '.'
*/
printf("planta %d, ", vehiculo.aparcamiento.planta);
printf("fila %c, ", vehiculo.aparcamiento.fila);
printf("número %d \n", vehiculo.aparcamiento.numero);
}
El resultado de la ejecución de este programa es:
>> Entrada al parking
>> Bajar una planta
>> Bajar una planta
>> Subir una planta
>> Bajar una planta
>> Bajar una planta
>> Aparcar
Vamos de compras ...
... pasamos más de 3 horas ...
... ¡y nos olvidamos de dónde hemos aparcamos el vehículo!
Solución: consultamos la posición de nuestro vehículo
en la app del parking :
Coche 7472GZZ : planta -3, fila C, número 23
10.4 Desplazamiento de elementos en un vector
Los desplazamientos en un vector se pueden producir cuando se añade o elimina un elemento de un vector.
10.4.1 Añadir un elemento
Queremos añadir un elemento dentro del vector de una tabla en una posición que no es la última. Por ejemplo, si queremos que los elementos que añadamos a un vector vayan a la posición inicial, antes habrá que desplazar el resto de los elementos una posición a la derecha:
Contenido inicial del vector:
[element2] [element5] [element1] [element7]
Antes de añadir el nuevo element6
, habrá que mover todos los elementos una posición hacia la derecha, para hacer un hueco al inicio:
[........] [element2] [element5] [element1] [element7]
Ahora ya se puede añadir el element6
en la primera posición del vector:
[element6] [element2] [element5] [element1] [element7]
Importante: como último paso, siempre debemos incrementar en 1 el atributo que contiene el tamaño de la tabla.
10.4.2 Borrar un elemento
Cuando queramos eliminar un elemento de un vector también se puede producir un desplazamiento de elementos. Por ejemplo:
Contenido inicial del vector:
[element2] [element5] [element1] [element7]
Para borrar element5
simplemente desplazamos el resto de los elementos que van a continuación una posición hacia la izquierda:
[element2] [element1] [element7]
Importante: como último paso, hay que decrementar en 1 el atributo que contiene el tamaño de la tabla.
10.5 Ejemplo: diasSemana
A continuación se adjunta un ejemplo inventado para consolidar la explicación anterior: la idea es que se pueda comparar la inserción de elementos en una tabla como hemos hecho habitualmente hasta ahora (añadidos al final del todo), con otra acción que permite añadir de forma ordenada los elementos en la tabla. En esta segunda inserción habrá que ir realizando desplazamientos hacia la derecha de los elementos del vector de la tabla para que vayan quedando ordenados.
/* Ejemplo ES1103 */
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
/* Definición de constantes */
#define MAX_DIAS 7
#define MAX_NOMBRE 9+1
/* Definición de la tupla tDia. */
typedef struct {
char nombre[MAX_NOMBRE];
int id;
} tDia;
/* Definición de la tabla tSemana */
typedef struct {
tDia dias[MAX_DIAS];
int numDias;
} tSemana;
/* Predeclaración de las acciones. */
/* Acción que inicializa la tabla tSemana,
* mediante la asignación de 0 a numDias.
*/
void inicializar(tSemana *semana);
/* Acción que añade un elemento de tipo tDia
* en la tabla tSemana.
*/
void insertarDia(tSemana *semana, tDia dia);
/* Acción que añade un elemento de tipo tDia
* en la tabla tSemana de forma ordenada.
*/
void insertarDiaOrdenado(tSemana *semana, tDia dia);
/* Acción que copia el contenido de un tDia origen (src)
* a un tDia destino (dst).
*/
void copiarDia(tDia *dst, tDia src);
/* Acción que muestra por pantalla el contenido
* de la tabla tSemana.
*/
void mostrar(tSemana semana);
/* Programa principal */
int main(int argc, char **argv) {
/* Definimos las variables. */
tSemana semana, semanaOrdenada;
tDia lun, mar, mie, jue, vie, sab, dom;
/* Inicializamos las variables de tipo tDia. */
strcpy(lun.nombre, "lunes");
lun.id = 1;
strcpy(mar.nombre, "martes");
mar.id = 2;
strcpy(mie.nombre, "miércoles");
mie.id = 3;
strcpy(jue.nombre, "jueves");
jue.id = 4;
strcpy(vie.nombre, "viernes");
vie.id = 5;
strcpy(sab.nombre, "sábado");
sab.id = 6;
strcpy(dom.nombre, "domingo");
dom.id = 7;
/* Inicializamos las variables de tipo
* tSetmana.
*/
inicializar(&semana);
inicializar(&semanaOrdenada);
/* Añadimos sin orden las variables
* tDia dentro de las tablas semana
* y semanaOrdenada. La diferencia
* entre ellas será el método utilizado
* para insertar los tDia: mientras que
* en la inserción por semana será
* lo habitual añadir al último
* elemento de la tabla, para la
* semanaOrdenada utilizaremos la acción
* insertarDiaOrdenado().
*/
insertarDia(&semana, jue);
insertarDia(&semana, lun);
insertarDia(&semana, mie);
insertarDia(&semana, dom);
insertarDia(&semana, sab);
insertarDia(&semana, mar);
insertarDia(&semana, vie);
insertarDiaOrdenado(&semanaOrdenada, jue);
insertarDiaOrdenado(&semanaOrdenada, lun);
insertarDiaOrdenado(&semanaOrdenada, mie);
insertarDiaOrdenado(&semanaOrdenada, dom);
insertarDiaOrdenado(&semanaOrdenada, sab);
insertarDiaOrdenado(&semanaOrdenada, mar);
insertarDiaOrdenado(&semanaOrdenada, vie);
printf("\nSemana sin ordenar : \n");
mostrar(semana);
printf("\nSemana ordenada : \n");
mostrar(semanaOrdenada);
return 0;
}
/* Implementación de las acciones. */
void inicializar(tSemana *semana) {
semana->numDias = 0;
}
void insertarDia(tSemana *semana, tDia dia) {
/* numDias indica el número de elementos
* de tipo tDia que contiene la tabla
* tSemana en cada momento.
*/
copiarDia(&semana->dias[semana->numDias], dia);
/* Una vez insertado un nuevo elemento
* tDia en la tabla, incrementamos el valor
* de numDias.
*/
semana->numDias = semana->numDias + 1;
}
/* Acción que añade un elemento de tipo tDia
* en una tabla tSemana de forma ordenada.
*/
void insertarDiaOrdenat(tSemana *semana, tDia dia) {
int i, j;
bool isInsertado;
isInsertado = false;
i = 0;
/* Para realizar la ordenación se utilizará el campo
* id de tDia.
*/
for (i = 0; i < semana->numDias && !isInsertado; i++) {
/* Si el id del tDia de la tSetmana > id del
* parámetro dia, a partir de este punto
* habrá que desplazar las tuplas tDia hacia
* la derecha. El objetivo es conseguir un
* lugar libre donde se añadirá el tDia dia
* pasado por parámetro a la acción.
*/
if (semana->dias[i].id > dia.id) {
/* Desplazamiento de los tDia hacia la derecha, a
* partir de la posición donde habrá que añadir
* el parámetro día pasado por valor.
*/
for (j = semana->numDias; j > i; j--) {
copiarDia(&semana->dias[j], semana->dias[j-1]);
}
/* Se añade el parámetro dia a la
* posición que le corresponde, una vez
* desplazadas las demás tuplas tDia
* hacia la derecha.
*/
copiarDia(&semana->dias[j], dia);
semana->numDias = semana->numDias + 1;
isInsertado = true;
}
}
/* Si en este punto todavía no se ha
* añadido el tDia, significa que debe ir
* al final de todo de la tabla tSemana.
*/
if (!isInsertado) {
copiarDia(&semana->dias[i], dia);
semana->numDias = semana->numDias + 1;
}
}
void copiarDia(tDia *dst, tDia src) {
dst->id = src.id;
strcpy(dst->nombre, src.nombre);
}
void mostrar(tSemana semana) {
int i;
for (i = 0; i < semana.numDias; i++) {
printf("%s\n", semana.dias[i].nombre);
}
}
La ejecución del programa generará la siguiente salida:
Semana sin ordenar :
jueves
lunes
miércoles
domingo
sábado
martes
viernes
Semana ordenada :
lunes
martes
miércoles
jueves
viernes
sábado
domingo
10.6 Ejemplo: pilaCartas
A continuación se expone un ejemplo donde, dada una pila de cartas inicial, lo que queremos es codificar la acción separarDiamantes
que nos permita separar de la pila todas aquellas que son DIAMANTES
, añadiéndolas a una nueva pila de cartas DIAMANTES
.
Ejemplo: si inicialmente tenemos la pila1:
[Q] de PICAS
[5] de DIAMANTES
[J] de TRÉVOLES
[A] de DIAMANTES
[3] de CORAZONES
Queremos que por un lado la pila1 contenga todas las cartas que no son DIAMANTES
:
[3] de CORAZONES
[J] de TRÉVOLES
[Q] de PICAS
Y por otro lado una nueva pila2 con todas las cartas DIAMANTES
:
[A] de DIAMANTES
[5] de DIAMANTES
Se han añadido comentarios dentro del código para facilitar su comprensión:
/* Ejemplo ES1104 */
#include <stdio.h>
#include <stdbool.h>
/* Definición del modelo de cartas según el enlace:
https://es.wikipedia.org/wiki/Baraja_francesa */
#define MAX_CARTAS 54+1
/* El término "palo" equivale a "baraja" */
typedef enum {DIAMANTES, PICAS, TREVOLES, CORAZONES} tPalo;
typedef struct {
char valor;
tPalo palo;
} tCarta;
typedef struct {
tCarta A[MAX_CARTAS];
int nelem;
} tStack;
/* Predeclaración de las funciones y acciones */
void initStack(tStack *s);
void push(tStack *s, tCarta e);
void pop(tStack *s);
void top(tStack s, tCarta *carta);
bool isEmptyStack(tStack s);
bool isFullStack(tStack s);
void printStack(tStack s);
void copiarCarta(tCarta *destino, tCarta origen);
void separarDiamantes(tStack *pilaCartas, tStack *pilaDiamantes);
/* Programa principal */
int main(int argc, char **argv) {
tCarta carta1, carta2, carta3, carta4, carta5;
tStack pilaCartas, pilaCartasDiamantes;
/* Creamos dos pilas (revisad el comentario inicial del bloque
* de implementación de las acciones/funciones de la pila).
*/
initStack(&pilaCartas);
initStack(&pilaCartasDiamantes);
/* Definimos una serie de cartas */
carta1.valor = '3';
carta1.palo = CORAZONES;
carta2.valor = 'A';
carta2.palo = DIAMANTES;
carta3.valor = 'J';
carta3.palo = TREVOLES;
carta4.valor = '5';
carta4.palo = DIAMANTES;
carta5.valor = 'Q';
carta5.palo = PICAS;
/* Y las añadimos a pilaCartas */
push(&pilaCartas, carta1);
push(&pilaCartas, carta2);
push(&pilaCartas, carta3);
push(&pilaCartas, carta4);
push(&pilaCartas, carta5);
/* Mostramos el contenido de pilaCartas por
* pantalla, con la acción adicional printStack.
*/
printf("\nContenido de la pila 'pilaCartas' :\n");
printStack(pilaCartas);
/* Ahora queremos separar de pilaCartas todas
* aquellas cartas que son DIAMANTES, las cuales
* formarán parte de una nueva pila de cartas.
*/
printf("\n¡Separamos las cartas en dos pilas!\n");
/* Explicación detallada dentro de la implementación
* de la acción.
*/
separarDiamantes(&pilaCartas, &pilaCartasDiamantes);
printf("\nContenido de la pila 'pilaCartas' sin DIAMANTES :\n");
printStack(pilaCartas);
printf("\nContenido de la pila 'pilaCartasDiamantes' :\n");
printStack(pilaCartasDiamantes);
return 0;
}
/* Implementación de las acciones/funciones de la pila: se ha hecho un copy/paste
* de la codificación C del ejemplo 19_04 de la xWiki, cambiando
* el genérico "elem" por "tCarta".
*/
void initStack(tStack *s) {
s->nelem=0;
}
void push(tStack *s, tCarta e) {
if (s->nelem == MAX_CARTAS) {
printf("\n Full stack \n");
} else {
s->A[s->nelem]=e; /* first position in C is 0 */
s->nelem++;
}
}
void pop(tStack *s) {
if (s->nelem == 0) {
printf("\n Empty stack\n");
} else {
s->nelem--;
}
}
void top(tStack s, tCarta *carta) {
if (s.nelem == 0) {
printf("\n Empty stack \n");
} else {
copiarCarta(carta, s.A[s.nelem-1]);
}
}
bool isEmptyStack(tStack s) {
return (s.nelem == 0);
}
bool isFullStack(tStack s) {
return (s.nelem == MAX_CARTAS);
}
/* Acción adicional que muestra por pantalla todos
* los elementos de una pila.
*/
void printStack(tStack s) {
tCarta cartaAux;
while (!isEmptyStack(s)) {
cartaAux = s.A[s.nelem-1];
if (cartaAux.palo == DIAMANTES) {
printf(" [%c] de DIAMANTES\n", cartaAux.valor);
} else if (cartaAux.palo == PICAS) {
printf(" [%c] de PICAS\n", cartaAux.valor);
} else if (cartaAux.palo == TREVOLES) {
printf(" [%c] de TRÉVOLES\n", cartaAux.valor);
} else if (cartaAux.palo == CORAZONES) {
printf(" [%c] de CORAZONES\n", cartaAux.valor);
}
pop(&s);
}
}
/* Acción que permite copiar el contenido
* de una tupla tCarta a otra
*/
void copiarCarta(tCarta *destino, tCarta origen) {
/* Recordemos:
* - si el parámetro es un puntero, se accede
* a los atributos con '->'
* - si el parámetro es un valor, se accede
* a los atributos con '.'
*/
destino->valor = origen.valor;
destino->palo = origen.palo;
}
/* Acción que recibe dos parámetros:
* - pilaCartas (inout)
* - pilaDiamantes (out)
* Esta acción separa de pilaCartas (inout) aquellas
* cartas con palo DIAMANTES, y las añade a
* PilaDiamantes (out). Así, una vez ejecutada la acción, tendremos:
* - pilaCartas: contendrá todas las cartas que no son DIAMANTES.
* - pilaDiamantes: contendrá todos los DIAMANTES.
*/
void separarDiamantes(tStack *pilaCartas, tStack *pilaDiamantes) {
tCarta cartaAux;
tStack pilaNoDiamantes;
/* Todas las cartas inicialmente están en pilaCartas.
* Se trata de ir añadiendo los DIAMANTES a la pilaDiamantes,
* y los que no son DIAMANTES a la pila temporal pilaNoDiamantes.
* Posteriormente asignaremos pilaNoDiamantes a pilaCartas (inout).
*/
/* Inicializamos la pila auxiliar. */
initStack(&pilaNoDiamantes);
/* Tenemos que tratar todos los elementos de la pila. */
while (!isEmptyStack(*pilaCartas)) {
top(*pilaCartas, &cartaAux);
if (cartaAux.palo == DIAMANTES) {
push(pilaDiamantes, cartaAux);
} else {
push(&pilaNoDiamantes, cartaAux);
}
pop(pilaCartas);
}
/* Se traspasa el contenido de la pila auxiliar
* pilaNoDiamantes a pilaCartas, que corresponde
* al parámetro de clase inout de la acción.
*/
while (!isEmptyStack(pilaNoDiamantes)) {
top(pilaNoDiamantes, &cartaAux);
push(pilaCartas, cartaAux);
pop(&pilaNoDiamantes);
}
}