Sie sind auf Seite 1von 12

Punteros y Arreglos/Registros Memoria Dinamica

1
PUNTEROS.
Para entender mejor los punteros es necesario revisar algunos conceptos basicos:
Variable.
La memoria de un computador est compuesta por unidades bsicas llamadas
"Bytes". Cada byte posee 8 bits los cuales slo pueden tomar dos valores, 0 1.
Cada Byte tiene una identificacin o direccin, llamada "Direccin de Memoria"
Una computadora opera manipulando direcciones de memoria y los valores
almacenados en dichas direcciones. Una variable es un identificador, que
representa una localidad de memoria. La ubicacin de la variable es hecha por el
compilador.
Una declaracin de variables como:
int temp, i;
float a,b,c;
Produce una asociacin entre los nombres de las variables y direcciones de
memoria. Las variables ocupan un espacio en memoria que depende del tipo de
dato asociado a ella.

La cantidad de espacio ocupado por un tipo estandar especifico,
depende del tipo de computador y del sistema operativo. La tabla
siguiente muestra a la derecha tiene el tamao en bytes que
ocupan los tipos estandar en un micro actual, con windows de 32
bits.
Las variables tienen entonces 2 valores asociados a ella. Un
valor es la direccin de memoria que le fue asignada por el
compilador y que no puede ser cambiada mientras el
programa se est ejecutando y otro valor que es el
contenido de la variable misma, el cual si puede ser
cambiado por las instrucciones del programa.

Arreglo.
Un arreglo es un conjunto ordenado de elementos del mismo tipo,
este tipo puede ser de los que proporciona el lenguaje (char, int,
float, double, etc). El tipo determina cuantos bytes ocupa cada
elemento y junto con el tamao determina el espacio que va a
ocupar toda la estructura.
Ejemplo:
int vector[5]={3,345,4,54,5};
char nombre[10]="MARIA";
struct t_nota
{ char cod;
int min, max;
};
t_nota Tabla[6]={{A,18,20},{B,15,17},
{C,10,14},{D,8,9}, {E,5,7},{F,0,4}};
En las declaraciones anteriores:

int Vector[5] = {3,345,4,54,5};
El espacio que ocupa Vector puede ser calculado multiplicando el numero de elementos que posee por el espacio
que ocupa cada elemento. Vector ocupa 20 Bytes: 5 Elementos * 4 Bytes/Elemento.
char nombre[10]=MARIA;
Punteros y Arreglos/Registros Memoria Dinamica
2
Se almacena un carcter por byte, agregando un fin de cadena (\0) despues del ultimo carcter. El fin de cadena
facilita la manipulacion del arreglo ya que determina hasta donde hay informacin valida.
Tabla es un arreglo de 6 registros, cada uno de los cuales tiene 3 campos. Cada registro ocupa 9 Bytes(cod 1 byte
+ min 4 byte + max 4 byte) por lo tanto la estructura ocupa 54 bytes.
La instruccin en C (sizeof()) permite obtener el tamao en bytes de un tipo especifico, una variable, un arreglo o una
estructura tipo registro.
Ejemplo:
void main()
{ int A, M[10];
float B,N[5][5];
struct T
{ char cod;
Int inf,sup;
}
T tabla[10] ;
cout<<sizeof(int) //Muestra el tamao en bytes del tipo de dato int
cout<<sizeof(A) //Muestra tamao en bytes de la variable A
cout<<sizeof(M) //Muestra tamao en bytes del arreglo M
cout<<sizeof(T) //Muestra tamao en bytes del tipo T
cout<<sizeof(tabla) //Muestra tamao en bytes del arreglo tabla
}

