CS 228 meeting -*- Outline -*- * classes in C++ (HR 3.2-3.4, DD 6, 7.1-4, 7.8) and now for the main (ADT/OO) feature of C++... ** initial class example (compare HR pp. 148ff) -------------------------- CLASSES IMPLEMENT ADTs in C++ // Ratl.h class Ratl { // ABSTRACTLY: math. rationals public: Ratl(int n, int d); // PRE: d != 0 // MODIFIES: self // POST: self = (n/d) int numr() const; // POST: FCTVAL == numerator of self int denr() const; // POST: FCTVAL == denominator of self private: int num; int den; }; -------------------------- The above is a class definition. Say we'll go over this quickly now, for overview, don't worry about all the details, we'll do those later slowly For abstract values, use mathematical rationals (ABSTRACTLY comment) Explain that this info has to be stored in the data members, point out where they are Explain the contsructor notation, and its job to initialize object Explain what the observers do, and what the const means in them, contrast with modifies clause. Define the terms: member, member function, data member. Tell them to pay attention to the semi-colon at the end, and don't use "extern" inside a class. Point out the public and private sections, we'll hide the private section usually... -------------------------- TYPICAL FILE STRUCTURE // Ratl.h class Ratl { // ABSTRACTLY: math. rationals public: Ratl(int n, int d); // PRE: d != 0 // MODIFIES: self // POST: self = (n/d) int numr() const; // POST: FCTVAL == numerator of self int denr() const; // POST: FCTVAL == denominator of self #include "Ratl.pri" }; // Ratl.pri private: int num; int den; // Ratl.C #include "Ratl.h" // CLASSINV: den > 0; int Ratl::numr() const { return num; } int Ratl::denr() const { return den; } Ratl::Ratl(int n, int d) { num = n; den = d; } -------------------------- Explain ------------------------- CLIENT CODE // RatClient.C #include #include "Ratl.h" Ratl add(Ratl a, Ratl b) // POST: FCTVAL == a + b { return Ratl(a.numr() * b.denr() + b.numr() * b.denr(), a.denr() * b.denr()); } void printRatl(Ratl r) { cout << r.numr() << "/" << r.denr(); } int main() { Ratl aThird(1,3); Ratl six = Ratl(6,1); Ratl ans = add(aThird, six); cout << "ans is "; printRatl(ans); cout << endl; return 0; } ------------------------- Note that Ratl is now used both as the name of a type and as the name of a function (the constructor) Point out the syntax used to invoke the member functions a.numr(), recall cin.get() Point out the declartion syntaxes for Ratl as it is, each declaration has to have an intializer or have to pass the arguments to the constructor, can't do Ratl ans; ans = add(aThird, six); ------------------------------- FOR YOU TO DO Implement the following #include "Ratl.h" Ratl multiply(Ratl a, Ratl b); // POST: FCTVAL == a * b void swap(Ratl & a, Ratl & b); // POST: a == b && b == a ------------------------------- ** classes in C++ (HR 3.2) *** private and public keywords --------------------- private: AND public: IN C++ class Password { char passStr[11]; public: void GetPassword(); Boolean ValidUser(); }; class Password { private: char passStr[11]; public: void GetPassword(); Boolean ValidUser(); }; class Password { public: void GetPassword(); Boolean ValidUser(); private: char passStr[11]; }; --------------------- private is the default This is a declaration, and thus ends with semicolon The { and } don't mark a block, they are just part of the syntax, which groups the members. *** members suppose we want to keep track of the money in a bank account, we would have to store the cents (exactly) but want to think of it in terms of a floating point number... ---------------------- ACCOUNT CLASS // Account.h class Account { // ABSTRACTLY: some number of dollars public: void Open(); // function member // MODIFIES: self // POST: balance of self is zero // NOTE: this must be called first void Deposit(int cts); // PRE: Open was called && cts >= 0 // MODIFIES: self // POST: the new balance is // self + cts/100 dollars double Dollars() const; // PRE: Open was called // POST: FCTVAL is approximately // the balance of self. #include "Account.pri" }; -------------------------------- -------------------------------- CLASS MEMBERS AND VISIBILITY // AccountClient.C -- client code #include #include "Account.h" int main() { Account acct1; Account acct2; acct1.Open(); acct1.Deposit(500); cout << acct1.Dollars() << endl; acct2.Open(); acct2.Deposit(1700); cout << acct2.Dollars() << endl; return 0; } ---------------------- Q: What does this print? point out what the function and data members are draw picture of the two instances, with different data member values explain what acct1.Deposit(5) means in terms of member access Note that a class is a type, can use it to declare variables distinguish members from instances (objects) *** kinds of operations -------------------------- MANIPULATING CLASS INSTANCES Account acct1, acct2; Can do: acct2.Open(); acct2.Deposit(20); double d = acct2.Dollars(); acct1 = acct2; ---------------------------- the latter copies the data members of acct2 into acct1 ---------------------------- Cannot do: double m = acct1 + acct2; Cannot do as a client: int k = acct1.numCents; ------------------------------ The latter is illegal, but not a syntax error it would be okay within the code of a member function. Q: what does this have to do with information hiding? *** class scope ----------------------------- CLASS SCOPE Local to Account class: Open, Deposit, Dollars Thus: // doesn't call Account::Deposit Deposit(2000); Account acct1; acct1.Deposit(2000); // does! // doesn't call Account::Deposit OtherType foo; foo.Deposit(2000); ----------------------------- *** const member functions emphasize how this provides limited view into object ------------------------------------ CONST MEMBER FUNCTIONS // AccountClient2.C -- client code #include "Account.h" int main() { Account acct1; acct1.Open(); acct1.Deposit(600); const Account acctConstant = acct1; double d = acctConstant.Dollars(); //ok acctConstant.Deposit(500); // error! return 1; } // Account.h class Account { // ABSTRACTLY: some number of dollars public: // ... double Dollars() const; // PRE: Open was called // POST: FCTVAL is approximately // the balance of self. //... }; // Account.C #include "Account.h" double Account::Dollars() const { return numCents / 100.0; } ------------------------------------ may also be helpful for compiler optimization, but the main thing is to let you manipulate constants, without changing them (C++ doesn't know what can be done to constants otherwise) might mention that const and MODIFIES are different concepts. e.g., simplify operation for Ratl, would modify noting, but is not const because bits change. ** specification and implementation recall the specification of class Account Point out the note, and the preconditions, which express ordering Clients need to #include "Account.h" ------------------------------------ IMPLEMENTATION OF Account AND CONCRETE SPECIFICATIONS // Account.pri private: int numCents; // ABSTRACTION MAP: dollar balance // is numCents / 100. // Account.C #include "Account.h" ------------------------------------ Fill this in on-line or on the screen. Remember to explain the use of :: in functions, such as void Account::Open() // POST: numCents == 0 { numCents = 0; } Discuss concrete vs. abstract specs and the correspondence between the specifications Member functions use numCents without . operator Q: how would you implement Accout::Deposit? ** constructors (HR 3.3) flaw in Account class, have to call Open first, error prone Q: why is that bad? it leaves some of the responsiblity for Account working right in hands of client solution is constructors... ------------------------------- CONSTRUCTORS GUARANTEE INITIALIZATION // Account.h #ifndef Account_h #define Account_h 1 class Account { // ABSTRACTLY: some number of dollars public: Account(); // default constructor // MODIFIES: self // POST: balance of self is zero Account(int cts); // another constructor // MODIFIES: self // POST: balance of self is cts/100 void Deposit(int cts); // PRE: cts >= 0 // MODIFIES: self // POST: the new balance is // self + cts/100 dollars double Dollars() const; // POST: FCTVAL is approximately // the balance of self. #include "Account.pri" }; #endif ------------------------------- Note name is the name of the class a default constructor takes no arguments Note no declaration of return type (not even void allowed) The job of constructor is to initialize the data members so doesn't return anything, just some code executed. ---------------------------- USE OF CONSTRUCTORS // AccountClient.C -- client code #include #include "Account.h" int main() { Account acct1; cout << acct1.Dollars() << endl; acct1.Deposit(500); cout << acct1.Dollars() << endl; Account acct2(1700); cout << acct2.Dollars() << endl; return 0; } ---------------------------- We'll usually use the second form. Q: could we declare acct1 as a const? acct2? -------------------------- IMPLEMENTING CONSTRUCTORS Account::Account(int cts) { numCents = cts; } FOR YOU TO DO Write the implementation of the default constructor. -------------------------- ** class invariants ------------------------------- CLASS INVARIANTS // Account.h #ifndef Account_h #define Account_h 1 class Account { // ABSTRACTLY: some number of dollars // CLASSINV: the balance is not negative public: // ... }; #endif // Account.C // CLASSINV: the numCents is not negative // ... ------------------------------- invariant states something true whenever object is "at rest" -------------------------------- WHAT AN INVARIANT MEANS // Account.h #ifndef Account_h #define Account_h 1 class Account { // ABSTRACTLY: some number of dollars public: Account(); // MODIFIES: self // POST: balance of self is zero // && the balance is not negative Account(int cts); // another constructor // MODIFIES: self // POST: balance of self is cts/100 // && the balance is not negative void Deposit(int cts); // PRE: cts >= 0 // && the balance is not negative // MODIFIES: self // POST: the new balance is // self + cts/100 dollars // && the balance is not negative double Dollars() const; // PRE: the balance is not negative // POST: FCTVAL is approximately // the balance of self. // && the balance is not negative #include "Account.pri" }; #endif -------------------------------- added to each pre and postcontition, except not to preconditions of constructors (or post of destructors, for that matter) at the concrete level, the invariant means the same thing ** example (p. 126ff) recall the rand module from chapter 2 ------------------------- FOR YOU TO DO In groups of 3 or 4, design a class RandGen that is like the rand module, and record your design with a header file, with comments. // RandGen.h class RandGen { public: #include "RandGen.pri" }; ------------------------- compare the designs, settle on one Q: what would client code that printed a random number look like? Q: what would be involved in implementing this? can look in the implementation on-line if time. or tell them to see p. 128-9 ** layered software (3.4) want to make off-the-shelf components out of others --------------------------- PROBLEM Design a type that tracks a person's name and their checking and savings accounts. --------------------------- sketch the following // TwoAccounts.h #include "Account.h" class TwoAccounts { // ABSTRACTLY: an object has an owner, // which is a string of charaters, // and two dollar amounts: // the savings and checking balance. public: TwoAccounts(const char owr[] int sv_cts, int chk_cts); // PRE: owr is null-terminated // && length of owr <= 20 // MODIFIES: self // POST: the owner is owr, // && the savings balance is // sv_cts/100 && the checking balance // is chk_cts/100 private: char name[20]; Account savings; Account checking; }; The problem is how to implement this, how to pass the sv_cts and chk_cts to the account constructors? --------------------------- PASSING ARGUMENTS TO CONSTRUCTORS OF DATA MEMBERS // TwoAccounts.pri private: char name[20]; Account savings; Account checking; // TwoAccounts.C #include "TwoAccounts.h" #include TwoAccounts::TwoAccounts(const char owr[], int sv_cts, int chk_cts) // PRE: owr is null-terminated // && length of owr <= 20 // MODIFIES: self // POST: name is copied from owr, // && savings == sv_cts/100 // && checking == chk_cts/100 : savings(sv_cts), checking(chk_cts) { strcpy(name, owr); } --------------------------- make a big deal out of the constructor initializer list following ":" The body is often empty. Q: can you implement a default constructor for TwoAccounts?