6-1 Computing Fundamentals with C++ Object-Oriented Programming and Design, 2nd Edition Rick Mercer Franklin, Beedle & Associates, 1999 Presentation Copyright 1999, Franklin, Beedle & Associates Students who buy and instructors who adopt Computing Fundamentals with C++, Object-Oriented Programming and Design by Rick Mercer are welcome to use this presentation as long as this copyright notice remains intact.
6-2 Chapter 6: Class Definitions and Member Functions Chapter Goals Read and understand class definitions (interface and state). –Exercises and Programming Projects to reinforce reading and understanding new class interfaces Implement class member functions using existing class definitions. Apply some object-oriented design heuristics. –Exercises and Programming Projects to reinforce member function implementation
The Interface Abstraction: Act of using and/or understanding something without full knowledge of the implementation Allows programmer to concentrate on essentials how does a grid object move? Not to worry. how does string::substr work? Don't need to know. Abstract level: the interface Implementation level: the algorithms and local objects in the member functions
Design Decisions with class bankAccount bankAccount could have had more operations more state It was kept simple because it is an introductory example. An account number wasn't present because it's easier to remember "Smith" than " "
6-5 What is "good" design? Design decisions may be based on making a software component more maintainable software that is easy to use software that can be reused in other applications There are usually tradeoffs to consider software may get to market more quickly than the competitors even if it has known errors --- this is a tradition There is rarely a perfect design Design is influenced by many things
6-6 Interface == class definition In a real-world bankAccount, there could be many more operations: applyInterest, printMonthlyStatement, averageDailyBalance choosing what operations belong to a class requires many design decisions The design is captured by the class interface: the collection of available messages that is captured in the C++ class definition see next slide
Class Definitions class class-name { public: class-name() ; // Default constructor class-name() ; // Default constructor class-name(parameter-list) ; // Constructor with parameters class-name(parameter-list) ; // Constructor with parameters function-heading ; // Member functions that modify state function-heading ; // Member functions that modify state function-heading ; function-heading ; function-heading const ; // Members that do not modify state function-heading const ; // Members that do not modify state function-heading const ; function-heading const ;private: object-declaration // Data members -- the state object-declaration // Data members -- the state object-declaration object-declaration } ;
6-8 class bankAccount { public: bankAccount(); bankAccount(); // post: construct a default bankAccount object // post: construct a default bankAccount object bankAccount(string initName, double initBalance); bankAccount(string initName, double initBalance); // post: Initialize bankAccount object with 2 arguments // post: Initialize bankAccount object with 2 arguments void deposit(double depositAmount); void deposit(double depositAmount); // pre: depositAmount > 0.00 // pre: depositAmount > 0.00 // post: credit depositAmount to this object's balance // post: credit depositAmount to this object's balance void withdraw(double withdrawalAmount); void withdraw(double withdrawalAmount); // pre: 0 <= withdrawalAmount <= balance // pre: 0 <= withdrawalAmount <= balance // post:debit withdrawalAmount from object's balance // post:debit withdrawalAmount from object's balance double balance() const; double balance() const; // post: return this object's current account balance // post: return this object's current account balance string name() const; string name() const; // post: return this object's identification name // post: return this object's identification nameprivate: string my_name; // The unique account identification string my_name; // The unique account identification double my_balance; // Stores the current balance double my_balance; // Stores the current balance}; Class definitions: An example with comments
6-9 Class Definitions continued The class definition shows what messages are available provides information necessary for sending messages it has a collection of member function headings member function name, return type, number of parameters represents the interface, which is the collection of available messages
6-10 Class Definitions continued The following things can be determined from a class definition The class name The name of all member functions The return type of any non-void function The number and class of arguments required in any member function call The action of each member function through postconditions if it has comments, that is
6-11 Objects are a lot about Operations and State The function-headings after public: represent the messages that may be sent to any object The data-members after private: are what stores the state of any object every instance of a class has it own separate state
6-12 The same class without pre and post conditions code oriented class bankAccount { public: // OPERATIONS //--constructors bankAccount(); bankAccount(); bankAccount(string initName, double initBalance); bankAccount(string initName, double initBalance);//--modifiers void deposit(double depositAmount); void deposit(double depositAmount); void withdraw(double withdrawalAmount); void withdraw(double withdrawalAmount);//--accessors double balance() const; double balance() const; string name() const; string name() const; private: // STATE string my_name; string my_name; double my_balance; double my_balance;};
6-13 class diagram summary of operations and state
6-14 Sample Messages #include #include using namespace std; #include "baccount" // for class bankAccount int main() { // Test drive bankAccount bankAccount defaultAccount; // Initialize bankAccount defaultAccount; // Initialize bankAccount anAcct("Bob", 50.00); // Initialize bankAccount anAcct("Bob", 50.00); // Initialize anAcct.withdraw(20.00); // Modify anAcct.withdraw(20.00); // Modify anAcct.deposit(40.00); // Modify anAcct.deposit(40.00); // Modify cout << anAcct.name() << endl; // Access cout << anAcct.name() << endl; // Access cout << anAcct.balance() << endl; // Access cout << anAcct.balance() << endl; // Access return 0; return 0;}
Constructors The first two function headings for bankAccount differs from the other members they have no return type they have the same name as the class These are the constructor functions create three default bankAccount objects: bankAccount a, b, c; bankAccount a, b, c; initialize one initialized instance of the class: bankAccount anAcct("Bob", ); bankAccount anAcct("Bob", );
6-16 Constructors continued Other constructor messages: string s1, s2, s3; string s1, s2, s3; string name("First I. L. Last"); string name("First I. L. Last"); bankAccount a1, a2, a3; bankAccount a1, a2, a3; bankAccount anotherAcct("CH1234", ); bankAccount anotherAcct("CH1234", ); General form for object construction: class-name object-name-list ; class-name object-name-list ;-or- class-name object-name ( initial-state ) ; class-name object-name ( initial-state ) ;
The State Object Pattern Many classes have these things in common private data members store the state of objects constructors initialize private data members modifiers alter the private data members accessors allow us to inspect the current state of an object or to have its state available in expressions
6-18 Object Pattern An object pattern is a guide for designing and implementing new classes The state object pattern describes objects that Maintain a body of data and provide suitable access to it by other objects and human users Object patterns help us understand new objects The state object pattern summarizes many of the classes you will implement at first
6-19 Pattern:State Object Problem: Maintain a body of data and provide suitable access to it by other object and human users class { Outline: class a-descriptive-class-name {public: // default state default-constructor // default state constructors(initial-state) modifying-functions accessing-functionsprivate: // the state data-members // the state};
Using Constructors, Modifiers, and Accessors Constructors initialize the state of an object: string s2("initial string"); // State of s2 is "initial string" string aDefaultString; // State of aDefaultString is "" bankAccount anAcct("Early Grey", ); // Early Grey has a starting balance of 2, bankAccount aDefaultAccount; // aDefaultAccount.name() would return "?name?" // and aDefaultAccount.balance() would return 0.0
6-21 Using Modifiers Modifiers alter the state of an object: string s2("initial string"); string s2("initial string"); s2.replace(1, 3, "NEW"); s2.replace(1, 3, "NEW"); // s2 is "iNEWial string" // s2 is "iNEWial string" aGrid.move(5); aGrid.move(5); // The mover is 5 spaces forward // The mover is 5 spaces forward anAccount.withdraw(120.00) anAccount.withdraw(120.00) // Balance is reduced by // Balance is reduced by
6-22 Using Accessors Accessors either return the current value of an object's data member, or return some information related to the state of an object: s2.length() // Return the dynamic length s2.length() // Return the dynamic length g.row() // Tell me where the mover is g.row() // Tell me where the mover is g.column() g.column() myAccount.balance()// Get current balance myAccount.balance()// Get current balance aBook.borrower() // who has the book aBook.borrower() // who has the book s2.substr(0, 3) // A piece of the string s2.substr(0, 3) // A piece of the string
6-23 Naming Conventions Rules #1, 2, and 3: 1: Always use meaningful names 2: Always use meaningful names 3: Always use meaningful names Rule #4 Constuctors:Name of the class Modifiers:Verbs borrowBook withdraw Accessors:Nouns length row nRows could use getLength, getRow, getnRows
public: or private: When designing a class, do this at least for now place operations under public: place object that store state under private: Public messages can be sent from the block in which the object is declared Private state can not be messed up like this bankAccount myAcct("Me", 10.00); bankAccount myAcct("Me", 10.00); myAcct.my_balance = myAcct.my_balance ; myAcct.my_balance = myAcct.my_balance ; //... //...
6-25 Protecting an Object's State Access modes make operations available to clients and also protect the state The scope of members is as follows: Access ModeWhere is the member known? scope public: In all class member functions and in the block of the client code where the object has been declared in main for instance private: Only to class members functions.
6-26 Why is the state private? Recommendations: consistently declare operations after public: and data members after private: A public operation can be used by the client private data avoids direct access to state: myAccount.my_balance = ; myAccount.my_balance = ; Now, to modify the state, we must go through the proper channels--deposit.
6-27 Another State Object Example class student { public://--constructors student(); student(); student(string initName, student(string initName, double initCredits, double initCredits, double initQualityPoints); double initQualityPoints); // --modifier(s) void set_name(string newName); void set_name(string newName); void recordCourse(double credits, void recordCourse(double credits, double numericGrade); double numericGrade); // --accessor(s) double GPA() const; double GPA() const; string name() const; string name() const;private: string my_name; // ID string my_name; // ID double my_credits; // credits completed double my_credits; // credits completed double my_QualityPoints; // sum of credits*grades double my_QualityPoints; // sum of credits*grades};
6-28 Active Learning Class name ______________? Operations names _______________? Names of objects that store state _____________? # arguments for recordCourse ____________? Return type of GPA _____________? # arguments for GPA ____________? Write code that initializes one student object records two courses and shows the GPA elsewhere What value is returned for your student ? ____?
Separating Interface from Implementation Class definitions are usually stored in.h files The implementations of those member functions are usually stored in.cpp files Separating interface from implementation is a sound software engineering principle But to construct objects when the class is in two separate files requires some extra work or the little "trick" on the next slide
6-30 Keeping things simple but not traditional to save compile time Most author-supplied classes have 3 files The file name with no dot '.' #includes both the class definition and the member function implementations automatically: // File name: baccount #ifndef _BACCOUNT_ // These safeguards prevent #define _BACCOUNT_ // duplicate compilation #include "baccount.h" // class bankAccount definition #include "baccount.cpp" // function implementation #endif
Implementing Class Member Functions Class member function implementation are similar to their non-member counterparts All class member functions must be qualified with the class name and :: resolution operator void bankAccount::withdraw(double amount) void bankAccount::withdraw(double amount) { // This function can reference private data! // This function can reference private data! } constructors cannot have a return type bankAccount::bankAccount(string initName, bankAccount::bankAccount(string initName, double initBalance) double initBalance)
Implementing Constructors The following default constructor (no arguments) bankAccount::bankAccount() bankAccount::bankAccount() { my_name = "?name?"; my_name = "?name?"; my_balance = 0.0; my_balance = 0.0; } is called three times with this code: bankAccount a1, a2, a3; bankAccount a1, a2, a3; Each has the same default state. output: ?__________? cout << a1.name() << a2.name() << a3.name(); cout << a1.name() << a2.name() << a3.name(); cout << a1.balance() << a2.balance() cout << a1.balance() << a2.balance() << a3.balance(); << a3.balance();
6-33 Constructors with parameters The following constructor with parameters bankAccount::bankAccount(string initName, bankAccount::bankAccount(string initName, double initBalance) double initBalance) { my_name = initName; my_name = initName; my_balance = initBalance; my_balance = initBalance; } is called whenever bankAccount objects are constructed with arguments like this: is called whenever bankAccount objects are constructed with arguments like this: bankAccount initialized("Glass", ); bankAccount initialized("Glass", ); bankAccount another("Dunham", ); bankAccount another("Dunham", );
Implementing Modifiers Modifying functions are implemented like non members except they must be qualified with class- name :: This gives the modifier access to the state that is to be modified. In this example, the private data member my_balance is changed: so do not use const void bankAccount::deposit(double depositAmount) void bankAccount::deposit(double depositAmount) { // post: my_balance has depositAmount added { // post: my_balance has depositAmount added my_balance = my_balance + depositAmount; my_balance = my_balance + depositAmount; }
Implementing Accessors: Accessor functions must also be qualified with class-name :: to gives access to the state to being used to return info. remember to write const double bankAccount::balance() const double bankAccount::balance() const { // no processing is necessary. Just return it. { // no processing is necessary. Just return it. return my_balance; return my_balance; } In this example, the private data member my_balance is made available like this: cout << anAcct.balance() << endl; cout << anAcct.balance() << endl;
Object-Oriented Design Heuristics Classes must be designed There are some heuristics (guidelines) to help us make design decisions Design Heuristic 6-1 Design Heuristic 6-1 All data should be hidden within its class All data should be hidden within its class Ramifications: Good: Can't mess up the state (compiler complains) Good: Have to create interface of member functions Bad: Extra coding, but worth it
Cohesion Within a Class A class definition provides the public interface-- messages should be closely related The related data objects necessary to carry out a message should be in the class Design Heuristic 6-2 Keep related data and behavior in one place Keep related data and behavior in one place Ramifications Good: Provides intuitive collection of operations Good: Reduces the number of arguments in messages Bad: None that I can think of
6-38 Cohesion continued For example, cohesion means the bankAccount class does not have operations like dealCardDeck or ambientTemperature the bankAccount class has access to the balance, something which often needs to be referenced. The balance (named my_balance ) is not maintained separately or passed as an argument
6-39 Cohesion continued Synonyms for cohesion: hanging together unity, adherence, solidarity Cohesion means data objects are related to the operations operations are related to the data objects data and operations are part of the same class definition
Why are Accessors const and Modifiers not? Have you noticed that const follows all accessing functions? but not any others It necessary to make our new classes behave like the standard classes specifically, it is necessary to allow objects passed by const reference to behave correctly such parameters should allow the accessing messages to be sent, but not any modifing message
6-41 const messages okay, all others are not void display(const bankAccount & b) { // OKAY to send name and balance messages since they // OKAY to send name and balance messages since they // were both declared with const member functions // were both declared with const member functions cout << "{ bankAccount: " << b.name() cout << "{ bankAccount: " << b.name() << ", $" << b.balance() << " }" << endl; << ", $" << b.balance() << " }" << endl; // Modifying message to non-const member function // Modifying message to non-const member function // was not tagged as const. It should be an ERROR // was not tagged as const. It should be an ERROR b.withdraw(234.56); b.withdraw(234.56);} Note: Turbo/Borland C++ reduces the error to a warning. This is not good.
6-42 A C++ specific heuristic This leads to another guideline that is particular to C++ Design Heuristic 6-3 Design Heuristic 6-3 Always declare accessor member functions as Always declare accessor member functions as const. Never declare modifiers as const const. Never declare modifiers as const This guideline is easy to forget Unless you thoroughly test, you may not get a compiletime error. You will not be told something is wrong until you try to pass an instance of your new class by const reference.
6-43 Forgetting const == compiletime error message The programming projects ask you to implement member functions the class definition is given If the class definition has const, you must write const in the member function implementation also ERROR: overloaded member function not found in 'X' ERROR: overloaded member function not found in 'X' // file X.h // file X.h class X { int foo() const; int foo() const; …}; // file X.cpp headings must match! // file X.cpp headings must match! int foo() { …}