Como muchos otros lenguajes, C++ tiene punteros. Ya sabes que las referencias y los punteros son similares, pero piensa en ellos como un nivel más cerca del funcionamiento interno de tu computadora. Los punteros son variables que contienen direcciones de objetos. Se usan para interactuar directamente con los objetos, lo que permite la asignación dinámica de memoria y una manipulación eficiente de los datos en C++.
Si es la primera vez que usas los punteros, pueden parecer un poco misteriosos, pero una vez que te acostumbras a ellos, son bastante sencillos.
Son una parte crucial de C++, así que dedícales un tiempo para entenderlos de verdad. La versión más básica de este concepto también se llama puntero tonto o puntero crudo. Con el C++ moderno también existen los punteros inteligentes; el tipo básico no es nada inteligente y tienes que encargarte de todo el trabajo manualmente.
Antes de profundizar en los detalles, vale la pena entender el uso de los punteros. Los punteros son una forma de compartir la dirección de un objeto con otras partes de nuestro programa, lo cual es útil por dos razones principales:
Una declaración de puntero en C++ implica especificar el tipo de dato al que apunta el puntero, seguido de un asterisco (*) y el nombre del puntero.
Cuando se declaran los punteros, no se inicializan automáticamente.
Sin una asignación explícita, un puntero suele contener un valor indeterminado, al que con frecuencia se le llama «dirección basura».
Aunque algunos compiladores podrían inicializar los punteros a nullptr, este comportamiento no está garantizado en todos los compiladores, así que es esencial no confiar en ello.
Es una buena práctica inicializar explícitamente los punteros crudos y verificar que no sean nulos antes de usarlos, para evitar posibles problemas.
int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid
Para asignar la dirección de una variable a un puntero, usas el operador de dirección (&).
Para desreferenciar un puntero se usa el operador de indirección (*).
std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};
Atención: la desreferenciación debe hacerse de forma explícita, mientras que las referencias simplemente funcionaban como un alias.
La aritmética de punteros te permite realizar operaciones aritméticas con punteros, lo cual es especialmente útil cuando trabajas con arrays. Sumar un entero a un puntero hace que apunte a un elemento diferente.
// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
La aritmética de punteros en C++ puede llevar fácilmente a un comportamiento indefinido si no se maneja con cuidado. El comportamiento indefinido puede manifestarse en resultados inesperados del programa, fallos, o incluso vulnerabilidades de seguridad. Un ejemplo tristemente célebre de las consecuencias del comportamiento indefinido ocurrió en la explosión del cohete Ariane 5 en 1996, donde una excepción de software causada por la conversión de un número de punto flotante de 64 bits a un entero con signo de 16 bits provocó un fallo catastrófico.
En C++, el operador -> se usa para acceder a los miembros de un objeto a través de un puntero a ese objeto.
Es una forma abreviada que simplifica el acceso a los miembros de los objetos a los que apuntan los punteros.
Por ejemplo, si ptr es un puntero a un objeto con una variable miembro x, en lugar de usar (*ptr).x, puedes usar directamente ptr->x.
Este operador mejora la legibilidad del código y reduce la verbosidad cuando trabajas con punteros a objetos.
Aquí tienes un ejemplo breve, con una struct Superhero que tiene una variable miembro superpower.
La función main crea un puntero dianaPrince a un objeto Superhero (que representa a Wonder Woman).
El operador -> se usa para acceder a la variable miembro superpower, que muestra el icónico «Lasso of Truth» de Wonder Woman.
struct Superhero {
std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;
Los punteros y las referencias permiten ambos el acceso indirecto a los objetos, pero difieren en sus capacidades y en sus consideraciones de seguridad. Los punteros ofrecen la flexibilidad de cambiar el objeto al que apuntan y se les puede asignar un valor nulo. Sin embargo, esta flexibilidad introduce riesgos, como desreferenciar punteros nulos o crear punteros colgantes. Las referencias, por otro lado, no pueden ser nulas y quedan vinculadas a objetos válidos desde su creación, lo que evita estos riesgos. Dado que son más seguras, conviene preferir las referencias sobre los punteros, a menos que se necesiten las funcionalidades adicionales que ofrecen los punteros.
¡Te damos la bienvenida, ingeniero! Eres uno de los últimos veteranos de la Fundación Speedywagon, una organización secreta que lleva décadas combatiendo amenazas antiguas como los Pillar Men. En el transcurso de ese esfuerzo, has pasado años manteniendo los sistemas tecnológicos de la Fundación, construidos con una mezcla de tecnología de punta y bibliotecas antiguas.
Sin embargo, últimamente los sensores que rastrean la actividad de los Pillar Men están fallando. Los sistemas de la Fundación son antiguos, y el código interactúa con una biblioteca heredada de C++ que no se puede actualizar. Tu tarea es implementar cuatro funciones principales que monitorean la actividad de los sensores de los Pillar Men usando una biblioteca anticuada basada en punteros.
Como ingeniero moderno de C++, preferirías usar punteros inteligentes, pero, ay, el código heredado exige respeto por las viejas costumbres. El destino de la humanidad puede depender de estos punteros, así que procede con cuidado, y que la energía Hamon te guíe.
Como las lecturas de los sensores pueden ser enormes, te proporcionamos un struct de ejemplo que se usa en la biblioteca real. El código ya está implementado en el archivo de encabezado para ti.
struct pillar_men_sensor {
int activity{};
std::string location{};
std::vector<int> data{};
};
connection_check)Tu primera tarea es asegurarte de que el sensor de los Pillar Men esté conectado correctamente.
No podemos permitir falsas alarmas provocadas por sensores desconectados.
Escribirás una función connection_check, que comprueba si el puntero del sensor es válido verificando si es nullptr.
pillar_men_sensor.true si el puntero del sensor no es nulo, y false en caso contrario.pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false
activity_counter)Los Pillar Men acechan en las sombras, y necesitamos saber si los sensores han detectado alguna actividad.
Escribirás la función activity_counter, que recibe un array de sensores y una capacidad que indica el número de sensores en el array.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123
alarm_control)No todos los sensores deberían activar una alarma, a menos que haya un peligro real.
La función alarm_control garantiza que un sensor solo active una alarma si su nivel de actividad es mayor que 0.
Esta función también debe verificar si hay sensores nulos para evitar que el sistema falle.
pillar_men_sensor.nullptr. Si el sensor es nullptr, devuelve false.true; en caso contrario, devuelve false.pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true
uv_alarm
En esta tarea, implementarás la función uv_alarm para determinar si se debe activar una alarma según los niveles de exposición a la luz UV y la actividad del sensor.
La función uv_alarm debe usar la función uv_light_heuristic que se proporciona, la cual opera sobre un vector de datos y devuelve un valor según ciertos umbrales.
Esta es una versión de ejemplo del código complejo que se ejecutará en producción; por favor, no cambies la interfaz.
Define la función uv_alarm en el espacio de nombres speedywagon. Debe:
pillar_men_sensor.false si el puntero del sensor es nulo.uv_light_heuristic, pasando la dirección del array data del sensor.true si el valor que devuelve uv_light_heuristic es mayor que el nivel de sensor->activity; en caso contrario, devuelve false.Regístrate en Exercism para aprender y dominar C++ con 19 conceptos100 ejercicios y mentoría humana real, todo gratis.