You are on page 1of 52

3.

More About Data Structures and ADTs:


C++ Data Types (Read 3.1 & 3.2)
C++ types an be classified as:
Fundamental (or simple or scalar):
A data object of one of these types is a single object.
- int, double, char, bool, complex,
and the related types (unsigned, short, etc.)
- enumerations
Structured:
Collections of data
- arrays, structs, unions, classes, valarrays, bitsets,
the containers and adapters in STL
1

Structs vs. Classes


Similarities
1. Essentially the same syntax
2. Both are used to model objects with multiple attributes
(characteristics) represented as data members
(also called fields or instance or attribute variables).
Thus, both are used to process non-homogeneous data sets.

Differences
1. C does not provide classes;
C++ provides both structs and classes
2. Members of a struct by default are public (can be
accessed outside the struct by using the dot operator.)
In C++ they can be declared to be private (cannot be
accessed outside the struct.)
3. Members of a class by default are private (cannot be
accessed outside the class) but can be explicitly
declared to be public.
3

C Structs vs. C++ Classes (& Structs)


("traditional" vs "OOP")
C++'s structs and classes model objects that have:
Attributes (characteristics) represented as data members
and
Operations (behaviors) represented as function members
(also called methods).

C structs

Operations

This leads to a whole new style of programming:


object-oriented. Objects are self-contained,
possessing their own operations commonly
called the I can do it myself principle
rather than being passed as a parameter to an
external function that operates on them and sends
them back.

Function
Members
Data
Members
Attributes

Declaring a Class
Common Forms:
class ClassName
{
// private by default
Declarations of private members
public:
Declarations of public members
};
class ClassName
{
public:
//"interface" of class given first
Declarations of public members
private:
Declarations of private members
};

Notes:
Data members are normally placed in the
private section of a class;
function members in the public section.
Some programmers prefer to put the
private section first:
- Using the default access for classes
- Can omit the private: specifier

Operations
public:
Function
Members
private:
Data
Members
Attributes

Other programmers put the public interface of the class first and the
hidden private details last. (In the text, the latter approach is used.)
Although not commonly done, a class may have several private and
public sections; the keywords private: and public: mark the
beginning of each.
6

Access to Class Members


A particular instance of a class is called an object:
ClassName object_name;
Private members can be accessed only within the class (object)
(except by friend functions to be described later).
Public members can be accessed within or outside the class
(object); to access them outside the class (object), one must use
the dot operator:
object_name.public_member_name
A class declaration is usually placed in a header file whose
name is ClassName.h . The library is then called a class
library.
7

Header file for class Time Version 1


/** Time.h -------------------------------------------------------This header file defines the data type Time for processing time.
Basic operations are:
Plus: Name, Course #, Lab/Project #, etc.
Set:
To set the time
Display: To display the time
----------------------------------------------------------------------*/
#include <iostream>
using namespace std;
class Time
{
/******** Member functions ********/
public:

Note the doc!

/* Set sets the data members of a Time object to specified values.


*
* Receive: hours, the number of hours in standard time
*
minutes, the number of minutes in standard time
*
AMPM ('A' if AM, 'P' if PM)
* Postcondition: The Time object containing this function has its
*
myHours, myMinutes, and myAMorPM members set to hours,
*
minutes, and am_pm, respectively, and myMilTime to
*
the equivalent military time
********************************************************************/

void Set(unsigned hours, unsigned minutes, char am_pm);

/* Display displays time in standard and military format using


* output stream out.
*
* Receive:
ostream out
* Output:
The time represented by the Time object containing
*
this function
* Passes back: The ostream out with time inserted into it
*****************************************************************/

void Display(ostream & out) const;


/********** Data Members **********/
private:
unsigned myHours,
myMinutes;
char
myAMorPM;
// 'A' or 'P'
unsigned myMilTime;
// military time equivalent
}; // end of class declaration
Notes:
1. "my" in data members names is a reminder of internal ("I can do it myself")
perspective.
2. const at end of Display()'s prototype makes it a const function
which means it cannot modify any of the data members.
Good idea to protect data members from accidental modification.

"Accessors"
vs.
"Mutators"