Se puede calcular el tamao del arreglo M = sizeof(M)/sizeof(M[0])
PUNTERO (Definicin, Usos y Ventajas)
Un puntero es una variable que contiene la direccin de otra variable, dicho de otra manera los punteros son variables
que almacenan unicamente direcciones de memoria.
Para declarar un puntero se usa el formato: tipo *nombre donde:
tipo indica el tipo de variables que se van a manejar a travs del puntero y corresponde a los tipos de variable
definidos en C o algn tipo definido por el usuario (struct). El tipo permite conocer la cantidad de bytes a ser
leidos al usar el puntero.
(*) indica que la variable que se encuentra a continuacion es un puntero
nombre es el nombre del puntero. Cualquier Identificador valido en c.
Por Ejemplo: int *pent; Declaran un puntero pent tipo int.
char *ptexto, T; Declaran un puntero ptexto tipo char.
float *preal, f; Declaran un puntero preal tipo float.
Usos y ventajas de los punteros:
Permiten el acceso a cualquier posicin de la memoria, para ser leda o para ser escrita (en los casos en que esto sea
posible)
Permiten la transferencia de argumentos a las funciones, de modo que puedan retener un valor nuevo, que resulta de
aplicarles la funcin.
Se usan en el proceso de definicin de memoria dinmica
Son el soporte de enlace que utilizan estructuras avanzadas de datos en memoria dinmica como las listas, pilas,
colas y rboles.
Operan ms eficientemente en los arreglos, en comparacin con el uso de subndices.
Operadores & y *
Cuando se hace necesario hacer referencia a la direccin de una variable se usa el simbolo (&) como un Operador de
direccin. Colocado delante de una variable permite referenciar la direccin donde esta alojada y se lee como "la
direccin de".
Ejemplo: En el codigo del ejemplo:

Punteros y Arreglos/Registros Memoria Dinamica
3
int a,*b,i,j;
i = 18;
a = i;
b = &i;
j = *b;
*b = 99;
En la 1 linea se declaran 4 variables, b es un
puntero. Se asignan direcciones a las cuatro variables.
Luego se asigna a la variable i un valor. El contenido
de la variable i se hace igual a 18.
Luego el contenido de la variable a se hace igual al contenido de la variable i.
cuando se usa una variable, se esta haciendo referencia a su contenido.
Si se quisiera usar la direccin de la variable i entonces se usa el perador &,
como en b=&i. A la variable b, que es un puntero, se le asigna el valor de la
direccin de la variable i.
Para extraer el contenido de una direccin de memoria, o sea un puntero, se coloca el operador (*) delante de la
variable puntero, como en j = *b. A la variable j se le asigna el contenido de la direccin que apunta b.
En La ultima linea se asigna a la variable que apunta b el valor 99. b esta apuntando a la variable i, por lo tanto el
contenido de la variable i se hace igual a 99.
En el analisis realizado es importante resaltar que el operador * se usa con diferentes propositos. Se usa en una
declaracin (int *b) para indicar que una variable es un puntero y se usa en una asignacion (j=*b o *b=99) para
hacer referencia al contenido de la variable que apunta un puntero.
Inicializacin de PUNTEROS.
Un puntero, como cualquier variable, adems de ser declarado (para comenzar a existir) necesita ser inicializado
(darle un valor de modo controlado). Al ser declarado posee un valor, el problema es que se trata de un valor aleatorio.
Intentar operar con un puntero sin haberlo inicializado es una frecuente causa de problemas. En relacin a la inicializacin
de un puntero existen tres posibilidades:
1. Estar indefinido (lo ms peligroso)
2. No apuntar a nada
3. Apuntar a una variable o direccin de memoria determinada.
En el siguiente ejemplo se muestra la inicializacin de un puntero.
Formas Incorrectas de inicializacin Formas Correctas de inicializacin
int a, *b;
b=a; // Error falta &
int a, *b;
b=&a; // b apunta a a
int alfa, *pa;
*pa = &alfa; // Error sobra *
int alfa, *pa = &alfa;
*pa = 99; // alfa = 99
int alfa, *pa;
*pa=99; // Error puntero indefinido
int *pa = NULL; // pa apunta a nada


Punteros y Arreglos/Registros Memoria Dinamica
4
EJEMPLO DEL USO DE PUNTEROS.
En el ejemplo de la derecha, se usan los punteros para
transferir informacion a una funcion.
El objetivo del programa es calcular las raices de un
polinomio de 2 grado. El programa principal lee los
coeficientes A, B, C, los transfiere a una funcion (RAICES)
la cual calcula las raices y devuelve los resultados al
programa principal, el cual los muestra y luego finaliza el
programa.
Ademas de los coeficientes la funcion recibe la direccin de
dos variables (R1 y R2) del programa principal para colocar
alli los resultados del calculo.

