Las soluciones a los ejercicios se pueden encontrar en el documento electrónico titulado «The Thinking in C++ Annotated Solution Guide», disponible por poco dinero en www.BruceEckel.com.
Convierta el fragmento de código «bird & rock» del principio de este capítulo a un programa C (utilizando estructuras para los tipos de datos), y que compile. Ahora intente compilarlo con un compilador de C++ y vea qué ocurre.
Coja los fragmentos de código al principio de la sección
titulada «Referencias en C++» y póngalos en un
main()
. Añada sentencias para imprimir
en la salida para que pueda demostrar usted mismo que las
referencias son como punteros que se dereferencian
automáticamente.
Escriba un programa en el cual intente (1) Crear una referencia que no esté inicializada cuando se crea. (2) Cambiar una referencia para que se refiera a otro objeto después de que se haya inicializado. (3) Crear una referencia nula.
Escriba una función que tome un puntero por argumento, modifique el contenido de lo que el apunta puntero, y retorne ese mismo contenido como si de una referencia se tratara.
Cree una nueva clase con algunos métodos, y haga que el objeto sea apuntado por el argumento del Ejercicio 4. Haga que el puntero pasado como argumento y algunos métodos sean constantes y pruebe que sólo puede llamar a los métodos constantes dentro de su función. Haga que el argumento de su función sea una referencia en vez de un puntero.
Coja los fragmentos de código al principio de la sección «referencias a puntero» y conviértalos en un programa.
Cree una función que tome como argumento una referencia a
un puntero que apunta a otro puntero y modifique ese argumento. En
main()
, llame a la función.
Cree una función que toma un argumento del tipo
char&
y lo modifica. En el
main()
imprima a la salida una variable
char
, llame a su función con esa variable e
imprima la variable de nuevo para demostrar que ha sido cambiada.
¿Cómo afecta esto a la legibilidad del programa?
Escriba una clase que tiene un método constante y otra que no lo tiene. Escriba tres funciones que toman un objeto de esa clase como argumento; la primera lo toma por valor, la segunda lo toma por referencia y la tercera lo toma mediante una referencia constante. Dentro de las funciones, intente llamar a las dos funciones de su clase y explique los resultados.
(Algo difícil) Escriba una función simple que toma un
entero como argumento, incrementa el valor, y lo retorna. En
main()
, llame a su función. Intente que el
compilador genere el código ensamblador e intente entender cómo
los argumentos se pasan y se retornan, y cómo las variables locales
se colocan en la pila.
Escriba una función que tome como argumentos un
char
, int
, float
y
double
. Genere el código ensamblador con su
compilador y busque las instrucciones que apilan los
argumentos en la pila antes de efectuar la llamada a
función.
Escriba una función que devuelva un double
.
Genere el código ensamblador y explique cómo se retorna el
valor.
Genere el código ensamblador de
PassingBigStructures.cpp
. Recorra y
desmitifique la manera en que su compilador genera el
código para pasar y devolver estructuras grandes.
Escriba una simple función recursiva que disminuya su argumento y retorne cero si el argumento llega a cero, o en otro caso que se llame a sí misma. Genere el código ensamblador para esta función y explique la forma en el compilador implementa la recurrencia.
Escriba código para demostrar que el compilador genera un constructor de copia automáticamente en caso de que no lo haga el programador. Demuestre que el constructor de copia generado por el compilador realiza una copia bit a bit de tipos primitivos y llama a los constructores de copia de los tipos definidos por el usuario.
Escriba una clase en la que el constructor de copia se
anuncia a sí mismo a través de
cout
. Ahora cree una función que pasa un
objeto de su nueva clase por valor y otra más que crea un
objeto local de su nueva clase y lo devuelve por valor.
Llame a estas funciones para demostrar que el constructor de
copia es, en efecto, llamado cuando se pasan y retornan
objetos por valor.
Cree un objeto que contenga un double*
. Que el
constructor inicialice el double*
llamando a
new double
y asignando un valor. Entonces, que
el destructor imprima el valor al que apunta, asigne ese
valor a -1, llame a delete
para liberar la memoria
y ponga el puntero a cero. Ahora cree una función que tome
un objeto de su clase por valor, y llame a esta función
desde main()
. ¿Qué ocurre? Solucione
el problema escribiendo un constructor de copia.
Cree una clase con un constructor que parezca un constructor de copia, pero que tenga un argumento adicional con un valor por defecto. Muestre que a pesar de eso se utiliza como constructor de copia.
Cree una clase con un constructor de copia que se anuncie a sí mismo (es decir que imprima por la salida que ha sido llamado). Haga una segunda clase que contenga un objeto miembro de la primera clase, pero no cree un constructor de copia. Demuestre que el constructor de copia, que genera automáticamente el compilador en la segunda clase, llama al constructor de copia de la primera.
Cree una clase muy simple, y una función que devuelva un objeto de esa clase por valor. Cree una segunda función que tome una referencia de un objeto de su clase. Llame a la segunda función pasándole como argumento una llamada a la primera función, y demuestre que la segunda función debe utilizar una referencia constante como argumento.
Cree una clase simple sin constructor de copia, y una función simple que tome un objeto de esa clase por valor. Ahora cambie su clase añadiéndole una declaración (sólo declare, no defina) privada de un constructor de copia. Explique lo que ocurre cuando compila la función.
Este ejercicio crea una alternativa a la utilización del
constructor de copia. Cree una clase
X
y declare (pero no defina) un
constructor de copia privado. Haga una función
clone()
pública como un método
constante que devuelve una copia del objeto creado
utilizando new
. Ahora escriba una función que tome
como argumento un const X&
y clone una
copia local que puede modificarse. El inconveniente de esto
es que es el programador el responsable de destruir
explícitamente el objeto clonado (utilizando
delete
) cuando haya terminado con él.
Explique qué está mal en Mem.cpp
y
MemTest.cpp
del Capítulo 7. Solucione el problema.
Cree una clase que contenga un double
y una
función print()
que imprima el
double
. Cree punteros a miembro tanto
para el atributo como al método de su clase. Cree un objeto
de su clase y un puntero a ese objeto, y manipule ambos
elementos de la clase a través de los punteros a miembro,
utilizando tanto el objeto como el puntero al objeto.
Cree una clase que contenga un array de enteros. ¿Puede recorrer el array mediante un puntero a miembro?
Modifique PmemFunDefinition.cpp
añadiendo un método f()
sobrecargado
(puede determinar la lista de argumentos que cause la
sobrecarga). Ahora cree un segundo puntero a miembro,
asígnelo a la versión sobrecargada de
f()
, y llame al método a través del
puntero. ¿Cómo sucede la resolución de la función
sobrecargada en este caso?
Empiece con la función
FunctionTable.cpp
del Capítulo 3. Cree una clase que contenga
un vector
de punteros a funciones, con métodos
add()
y remove()
para añadir y quitar punteros a función. Añada una función
denominada run()
que recorra el
vector
y llame a todas la funciones.
Modifique el Ejercicio 27 para que funcione con punteros a métodos.