3. Why make data members private?

(p. 91)

"Hidden" data members:


Cannot be accessed outside class
Application programs must interact with an object through its interface
Public member functions control interaction between programs and class.
Application programs need not know about implementation!
Implementation may change (improve storage, simpler
algorithms. etc.)
If interface is constant, programs using an object need not be changed.
What's wrong with tying application code to implementation details?
A change in implementation forces a change in the application code
Increased upgrade time
Increased programmer cost
Decreased programmer productivity
Reduced profits due to
Delayed releases/upgrades
Loss of customer confidence in software reliability

Always define data members of a class as private. 10

Implementation of a Class
Class declaration contains:
Declarations of data members
Prototypes (declarations) of function members
Definitions of function members are not usually placed in class declaration
Avoid cluttering up the interface
Definitions placed outside the class declaration must tell compiler where the
corresponding declaration/prototype is :
Use the scope operator :: which has the form
ClassName::ItemName
(the qualified or full name of ItemName.)
This applies also to constants and types
declared within a class.

e.g., Lab 3

11

Example:

See slide 19

Use of static locals


inside functions

class Something
{
public:
static const int CAPACITY = 100;
typedef double ArrayType[CAPACITY];
void Print(ArrayType a, int itsSize);
. . .
};
. . .
Something::ArrayType x = {0};
for (int i = 0; i < Something::CAPACITY; i++)
. . .
void Something::Print(Something::ArrayType a,
int itsSize)
{ . . . }

12

Traditionally, definitions of member functions have been put in an


implementation file ClassName.cpp corresponding to the class'
header file. This is done to enforce data abstraction
separating the interface of the ADT from its
implementation details.
(Unfortunately, the class data members, which store data and are
therefore part of the implementation, must be in the .h file.)
With the increasing use of templates, however, this practice
is becoming less common because current compiler technology
doesn't permit this split for templates everything has to be in the
same file. Thus the reason for dropping the ".h" from standard
class libraries. They're really class-template libraries, and there are
therefore no corresponding ".cpp" files.
13

Implementation of class Time Version 1


// Time.cpp -- implements the Time member functions
#include "Time.h"
/*** Utility functions ***/
/* ToMilitary converts standard time to military time.
Note the
Receive: hours, minutes, am_pm
doc
Return: The military time equivalent
Could implement this as a private class method
*/
int ToMilitary (unsigned hours, unsigned minutes, char am_pm)
{
if (hours == 12)
hours = 0;
return hours * 100 + minutes + (am_pm == 'P' ? 1200 : 0);
}

14

Implementation of class Time cont.


//--- Function to implement the Set operation

Note the
doc

void Time::Set(unsigned hours, unsigned minutes,


char am_pm)
{
See slide 21
// Check class invariant
if (hours >= 1 && hours <= 12 &&
minutes >= 0 && minutes <= 59 &&
(am_pm == 'A' || am_pm == 'P'))
{
myHours = hours;
myMinutes = minutes;
myAMorPM = am_pm;
myMilTime = ToMilitary(hours, minutes, am_pm);
}
else
cerr << "*** Can't set time with these values ***\n";
// Object's data members remain unchanged
}

15

Implementation of class Time cont.


//--- Function to implement the Display operation
void Time::Display(ostream & out) const
{
out << myHours << ':'
<< (myMinutes < 10 ? "0" : "") << myMinutes
<< ' ' << myAMorPM << ".M. ("
<< myMilTime << " mil. time)";
}

16

Test driver for class Time


#include "Time.h"
#include <iostream>
using namespace std;

Develop and test


classes incrementally!

int main()
{
Time mealTime;
mealTime.Set(5, 30, 'P');
cout << "We'll be eating at ";
mealTime.Display(cout);
cout << endl;
}

Execution:
We'll be eating at 5:30 P.M.

(1730 mil. time)

17

Object-Oriented Perspective
Procedural: Send object off to some function for processing
OOP:

Send a message to the object to operate on itself.

To set my digital watch to 5:30 P.M., I don't wrap it up and mail


