C++ Seminar: 1094 Session 4
Handouts: None!
Your goals for today: Quick
review of Day 5 and the start of Day 6,
then work through Day 6 and Day 7.
Review: Day 5 and the first part of Day 6
We'll briefly step through the C++ function stuff from Day 5, giving
you a chance at some debugging, and then recap the idea of a class
from Day 6. Please ask questions!
- Some things to remember about functions:
- Use a prototype for every function. It should go before
the function definition, which means at the top of the file
or in a separate file, called a "header" file
(which will have the same name, but end with ".hpp"), which
is read using an #include statement. We'll see some of these later today.
- The prototype ends with a semicolon, but the first line
of the function definition (which looks very similar) does
not.
- The "scope" of a variable (that is, where the variable is
recognized) that is declared within a function is, at most, that function
(we say it is "local" to that function).
If declared within a "block" delimited by {}'s, the
variable is only known within that block (we say it is "local"
to that block). While we saw examples where variables with the
same name could be used with different scopes, this is
bad programming practice. Use a different name for every
variable (except dummy variables such as those
used in for loops, which won't get confused).
- Global variables, which are defined outside of any function and are
accessible to all, seem very convenient. However, in general they are not
a good idea. Using classes, in which variables are hidden from
the outside on purpose, leads to more robust programs that can
be modified and reused without fear of introducing subtle (or unsubtle!)
bugs.
- A function can have more than one return statement.
The type of the variable returned must match the declared type of
the function. If there is no return statement at all, the
function must be declared void.
- Let's try a couple of debugging exercises left over from Day 5
(so you'll find the programs under Session 3, Day 5).
- What is wrong with the function in the code in
Bug0503.cpp? [Try to identify any problems before
compiling, then follow the error messages from the compiler.]
- What is wrong with the function in the code in
Bug0504.cpp? [Try to identify any problems before
compiling, then follow the error messages from the compiler.]
- In Day 6, we introduced the "class", which contains data and
functions that operate on that data.
-
The data are referred to either as "member variables" or "data
members" (i.e., these are synonyms)
while the functions are either "member functions" or
"methods".
-
You'll want to keep straight the difference between a class and
an object of that class. The book says: "An object is an individual
instance of a class." So Frisky is an object of type
Cat. [This is similar to declaring int radius
and then confusing int, which is the type, with
radius, which is an instance of an integer.] The common
mistake is to try to access member variables of the class rather than
the object:
Cat Frisky; // declare Frisky to be an object of class Cat
cout << "Its weight is: " << Cat.itsWeight << endl; // wrong!
cout << "Its weight is: " << Frisky.itsWeight << endl; // ok!
-
We have to both declare a class and then give definitions of its
methods.
We'll usually put the class declaration in
a separate file (called a header file) from the definitions. The
usual convention is that these two files are Cat.hpp and Cat.cpp
(that is, the class name and then the extension hpp for the header
and cpp for definitions).
-
Member variables and member functions are both referenced outside
the class definitions using the "dot" operator:
cat_weight = Frisky.itsWeight; // get Frisky's weight
Frisky.Meow(); // invoke function Meow defined in class Cat
So far, we've only seen
examples of the member data. Let's introduce other aspects
of classes.
Day 6: Understanding Object-Oriented Programming (cont.)
Here we'll continue with Day 6. Hopefully you've had a chance to
read this chapter in the
online version of Day 6 (please read this later if you haven't
yet).
- In Session 3, you looked at List0601.cpp,
which introduced a class named Cat that had two public
variables for a particular cat's age (itsAge) and weight
(itsWeight). Since they were public, they could be modified
directly by any other function. What we'll do now is hide
or "encapsulate" those variables, by declaring them private.
[By default, variables are private, but always declare them explicitly
private to avoid confusion.]
At the same time, we have to introduce functions that set and get the
variables. These are called "public accessor methods".
Appropriate declarations for the Cat class are shown
in List0602.cpp; for each class function there is a function
prototype that appears in the class definition.
The keyword unsigned simply
means that the integer cannot be negative. There's not much
to see or do yet; we need to define the actual functions ("methods"), which
we'll do next. But note how functions and data are mixed, but also
divided into public: (accessible from outside the class)
and private: (accessible only inside the class). There is
also an unspecified function called Meow. Why do you think
SetAge and SetWeight are declared as void?
- We take the next step in List0603.cpp. It has a simplified
class definition for Cat (which has the cat's age stored but not
its weight) but also definitions for the SetAge "public assessor"
and the Meow "method" (which is just a name for a function associated
with a class). Take a look at the code. Note that the functions have
to be defined with Cat:: prepended to their names (this should
remind you of std::cout and std::endl).
- Compile and run it to make sure it works. Note that the
accessor functions can refer to the private variables directly.
You might think that
having accessor functions to set and get variables is just another
layer of bureaucracy. The great advantage, however, is that you
separate how data is stored from how it is used.
This enables you to modify how the data is stored without forcing
you to rewrite the code that uses the data. You can change these
aspects independently. This is valuable, especially in a large
code or to enable reusable parts.
- Now make this consistent with the declaration in
List0602.cpp by adding a private member variable
itsWeight and the accessor member functions
SetWeight and GetWeight (i.e., write
the code for these and put them in the declaration
appropriately). Set Frisky's
weight in main and print out its weight (like was
done with its age).
- Try adding to main a call to Frisky.bark()
without defining this function. What error message do you get?
- Try accessing itsAge in main just as we did
before when this variable was public (i.e., via
Frisky.itsAge). What error message do you get?
- Next we consider "constructors" and "destructors". There are
functions associated with a given class that are invoked when an object
of that class is created and destroyed. An example to follow along
with is in List0604.cpp.
- The constructor is used to initialize the object, similar
to how we initialize an ordinary variable
(e.g., int radius = 1;). It has the same name as the
class (so in the example it is defined as Cat::Cat).
As you can see, it can take an argument. Add your variable and
functions for weight to this example, and change the constructor
so that it takes two arguments, the initial age and
weight. Modify main appropriately to try it out.
[Note: we didn't need to define a constructor before, because
a trivial one that doesn't take any arguments is defined by
default.]
- The destructor is used when an object is deleted. We'll see
how to take advantage of this
in some future week, so for now the destructor won't do anything.
A destructor always comes with a ~ before the class name. Find it
in the example.
- Let's pause (no pun intended) and do a debugging exercise.
Download and try to compile List0605.cpp. You'll find several
errors that you have to diagnose and fix. One of them involves a
member function defined as const. You should add
const to the (end of the)
declaration of any member function that does not change the value
of any member variables. This is not required, but is good programming
practice that can catch dangerous bugs.
- Let's talk about header files. The standard approach is to put
the declarations in a header file ending in .hpp and the
class definitions in a file ending in .cpp, both starting
with the class name. Take a look at Cat.hpp and
Cat.cpp. The header file is included with the
#include "Cat.hpp"
line. The use of ""'s rather than <>'s with
the include statement tells C++ to look in the current
directory for the file. In general, we'll want the main
function and any other functions not in the Cat class to be in a
separate file (or files) from Cat.cpp. This will require us
to learn how to build a Dev-C++ project (later!).
- Ok, it's time to plan out a bigger example, which involves building
up a complicated class by starting with simpler classes and including them
when you declare the more complex class. Imagine we want to manipulate
rectangles, which are defined in terms of x-y coordinates. So design
a class Rectangle, which uses the class Point.
[When you get stuck, take a look at Rectangle.hpp and
Rectangle.cpp, which are possible solutions.]
- Start with the class Point, which uses the default constructor
and destructor. A Point object has an x and a y coordinate, which
will be private member variables (give them appropriate names).
What accessor functions do you need?
What does your class definition look like? [If you don't type fast,
just sketch it out on a piece of paper and check your answer
against the examples.]
- Now try to design the class Rectangle, which is initialized
with integers for the y-coordinate of the top, the x-coordinate
of the left, the y-coordinate of the bottom, and the x-coordinate
of the right side. The private member variables will be
these coordinates and also Point objects for each of the corners.
What member functions will the Rectangle class need?
(In the answer here, there are eight "get" methods and eight "set"
methods.)
-
What should the constructor look like? You are very likely stuck
to be stuck by this point.
Look at the example and try to step through the
whole thing. Once you have worked
through several complex examples, you'll be
surprised how easy it is to build up a class.
- The only non-accessor function is GetArea, which
calculates the area of a given rectangle. Write a method called
GetPerimeter that calculates the perimeter of a rectangle.
Try it out!
- Here are
some of the Day 6 Quiz questions to try
(answers are in the handout from Session 1):
- What is the dot operator and what is it used for?
- What is the difference between public and private data members?
- Can member functions be private?
- Can member data be public?
- If you declare two Cat objects, can they have
different values in their itsAge member data?
- Do class declarations end with a semicolon? Do class method
definitions?
- What function is called to initialize a class?
- Exercise: Look at Employee.hpp and Employee.cpp.
Change the Employee class so that you can initialize
itsAge, itsYearsOfService, and itsSalary
when you create an employee. Try it out with the numbers used
in main (so by initializing the two employees this way you
should eliminate six calls to accessor functions).
- Exercise: Look at Ex0608.cpp. What three bugs in
the code should the compiler find? Answer first, then compile, then
fix it.
Day 7: More on Program Flow
This Day focuses on loops and alternatives to if ... else
statements.
You can find more information about these things in the
online version of Day 7.
- A useful way to repeat a sequence of statements as long as some
condition is true is to use a while loop. The condition can
be any expression that evaluates to either true or false. Take a look
at List0702.cpp as a first example. We want to evaluate a
print (cout)
statement 5 times, so we assign an int variable
called counter and increase it by one each time we print,
checking each time to see if it is less than 5.
Note carefully what the value of counter is just before exiting the
loop and what it is outside. Can you explain these values?
Try modifying the program so
that it counts up to 10 by 2's (so you have to use something besides
the counter++; statement).
- A more complicated example using while is given in
List0703.cpp. Check out the expression that is tested
for the while statement. The operator &&
is the logical "and" operator (so it returns true only if both
sides are true). When does the while loop stop?
Sometimes you want to "break out" of the while loop before
its condition is true. This is done with the break
statement, which kicks you immediately out of the while
loop. An example building on List0703.cpp
is given in List0704.cpp. Compile and run it, then check
that you can explain the result. Make it write a dot every 1000 lines.
What does the % operator do?
- The continue statement is used when you don't want to break out of
the while loop but you want to skip the rest of the statements
in the block being executed and just continue with the loop.
Look at List0704.cpp for an example. It's not important that
you master all of these details now, just that you are aware these options
exist. If you need them, look up a sample program like this or check a C++
reference. To check that you can follow what is going on in this
example, modify the
program so that rather than skipping every skip numbers, it
skips odd numbers and stops when large is less than 5.
- Sometimes you want the condition to be tested to appear after
the block of statements to be executed repeatedly. Then you want
a do while construction. Look at List0707.cpp for an
example. Change this example so that every time through the loop it
reads in an integer from the user (using cin) and prints out
the input number until the number 13 is entered, at which point the
loop should be exited and the program stops.
- A "for loop" is used to step through the value of some integer parameter
(which is called counter in the examples here) that keeps track of how
many times a block of statements (which should be enclosed in {}'s)
is executed. The basic form is given in List0709.cpp.
In the ()'s after for are three sets of statements,
separated by semicolons. The first one shows the start value
(counter = 0), the second one shows the test condition
(counter < 5 means to keep going as long as the value of
counter is less than 5), and the third one tells what to do
every time through the loop (counter++ means to increment
counter by 1 each time).
- In a previous session, you were asked to find Fibonacci numbers
by recursion. (Recall that the Fibonacci sequence is 1, 1, 2, 3, 5,
8, 13, ..., with each new number the sum of the two previous numbers.)
Devise a way to calculate the n'th Fibonacci number
using a for loop instead. Check your solution against
List0715.cpp.
- Our final new programming element is the switch statement,
which tells C++ to take different actions based on the value
of a test variable. An example is given in List0716.cpp.
In this example, number is the test variable. Each case
statement corresponds to a possible value of number (i.e.,
it can be 0, 5, 4, 3, 2, or 1). If it matches one of the listed values,
the statements following that case is executed (and if there is
a break statement the program exits the block marked by
{}'s). If it doesn't match one of the possibilities, the
statement following default: is executed.
-
To check that you get it, add a new case corresponding to 10, which
prints "You fool!" and then exits the block. Check that it works.
-
List0717.cpp shows how you can create a
simple menu interface using a for loop without arguments (this runs
until a break is encountered) and a switch statement.
Try adding a menu item labeled by 0.
- Exercise: What's wrong with this code:
for (int counter = 0; counter < 10; counter++);
{
cout << counter << " ";
}
- Exercise: What's wrong with this code:
int counter = 100;
while (counter < 10)
{
cout << "counter now: " << counter;
counter--;
}
C++ Seminar: 1094 Session 4.
Last modified: 07:31 pm, November 11, 2006.
furnstahl.1@osu.edu