Classes ------- Reading: Chap 3, 6, 17.1 0) Introduction - until now, looked at some important language constructs in C++ - these constructs form the C subset of C++ - today, we start with the object-oriented aspects of C++ - outline - motivation for classes - declaration and use - member functions - operator overloading - private and public members - example implementation - dynamic allocation of class objects - constructors A) Motivation for Classes - consider the following definition int x; // integer is a data type // tells you that x is of integer type - x can take values within some range (2^32 - 1 to 2^32) - certain operations can be performed on x such as +, -, *, <, etc. - similarly for "float x;" - range is different - mechanics of operations are different - similarly for char x - now consider the following definition time x; // what does that mean to you? - x is a variable of type time - x can take values that are triplets (hour, minute, second) - e.g. (13, 45, 40) - where 0 <= hour < 24, 0 <= minute < 60, 0 <= second < 60 - thus (26, 30, 1) is an invalid time - also operations such as + can be performed - e.g. (0, 30, 40) + (1, 10, 30) = (1, 41, 10) - C++ (or most languages) do not have a predefined "time" type - classes are language constructs that allow us to add types B) Declaration and Use - declaration requires specification of data and operations class class_name // class is keyword, class_name is any name { object_data; // define the data in each object of the class class_functions; // define the operations on the class }; // don't forget ; - you can create one or more objects of the same class - similar to creating multiple integer variables - data is modifiable and unique to each object of the class - functions are "read-only" and shared by all the objects of the class - functions are shared but they operate on data members of specific objects - e.g. class Time { int hour, minute, second; // data in each object Time(); // constructors, seen in APS105 Time(int h, int m, int s); // have no return value Time operator+(Time t); Time operator-(Time t); void AddtoTime(int h, int m, int s); void Print(); }; - this is a class declaration, not the implementation - implementation is separate from the declaration (unlike Java) - with this definition, we can declare a time variable as Time z; // default constructor (described below) Time x(10, 40, 1); Time y(5, 49, 31); - we can also perform operations such as z = x + y // z = (16, 29, 32); z = x - y // z = (4, 50, 30); - meaning of these operations is very different from int operations - can access data fields individually std::cout << z.hour << std::endl; C) Member Functions - similar to Java z.Print(); - member functions operate on the object's data void Time::Print() { cout << "(" << hour << ", " << minute << ", " << second << ")\n"; } - z is implicit parameter - implicit parameter is passed by reference so it can be changed - the member function can change the value of hour, minute, second - implicit parameter can be accessed using the "this" pointer this->hour D) Operator Overloading - consider the definition Time sunrise(7, 03, 0); - say we want to add a minute to sunrise time sunrise.AddtoTime(0, 1, 0); - another way to do so Time m(0, 1, 0); Time new_sunrise; new_sunrise = sunrise.operator+(m); - this calls the operator+ function of the object called sunrise - passes the function the parameter M - returned object is copied into new_sunrise - intent is to add the parameter time to the calling object's time - C++ allows us to conveniently write "sunrise.operator+(m)" as sunrise + m - called operator overloading E) Private and Public Members - private members restrict access to the components of an object - Reasons will be discussed in detail later - Similar to Java class Time { public: // functions can be accessed outside class implementation Time(); ... // everything else is similar private: // data can't be accessed outside class implementation int hour, minute, second; }; - public part is also called class interface - use car analogy - describes "what" it does - implementation describes "how" it does it int main() { Time sunrise; cout << sunrise.hour << end; // error } - making items private reduces chances of error - called encapsulation F) Dynamic Allocation of Class Objects - similar to dynamic allocation of other types of objects Time *t, *t2; t = new Time(); - allocates one object of type time using default constructor - returns a pointer to a newly created object t2 = new Time(10, 40, 1); // another constructor t2->AddtoTime(0, 1, 0); // adds (0, 1, 0) to t2 *t = *t2; // assignment *t = *t + *t; // another addition G) Constructors - special functions - they are not called directly by any function - they create objects while other functions take a created object - they help to correctly initialize an object when it is created - good software engineering practice - essential when members are private - since private members can't be initialized outside the implementation - can be called in several ways 1) Time today(10, 40, 1); 2) Time today = Time(10, 40, 1); - creates a temporary time variable (constructor) - copies this temporary to today (copy constructor) - destroys the temporary 3) Time today; today = Time(10, 40, 1); - two constructors called - one default constructor - creates a temporary time variable (constructor) - copies this temporary to today (assignment) - destroys the temporary 4) Time today = 10; - for a one argument constructor, similar to ints - default constructors - compiler defines one when no other constructor is defined - compiler defined default constructor doesn't do anything - if any other constructor is defined, then compiler doesn't define one - defined using two ways - explicit default constructor (as shown in example) - with all default args (function overloading) Time::Time(int h = 0, int m = 0, int s = 0) { ... } - Time time(); <--------- not a constructor - why? - declares a function time with return value Time - Time time; // constructor - arrays of objects - must have default constructor! - e.g. Time time[4]; Time time[4] = { Time(10, 40, 1}, }; // involve a temporary creation, copy, destr Classes with Pointers --------------------- Reading: Chap 10.3, 18.3-18.4 0) Introduction - previously, we have discussed the notion of classes - those classes had simple data types in them, int, float, etc. - they did not have pointers in them - classes that have pointers in them introduce several issues - we will look at these here - outline - example pointer class - assignment operator - copy constructor - destructor - reference variables A) Time Class With a Pointer 1) Original class class Time { private: int hour, minute, second; public: // Constructors Time(); Time(int h, int m, int s); // Overloaded operators Time operator+(Time &); Time operator-(Time &); // Other functions void AddtoTime(int h, int m, int s); void Print(); }; 2) Pointer class struct _time { int hour; int minute; int second; }; class Time { private: struct _time *tt; // replace the 3 ints with pointer to struct public: same as above }; - changes in implementation - replace hour with tt->hour, etc. - e.g. constructor shown below - similar changes to other functions Time::Time(int h, int m, int s) { tt = new struct _time; // need to allocate _time tt->hour = h; tt->minute = m; tt->second = s; } - why would you do this? - save space in some cases - e.g., (bad) if tt is not needed for all Time classes - sharing of data (many to one relationship) - avoids duplication and inconsistency B) Assignment Operator - now consider the following Time x(1, 0, 0); Time y; x y ______ ______ ______ ______ | | | | | | | | | tt |---->| 1 | | tt |---->| 0 | |______| |______| |______| |______| | | | | | 0 | | 0 | |______| |______| | | | | | 0 | | 0 | |______| |______| - what does the following mean? y = x; - each C++ class comes with a default assignment operator - makes a field-by-field copy of the RHS to the LHS - old implementation: copies x.hour to y.hour, x.minute to y.minute, etc. - new implementation: pointer tt is copied - show picture x.Print() y.Print() y.AddtoTime(1, 0, 0) y.Print() x.Print() - problem with default assignment operator - it copies the pointer and not the data - C++ compiler doesn't know better and makes SHALLOW copy - memory leak - solution: assignment operator must be redefined - whenever classes have dynamically allocated structures (with pointers) Time Time::operator=(Time t) // not ideal syntax, will fix later { tt->hour = t.tt->hour; tt->minute = t.tt->minute; tt->second = t.tt->minute; return *this; // why is this returned? } - show picture - does this really solve the problem? - how are parameters passed and the return value returned? - pass and return by value - parameter t is copied - how is the copy done? - SHALLOW copy C) Copy Constructor - we have fixed the assignment operator, but what about object copies - each C++ class comes with a default copy constructor - invoked when an object is created and initialized from another object - the second object must be of same type - makes a field-by-field copy of the RHS to the LHS - function prototype: Time(const Time &) // explain const and & later - in above function, two shallow copies made - when parameter is passed - when return is executed - in above case, the function actually works fine - when parameter is passed, an additional copy of y is made - this copy is destroyed on function return - when return is executed, an additional temp. copy of x is made - this copy is then destroyed - the copy is never used since the return value of (y = x) is not used - no observable harmful effects - examples when copy constructor is called Time y(x); // x is an already existing object // invokes y.Time(x), y is created and init. from x Time y = x; // doesn't invoke assignment but copy constructor Time *py = new Time(x); // calls copy constructor to copy x - default copy constructor is shallow - has the same problems as default assignment - redefinition of copy constructor Time::Time(const Time & t) { tt = new struct _time; // creates the _time object *tt = *t.tt; // default assignment of _time struct! } - explain how it works - remember it is a constructor, no return value - parameter must be passed by reference! - pass by value simply won't work in this case (compiler error!) - const ensures that we don't modify t by mistake - e.g. const int x = 5; x = 6; // error - for assignment operator, we can avoid the two copies - we can pass the parameter by reference - return by reference Time & // added & Time::operator=(const Time & t) // added const and & { tt->hour = t.tt->hour; tt->minute = t.tt->minute; tt->second = t.tt->minute; return *this; } - compiler provides a default constructor - does nothing (unlike default assignment and copy constructor) - if any other constructor defined, compiler doesn't provide default D) Destructor - method that is called when an object is destroyed - when is an object destroyed - global variables: when program quits - local variables: when variable goes out of scope - includes temporary variables Time Time::operator+(Time & t) <- no copy { int TotalTime; Time sum; <- sum constructor ... return (sum); } <- sum destructor <- temp copy constructor and destructor - heap variables: when delete is called Time *t = new Time; ... delete t; - destructor is rarely (if never) called explicitly! - in old implementation - destructor didn't need to do anything - in new implementation - destructor needs to delete the dynamically allocated struct - in general, if you use "new" in the constructor - or perhaps in some other member function to allocate storage dynamically - then you need to use "delete" in the destructor - show with a picture Time::~Time() { delete tt; } - with this destructor, the copy-based assignment operator would not work - with the default copy constructor - explain why E) Reference Variables