it off to Casio. I push a button!
To display the time on my watch, I don't wrap it up and mail it off
to Casio and have them tell me what time it is. I have it display
the time itself, perhaps pushing a button to turn on the backlight
so I can see it .

18

Notes:
1. Member functions: "Inside" an object, so don't pass object to them as a
parameter. (They receive the object to be operated on implicitly, rather than
explicitly via a parameter.)
Non-member functions: "Outside" an object, so to operate on an object, they
must receive it via a parameter.
2. Public items must be qualified when referred to outside the class declaration:
ClassName::ItemName
Public constants are usually declared static so they are global class
properties that can be accessed by all objects of that class type rather than each
object having its own copy.
3. Simple member functions: Usually specified as inline functions.
This suggests to compiler to replace a function call with actual code of
the function with parameters replaced by arguments
saves overhead of function call.

Danger:
"Code Bloat"

19

Two ways to inline a class function member:


1. Prototype in class declaration; inline definition below
the class declaration in the header file,
qualifying the name as usual.
In ClassName.h:
class ClassName
{
public:
RetType SimpleFun(param_list);
. . .
};

//prototype

inline RetType ClassName::SimpleFun(param_list)


{ . . . }
// definition

2.Put functions definition (instead of prototype) inside


class declaration compiler will treat it as inlined.

Interface
clutter

20

Class Invariant: A condition (boolean expression) that ensures


that the data members always contain valid values.
Example:

1 < myHours < 12 && 0 < myMinutes < 59 &&


myAMorPM == 'A' or 'P' && 0 < myMilTime < 2359

Operations that modify data members must


check the class invariant.
Then other operations can be sure data
members have valid values.

Mutators

