Programming and Data Structures Programming and Data Structures
Lecture 2 Lecture 2
Dr Piotr Cybula <piotr.cybula@wmii.uni.lodz.pl>
Dr Piotr Cybula <piotr.cybula@wmii.uni.lodz.pl>
Encapsulation : data protection Encapsulation : data protection
●
code safety and independence
●
better team support with the code separation
●
without «giving a monkey a razor» (black-box solution)
●
different levels of data access in a class (through access specifiers):
●
public – available for everyone
●
private – available only for the member functions of that class (methods)
●
protected – like private, but additionally available for the inherited classes
●
friend – available for implicitly pointed non-member functions or other classes
●
constant methods guaranteeing no changes of an object they are called for
Data protection : struct Data protection : struct
Within the struct statement all members are public by default:
struct Student
{ private: //hidden members string name;
int index;
bool checkIndex(int _index); //internal method public: //public interface
Student(string _name);
Student();
~Student();
int getIndex() const; //getter
void setIndex(int _index); //setter … //inne metody
};
Student s(”Scott Tiger”);
s.setIndex(12345); //public method call s.checkIndex(); //compile-time error
Data protection : class Data protection : class
Within the class statement all members are private by default:
class Student
{ //hidden members, private keyword optional string name;
int index;
bool checkIndex(int _index); //internal method public: //public interface
Student(string _name);
Student();
~Student();
int getIndex() const; //getter
void setIndex(int _index); //setter … //inne metody
};
Student s(”Scott Tiger”);
s.setIndex(12345); //public method call s.checkIndex(); //compile-time error
Invariants Invariants
●
rules (logical sentences) describing the consistent state of a structure/class (correct attribute values)
●
private auxiliary methods that check these rules
●
each public method (including constructors) assumes that the object for which it was called is in a consistent state and guarantees that it will remain in such a state after its completion (design based on a contract – design by contract)
●
the public method changing the state of an object (modifier) is to check the compliance of such a change with the rules described by invariants
●
if the rules are exceeded, the method is obliged to report an exceptional situation (throw exception)
●
the exception thrown in the constructor prevents the object from being
created (no destructor is called)
Invariants Invariants
#include <stdexcept>
class Student { string name;
int index;
float avg;
//class invariants:
//index is a positive number //avg is between 2.0 and 5.0 public: //interfejs publiczny Student(string _name);
void setIndex(int _index) //modyfikator
{ if(_index <= 0) throw invalid_argument(”Error”);//exception index = _index;
}};
Student s1(”Scott Tiger”), s2(”John Smith”);
try {
s1.setIndex(12345); //ok
s2.setIndex(-1234); //exception throw
} catch(exception &e) { cout << e.what(); } //exception catch
Overloading of operators Overloading of operators
●
redefinition of the built-in operator functions for a class objects as operands
●
name of the function consists of the keyword operator followed by the operator name or symbol, i.e. operator+, operator=, etc.
●
overloaded operators can be methods or friend functions (some of them must be methods)
●
there is no possibility to create any new operator, and to change the priority of the evaluation of the operators
●
it's «syntactic sugar» – do not overload any operator, when it is not necessary
for the improvement of the code readability
Unary operators Unary operators
●
without arguments
●
it should be a constant method
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } Vector operator-() const
{ return Vector(-x, -y);
}};
Vector v1(2, -3);
Vector v2(-v1); //unary operator call v2 = -v1; //also here
v2 = v1.operator-(); //alternative call
Binary operators Binary operators
●
with only one argument if overloaded as a class method (member function)
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } Vector operator+(const Vector &v) const
{ return Vector(x + v.x, y + v.y);
}};
Vector v1(2, -3), v2(-1, 4), v3;
v3 = v1 + v2; v3 = v1 + 2; //binary operator calls v3 = v1.operator+(v2); //alternative call
Binary operators Binary operators
●
with two arguments if overloaded as a friend function with constant
arguments to enable the automatic conversions of the both operands from simpler types
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; }
friend Vector operator+(const Vector &v1, const Vector &v2);
};
Vector operator+(const Vector &v1, const Vector &v2) { return Vector(v1.x + v2.x, v1.y + v2.y);
}Vector v1(2, -3), v2(-1, 4), v3;
v3 = v1 + v2; v3 = v1 + 2; //binary operator calls v3 = 2 + v1; //binary operator call
v3 = v1.operator+(v2); //compile-time error v3 = operator+(v1, v2); //alternative call
Indexing oparator [ ] Indexing oparator [ ]
●
must be as a class method (member function) with single argument
●
should return a reference for use on the left-hand side of the assigment operator
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } double& operator[](int i)
{ return (i == 1) ? x : y;
}};
Vector v1(2, -3), v2(-1, 4);
double d = v1[1] + v2[2]; //indexing operator calls
v1[1] = 5; //also here (thanks to the reference returned)
Shift operators << and >>
Shift operators << and >>
●
overloaded typically for input/output streams
●
the left operand is of the other type (ostream or istream), so the operator must be a friend function and it should pass and return references to the stream (for the cascade call possibility)
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; }
friend ostream& operator<<(ostream &stream, const Vector &v);
friend istream& operator>>(istream &stream, Vector &v);
};ostream& operator<<(ostream &stream, const Vector &v) { stream << '[' << v.x << ',' << v.y << ']';
return stream;
}Vector v1(2, -3), v2(-1, 4) ;
cout << v1 << ' ' << v2 << endl; //shift operator cascade calls
Assignment operator = Assignment operator =
●
must be a class method (member function) with single argument being a reference to an existing object of the same type and it should return a reference for cascade usage
●
automatically created if (and only if) no explicitly defined but copying the fields by simple bit-copy (risky when there are some pointer fields), so always
overload the assignment operator for classes with pointer fields
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } Vector& operator=(const Vector &v)
{ if (this != &v) { x = v.x; y = v.y; } return *this;
}};
Vector v1(2, -3), v2, v3;