While engaging with this module, you will...

- learn new techniques for working with a class' member variables
- be able to decide when and how it is appropriate to provide read or write access to a class' internal data

Let’s provide some functionality that should natural to fractions. We will want to multiply fractions at some point, so let’s define a non-member function that will get the job done for us.

// in the Fractions.cpp file

Fraction mult_fracs(const Fraction & lhs, const Fraction & rhs)

{

Fraction temp; // a fraction to build locally and then return

temp.m_Numerator = lhs.m_Numerator * rhs.m_Numerator;

****** STOP – THIS WON’T COMPILE !! ********

In this code we have attempted a cardinal sin, and it won’t compile. In this last line of code, we have tried to access directly the private member variables of Fraction objects. But we did that in the last lesson, you say! Yes, but those were *member functions*. The function above is a non-member (or global) function. How do deal with this issue then? There are two approaches.

A note before we go on: You will notice that I named the parameters oddly. lhs and rhs are short for left-hand side and right-hand side, common names for parameters to binary operations. Also, I made them reference parameters so as to avoid the overhead of copying Fraction type objects into the parameters. This is a common practice with user-defined types, which can possibly be large aggregates, thus causing undue overhead.

Accessor and mutator functions (a.k.a. set and get functions) provide a direct way to change or just access private variables. They must be written with the *utmost care* because they have to provide the protection of the data that gives meaning to a class in the first place. Remember the central theme of a class: data members are hidden in the private section, and can only be changed by the public member functions which dictate allowable changes. Let’s see how we might code up these functions for our example class.

class Fraction

{

public:

void readin();

void print();

Fraction reciprocal();

void unreduced(const int multiplier);

int getNum(); // accessor or get function

int getDen(); // likewise

void setNumer(const int numer); // mutator or set function

bool setDenom(const int denom); // likewise

private:

int m_Numerator;

int m_Denominator;

};

...and here are the definitions:

// these would go in the Fraction.cpp file

int Fraction::getNumer()

{

return m_Numerator;

}

int Fraction::getDenom()

{

return m_Denominator;

}

void Fraction::setNumer(const int numer)

{

m_Numerator = numer;

return;

}

bool Fraction::setDenom(const int denom)

{

bool set = false;

if (denom != 0) // prohibits setting denominator to zero!

{

set = true;

m_Denominator = denom;

}

return set;

}

Now you see that all of these functions are straight forward. The get functions will return the values of the members and the set functions will set the values of the members. Let’s analyze how we defined these. The gets are obvious: return the appropriate value. But let me show you how you might want to write such an obvious function. You can “inline” them into the class definition:

class Fraction

{

public:

...

int getNumer() {return m_Numerator;}

int getDenom() {return m_Denominator;}

...

}

With quite obvious and simple functions, this acceptable and you are giving away nothing.

As for the set functions, we see that we are able to set the numerator to anything. And that makes sense; a numerator can be any integer, including zero. However, it’s different with the value for the denominator. We mustn’t let the denominator be set to zero. There are many ways to code this, and you will learn all about exception handling in cs 153, which is probably the best way to handle it. However, not knowing that technique, I have chosen to do the above. I have the setDenom function return a bool to represent whether or not the value of the denominator was set or not. Let’s see how we might use these functions now back in the example I started at the beginning of this lesson.

// in the Fractions.cpp file

Fraction mult_fracs(const Fraction & lhs, const Fraction & rhs)

{

Fraction temp; // a fraction to build locally and then return

temp.setNumer(lhs.getNumer() * rhs.getNumer());

if (! (temp.setDenom(lhs.getDenom() * rhs.getDenom()))

{ //if set returns true, denom is set, all ok

cout<<"ERROR: denominator is 0 "<<endl; // if set returns false, denom not set

exit(2); // and program bombs

}

return temp;

}

If the argument to the *setDenom()* function were to be zero, the program would output a message and then exit. Of course, in this case, some fraction’s denominator would have to be zero for this to happen, which we hope could not happen. Well, the way we have the *readin()* written at this point, it is well possible! We need to rewrite that function:

In general, just because you can write a set or get function for a private member variable, that does not imply that you should. You should only write these functions if they are warranted. Let’s consider an example. Suppose you write a program for keeping grades for a course. You have to write a “student” class. Member variables include an array of quiz grades, an average, and a letter grade. You will have public member functions that include *void calc_grade()*. When called, this function’s responsibility is to average the quiz scores, set the average member, and then set the letter grade based on that average. This last exercise might be implemented by the *calc_grade()* function call a *compute_grade()* function that sets the letter grade based on average. In any case, is it reasonable to have a public *setAverage()* function? I think not. It isn’t reasonable to be able to set the average arbitrarily to, say, 65 when the quiz grades would indicate an average of 90!

Course Menu

0. Getting Started

1. The Big Picture

1.0. Introduction

2. Programming Fundamentals

3. Operators

4. Branching

4.0. If-Else Statement

5. Loops

6. Advanced Branching

7. Odds and Ends

7.0. Introduction

8. Functions

8.0. Function Basics

8.1. Reference Parameters

8.2. Code Documentation

8.3. Dedication of Duty

8.4. Default Arguments

8.5. Function Overloading

8.6. Static Variables

8.7. Inline

8.8. Templates

9. Random Number Generation

9.0. Introduction

10. Multiple Files

10.0. Header and Source Files

11. Arrays

11.0. Introduction

11.1. Working with Arrays

12. Structs

12.0. Introduction

13. Character Arrays

14. File I/O

15. Objects

15.0. Object-Oriented Paradigm

15.1. Defining Classes

15.2. Accessor Mutators

15.3. Const Functions

15.4. Friend Functions

15.5. Constructors

15.6. Structs vs. Classes

15.7.0 Overloading Operators

15.7.1 Multiplication and Chaining

15.7.2 IsEquals

15.7.3 Negation

15.7.4 Constructor Overload

15.8.0 Assignment Operator

15.8.1 Bracket Operator

15.8.2 Function Evaluation

15.9. Static Members

15.10. Template Classes

16. Output Formatting

16.0. Overview

17. Namespaces

17.0. Introduction

18. Enumerations

18.0. Introduction

19. Sample Homework

19.0. Assignment #1

19.1. Assignment #2

19.2. Assignment #3

19.3. Assignment #4

19.4. Assignment #5

19.5. Assignment #6

19.6. Assignment #7

19.7. Assignment #8

19.8. Assignment #9

19.9. Assignment #10

Site Menu