Creación de clases en C++ (I)

Anuncio
Clases en C
Creación de clases en
C++ (I)
• Convertir estructura InfoAlumno en una
clase
• Escribir una clase simple en C++, con
constructores
Programación Orientada a Objeto
Ing. Civil en Telecomunicaciones
Estructura de datos por alumno
• Estructura de datos que almacena
información de cada alumno
struct InfoAlumno {
std::string nombre;
double examen1, examen2;
std::vector<double> notas;
};
• Problema: programadores acceden
directamente a los elementos de los datos
del tipo InfoAlumno
sobrecarga de operadores
Principio de ocultamiento de
información (Information hiding)
• Al programador se le debe
Esconder los detalles de la implementación
Podría ser cambiada o mejorada en el tiempo
Esconder los datos
Limitar fuentes de errores
• Exponer sólo lo necesario para poder
manipular la información (interfaz)
Exponer los métodos mediante
los cuales
acceder a los datos
La interfaz debe ser inmutable
Interfaz para InfoAlumno
Función miembro de lectura de datos
• Agregar dos funciones miembros a
estructura InfoAlumno
• Función es parte de una estructura InfoAlumno
struct InfoAlumno {
std::string nombre;
double examen1, examen2;
std::vector<double> notas;
std::istream& leeAlumnos(std::istream&);
double nota() const;
};
© 2015 Mario Medina C.
Nombre debe ser InfoAlumno::leeAlumnos
No necesita recibir la estructura
como argumento
Tiene acceso directo a datos de estructura
istream& InfoAlumno::leeAlumnos(istream& in) {
in >> nombre >> examen1 >> examen2;
// Llama a funcion leeNotas() anterior
leeNotas(in, notas);
return in;
}
1
Función miembro nota()
• Invoca a función notas() que no es
miembro de InfoAlumno
Versión que recibe dos doubles y un
vector<double>
• Función miembro const
No puede modificar datos de la estructura
double InfoAlumno::nota() const {
return ::nota(examen1, examen2, notas);
}
Clase InfoAlumno
class InfoAlumno {
public: // Define la interfaz
std::istream& leeAlumnos(std::istream&);
double nota() const;
private: // Define la implementación
std::string nombre;
double examen1, examen2;
std::vector<double> notas;
};
Funciones de acceso
• Ejemplo anterior tiene los datos privados
Cómo se puede conocer sus valores?
• Mediante funciones de acceso (accessor
functions)
• Ejemplo: se necesita acceso al nombre del
alumno para imprimir el informe
std::string nombre() const {
return n;
}
© 2015 Mario Medina C.
Protección de datos
• Programadores pueden acceder a
estructura a través de funciones miembros
Pero también pueden acceder directamente
a los datos
• Solución: proteger los datos
public: accesible a todo el código
private: accesible sólo a funciones
miembros de InfoAlumno
Comandos class y struct
• La única diferencia entre class y struct
es la protección por omisión
Protección por omisión en una clase es
private
En una estructura, la protección por omisión
es public
• En general, se prefiere usar class para
clases de C++ y struct para estructuras de
C
Clase InfoAlumno
class InfoAlumno {
public: // Define la interfaz
std::istream& leeAlumnos(std::istream&);
double nota() const;
std::string nombre() const {
return n;
}
private: // Define la implementación
std::string n;
double examen1, examen2;
std::vector<double> notas;
};
2
Función nombre()
Funciones en línea (inline functions)
• Nombre de variable cambiada para evitar
conflictos entre el dato n y la función
• Función miembro const
• En C, comando inline declara una función
en línea
No puede modificar variable n
Por eso, retorna copia de string en vez de
referencia a string
• Compilador expande funciones in situ
Múltiples copias del código
Evita costo de llamada
• Función está definida dentro de la clase
Función en línea (inline
Sugerencias al compilador
function)
a función
• Funciones miembro definidas dentro de la
definición de la clase son funciones en línea
Función compara()
Función valido()
• Función no-miembro utilizada para
ordenar los registros InfoAlumno en
forma alfabética
• Acceso a nombre de alumno via función
de acceso nombre()
• Función en línea que verifica que exista al
menos una nota de tarea
bool compara(const InfoAlumno& x, const
InfoAlumno& y) {
return x.nombre() < y.nombre();
}
Clase InfoAlumno
class InfoAlumno {
public: // Define la interfaz
std::istream& leeAlumnos(std::istream&);
double nota() const;
std::string nombre() const {
return n;
}
bool valido() const {
return !notas.empty();
}
private: // Define la implementación
std::string n;
double examen1, examen2;
std::vector<double> notas;
};
bool compara(const InfoAlumno&, const InfoAlumno&);
© 2015 Mario Medina C.
Usa función miembro de vector .empty()
class InfoAlumno {
public:
bool valido() const {
return !notas.empty();
}
}
Constructores
• Cómo crear un nuevo objeto
InfoAlumno?
Mediante una función constructor
Funciones miembro especiales que crean un
objeto y (quizás) lo inicializan
• No son invocadas directamente
Se invocan al crear un nuevo objeto
3
Constructores
Inicialización de objetos
• Objetos de bibliotecas estándar incluyen
constructores para objetos vacíos e
inicializaciones
• Compilador genera constructor vacío por
omisión
• Inicialización por omisión
Inicializa variables miembros a un valor que
depende del tipo de objeto creado
Para objetos de una clase dada
Clase debe incluir una función de inicialización
por omisión
Creación de objetos string invoca constructor
por omisión de objetos string
Crea un objeto string vacío
Para primitivas básicas
Variables son indefinidas
Inicialización de objetos
Constructor de InfoAlumno
• Inicialización por valor
• Algunos contenedores como map
inicializan primitivas básicas por valor
• Objeto InfoAlumno contiene 4
elementos:
Al crear un nuevo elemento pair de map, se
crea un entero de tipo int inicializado a cero
std::string (inicializado
std::vector (inicializado
a string vacío)
a vector vacío)
Dos variables double
Es necesario inicializarlas explícitamente
Constructor de InfoAlumno
• Constructor vacío
InfoAlumno::InfoAlumno(): examen1(0),
examen2(0) { }
Compilador convierte int a double
• Constructor a partir de flujo de entrada
InfoAlumno::InfoAlumno(istream& in) {
leeNotas(in);
}
© 2015 Mario Medina C.
Clase InfoAlumno (I)
// Clase que almacena la informacion de un alumno
class InfoAlumno
{
public: // Define la interfaz
InfoAlumno() : examen1(0), examen2(0) {}
InfoAlumno(std::istream& in) {
leeAlumnos(in);
}
std::istream& leeAlumnos(std::istream& in) {
in >> n >> examen1 >> examen2;
leeNotas(in, notas);
return in;
}
4
Clase InfoAlumno (II)
double nota() const {
return ::nota(examen1, examen2, notas);
}
std::string nombre() const {
return n;
}
bool valido() const {
return !notas.empty();
}
Usando la clase InfoAlumno (I)
int main()
{
vector<InfoAlumno> alumnos;
InfoAlumno alumno;
string::size_type maxlen = 0;
// Lee los datos desde entrada estandar
while(alumno.leeAlumnos(cin))
{
maxlen = max(maxlen,
alumno.nombre().size());
alumnos.push_back(alumno);
}
Usando la clase InfoAlumno (III)
try
{
double nota_final = alumnos[i].nota();
streamsize prec = cout.precision();
cout << setprecision(3) << nota_final
<< setprecision(prec) << endl;
} catch(domain_error e)
{
cout << e.what() << endl;
}
}
return 0;
Clase InfoAlumno (III)
private: // Define la implementación
std::string n;
double examen1, examen2;
std::vector<double> notas;
istream& leeNotas(istream& in, vector<double>& notas)
{
if (in) {
notas.clear();
double temp;
while (in >> temp) {
notas.push_back(temp);
}
in.clear();
}
return in;
}
};
Usando la clase InfoAlumno (II)
// Ordena los registros de alumnos
sort(alumnos.begin(), alumnos.end(), compara);
// Escribe los nombres y las notas
for (vector<InfoAlumno>::size_type i = 0;
i != alumnos.size(); ++i) {
cout << alumnos[i].nombre()
<< string(maxlen + 1 –
alumnos[i].nombre().size(), ' ');
Ejemplo: la clase punto
• La clase punto representa un punto en el
espacio cartesiano
Tiene dos variables miembro:
x_coord
y_coord
// coordenada x
// coordenada y
Como primera idea, construir una estructura
punto de lenguaje C, y convertirla en una
clase de C++
}
© 2015 Mario Medina C.
5
struct versus class
// Estructura de C
struct punto {
double x_coord;
double y_coord;
};
// Clase de C++
class punto {
double x_coord;
double y_coord;
};
Ahora, agregaremos
funciones miembro a la
clase
Agregar funciones de acceso
class punto {
// Interface
public:
double x() const {
return x_coord;
}
double y() const {
return y_coord;
}
void setX(double x) {
x_coord = x;
}
void setY(double y) {
y_coord = y;
}
// Implementacion
private:
double x_coord;
double y_coord;
};
Definir constructor vacío
Definir constructor con argumentos
• Constructor vacío inicializa variables
miembro a 0.0
• Constructor inicializa variables miembro a
partir de los argumentos
punto():x_coord(0), y_coord(0) {
}
• Otra forma de escribir el constructor vacío
punto() {
x_coord = 0.0;
y_coord = 0.0;
}
punto(double x, double y) {
x_coord = x;
y_coord = y;
}
• Forma de uso
punto p1, p2(1.0, 2.0);
Operaciones con objetos punto
Comparando puntos
• Nos gustaría poder usar las operaciones
aritméticas comunes con objetos puntos
• Dos puntos son iguales si sus componentes
son iguales y diferentes si sus componentes
son diferentes
Suma, resta, multiplicación,
división por una
constante
Suma y resta de dos objetos punto
Comparación de objetos punto
• Cómo? Sobrecargando los operadores
estándar
bool operator==(punto& p) {
return(x_coord == p.x() &&
y_coord == p.y());
}
bool operator!=(punto& p) {
return !(x_coord == p.x() &&
y_coord == p.y());
}
• Uso: cout << (p1 == p2) << endl;
© 2015 Mario Medina C.
6
Comparando puntos
Multiplicando punto por constante
• Ambas funciones anteriores son funciones
miembro de la clase punto
• Comparación de objetos punto p1 == p2
es interpretada por el compilador como
p1.==(p2)
• Multiplicar un punto por una constante k
multiplica sus componentes por la
constante
Se llama a la función miembro
correspondiente
Operación debería retornar un nuevo objeto
punto
No modifica el objeto actual
• Cómo crear un nuevo objeto punto?
Solicitar creación de objeto al sistema via
comando new
Multiplicando punto por constante
punto operator*(double k) const {
punto *temp = new punto;
temp->setX(x_coord*k);
temp->setY(y_coord*k);
return *temp;
}
• Lo mismo puede hacerse para la
división
Uso:
Creación dinámica de objetos
• La expresión new T crea un nuevo objeto
del tipo ó clase T
Expresión retorna un puntero al nuevo objeto
Objeto inicializado
por omisión
También puede ser inicializado con argumentos
InfoAlumno *ia = new InfoAlumno();
punto *p = new punto();
Objeto se elimina
con delete
delete ia;
p2 = (p1/5.0)*9.0;
Creación dinámica de vectores
• La expresión new T[n] crea un vector de
n objetos del tipo ó clase T
Expresión retorna un puntero de tipo T* al
primer elemento del vector
Todos los objetos inicializados por omisión
Inicializados a través del constructor por omisión
Vector se elimina
con delete[]
Elementos son destruidos en orden inverso de
creación
© 2015 Mario Medina C.
Suma punto con constante
punto operator+(double k) {
punto *temp = new punto;
temp->setX(x_coord + k);
temp->setY(y_coord + k);
return *temp;
}
Pero, qué pasa al sumar un punto con
otro punto?
7
Suma punto con otro punto
punto operator+(const punto& p) {
punto *temp = new punto;
temp->setX(x_coord + p.x());
temp->setY(y_coord + p.y());
return *temp;
}
• Cómo sabe el sistema cuál función usar?
Por los argumentos:
p1 = p2 + 3;
p3 = p1 + p2;
Sobrecarga de operador +=
punto& operator+=(double k) {
x_coord = x_coord + k;
y_coord = y_coord + k;
return *this;
}
punto& operator+=(const punto& p) {
x_coord = x_coord + p.x();
y_coord = y_coord + p.y();
return *this;
}
Sobrecarga de operaciones += y -=
• Operación A += B sobreescribe A con la
suma de A + B
• Sobrecarga de operador debe modificar
objeto y retornar objeto modificado
• Cómo puede un objeto retornarse a sí
mismo?
Comando this hace referencia
al objeto
Sobrecarga de operador *=
punto& operator*=(double k) {
x_coord = x_coord*k;
y_coord = y_coord*k;
return *this;
}
punto& operator*=(const punto& p) {
}
• Función debe retornar referencia al objeto
actual, no una copia!
• Pero, qué debería hacer esto?
Sobrecarga de operador *=
Reglas básicas de sobrecarga de
operadores
• Qué debería hacer la multiplicación
de dos objetos punto p1*p2?
Producto punto ó interno
Producto cruz ó externo
Multiplicación
elemento-a-elemento
• Sólo sobrecargar operadores si la
operación a realizar sobre los objetos
está claramente definida
© 2015 Mario Medina C.
1. No sobrecargar un operador si la
operación no es “obvia”
2. Siempre atenerse a la semántica “obvia”
de la operación
•
A – B resta el valor de B de A
3. Siempre proveer las operaciones
relacionadas
Si se sobrecarga +, también sobrecargar +=
8
Operadores que pueden sobrecargarse
• Aritméticos: + - * / += -= *= /= ++ -+(unario) -(unario)
• Manipulación de bits: & | ^ << >> &= |=
^= <<= >>= ~
• Comparación: == != < > <= >= || && !
(unario)
• Manejo de memoria: new new[] delete
delete[]
• Otros: () , [] -> * &(prefijos)
Sobrecarga de == usando función
no-miembro
• La siguiente función no-miembro compara
dos objetos punto
bool operator==(const punto& p1, const
punto p2) {
return(p1.x() == p2.x() &&
p2.x() == p2.y());
}
• Cómo debería sobrecargarse el operador ==?
© 2015 Mario Medina C.
Sobrecarga mediante funciones nomiembro
• Operadores pueden ser sobrecargados en
funciones miembro ó no-miembro
Funciones no-miembro reciben dos
operandos
Declaradas fuera de la clase
Funciones miembro tienen un operando
implícito: el objeto al cual pertenecen
Declaradas dentro de la clase
Funciones miembro vs. no-miembro
• Operadores =, ->, [], (), sólo pueden
sobrecargarse como funciones miembro
• Operadores =<<, >> sólo pueden sobrecargarse
en funciones no-miembro
• Operadores unarios: recomendable sobrecargar
en funciones miembro
• Operadores binarios que tratan ambos
argumentos de igual forma: recomendable
sobrecargar en funciones no-miembro
• Operadores binarios que modifican argumento:
recomendable sobrecargar en funciones
miembro
9
Descargar