Created: 2025-03-04 mar 19:40
Varía de unos autores a otros, incluso de una clase a otra, pero es bastante común uno como el siguiente:
class T {
public:
T (); // 1. Const. por defecto
T (const T& t); // 2. Const. de copia
~T (); // 3. Destructor
bool operator== (const T& t); // 4. Comparación
T& operator= (const T& t); // 5. Asignación
}
T t1, t2; // Const. por defecto
T* t3 = new T(t1); // Const. de copia
t1 = t2; // Op. de asignación
...
if (t1 == *t3)... // Op. de comparación
Al redefinir el operador de asignación en una clase debemos prestar especial atención a esta situación:
T t1;
t1 = t1;
T&
?Presta atención, no es lo mismo:
T t1, t2;
t2 = t1;
Que…
T t1;
T t2 = t1; // Equivale a T t2(t1)
class T {
public:
T (); // Const. por defecto
...
operator bool () const; // Conversión de T -> bool: if (t)...
operator int () const; // Conversión de T -> int: int n = t;
...
}
Disponemos de una serie de operadores nuevos de conversión de tipo:
static_cast<new_type>(expression)
:
Es una conversión de tipo hecha en tiempo de compilación.
int n = static_cast<int>(4.3);
const_cast<new_type>(expression)
:
int main () {
const int * p = new int(3);
int* q;
//q = p; <-- Error!
q = const_cast<int*>(p); // Ok ahora
return 0;
}
reinterpret_cast<new_type>(expression)
:
int main () {
int * p = new int(3);
void* q;
q = p; // Ok
// p = q; // Error
p = reinterpret_cast<int*>(q); // Ok ahora
return 0;
}
dynamic_cast<new_type>(expression)
:
Comprueba si podemos hacer un downcast.
class B {};
class D : public B {};
D d; // Un D
B* b = &d; // upcast, dynamic_cast may be used,
// but unnecessary
D* new_d = dynamic_cast<D*>(b); // downcast
el almacenamiento global
MyClass obj;
int main () {...}
en la pila (stack)
void f () { MyClass obj;... }
en el almacenamiento dinámico (heap)
MyClass* objptr = new MyClass;
abstract
.En C++ un método abstracto es aquel que no tiene implementación y eso se indica así:
class T { // T es una clase abstracta
public:
T (); // Const. por defecto
virtual void doSomething () = 0; // Método abstracto,
// debe tener enlace dinámico!
}
En los LOO que las soportan, son aquellas declaradas dentro de
un función, por tanto sólo se pueden usar allí…¿sólo?
C++ permite esto desde C++14:
// Compilar con --std=c++14 o superior
auto f() { // Deducción del tipo de resultado
class T {
public:
int n;
} fT;
fT.n = 3;
return fT;
}
int main () {
auto n = 2;
auto frt = f ();
return frt.n;
}
Evidentemente, el resultado obtenido debe revisarse, juzgarse y adaptar convenientemente al problema que estemos resolviendo.
El concepto de espacio de nombres está relacionado con el de ámbito.
En cierto modo ya lo habeis usado desde Programación I, p.e.:
void f () { int n; ...} // No hay colisión con el nombre de la
void g () { int n; ...} // variable 'n' igual en ambas funciones
Cada función tiene su propio ámbito, su propio espacio de nombres.
C++ nos da soporte sintáctico para crearlos, pero aunque el lenguaje de programación que usamos no lo tenga, siempre podemos emplear un truco…
Recordad los nombres de las funciones de la práctica 2:
ListPtr listCreate (); // Espacio de nombres list
Position listLocate (ListPtr l, Element x);
NodePtr listRetrieve (ListPtr l, Position i);
...
TreePtr treeCreate (); // Espacio de nombres tree
void treeDestroy (TreePtr t);
NodePtr& treeRoot (TreePtr t);
...
GraphPtr graphCreate (); // Espacio de nombres graph
void graphDestroy (GraphPtr g);
void graphMakeNull (GraphPtr g);
...
La construcción sintáctica en C++ es la siguiente:
namespace NOMBRE {
class A {
void m();
...
};
int n;
void A::m() { ... }
void f () {...}
namespace INTERNO {
...
}
} // <- No lleva ';' al final
Unos identificadores como los de la práctica:
TreePtr treeCreate (); // Espacio de nombres tree
void treeDestroy (TreePtr t);
NodePtr& treeRoot (TreePtr t);
Se definirían y usarían así:
// tree.h
namespace tree {
...
struct Tree {
NodePtr _root;
};
typedef Tree* TreePtr;
...
TreePtr create ();
void destroy (TreePtr t);
NodePtr& root (TreePtr t);
}
// tree.cc
#include "tree.h"
tree::TreePtr tree::create () {...}
void tree::destroy (tree::TreePtr t) {...}
tree::NodePtr& tree::root (tree::TreePtr t) {...}
Hay un modo de sacar a la vez de su espacio de nombres todos los identificadores que define -como te imaginarás esto puede desencadenar colisiones de nombres, úsalo con juicio-:
namespace tree {...}
using namespace tree;
También es posible extraer de un espacio de nombres sólo un símbolo:
#include <iostream>
using std::cout;
...
cout << "Hola...";
std::string
, std::cout
, std::endl
,
etc….Si en lugar de un espacio de nombres se tratara de una clase, sería parecido:
// tree.h
class Tree {
public:
typedef Tree* TreePtr;
...
TreePtr create ();
void destroy (TreePtr t);
NodePtr& root (TreePtr t);
private:
...
};
// tree.cc
#include "tree.h"
Tree::TreePtr Tree::create () {...}
void Tree::destroy (Tree::TreePtr t) {...}
Tree::NodePtr& Tree::root (Tree::TreePtr t) {...}
Modo por defecto: sobreescribe contenidos:
#include <iostream>
#include <fstream> // cabecera con las declaraciones
using namespace std;
int main () {
ofstream outputfile;
outputfile.open ("libro.txt");
outputfile << "Escribimos en el archivo.\n";
outputfile.close();
return 0;
}
El método open (filename, mode);
permite especificar el modo en
el que abrimos el archivo: ios::in
, ios::out
, ios::binary
,
ios::ate
, ios::app
, ios::trunc
:
ofstream data;
data.open ("data.bin", ios::out | ios::app | ios::binary);
theFile.close()
Lectura de lineas:
string line;
ifstream theFile ("book.txt");
if (theFile.is_open()) {
while ( getline (theFile,line) ) {
cout << line << '\n';
}
theFile.close();
} else cout << "Hubo un problema al abrir el archivo.";
if (myfile.is_open()) { }
bad()
, fail()
, eof()
, good()
Podemos escribir y leer en ellos con read
y write
:
char* memblock = new char [size];
...
theFile.write ( memory_block, size );
theFile.read ( memory_block, size );
Podemos simplificar el mostrar la información de un objeto en un stream de salida redefiniendo como función amiga de su clase este operador, p.e.:
#include <iostream>
class Robot {
public:
Robot(std::string n, float bl = 1.0) {
theName = n;
theBattLevel = bl;
}
friend std::ostream& operator<< (std::ostream& os, const Robot& r);
virtual ~Robot() {}
private:
std::string theName;
float theBattLevel;
};
// definimos operator<< , se podria hacer dentro de la clase
std::ostream& operator<< (std::ostream& os, const Robot& r) {
os << " Name: " << r.theName
<< "\n BL: " << r.theBattLevel << '\n';
return os; //<-- ¿por qué se devuelve este valor?
}
int main(int argc, char *argv[])
{
Robot r1 = Robot("R1", 0.75);
Robot r2 = Robot("R2", 0.2);
Robot r3 = Robot("R3", 0.5);
std::cout << r1 << r2;
std::cout << r3;
return 0;
}
De todo lo descrito hasta ahora es sencillo intuir que la ejecución de una aplicación escrita con un LOO y siguiendo los principios de la POO consiste en:
main
los objetos iniciales de las clases
pertinentes de nuestro diseño.main
le enviaremos un mensaje que
desencadene el inicio de la aplicación.// Design Patterns: Elements of Reusable Object-Oriented Software
typedef float Coord;
using namespace std;
class Point {
public:
static const Point Zero; // Point (0, 0);
Point (Coord x = 0.0, Coord y = 0.0);
Coord X () const; void X (Coord x);
Coord Y () const; void Y (Coord y);
friend Point operator+ (const Point&, const Point&);
friend Point operator- (const Point&, const Point&);
friend Point operator* (const Point&, const Point&);
friend Point operator/ (const Point&, const Point&);
Point& operator+= (const Point&);
Point& operator-= (const Point&);
Point& operator*= (const Point&);
Point& operator/= (const Point&);
Point operator- ();
friend bool operator== (const Point&, const Point&);
friend bool operator!= (const Point&, const Point&);
friend ostream& operator<< (ostream&, const Point&);
friend istream& operator>> (istream&, Point&);
};
const Point Point::zero = Point(0,0);
// Design Patterns: Elements of Reusable Object-Oriented Software
class Rect {
public:
static const Rect Zero; // Rect (Point (0, 0), Point (0, 0));
Rect (Coord x, Coord y, Coord w, Coord h);
Rect (const Point& origin, const Point& extent);
Coord Width () const; void Width (Coord);
Coord Height () const; void Height (Coord);
Coord Left () const; void Left (Coord);
Coord Bottom () const; void Bottom (Coord);
Point& Origin () const; void Origin (const Point&);
Point& Extent () const; void Extent (const Point&);
void MoveTo (const Point&);
void MoveBy (const Point&);
bool IsEmpty () const;
bool Contains (const Point&) const;
};
void show_vehicle_clount_address();
private:
string brand;
static uint32_t vehicle_count;
};
// -- Vehicle methods: ----------------------------------------------------
// Reserve space for class variable.
uint32_t Vehicle::vehicle_count = 0;
Vehicle::Vehicle(const string& br) {
brand = br;
vehicle_count++;
cout << "Created vehicle: " << brand << " [" << this << "]\n";
}
Vehicle::~Vehicle() {
cout << "Destroyed vehicle: " << brand << " [" << this << "]\n";
}
uint32_t Vehicle::get_number_of_vehicles() {
// Prueba a descomentar esta linea:
// cout << "Asking number of vehicles for this = " << this << '\n';
return vehicle_count;
}
const string &Vehicle::brand_name() {
cout << "Asking for brand-name of [" << this << "]\n";
// return brand;
// ^^^^^ is a shorthand for:
return this->brand;
}
void Vehicle::set_brand_name(const string &brand) {
// this-> is mandatory here.
this->brand = brand;
}
void Vehicle::show_vehicle_clount_address() {
cout << "For vehicle " << this->brand
<< " its vehicle_count address is @: ["
<< &Vehicle::vehicle_count << "]\n";
}
// -- Main: ---------------------------------------------------------------
int main(int argc, char *argv[])
{
Vehicle s("Seat"), a("Audi"), m("Mercedes");
cout << "\nTotal vehicles created-a: "
<< Vehicle::get_number_of_vehicles()
<< '\n';
// Works too in C++!
cout << "Total vehicles created-b: "
<< s.get_number_of_vehicles()
<< "\n\n";
// Getting brands:
cout << "s: " << s.brand_name () << '\n';
cout << "a: " << a.brand_name () << '\n';
cout << "m: " << m.brand_name () << '\n';
cout << "\n";
// Change seat's brand:
s.set_brand_name("Cupra");
cout << "Changed seat's brand to: " << s.brand_name () << '\n';
cout << "\n";
cout << "Show vehicle_counter static-var address-of each vehicle:\n";
s.show_vehicle_clount_address();
a.show_vehicle_clount_address();
m.show_vehicle_clount_address();
cout << '\n';
return 0;
}
class X {
private:
int a;
// Declaración de amistad
friend void friendSetA(X& xr, int i);
public:
// Es un método de instancia
void memberSet(int i) { a = i; }
};
// Es amiga de la clase X
// Por eso puede acceder a 'a'.
void friendSetA(X& xr, int i) {
xr.a = i;
}
std
.<array>
, <vector>
, <queue>
, etc…<algorithm>