Sobrecarga de Operadores y E/S en C++ Antonio LaTorre de la Fuente Índice • Sobrecarga de Funciones • Sobrecarga de Operadores • Entrada/Salida sobre streams básicos • Entrada/Salida sobre streams de fichero • Entrada/Salida sobre streams de cadena Sobrecarga de Funciones • Nuevo con respecto a C • Funciones con el mismo nombre pero distinto prototipo: número y/o tipo de argumentos • Útil si se quiere poder realizar una misma operación sobre distintos tipos de datos Sobrecarga de Funciones void print (double number) {cout << number << endl;} void print (int number) {cout << number << endl;} void print (int number, string s = “hola”) { cout << number << “, “ << s << endl; } Ambigüedad • ¿Qué pasa si incluimos en nuestro código…? unsigned num = 3; print (num); • El compilador no sabe a qué función llamar: func_over.cc:18: error: call of overloaded print(unsigned int&) is ambiguous func_over.cc:6: note: candidates are: void print(int) func_over.cc:7: note: void print(int, std::string) func_over.cc:8: note: void print(double) Ambigüedad • Se soluciona usando static_cast unsigned num = 3; print (static_cast<double>(num)); • ¿Qué pasaría si hiciéramos…? int num = 3; print (num); Name Mangling • Estrategia que usa el compilador para poder diferenciar dos funciones con el mismo nombre int f (void) {return 1;} int f (int) {return 0;} void g (void) {int i = f (), j = f (0);} • Esto se traduciría en: int __f_v (void) {return 1;} int __f_i (int) {return 0;} void __g_v (void) {int i = __f_v (), j = __f_i (0);} Índice • Sobrecarga de Funciones • Sobrecarga de Operadores • Entrada/Salida sobre streams básicos • Entrada/Salida sobre streams de fichero • Entrada/Salida sobre streams de cadena Operadores en C++ • Lista de operadores válidos en C++ + - * / % ^ & | ~ ! , = < > <= >= ++ -- << >> == != && || += -= *= /= %= ^= &= |= <<= >>= [] () -> ->* new delete • Se declaran de la siguiente forma: tipo operator operador([argumentos]) Operadores en C++ • Hay algunos operadores especiales que no pueden ser sobrecargados • • • • • • :: (resolución de ámbito) . (selección de un miembro) .* (selección de un miembro referenciado por un puntero) ?: (operador condicional) sizeof (tamaño de) typeid (identificación del tipo) Precedencia de Operadores • Los operadores en C++ tienen distinta precedencia, y a veces no es obvio • Lo mejor es usar () en casa de no estar seguro • Tabla de precedencias: Inglés: http://www.cplusplus.com/doc/tutorial/operators/ Español: http://es.wikipedia.org/wiki/Operadores_en_C_y_C%2B%2B Sobrecarga de operadores • Podemos sobrecargar tanto operadores unarios como binarios tipo_ret operator op_unario (tipo1 arg1); tipo_ret operator op_binario (tipo1 arg1, tipo2 arg2); • En este caso, ambos argumentos se muestran de manera explícita Sobrecarga de operadores • Ambas notaciones son equivalentes C operator- (C n) {…} C operator- (C n, C m) {…} int main (void) { int main (void) { C a, b, c, d; c = -b; d = b – a; } C a, b, c, d; c = operator- (b); d = operator- (b, a); } Sobrecarga de operadores • Si los operadores son métodos de una clase, uno de los argumentos es implícito (aquél que invoca al operador) class C { public: C operator- (); C operator- (C); } Sobrecarga de operadores • Estas dos notaciones son equivalentes int main (void) { int main (void) { C a, b, c, d; C a, b, c, d; c = -b; c = b.operator-(); d = b – a; d = b.operator-(a); } } Sobrecarga de operadores • Ejemplo: Números Complejos class Complejo { public: Complejo (double r, double i) : real(r), imag(i) {} … private: double real; double imag; } Operadores Aritméticos • Suma de números complejos: Complejo Complejo::operator+ (const Complejo& z2) const { Complejo res (0, 0); res.real = this->real + z2.real; res.img = this->img + z2.img; return res; } Operadores Relacionales • Comparación de números complejos: bool Complejo::operator== (const Complejo& z2) const { return this->real == z2.real && this->imag == z2.imag; } Operadores de auto-incremento • Son operadores unarios • Permiten pre-incrementar y post-incrementar el valor de una variable Pre-incremento: C C::operator++() Post-incremento: C C::operator++(int x) Complejo Complejo::operator++() { real += 1; return *this; } Funciones “Amigas” • Son funciones ajenas a una clase que pueden acceder a los elementos privados de esa clase • Ejemplo: • Tenemos dos clases: Vector y Matriz • Queremos definir el producto vectorial entre ambas clases • El contenido de los objetos es privado Funciones “Amigas” class Vector { class Matriz { friend operator* (const Matriz& matriz, const Vector& vector); friend operator* (const Matriz& matriz, const Vector& vector); public: public: … … private: int size; int* data; } private: int rows, columns; int** data; } Vector operator* (const Matriz& matriz, const Vector& vector) {…} Operadores de asignación • Se sobrecargan para evitar la copia binaria de objetos class Vector { public: Vector& operator= (const Vector& vector) { size = vector.size; data = new int[vector.size]; for (unsigned i = 0; i < size; i++) data[i] = vector.data[i]; return *this; } private: int size; int* data; } class Vector { public: Vector& operator= (const Vector& vector) { size = vector.size; data = vector.data; return *this; } private: int size; int* data; } Operadores de inserción y extracción • Permiten escribir / leer en la salida / entrada estándar • Hay que sobrecargar los operadores: ostream& operator<< (ostream& os, const T& arg) istream& operator>> (istream& is, T& arg) • Podemos hacer que estos métodos sean amigos de nuestra clase o implementar un método público que imprima los datos Operadores de inserción y extracción class Complejo { public: void printComplejo(ostream & os); … private: double real; double imag; } ostream& operator<< (ostream& os, const Complejo& c) { c.printComplejo(os); return os; } class Complejo { public: friend ostream& operator<< (ostream& os, const Complejo& c); … private: double real; double imag; } ostream& operator<< (ostream& os, const Complejo& c) { os << c.real << … } Operador de Indexación • Es el operador [] • Es un operador binario y de clase: recibe el objeto donde se aplica y el índice facilitado • Útil, p.ej., para acceso seguro a datos: int Vector::operator[] (int i) { if (i >= 0 && i < this->size) return data[i]; else { cout << “Posición incorrecta: “ << i << endl; return 0; } } Conversión de tipos • Hay dos posibilidades para llevar a cabo conversiones entre tipos definidos por el usuario: • Los constructores de conversión • Los operadores de conversión • Son útiles para hacer, por ejemplo: Complejo a (2, 3), b (0, 0); b = a + 5; Constructores de conversión • Hay que añadir un constructor que nos permita crear el objeto a partir de un objeto del tipo que queramos convertir class Complejo { public: Complejo (double r, double i) : real(r), imag(i) {} Complejo (int r) : real(r), imag(0) {} Complejo operator+ (const Complejo& z2) const; … private: double real, imag; } Complejo a (2, 3), b (0, 0); b = a + 5; Operadores de conversión • Se usan para proporcionar una conversión de un tipo de usuario a otro tipo (de usuario o no) • Tiene la sintaxis: C::operator T(); • Donde C es el tipo de usuario que queremos convertir y T es el tipo en el que queremos convertir C Operadores de conversión class Complejo { public: Complejo (double r, double i) : real(r), imag(i) {} operator double() {return real;} Complejo operator+ (const Complejo& z2) const; … private: double real, imag; } Complejo a (2, 3); double b = a + 5; double c = a.operator double() + 5; double d = static_cast<double>(a) + 5; Ambigüedades en la conversión Surgen si hay varias conversiones implícitas posibles class Complejo { public: Complejo (double r = 0, double i = 0) : real(r), imag(i) {} operator double() {return real;} Complejo operator+ (const Complejo& z2) const; … private: double real, imag; } Complejo a(2, 3), c(0,0); double b = 5; c = a + b; El compilador no sabe si convertir “b” a “Complejo” y realizar la suma o convertir “a” a “double”, hacer la suma y convertir el resultado a Complejo. Operador de llamada a función • Es el operador () • Siempre debe ser definido dentro de una clase • x (y, z) se interpreta como x.operator (y, z) • Útil para implementar callbacks (pasar código como argumento de una función). Los denominados Objetos Función • También para el acceso a matrices multidimensionales Operador de llamada a función class Inicializar { private: double val; public: Inicializar (double x = 0) : val(x) {} void operator() (Vector& v); }; void Inicializar::operator() (Vector& v) { for (unsigned i = 0; i < vector.size(); i++) v[i] = this->val; } int main (void) { Vector v(5); Inicializar ini(1); ini(v); } Operador new • • Al reservar memoria con new y new[] se invoca a las funciones operator new y operator new[]: void* operator new (size_t tamaño); void* operator new[] (size_t tamaño); • Estas funciones reservan y memoria y luego llaman el constructor correspondiente Se pueden sobrecargar para una clase C: • • void* C::operator new (size_t tamaño); void* C::operator new[] (size_t tamaño); Son funciones estáticas aunque no se declaren como tal (static), porque se invocan antes que el constructor Primero se busca en la clase (y clases base) y luego la global Operador new void* Vector::operator new (size_t tam) { reservarMemoria (tam, 0); } void* Vector::operator new[] (size_t tam) { reservarMemoria (tam, 0); } void* Vector::reservarMemoria (size_t tam, char c) { void* p = malloc (tam); if (p == 0) { cout << “Error reservando memoria.” << endl; exit(-1); } memset (p, c, tam); return p; } Operador delete • • Al liberar memoria con delete y delete[] se invoca a las funciones operator delete y operator delete[]: void operator delete (void*); void operator delete[] (void*); • Estas funciones llaman al destructor y luego liberan la memoria del objeto Se puede sobrecargar para una clase C: • • void C::operator delete (void*, [size_t]); void C::operator delete[] (void*, [size_t]); Son funciones estáticas aunque no se declaren como tal (static), porque se invocan después del destructor Primero se busca en la clase (y clases base) y luego la global Operador delete void Vector::operator delete (void* p, size_t tam) { if (p) memset (p, 0, tam); free (p); } void Vector::operator delete[] (void* p, size_t tam) { if (p) memset (p, 0, tam); free (p); } Índice • Sobrecarga de Funciones • Sobrecarga de Operadores • Entrada/Salida sobre streams básicos • Entrada/Salida sobre streams de fichero • Entrada/Salida sobre streams de cadena Visión General de la E/S en C++ • Streams y Buffers • Especializaciones según el tipo de operación y el origen/destino de los datos Clases istream y ostream • Derivan de la clase ios • Deben ser conectadas a un buffer (del tipo que sea) • cin y cout son de tipo istream y ostream • Normalmente, para hacer E/S sobre ficheros o cadenas se usan las clases especializadas, pero se podrían usar estas clases básicas • Para usarlas: #include <iostream> Clases istream y ostream • Los Manipuladores nos permiten cambiar las opciones de formato de los streams (#include <iomanip>) • Son funciones especialmente diseñadas para trabajar junto con los operadores de inserción (<<) y extracción (>>) • Se modifica el formato encadenándolos al objeto que representa el stream cout << hex << showbase << 20 << endl; Clases istream y ostream • Manipuladores sin parámetros: (no)boolapha: Mostrar booleanos como true y false (no)showbase: Mostrar prefijo de tipo (0x, 0, etc.) (no)showpoint: Forzar la escritura del punto decimal (no)showpos: Forzar la escritura de un ‘+’ para valores positivos (no)skipws: Permite ignorar caracteres de espaciado (no)unitbuf: Forzar el vaciado del buffer tras cada operación (no)uppercase: Mostrar caracteres alfabéticos en mayúsculas dec, hex, oct: Cambiar base (decimal, hexadecimal y octal) fixed, scientific: Usar notación de coma fija o científica internal, left, right: Alinear el texto endl, ends, flush: Finalizar línea, cadena y vaciar buffer Clases istream y ostream • Manipuladores con parámetros setprecision (int): Fija la precisión en coma flotante setw (int): Fija el tamaño de un campo (que se rellena automáticamente en caso de ser necesario) setfill (char): Selecciona el carácter para el relleno setbase (int): Fija la base numérica a usar (8, 10 ó 16) setiosflags (mask): Fija cualquiera de los anteriores flags resetiosflags (mask): Reinicia los flags a los valores por defecto • Otra forma de hacer lo mismo • • Manipuladores sin parámetros: métodos setf y unsetf Manipuladores con parámetros: métodos propios (fill, width, precision, etc.) Clases istream y ostream • Ejemplos cout << fixed << setprecision(5) << setw(10) << setfill(‘*’) << 2.87 << endl; cout.setf(ios::fixed); cout.precision(5); cout.width(10); cout.fill(‘*’); cout << 2.87 << endl; • Ambas imprimen el mismo resultado ***2.87000 Clase ostream • Permite hacer operaciones de escritura con y sin formato • Sin formato: put y write • Con formato: operator<< • Permite manejar distintas situaciones de error • También se puede posicionar el puntero de escritura en posiciones determinadas del flujo de salida Clase ostream • La sobrecarga del operador de inserción permite realizar escritura con formato • Se puede sobrecargar para cualquier tipo ostream& operator<< (ostream& os, const T& data) • Y usar los manipuladores vistos anteriormente Clase ostream • El método put permite escribir un carácter en el stream ostream& put (char ch) • El método write permite escribir n caracteres de un array en el stream ostream& write (const char* pch, int n) • El método flush fuerza el vaciado del buffer asociado al stream ostream& flush () Clase ostream • El método tellp permite obtener la posición en el stream de salida (principalmente ficheros) pos_type tellp () • Los métodos seekp permiten cambiar la posición de escritura en relación a la posición actual… ostream& seekp (pos_type pos) • …o en relación a una posición de referencia ostream& seekp (off_type des, ios_base::seekdir pos) Clase ostream • Ejemplos: const char* cadena = “Hola mundo”; cout << cadena << endl; cout.write (cadena, 10); cout.write (“\n”, 1); cout.flush (); for (unsigned i = 0; i < 10; i++) cout.put (cadena[i]); cout.put (‘\n’); cout.flush (); Clase istream • Permite realizar operaciones de lectura con y sin formato: • Sin formato: get, getline, read, ignore, peek, gcount, unget, putback • Con formato: operator>> • Al igual que la clase ostream, permite gestionar errores y reposicionar el puntero de lectura Clase istream • La sobrecarga del operador de extracción permite realizar lectura con formato • Se puede sobrecargar para cualquier tipo istream& operator>> (istream& is, T& data) • Y usar los manipuladores vistos anteriormente (aunque algunos pueden tener sentido únicamente para la clase ostream) Clase istream • El método get permite leer un carácter del stream: istream& get (char& ch) • El método getline permite leer una línea, con una longitud máxima hasta encontrar un delimitador istream& getline (char* ch, int n, char delim = “\n”) • El método read permite leer n caracteres istream& read (char* ch, int n) Clase istream • El método ignore extrae y descarta una cadena de hasta n caracteres, mientras no encuentre un delimitador dado istream& ignore (int n = 1, int delim = eof ()) • El método peek devuelve el siguiente carácter del stream sin extraerlo int peek () const Clase istream • El método gcount devuelve el número de caracteres leídos en la última extracción sin formato int gcount () const • El método unget devuelve el último carácter extraído al stream istream& unget () • El método putback devuelve c al stream istream& putback (char c) Clase istream • El método tellg permite obtener la posición en el stream de entrada (principalmente ficheros) pos_type tellg () • Los métodos seekg permiten cambiar la posición de lectura en relación a la posición actual… istream& seekg (pos_type pos) • …o en relación a una posición de referencia istream& seekg (off_type des, ios_base::seekdir pos) Clase istream • Ejemplos char c, cad [100], cad2[10]; cin >> cad; // Introducimos por teclado Hola mundo cin.get(c); // cad = Hola , c= cin.getline (cad, 100, \n ); // cad= mundo cin.read (cad2, 10); // Introducimos por teclado // Hola mundo cruel.Adios cout << cin.gcount() << endl; // cad2= Hola mundo . Imprime 10 cin.ignore (10, '.'); // Ignora cruel. cin.putback('!'); // Inserta ! en el stream cin >> cad; // cad= !Adios! Clase iostream • Clase derivada tanto de istream como de ostream • Proporciona acceso de lectura/escritura a streams • Presenta los mismos métodos que las clases de las que deriva Gestión de errores • Los streams manejan 4 bits de error: • • • • eofbit: Se alcanzó el final del fichero en una operación de E/S failbit: El método de E/S falló badbit: Hubo un problema con el stream al realizar la operación goodbit: Ninguno de los anteriores bits está activado • Los métodos eof, fail, bad y good devuelven el valor de estos bits Índice • Sobrecarga de Funciones • Sobrecarga de Operadores • Entrada/Salida sobre streams básicos • Entrada/Salida sobre streams de fichero • Entrada/Salida sobre streams de cadena E/S sobre streams de fichero • Proporcionada por el fichero de cabecera: #include <fstream> • Provee de las clases ofstream (salida), ifstream (entrada) y fstream (entrada/salida) • Distintos modos para abrir un fichero: ios::in, ios::out, ios::trunc, ios::app, ios::binary, ios::ate • El fichero se puede abrir en el constructor o con el método open Métodos de apoyo • El método eof indica si se alcanzó el final del fichero: bool eof () const • El método is_open indica si existe un fichero abierto asociado con el stream bool is_open () const • El método close cierra el fichero abierto y lo desliga del stream bool close () Clase ofstream • Clase derivada de ostream especializada en realizar escrituras sobre ficheros • El fichero se puede abrir al construir un objeto de tipo ofstream o con el método open ofstream (const char* nombre_fichero, ios_base::openmode modo = ios::out | ios::trunc) void open (const char* nombre_fichero, ios_base::openmode modo = ios::out | ios::trunc) Clase ofstream • Ejemplo: ofstream of ( fichero.txt"); of.write ("Hola mundo\n", 11); of << "Adios mundo" << endl; for (char c = 'a'; c <= 'z'; c++) { of.put(c); of << ", "; } of << endl; of.close(); Clase ifstream • Clase derivada de istream especializada en realizar lecturas desde ficheros • El fichero se puede abrir al construir un objeto de tipo ifstream o con el método open ifstream (const char* nombre_fichero, ios_base::openmode modo = ios::in) void open (const char* nombre_fichero, ios_base::openmode modo = ios::in) Clase ifstream • Ejemplo: ifstream f ( fichero.txt"); char buffer[100]; f.getline (buffer, 100); cout << buffer << endl; f.ignore(100, '\n'); for (unsigned i = 1; i <= 26; i++) { char c; f >> c; cout << c << endl; f.ignore(2, ','); } Clase fstream • Combina las funcionalidades de ifstream y ofstream • El fichero se puede abrir al construir un objeto de tipo fstream o con el método open fstream (const char* nombre_fichero, ios_base::openmode modo = ios::in | ios::out) void open (const char* nombre_fichero, ios_base::openmode modo = ios::in | ios::out) E/S usando Registros • Podemos escribir estructuras de datos complejas • Los datos se guardan en formato binario, en lugar de escribirse carácter a carácter • Podemos hacerlo con los métodos read y write • Requiere una conversión de la estructura en un array de caracteres • Uso de reinterpret_cast<char*>() E/S usando Registros struct nodo_agenda { char nombre[30]; char direccion[40]; long telefono; }; nodo_agenda persona; cin.getline (30, persona.nombre); cin.getline (40, persona.direccion); cin >> persona.telefono; ofstream ofs (“agenda”); ofs.write (reinterpret_cast<char*>(&persona), sizeof(nodo_agenda)); ofs.close (); Índice • Sobrecarga de Funciones • Sobrecarga de Operadores • Entrada/Salida sobre streams básicos • Entrada/Salida sobre streams de fichero • Entrada/Salida sobre streams de cadena La clase String • Clase que sirve para trabajar con cadenas de caracteres • También almacena el tamaño de la cadena • Gestiona automáticamente la memoria dinámica asociada a la cadena • Facilita las operaciones más habituales con cadenas de caracteres • Hace falta incluir el siguiente fichero de cabecera: #include <string> La clase String • Cómo construimos un objeto de tipo string: string cad1; // Construye una cadena vacía string cad2 (cad1); // Constructor de copia char arr[] = “Hola mundo”); string cad3 (arr); // Constructor a partir de un array de char • Podemos consultar el tamaño de una cadena: cout << “El tamano de cad1 es: “ << cad1.size() << endl; • Podemos acceder carácter a carácter: for (unsigned i = 0; i < cad1.size(); i++) cout << cad1[i]; La clase String • Podemos asignar cadenas sin miedo (la memoria la gestiona la propia clase): string cad1 = “Hola mundo”; string cad2 = “Adios mundo”; cad2 = cad1; • Si nos interesa, podemos recuperar el array de caracteres asociado al objeto string string cad1 = “abcdefgh”; const char* arr1 = cad1.c_str(); // Sí añade ‘\0’ const char* arr2 = cad1.data(); // No añade ‘\0’ char buffer[80]; cad1.copy (buffer, cad1.size(), 0); La clase String • También podemos realizar las operaciones de comparación habituales: ==, !=, <, >, <= y >= • Se comparan letra por letra y se usa el orden alfabético string str1 = “abcdefg”, str2 = “Abcdefg”; if (str1 < str2) cout << str1 << endl; else cout << str2 << endl; La clase String • Se pueden insertar caracteres en una cadena: string cad = “Hola”; cad += “ mundo”; // Añade “ mundo” al final cad.append (“ cruel”); // Añade “ cruel” al final cad.insert (11, “nada “); // Inserta “nada “ en la posición 11 cout << cad << endl; // Imprime “Hola mundo nada cruel”; • Y concatenar dos cadenas: string cad1 = “Hola”; string cad2 = “ mundo”; string cad3 = cad1 + cad2; cout << cad3 << endl; // Imprime “Hola mundo” La clase String • También podemos buscar subcadenas: int pos; string cad = “abcdefghijk”; pos = cad.find (“efg”); // pos = 4 pos = cad.find (“i”); // pos = 8 • Reemplazar subcadenas: cad.replace (cad.find (“efg”), 2, “ zyx ”); // cad = “abcd zyx ghijk” • Y extraer subcadenas: string cad1 = “Esto es una cadena”; string cad2 = cad1.substr (12, 6); // cad2 = “cadena” Entrada/Salida sobre streams de cadena • Proporcionada por el fichero de cabecera: #include <sstream> • Provee de las clases ostringstream (salida), istringstream (entrada) y stringstream (e/s) • Permite realizar operaciones de E/S sobre objetos de tipo string (parecido al sprintf de C) • Se pueden usar los métodos de las clases básicas istream, ostream y stream (respectivamente) Entrada/Salida sobre streams de cadena • Definen dos métodos adicionales: string str ( ) const void str (const string & s) • Permiten recuperar e inicializar la cadena asociada al stream • También se les puede asociar una cadena en el constructor istringstream (const string& str, ios_base::openmode modo = ios_base::in) Ejemplo con ostringstream ostringstream buffer; string nombre = "Antonio"; int edad = 27; char aficion[] = "leer"; buffer << "Hola. Me llamo " << nombre << ", tengo " << edad << " tacos y me gusta " << aficion << endl; cout << buffer.str() << endl; Ejemplo con istringstream string nombre2; int edad2 = 0; char aficion2[80]; istringstream buffer2 ("Antonio , 27 , leer"); buffer2 >> nombre2; // nombre2 = Antonio buffer2.ignore(100, ','); // Descartamos la , buffer2 >> edad2; // edad2 = 27 buffer2.ignore(100, ','); // Descartamos la , buffer2 >> aficion2; // aficion2 = leer