Week 9 Lecture Summary for CSC 309
These notes are not intended to be complete. They serve as an outline to the class and as a supplement to the text.
Code Reuse
- Functions
- Templates.
- Composition
- Inheritance
Class Templates
Recall the dictionary class used a vector of pair objects to represent the mapping of english words to spanish words. That pair class mapped strings to strings. Suppose we wanted to map some other type T to another (possible different) type U. Using class templates we can write a prototype for the pair class (or any class) and let the compiler generate type specific classes.
Here is the definition for such a class.
template <typename T, typename U> class Pair { private: T key; U value; public: Pair(T k, U v = U()) : key(k), value(v) {} T getKey() const {return key;} U getValue() const {return value;} void setValue(U ); };
- Symbols T and U are called class parameters
- Any legal C++ identifier can be used as a class parameter.
- Since the
setValue
method is not defined in the class definition it will be defined outside the class definition. Here is its definition
template <typename T, typename U> void Pair<T,U>::setValue(U v) { value = v; }
- When defining a function outside the class definition the template header has to be repeated.
- Pair is a template class. The class name is
Pair<T,U>
. - To use the Pair class we pop the data types in during object creation
Pair<int,int> p1(34,56); Pair<char,int> p2('A',65); Pair<string,string> p3("hello", "hola"); Book b("Time", "Wells", "1-345-4567-X", "34.95") Pair<string,book> p4("1-345-4567-X", b);
- Template classes may occur as a data type in a parameter list.
- The definition to overload the output operator for the
Pair<T,U>
class is
template <typename T, typename U> ostream& operator<< (ostream& out, const Pair<T,U> & p) { out << "(" << p.getKey() << ", " << p.getValue() << ")" ; return out; }
Now we could execute the following piece of code.
cout << p1 << endl << p2 << endl << p3 << endl;
Pair<int,int>, Pair<char,int>, Pair<string,string>
are template instantiations.- There is no object whose type is
Pair or Pair<T, U>
- Can write a method to take in a specific instantiation
bool lessThanK(const Pair<string,string> & p) { return p.getKey()[0] < 'K' || p.getKey()[0] < 'k' ; }
Composition
Composition- A form of aggregation in which a class has an instance variable of some class type. Objects built from these classes will have references to other objects. Composition models the HAS - A relationship.
We can say:
- A Friend has-a Name.
- Player has-a PairOfDice
- A Car has-a(n) engine
Consider the following classes that could be used in maintaining a phone directory.
name.h
#ifndef NAME_H #define NAME_H #include <string> #include <iostream> using namespace std; class Name { private: string first; string middle; string last; public: Name(string f, string m, string l) : first(f), middle(m), last(l) {} Name(string f, string l) : first(f), middle(""), last(l) {} Name(): first(""), middle(""), last("") {} string getFirst() const {return first;} string getMiddle() const {return middle;} string getLast() const {return last;} string toString() const; }; ostream& operator<<(ostream& out, const Name & n); #endif
friend.h
#ifndef FRIEND_H #define FRIEND_H #include "name.h" #include <string> #include <iostream> using namespace std; class Friend { private: Name name; string phone; public: Friend(Name n, string p) : name(n), phone(p) {} Friend(string first, string last, string p) : name(Name(first,last)), phone(p) {} Friend () {} Name getName() const {return name;} string getPhone() const {return phone;} string toString() const; }; ostream& operator<<(ostream& out, const Friend & fr); #endif
Inheritance
- Inheritance is a form of code reuse.
- Inheritance allows you to create a new class from an existing class.
- The idea is to reuse some or all of the methods and fields from the existing class and add new methods and fields to the new class that are needed to reflect the new class.
- The new class is called a derived class or subclass.
- The original class is called the base class or superclass.
- Inheritance models the is - a relationship.
- A Savings Account is-a BankAccount
- A Salaried Employee is-a(n) Employee
- A 3D-Point is-a Point
- A Human is-a Mammal
We can say
When designing a project, this feature allows you to arrange classes into a hierarchy where the higher-level classes group together the common properties and behaviors. Lower level classes can extend the higher level classes and do not need to repeat the common fields and behaviors.
For example the classes involved in a bank application used for customer accounts could be of type.
BankAccount, SavingsAccount, CheckingAccount ,MonyMarketAccount, CDAccount etc...
All accounts have a customer name, address, balance etc ..
Some of the accounts have a minimum balance.
Some accounts have a fee per check. etc...
Some accounts are timed
Here is one way to relate these classes.
BankAccount Definition
bankaccount.h
#ifndef BANKACCOUNT_H #define BANKACCOUNT_H #include <string> #include <iostream> using namespace std; class BankAccount { private: string name; protected: double balance; public: BankAccount(string n, double b) : name(n), balance(b) {} BankAccount () : name(""), balance(0.0) {} string getName() const {return name;} double getBalance() const {return balance;} void withDraw(double amount); void deposit(double amount); }; #endif
- The BankAccount definition looks like a normal class with a new feature, visibility modifier protected.
- Protected members can be directly accessed by derived classes but still remain private to users of the class.
- To derive a new class from BankAccount , say CheckingAccount, we write.
class CheckingAccount : public BankAccount { : : };
CheckingAccount
which is derived from BankAccount
BankAccount
is a base class of CheckingAccount
BankAccount
is also a member of CheckingAccount
.
CheckingAccount
can define additional members specific to CheckingAccounts.checking.h
#ifndef CHECKING_H #define CHECKING_H #include "bankaccount.h" #include <string> using namespace std; class CheckingAccount : public BankAccount { private: double fee; public: CheckingAccount() : BankAccount(), fee(0.0) {} CheckingAccount (string n, double b, double f ) : BankAccount(n,b), fee(f) {} double getFee() const {return fee;} void withDraw(double amount); }; #endif
More Features
- Constructors are Not inherited . CheckingAccount defines its own constructor.
- Overloaded assignment and destructor are not inherited.
- Methods
getName, getBalance and deposit
are public in BankAccount, therefore public in CheckingAccount. CheckingAccount objects can call those methods.
BankAccount ba("tony", 1000); // base class object CheckingAccount ca("kathy",1000, .05); // subclass class object ba.getName(); ba.deposit(1500); ba.getBalance(); ca.getName(); ca.deposit(500); ca.getBalance();
withDraw
method.getFee
method. This method can only be invoked on CheckingAccount objects or derived classes from CheckingAccount, not BankAccount.ba.getFee() // error ca.getFee() // okay ba.withDraw(200); // okay, calls withdraw from BankAccount class ca.withDraw(100); // okay, calls withdraw from CheckingAccount class.
CheckingAccount (string n, double b, double f ) : name(n), balance(b), fee(f) {} // name is private in class BankAccount
CheckingAccount (string n, double b, double f ) : BankAccount(n,b), fee(f) {}
Method Implementations
BankAccount
void BankAccount::withDraw(double amount){ if (amount <= balance){ balance -= amount; }else{ cout << "insufficient funds" << endl; } } void BankAccount::deposit(double amount){ if(amount >=0){ balance += amount; }else{ cout << " can't deposit a negative amount " << endl; } }
CheckingAccount
void CheckingAccount::withDraw(double amount){ this -> BankAccount::withDraw(amount); balance -= fee; }
Polymorphic Variables
- A variable of base class type can be used for any object of base class type or derived type.
- Consider the two functions.
void print(BankAccount acct) { cout << acct.getName() << " has a balance of ... " << "$" << acct.getBalance() << endl; } bool operator<(BankAccount acct1, BankAccount acct2) { return acct1.getName() < acct2.getName(); }
- These two functions will work for BankAccount or CheckingAccount objects and any derived objects.
- Note the above two functions are only using methods common to all
BankAccounts
.
print(ba); print(ca); cout << (ba < ca) << endl; cout << (ca < ba) << endl; ba = ca; // Danger
- Since they are objects should pass by reference
- When passing by reference the reference variable will bind to the BankAccount portion of a CheckingAccount object.
void print(const BankAccount & acct) { cout << acct.getName() << " has a balance of ... " << "$" << acct.getBalance() << endl; } bool operator<(const BankAccount & acct1, const BankAccount & acct2) { return acct1.getName() < acct2.getName(); }
Visibility Rules
- Under public Inheritance
- A private member in the base class is visible only in the base class. A private member of a base class is inherited by the subclass and can be indirectly accessed using a base class method.
- A protected member in the base class is visible in the base class and a derived class, but private else where.
- A public member in the base class is visible everywhere.
- Under Protected Inheritance
class X : protected Y { }
- Each private member is visible only in the base class.
- Each protected member in the base class is protected in the subclass.
- Each public member in the base class is now protected in the subclass.
- Under Private Inheritance
class X : private Y { }
- Each private member is visible only in the base class.
- Each protected member in the base class is private in the subclass.
- Each public member in the base class is private in the subclass.
- Default Inheritance Mode:
class X : Y {} is taken to mean class X : private Y { }