OPERACIONES CON PUNTEROS.
La tabla que se muestra a la derecha resume las
operaciones bsicas que se pueden realizar usando
punteros:
Asignacion.
- Usando el operador & se puede asignar a un
puntero la direccion de una variable.
- Es posible asignar a una variable puntero el valor
de otra variable puntero, siempre que las dos
variables sean del mismo tipo: int *ptr1, *ptr2;
ptr1=ptr2;

ptr2 apuntara a la misma direccin que apunta ptr1. Es importante tener en cuenta que si ptr2 estaba apuntando
a una variable definida en forma dinamica, al cambiar su valor se pierde la direccin del area de memoria a la
que apuntaba y no se puede recuperar.
- Se puede asignar a un puntero un valor NULL, equivalente a inicializar un acumulador = 0. Un puntero con n
valor NULL no apunta a nada.
Comparacion.
- Se puede comparar dos punteros usando operadores relacionales de igualdad (==), desigualdad (=) y
comparacion (<, >, =<, >=). Dos punteros son iguales si apuntan a la misma variable dinamica o si fueron
inicializados a NULL.
Incremento/Decremento.
- La variable tipo puntero se pueden incrementar o decrementar en valores enteros ptr1=ptr+4; ptr1++; ptr2--;
- El incremento/decremento produce el cambio en el valor de la direccin que apunta que depende del tipo de dato
manejado por el puntero. Asi por ejemplo un incremento de una unidad (ptr++) en un puntero tipo char mueve
el puntero al byte siguiente, pero un incremento de una unidad en un puntero tipo int mueve el puntero 4 bytes
hacia delante.
- No esta permitida ninguna otra operacin con los punteros (multiplicacion, division, etc)
Arreglos y punteros.
Arreglos Unidimensionales y Punteros
El siguiente programa muestra diferentes maneras de utilizar un arreglo mediante punteros:
Punteros y Arreglos/Registros Memoria Dinamica
5

Debido a la naturaleza "variable" de pp en el programa ejemplo, todas las asignaciones son validas. Al ejecutar el
programa del recuadro se obtiene la siguiente salida: 10 20 30 40 50
El concepto de arreglo esta ligado al de puntero. De hecho, el identificador o nombre de un arreglo es equivalente a la
direccin de su primer elemento y como un puntero guarda la direccin del elemento que apunta, entones el nombre del
arreglo y el puntero al arreglo, son la misma cosa. Ejemplo: Dadas las siguientes declaraciones:
int N[5];
int *pp
Las siguiente declaracines serian validas:
pp = N o pp=&N[0] pero pp=&N no es valida
Esto hace que pp y N sean equivalentes aunque existe una diferencia importante la cual permite que a pp se le pueda
asignar cualquier otro valor mientras que N siempre apuntara al primer elemento del arreglo de 5 elementos N. De esta
manera se dice que pp es una variable puntero, mientras que N es una constante puntero (el nombre de un arreglo es
una constante puntero). Como consecuencia de lo anterior la asignacin N = pp no es valida:
Punteros y Cadenas.-
En C, las cadenas son arreglos de caracteres terminado con un cero binario (escrito
'\0').
Si se tiene la siguiente declaracin: char mi_cadena[40], se puede inicializar de las
siguientes maneras:
(3) mi_cadena[0] = ' U ';
mi_cadena[1] = ' N ';
mi_cadena[2] = ' E ';
mi_cadena[3] = ' X ';
mi_cadena[4] = ' P ';
mi_cadena[5] = ' O ';
mi_cadena[6] = ' \0 ';
(1) char mi_cadena[40]={ ' U ', ' N ', ' E ', ' X ', ' P ', ' O ', ' \0 '}
(2) char mi_cadena[40]={ "UNEXPO" }
Todas estas formas dan el mismo resultado. Es importante observar el uso de las comillas. Unas son simples y otras son
dobles, si se usan comillas dobles, el carcter \0 se agrega automticamente al final de la cadena. En el ejemplo de la
derecha se declaran dos cadenas de 80 caracteres c/u.
Debido a que se definen "globales", son inicializadas con '\0'
en todos sus elementos. cadA se inicializa con el texto "Una
cadena de prueba" mas el carcter '\0' al final.
En el programa principal se declaran dos punteros char *pA,
*pB; luego se muestra cadA. pA se apunta a cadA mediante la
asignacin pA = cadA y luego se muestra lo que apunta pA.
La instruccin while se ejecuta mientras el contenido de la
direccin que apunta pA sea diferente a '\0'.