How?
1. Use an if statement see Set()
2. Use assert(class_invar); (must #include <cassert>)
true
false
continue execution halt execution and display error message
21

3. Throw an exception that the calling function can catch


and take appropriate action:

App.
D.4

//----- Function to implement the Set operation ----void Time::Set(unsigned hours, unsigned minutes, char am_pm)
{
// Check class invariant
if (hours >= 1 && hours <= 12 &&
minutes >= 0 && minutes <= 59 &&
(am_pm == 'A' ||am_pm == 'P'))
{
. . .
}
else
{
char error[] = "*** Illegal initializer values ***\n";
throw error;
}
}

22

To catch this exception, a calling function might contain:


try
{
mealTime.Set(13, 30, 'P');
cout << "This is a valid time\n";
}
catch (char badTime[])
{
cerr << "ERROR: " << badTime << endl;
exit(-1);
}
cout << "Proceeding. . .\n";

When executed, the output produced will be


ERROR: *** Illegal initializer values ***

23

Class Constructors
Constructing an object consists of:
(1) allocating memory for the object, and
(2) initializing the object.
In our example, after the declaration
Time mealTime;
memory has been allocated for object mealTime and it's data
members have some default (perhaps "garbage") initial values.
Need to:
Specify initial values for mealTime
Provide default values to be used if no initial values are
specified.
This is the role of a class' constructor.
(Later, it will also allocate memory.)

24

Class Constructors - Properties


1. Names are always the same as the class name.
2. Initialize the data members of an object with default values or
with values provided as arguments.
3. Do not return a value; have no return type (not even void).
4. Often simple and can be inlined.
5. Called whenever an object is declared.
6. If no constructor is given in the class, compiler supplies
a default constructor which allocates memory
and initializes it with some default (possibly garbage) value.
A default constructor is one that is used when the declaration of
an object contains no initial values:
ClassName object_name;
An explicit-value constructor is used for declarations with initial values:
ClassName object_name(list-of-init-values);
7. If we supply a class constructor, we must also provide a
default constructor or we can't use first kind of declaration.

25

Constructors for Time class


In Time.h
class Time
{
public:
/* --- Construct a class object (default).
* Precondition: A Time object has been declared.
* Postcondition: Data members initialized to 12, 0, 'A', and 0.
****************************************************************/

Time();

/* --- Construct a class object (explicit values).


* Precondition: A Time object has been declared.
* Receive:
Initial values initHours, initMinutes,and
*
initAMPM
* Postcondition: Data members initialized to initHours,initMinutes,
*
initAMPM, & correspoding military time
**********************************************************/

Time(unsigned initHours, unsigned initMinutes, char


initAMPM);
. . .
// other member function prototypes

private:
/********** Data Members **********/
. . .

26

Constructors for Time class - Implementation


In Time.h, after class declaration:
inline Time::Time()
{
myHours = 12;
myMinutes = 0;
myAMorPM = 'A';
myMilTime = 0;
}
In Time.cpp:
Time::Time(unsigned initHours, unsigned initMinutes, char initAMPM)
{
// Check class invariant
assert(initHours >= 1 && initHours <= 12 &&
initMinutes >= 0 && initMinutes <= 59 &&
(initAMPM == 'A' || initAMPM == 'P'));
Remember..
myHours = initHours;
. exceptions
myMinutes = initMinutes;
myAMorPM = initAMPM;
myMilTime = ToMilitary(initHours, initMinutes, initAMPM);

27

Testing #1:
Time mealTime,
//default constructor
bedTime(11,30,'P'); //explicit-value constructor

Create and initialize 2 Time objects:


Default Constructor

Explicit Value Constructor

Time();

Time(11,30,'P');

mealTime
myHours
myMinutes
myAMorPM
myMilTime

12
0
A
0

Function members
mealTime.Display(cout);
cout << endl;
bedTime.Display(cout);
cout << endl;

bedTime
myHours
11
myMinutes
30
myAMorPM
P
myMilTime 2330

Function members

Execution:
12:00 A.M. (0 mil. time)
11:30 P.M. (2330 mil. time)

28

Constructors for Time class Default Arguments


App. 1 from
"Other Links"

Can combine both constructors into a single constructor function by


using default arguments:
Replace constructors in Time.h with:
/*--- Construct a class object.
Precondition: A Time object has been declared.
Receive:
Initial values initHours, initMinutes, and
initAMPM (defaults 12, 0, 'A')
Postcondition: Data members initialized to initHours,
initMinutes, initAMPM, & correspoding
military time.
*/

Time(unsigned initHours = 12, unsigned initMinutes = 0,


char initAMPM = 'A');

Note: Any parameter with default argument must appear after all
parameters without default arguments.
29

Testing:
Time mealTime, t1(5), t2(5, 30), t3(5, 30, 'P');
Creates 4 Time objects:

12
0
A
0

5
0
A
500

mealTime.Display(cout);
cout << endl;
t1.Display(cout); cout << endl;
t2.Display(cout); cout << endl;
t3.Display(cout); cout << endl;

5
3
0A
530

5
3
0P
1730

Execution:
12:00 A.M. (0 mil. time)
5:00 A.M. (500 mil. time)
5:30 A.M. (530 mil. time)
5:30 P.M. (1730 mil. time)

30

Copy Operations
Two default copy operations are provided:
1. Copy in initialization (via copy constructor )
2. Copy in assignment (via assignment operator )
Each makes a raw (bitwise) copy of the memory allocated to the
data members of the object.
Note:
Type t = value;

Type t;
t = value;

This is OK
for now

31

Examples:

Time t = bedTime;
Time t(bedTime);

Both:
1. Allocate memory for t
2. Copy data members of bedTime so t is a copy of bedTime
11
30
P
2330

11
30
P
2330

In contrast:
Time t = Time(11, 30, 'P');

calls the explicit-value constructor to construct a (temporary)


Time object and then copies it into t.
What about
Note: These are not assignments; a default
Time t = 3;
copy constructor is called.
?

In mixed-mode operations, compiler will look for a


constructor to convert one value to the type of the other.

32

There is a default copy operation for assignment.


Example:

This is OK
for now

t = mealTime;

copies the members of mealTime into t, replacing


any previous values:
12
0
A
0

12
0
A
0

It returns this Time object as the value of this expression.


Why?
So we can chain =

33

Access Member Functions


Data members are private: they cannot be accessed outside the class. To make the
values stored in some or all of these members accessible, provide accessor
(or extractor) member functions. (They are non-mutators; mutators change
data members.)
Example:
To provide access to myHours of class Time.
(Access for the remaining members is analogous.)
simply retrieves and returns the value stored in a data member
inline, because it's simple
prototype (and define) as a const function
Add in Time class declaration
/* Hour Accessor
* Return: value stored in myHours data member of
*
Time object containing this function
*/
unsigned Hour() const;

34

Add below Time class declaration

inline unsigned Time::Hour() const


{ return myHours; }
Testing:
Time mealTime;
. . .
cout << "Hour: " << mealTime.Hour() << endl;
Execution:
Hour: 12

35

Output and Input


Add output operation(s) to a class early so it can be used for
debugging other operations.
Example: overload operator<<() for a Time object
Instead of:
cout << "We'll be eating at " ;
mealTime.Display(cout);
cout << endl;
we can write:
cout << "We'll be eating at "
<< mealTime << endl;
36

Overloading operators
In C++, operator can be implemented with the function
operator().
If a member function of a class C, and a is of type C, the compiler
treats a b as
a.operator(b)
If not a member function of a class C, the compiler treats a b
as
operator(a, b)

37

Overloading Output Operator <<


Can operator<<() be a member function?
No, because the compiler will treat
cout << t

as
cout.operator<<(t)
which means that operator<<(const Time &) would
member of class ostream (or cout be of type Time)

have to be a

and we can't (or don't want to) modify standard C++ classes.
Putting the prototype
ostream& operator<<(ostream & out, const Time& t);

inside our class declaration produces a compiler error like:


'Time::operator <<(ostream &, const Time &)'
must take exactly one argument

38

Why is out a reference parameter?


So corresponding actual ostream argument gets modified
when out does.
Why is t a const reference parameter?
Avoid overhead of copying a class object.
Why is return type ostream & (a reference to an ostream)?
Else a copy of out is returned.
Why return out?
So we can chain output operators.
Since << is left - associative:
( ( ( cout << t1)<< endl) << t2)<< endl;

operator<<(cout, t1) << endl << t2 << endl;


first function must return cout
cout << endl << t2 << endl;
operator<<(cout, endl) << t2 << endl;
cout << t2 << endl;
. . .

39

Overloading << for a Class


Method 1: Put definition in .h file, after class declaration, and
have it call an output function member.
Inline it, because it's simple.

. . .
} // end of class declaration
. . .
/* operator<< displays time in standard and military
format.
Receive:
ostream out and Time object t
Output:
time represented by Time object t
Pass back: ostream out with t inserted into it
Return:
out
*/
inline ostream & operator<<(ostream & out, const Time & t)
{
t.Display(out);
return out;
Note: No Time::
40
}
It's not a member function!

Method 2: Define operator<<()outside the class declaration


as a non-member function and:
(i) use accessors to display data members, or
(ii) declare it to be a friend in the class declaration.
In class declaration
/* doc. as before */
friend ostream & operator<<(ostream & out, const Time & t);

Outside class declaration (in .cpp file)

Note: No friend

ostream & operator<<(ostream & out, const Time & t)

{
out <<
<<
<<
<<
<<
return
}

t.myHours << ':'


(t.myMinutes < 10 ? "0" : "")
t.myMinutes
' ' << t.myAMorPM << ".M. ("
t.myMilTime << " mil. time)";
out;

41

Friend Functions
A function that a class declares as a friend is a
non-member function to which the class has granted
permission to access members in its private sections.
Note: Because a friend function is not a function member:
Don't qualify its definition with
class name and scope operator (::).
Don't put friend in definition.
It receives the object on which it operates as a parameter.
It uses the dot operator to access the data members.
42

To add an input operator to our Time class, we proceed in much the


same way as for output. We could either:
1. Add a member function ReadTime() that reads values and stores
them in the data members of a Time object; then call it from nonmember function operator>>()
2. Declare operator>>() to be a friend function so that it can
access the data members of a Time object and store input values in
them.
Is one of two methods for input/output preferred?
We'll see later when we study inheritance and polymorphism that the
first method is preferred (or perhaps required).

43

Relational Operators (<)


Specification:
Receives: Two Time objects
Returns: True if the first object is less than the second; false otherwise.
Should operator<() be a member function?
Internal perspective: I compare myself with another Time object and
determine if I am less than that other object
External perspective: Two Time objects are compared by an external function
to determine if the first is less than the second.
OOP: "I-can-do-it-myself" principle ( objects self-contained):
Use member functions whenever possible.
Rephrased specification:
Receives: A Time object (and the current object implicitly)
Returns: True if I (the Time object containing this function) am
less than the Time object received; false otherwise.

44

// Internal perspective : Add to Time.h


class Time
{
public:
// member functions
. . .
/***** Relational operators *****/
/* operator< determines if one Time is less than another Time
* Receive: A Time t (and the current object implicitly)
* Return: True if time represented by current object is < t.
*/
bool operator<(const Time & t) const;
. . .
}; // end of class declaration
inline bool Time::operator<(const Time & t) const
{ return myMilTime < t.myMilTime; }

However . . .
45

Caveat re Operator Overloading


Internal perspective may lead to seeming inconsistencies:
Example: Suppose a and b are objects of type class C.
a < b okay?
Yes, equivalent to a.operator<(b)
b < a okay?
Yes, equivalent to b.operator<(a)
a < 2 okay?
Yes, if there is a constructor that promotes 2 to type C,
since this is then equivalent to a.operator<(C(2))
2 < a okay?
No, equivalent to 2.operator<(a), which is meaningless.
May confuse an application programmer to support a < 2 but disallow
2 < a. probably best to use friends here.
46

Overloaded Operator as Friend


External perspective: (Permits a < 2 and 2 < a , if 2 can be promoted)
class Time
{
public:
// member functions
. . .
/* operator<
* Receive: Two Times t1 and t2
* Return:
True if time t1 is less than time t2/
*/
friend bool operator<(const Time & t1, const Time & t2);
. . .
}; // end of class declaration
inline bool Time::operator<
(const Time & t1, const Time & t2)
{ return t1.myMilTime < t2.myMilTime; }
Or don't use friend and compare values obtained by accessors.

47

AddingIncrement/DecrementOperators

Specification:
Receives: A Time object (perhaps implicitly)
Returns: The Time object with minutes incremented by 1
minute.
Yes
Question: Should it be a member function?
Add to Time.h:
/***** Increment operator *****/
/* --- Advance() increments a Time by 1 minute.
Postcondition: The Time object has its minutes
incremented by 1.
-----------------------------------------------*/
void Advance();

48

AddtoTime.cpp:
//----- Function to implement Advance() ----void Time::Advance()
{
myMinutes++;
myHours += myMinutes / 60;
myMinutes %= 60; myHours %= 12;
if (myMilTime == 1159)
myAMorPM = 'P';
else if (myMilTime == 2359)
myAMorPM = 'A';
// else no change
myMilTime =
ToMilitary(myHours, myMinutes, myAMorPM);
}
49

++
We could replace Advance() with overloaded operator+
+().
Question: How do we distinguish between prefix ++ and
What's
postfix ++?
the
Solution: In C++, when the
differenc
Compiler encounters:
It looks for:
e?
Prefix ++:
operator++() with no
parameters
Postfix +
operator++(int) with one
+:
int parameter
(which is not used in the
definition.)
50

Redundant Declarations
Class Time might be used in different programs, libraries, etc., so it
could be included several times in the same file
e.g.,
Program needs Time class, so it #includes "Time.h"
Program also needs library Lib, so it #includes "Lib.h"
. . . but Lib.h also includes "Time.h
This can cause "redeclaration" errors during compiling.
How do we prevent the declarations in Time.h from being included
more than once in a file?
51

Conditional Compilation
Wrap the declarations in Time.h inside preprocessor directives.
(Preprocessor scans through file removing comments, #including files, and
processing other directives (which begin with #) before the file is passed to the
compiler.)
#ifndef TIME
#define TIME
:
:
#endif

Usually the name of the class in all caps

The first directive tests to see whether the identifier TIME has been defined.
If not defined:
Proceed to second directive ( define TIME )
Continue to the #endif and beyond.
If defined:
Preprocessor removes all code that follows until a
#elsif, #else, or #endif directive encountered.

52

You might also like