La instruccin que se repite en el ciclo while es "copiar el contenido de la direccin que apunta pA, en el contenido de la
direccin que apunta pB", luego se incrementa en una unidad la direccin que apuntan pA y pB.
El lazo finaliza cuando se consigue el carcter '\0' en la cadena cadA, sin embargo no se copia este ultimo carcter, por lo
tanto es necesario agregar el fin de cadena a cadB.
En resumen en el programa se muestra la cadA de dos formas diferentes y luego se copia la cadena en cadB y se
muestra esta ultima produciendo el mismo resultado.
Punteros y Arreglos/Registros Memoria Dinamica
6
Lo que muestra el programa es una forma de copiar
una cadena de caracteres. De esta forma se podra
definir una funcin CopiarCadena como se muestra a
la derecha:

La definicin de la funcin, segn el esquema: <tipo><nombre_de_funcion>(parmetros)

indica que el tipo de dato devuelto es un puntero (*) y los parmetros de la funcin son tambin punteros. Se observa en
la funcin que no aparece por ningn lado tamaos de los arreglos, ya que se pasan solo las direcciones iniciales
Destino[0], Origen[0]. Esta estrategia puede ser usada tambin con arreglos de datos tipo enteros/reales teniendo en
cuenta que el fin del arreglo no estara determinado con un carcter especial, como el nul.
Por ejemplo se podria copiar un arreglo de valores enteros positivos marcando el final del arreglo con un valor entero
negativo. Otra forma seria pasar junto con los punteros Origen/Destino, el nmero de elementos a copiar.
Punteros y Arreglos multidimensionales.
La declaracin e inicializacion del siguiente arreglo:
char M[4][5]={{'A','B','C','D','E'}, {'0','1','2','3','4'},
{'F','G','H','I','J'},{'5','6','7','8','9'}};

Corresponde a un arreglo de dos
dimensiones como el que se muestra en la
figura de la derecha.
Este arreglo de dos dimensiones se guarda
en la memoria, que es de una dimension, por
filas.
Debido a que los datos originales, o sea la matriz, es de dos dimensiones, y
la memoria es de una dimension, se debe organizar la informacion de
alguna manera que facilite la ubicacin de los elementos de la matriz en la
memoria. La matriz se guarda por filas, quedando en la memoria como se
muestra en la figura de la derecha.
El primer indice (I) de M[I][J], representa la fila, o sea que el primer elemento de la primera fila, que coincide con el
primer elemento de la matriz, se puede direccionar como M[0] y si usamos punteros tenemos que M[0] equivale a
*(M+0) y en general M[fil] equivale a *(M +fil).
Para ubicar una fila es necesario hacer un desplazamiento (DFil) que se calcula como:
DFil = fil * columnas * sizeof(tipo)
Donde: fil representa la fila que queremos ubicar.
columnas cantidad de columnas que tiene la matriz
sizeof(tipo) cantidad de bytes que ocupa el tipo de dato almacenado
para ubicar la columna usamos el segundo indice (J). Una vez que se ubica la fila se procede a ubicar la columna para lo
cual es necesario desplazarse una cantidad de bytes que se calcula como:
DCol = col * sizeof(tipo)
Donde: col representa la columna que queremos ubicar.
sizeof(tipo) cantidad de bytes que ocupa el tipo de dato almacenado
Punteros y Arreglos/Registros Memoria Dinamica
7
Por ejemplo si queremos ubicar en la memoria el elemento I de la matriz, el cual esta ubicado en la fila 2, columna 3, se
utilizan las siguientes relaciones:
DFil = fil * columnas * sizeof(tipo)
DFil = 2 * 5 * 1 = 10
DCol = col * sizeof(tipo)
DCol = 3 * 1 = 3

M[fil][col] * ( * ( M + fil ) + col )
*(*(M + DFil) + DCol)
El siguiente programa ilustra el uso de punteros en arreglos bidimensionales.

Al ejecutar este programa se observa la siguiente salida:

El resultado de usar ndices o punteros es el mismo
PUNTEROS y Estructuras.
Un puntero tambien puede apuntar a una estructura tipo registro. La declaracin del puntero se hace usando el mismo
formato: tipo *nombre
Donde tipo indica el tipo de variables que se van a manejar a travs del puntero, en este caso corresponde al tipo
definido en la estructra, o sea un tipo definido por el usuario y nombre es el nombre del puntero.
Ejemplo: La tabla que se muestra en la pagina siguiente representa la equivalencia de calificaciones de escala numerica
(0-20) a escala literal (A-F). Esta estructura se declara e inicializa:







Se almacena en memoria como se
muestra a la derecha. El campo cod
ocupa un byte y los campos min/max
ocupan 4 bytes cada uno, entonces un
registro completo ocupa (1+4+4) 9 bytes
y el arreglo (Tabla[6]) ocupar (9*6) 54
bytes.

Punteros y Arreglos/Registros Memoria Dinamica
8
El puntero se define e inicializa: t_nota *pt = Tabla;
La estructura se almacena como se muestra arriba y se procesa de la
misma manera que un vector, con la diferencia que los elementos de la
estructura o arreglo de registros (Tabla) contiene a su vez varios
campos (cal, min, max).
Si se usan indices para manejar el arreglo de registros se utiliza un
indice para ubicar la fila (Tabla[indice]) y para ubicar un campo se
coloca un punto (.) y despues se pone el nombre del campo. Ejempo
Tabla[2].cal = 'C'.
Si se usan indices para manejar el arreglo de registros se utiliza un indice para ubicar la fila (Tabla[indice]) y para
ubicar un campo se coloca un punto (.) y despues se pone el nombre del campo. Ejempo Tabla[2].cal = 'C'.
Si se utiliza un puntero para manejar el arreglo de
registros se declara e inicializa el puntero como se
muestra a la dereha. De esta manera el puntero
queda apuntando al primer elemento del arreglo (pt
= Tabla).
Para ubicar un campo dentro del registro se utiliza el
simbolo (->). Por ejemplo: pt ->cal, hace referencia
al campo cod del registro apuntado por pt, en ese
momento. El programa siguiente muestra el arreglo
de registros definido e inicializado arriba.

MEMORIA DINAMICA.-
Las variables, tanto simples (int, float, char) como estructuradas (arreglos, registros) son variables estaticas. Esto
significa que comienzan a existir y ocupan un espacio constante en la memoria del programa (segmento del
programa), desde el momento en que son declaradas y el espacio que ocupan no puede ser cambiado mientras se esta
ejecutando el programa. En el caso de los arreglos, es necesario definir el tamao en el momento de la declaracin y no
se puede cambiar mientras el programa esta corriendo. Esta memoria se libera al terminar el programa.
Si no se conoce el tamao del arreglo es necesario estimar un tamao y la estimacion podria quedar corta lo que haria
fallar el programa o muy grande lo que significa un desperdicio del recurso memoria. Una solucion deseada sera poder
leer el tamao necesario y luego con ese valor dimensionar el arreglo.
Existe una forma de resolver este inconveniente usando punteros y tecnicas de asignacion dinamica de memoria
(ADM) que provee el lenguaje c++ es a traves de los operadores new y delete.

Punteros y Arreglos/Registros Memoria Dinamica
9
El operador new.
El formato es: new tipo_de_dato;
Asigna un bloque de memoria que es el tamao del
tipo_de_dato. Este bloque de memoria se toma
del free storage, no del segmento del
programa.
El free storage es una area de memoria que esta
disponible para ser utilizada como memoria
dinamica por cualquier programa.
El tipo_de_dato puede ser un int, float, un arreglo,
una estructura o cualquier otro tipo de dato.

Ejemplo 1:
char *p1;
int *p2;
p1=new char;
p2=new int
*p1='A';
*p2=333;
Ejemplo 2:
char *p1=new char;
int *p2=new int;
*p1='A';
*p2=333;
Los ejemplos anteriores son dos versiones de utilizacin del operador, alli se declaran dos punteros p1 y p2, luego se
crean 2 variables simples utilizando los punteros para almacenar la direccin que devuelve new. Finalmente usando
el operador (*) de desreferencia se almacena en las direcciones que apuntan p1/p2 valores correspondientes a los
tipos de cada puntero (*p1='A', p2=333).
El operador delete.
Debido a que la necesidad de memoria dinamica esta limitada a momentos especificos dentro de un programa, una vez
que se termina la tarea para la cual se cre, deberia ser liberada de manera que pueda ser utilizada por otros
programas. Este es el propsito del operador delete cuyo formato es:
delete puntero;
Creacion dinamica de ARREGLOS
Si se quiere crear un arreglo (vector) en forma dinamica se usa el siguiente formato:
tipo_dato *puntero;
puntero = new tipo_dato[tamao];
En forma resumida:
tipo_dato *puntero = new tipo_dato[tamao];
Ejemplo 1:



Punteros y Arreglos/Registros Memoria Dinamica
10



Ejemplo 4:

El ejemplo muestra como se define un vector en
forma dinamica usando los operadores new y
delete.
El algoritmo pide el numero de elementos que va a
tener el vector, lo define, lo llena con valores
aleatorios entre 0 y 99, busca el elemento mayor y
luego libera el espacio reservado. De esta forma se
pasa por las tres etapas, creacin, uso y liberacin
de un vector de forma dinamica.
En la linea 5 se define el puntero vector de tipo
entero que va a almacenar la direccin del primer
elemento del vector dinamico.


Luego en la linea 9, despues de conocer el numero de
elementos que va atener el vector (n) se hace la definicin
dimamica del vector y de alli en adelante se puede usar
vector para referenciar los elementos del vector ya sea
usando indices o usando punteros.
Se puede hacer la referencia a los elementos del vector
usando punteros


Punteros y Arreglos/Registros Memoria Dinamica
11
Memoria Dinamica y Matrices.
Debido a la forma como se almacenan las matrices (Dos
dimensiones) en la memoria (Una dimension), el manejo de la
memoria dinamica es un poco diferente en el caso de creacin de
matrices en forma dinmica. Para explicar este manejo es
necesario revisar otro aspecto de los punteros:
Puntero a Puntero.- Puede definirse como una variable tipo
puntero que almacena la direccin de otro puntero.
En el ejemplo a la derecha, la variable q es lo que se llamaria un
puntero a puntero ya que esta almacenando la direccin de p
(&p) que a su vez es otro puntero.
int A = 5;
int *p;
int **q;
p=&A;
q=&p;

Para llegar al valor de A se tienen 3 posibilidades:
Referencia directa Ejemplo: A = A + 1
Usando *p Ejemplo: B = *p
Usando **q Ejemplo: **q = 5 **q equivale a *(*q), o sea *(p).
Arreglos de Punteros.-
Cierto tipo de problemas que manejan estructuras de datos (arreglos) pueden requerir el uso de punteros por lo que
se facilita la tarea si los punteros se organizan en arreglos. Un arreglo de punteros se declara de manera similar a
un arreglo "normal":
Tipo Nombre_Arreglo[Tamao];
Ejemplo: int A[100]; // Arreglo de 10 variables enteras
Tipo *Nombre_Arreglo[Tamao];
Ejemplo: int *PP[10]; // Arreglo de 10 punteros a variables enteras
int *QQ=new int[N]; // Arreglo dinamico de N variables enteras
int **RR=new int*[N] // Arreglo dinamico de N punteros a variables enteras
Ejemplo:

Al ejecutar este programa se muestra: var[0] = 10
var[1] = 100
var[2] = 200
Creacion dinamica de la matriz.
Una matriz o arreglo de dos dimensiones puede ser vista como un arreglo de vectores, o sea que cada fila de la matriz se
puede considerar un vector. La direccin de cada vector-fila se guarda en un vector que tendra tantos elementos como
filas la matriz MAT[F].
Punteros y Arreglos/Registros Memoria Dinamica
12
Como el vector MAT contiene direc-ciones
de memoria (punteros) y una variable tipo
puntero que contiene la direccin de otro
puntero es un pun-tero doble, la
declaracin de MAT debe ser puntero
doble (**MAT).
En la figura que se muestra a la derecha
se representa el almace-namiento de una
matriz de 3 filas x 4 columnas.

La creacion dinamica del vector MAT[F],
siendo F el numero de filas que tendr la
matriz es:
int **MAT;
MAT=new int*[F];

Luego se hace necesario crear cada uno de los vectores-fila que contiene tantos elementos como columnas tiene la matriz
a crear.
for(i=0;i<F;i++) MAT[i]=new int[C];
El programa que se muestra define en forma dinamica una matriz de dos dimensiones.

Das könnte Ihnen auch gefallen