Professional Documents
Culture Documents
An Introduction to
Functions and Classes
John P. Baugh
This work is dedicated to my mother Ellen Arlene Baugh (née Phillips)
whose dedication and support are the major reason
I am where I am today.
9. Make sure that Console Application is selected and that you have Empty Project
checked
10. Click Finish
11. You should see the Solution Explorer showing your new Solution containing the
new Project
If you do not see Solution Explorer, go to View—>Solution Explorer
12. Now it’s time to create an actual code file. Right-click Source Files, go to Add—
>New Item
13. Make sure that C++ File (.cpp) is selected
If you were working with classes and wanted a header file, you’d select
Header File (.h)
14. Give the file a good name – I’ve chosen main.cpp
Usually, you can leave the file’s location alone – Visual Studio will put the
file within the project folder automatically
15. Click the Add button
16. You should now see the C++ file you just created under Source Files in your
project
17. You should be able to add code in the source code editor portion of the workspace
18. Add the code exactly as above. For your convenience, here it is in a text format:
#include <iostream>
using namespace std;
int main()
{
cout<<“Hello World!”<<endl;
return 0;
}
19. Now, we have C++ source code! We need to convert it to machine code that the
computer can understand. Let’s do this by going to Build—>Build Solution
Some editions of Visual Studio might have slightly different interface
options here
20. Once it is built, it can be run to test it. To do this, go to Debug—>Start without
Debugging
Start with Debugging is fine also
21. You should get output such as the following:
Easy, right? Congratulations, you’ve just written a very simple C++ program and run it in
Visual Studio! Hopefully if you were not familiar with Visual Studio that this at least gets
you started with the very basics.
Now, on to a different IDE!
Say “Hello World” using Code::Blocks
Now we’re going to take a look at the same process in Code::Blocks (also just called
CodeBlocks or Code Blocks) so that we can have a little bit of variety. However, you’ll
notice a lot of what we do will be the same, or at least very similar.
1. Start Code Blocks
2. Go to File —> New —> Project…
Sound familiar?
3. In the New from template dialog, select Console application
4. Click Go
5. If you get the following dialog, click Next
You can check “Skip this page next time” if you don’t want it to show again
6. Select C++ from the Please make a selection list
7. Click Next
8. You should give your project a useful title, and also select the location to put the
project in
9. Click Next and then click Finish
You can leave the default compiler (usually it’s the g++ compiler, which is
an open source compiler used on many different platforms.)
10. Under your workspace, you should see your new project, as well as a Sources
folder
11. If you expand the Sources node, you’ll see that they have already provided a Hello
World source file for you. Cool huh?!
If you needed additional source (or header) files, you’d just go to File —>
New File, and select the appropriate type, giving it a useful name and
location
Note that the full path is expected, so it’s best just to click the … (ellipsis)
button and give your new file a name in the Select filename dialog
12. To build your application, go to Build —> Build
13. To run the application, go to Build —> Run
Super easy, right?!
Most IDEs are fairly easy to get started with. Obviously some are more difficult than
others. However, Visual Studio is an industry-standard development environment for
Windows, and Code Blocks is used a lot in industry, academia, and for personal use on
many different platforms.
Chapter 2: A Quick and Dirty Review of C++
Fundamentals
2.1 Variables and Arithmetic Operators
In computer programming, a variable is simply a named location in memory
(RAM, typically) where data is stored. The data type of the variable is the type of data
that the variable can hold. Data types can include all sorts of types ranging from integers
(whole numbers), floating-point numbers, also called floats (real numbers – those that
can have decimal points), characters (single lexical symbols like single letters, special
symbols, etc.) and Boolean data (a variable which contains the value of either true or
false.) Additionally, in C++, we can create custom types, typically by making use of
classes, which we will see later on.
In order to use a variable, you must first declare the variable. This typically involves
stating a data type, followed by a name for the variable, formally called the variable’s
identifier. You can also store an initial value in the variable using the assignment
operator, the = symbol.
Let’s take a look at some code that assigns values to some variables.
1 #include <iostream>
2
3 using namespace std;
4
5 int main()
6 {
7 int myNum = 15;
8 float myPi = 3.14159;
9 char myFirstLetter = ‘J’;
10 bool isHappy = true;
11
12 return 0;
13 }
In the code above, we can see on lines 7-10, several variables being declared
andinitialized on the same line on which they are declared. On line 7, we declare
an int named myNum . Its value is 15. On line 8, we declare a float named myPi and set
its value to 3.14159. Then, we declare a character variable named myFirstLetter and set
its value to be the character ‘J’. Finally, on line 10, we declare a Boolean
variable, isHappy , and set its value to true .
Note that we can declare and initialize variables on separate lines.
For example:
int myNum;
myNum= 15 ;
We can apply various arithmetic operators to variables and values in order to obtain
some sort of result. The standard arithmetic operators in C++ are the following:
* Multiplication int a = 4;
operator int b = a * 3; //b = 12
Note that we can also use compound operators to quickly do any of the above operations
to a variable and some other operand and store the value back into the left-hand variable.
For example,
int a = 150;
a += 50; //a now contains 200, because this is the same as a = a + 50;
a *= 2; //a is now 400
a /= 5; //a is now 80
a %= 3; //a is now 2, since 80 / 3 = 26 (the quotient), and the remainder is 2.
The typical order of operations (PEMDAS), with an additional M for Modulus
(PEMMDAS) applies in C++ just as it does in mathematics.
As a note of terminology, the “things” on either side of an operator are called operands.
So, if we have the following statement:
5 + 12
We would say that the + symbol (the addition operator) has two operands: the 5 and the
12. Note that any operator with two operands is called a binary operator. There are
unary operators in C++ as well, such as:
int a = 5;
a++; //a is now 6
a++; //a is now 7
a—; //a is now 6
2.2 Relational and Logical Operators
We are not limited to simply doing arithmetic. We also have ways of writing
programs so that they can make decisions, which are very useful, particularly with the
control structures we will see in just a little bit.
The relational operators are as follows:
The relational operators are all binary operators (they take two operands.) The types of
the operands are frequently numeric, but they do not have to be. C++ allows operator
overloading, which means that the operators are often defined to work with many different
types – including objects that are created from classes.
The logical operators are as follows:
Symbol Name Example
The if-else statement allows you to select to execute one statement (or block) or another
explicitly. This is why the if-else statement is often described as a double-selection
control statement. Note that you can chain or nest if-else statements as needed to
simulate selecting among multiple alternatives.
switch(someInteger)
{
case 10:
//do something specific if its value is 10
break;
case 15:
//do something specific if its value is 15
break;
default:
//by default, do whatever is here
}
Repetition structure allows the program to repeat over and over again until a condition is
met. This condition is called the loop continuation condition.
The repetition control statements available in C++ are:
while
do-while
for
Note that since the C++ 11 standard, there is also a special variation of the for loop called
the range-based for loop. This is outside the scope of our discussion and necessity for
this review. Just be aware that if you need to iterate over one of many kinds of collections
of data (called data structures), then you can use the range-based for loops in many of
these situations.
The while loop is structured so that it tests its loop continuation condition first, and if the
condition is true, the body of the loop is executed. Then, the condition is checked again,
and so on. Since it checks its loop continuation condition before executing the body of the
loop, a while loop is classified as a pre-test loop.
Note that if we do not increment the counter (assuming a counter-controlled loop like the
following), the loop will never terminate – at least until you (or your operating system)
force the program to close. This type of look is called an infinite loop, because
algorithmically and theoretically it would continue for eternity without outside
intervention.
int count = 0;
while(count < 10)
{
//do something 10 times
count++; //we MUST move toward termination of the loop
}
The do-while loop is the only post-test loop in C++. It is the reckless younger sibling of
the while and for loops. It executes the code in its body and then checks the loop
continuation condition. That’s why it is classified as post-test.
int count = 0;
do
{
//do something 10 times
count++;
}
while(count < 10);
Note that after the while near the bottom of the do-while loop, there is a semicolon. This
is required.
The for loop is a pre-test loop like the while loop, and it is excellent for loops that require
counter-controlled repetition, because the counter initialization, loop continuation
condition check, and the modification of the counter variable are all done in the header of
the loop.
2.4 Closing Comments and Summary for our Quick and Dirty C++
Review
Even if you’ve been studying C++ for a couple semesters – or even many years –
you might find some of the terminology and classifications in this chapter overwhelming.
Terminology is important. During the past 10 years teaching computer science and
software engineering at various levels, I have had many students come back to me and tell
me that they were able to impress their interviewers, more effectively communicate with
their co-workers, and overall, understand programming better because of knowing the
terminology.
Granted, just knowing definitions does not make you a good programmer automatically.
However, knowing terms like control structure, iterative control, selection control, post-
test loop, pre-test loop, arithmetic operator, relational operator, logical operator, and
many others can help you more fully grasp the language. Also, knowing these terms will
help you become more prepared to read other literature on your own to become a better
developer in many different languages. Don’t let the terms overwhelm you – learn them –
even at a high-level or basic level. Be able to classify them and know what they apply to.
So, I encourage you to, at very least, know the following categories and terms:
Variable
Value
Data type
Control Structure
Three categories:
1. Sequential control structure
2. Selection control structure
if
if-else
switch
3. Repetition/Iterative control structure
while
do-while
for
Post-test vs pre-test loops
Operators and Operands
Arithmetic operators
Relational operators
Logical operators
If you are comfortable with the above terms, can list any of the operators that you’re asked
about (e.g., if I say “What are all the relational operators? What are they used for?”), you
should be able to do well in the subsequent parts of this book.
Chapter 3: Functions
3.1 Function Concepts
In this chapter, we’ll start our journey into functions, which are self-contained,
reusable, callable blocks of code. They are self-contained in that they are given a name
and other information and contain executable code within their associate function code
block. They are reusable and callable in that they promote code reusability by allowing
the programmer (yourself or someone else) to call them (invoke them) wherever they are
needed.
If some of these concepts are a little weak right now, don’t worry. Hopefully they will
make sense in just a bit. Like variables, functions have a type. Specifically, they have a
return type, which is the data type that the function returns or generates in place of a call
to itself. Additionally, you can send information using parameter passing. We will take a
look at that in just a moment.
The general form of a function is as follows:
returnType functionIdentifier([parameters])
{
//do something here
//if it’s value-returning, return a value here
}
Note that the parentheses do appear in the header of the function. However, the square
brackets are there surrounding the word “parameters” to indicate that the parameters are
optional.
3.2 Return Values and Parameters
Functions can be classified in multiple ways. Two popular ways of classifying a
function is by its return type and by its parameters (or lack thereof.)
Return Type Classification
In terms of return type, a function may be classified as one of two kinds:
void function
value-returning function
A void function is a function that performs some tasks and then when it is done, it simply
stops. Its function call (also called function invocation) is not replaced by a value. Let’s
consider the following void function:
void DoSomething()
{
cout<<“In the DoSomething Function!”<<endl;
}
Our DoSomething function starts with the C++ keyword void , because it is… a void
function. No surprise there! This indicates that when the function is called, it will not
leave a value in its place when it’s done performing its tasks. It simply performs its tasks,
and then stops.
In the context of a program, you could write the function and call it like in the following
example (notice I said could – there’s a better, cleaner way that I’ll show you in a
moment.)
1 #include <iostream>
2 using namespace std;
3
4 void DoSomething()
5 {
6 cout<<“In the DoSomething Function!”<<endl;
7 }
8
9 int main()
10 {
11 DoSomething();
12
13 cout<<“In main!”<<endl;
14
15 DoSomething();
16
17 return 0;
18 }
This particular output comes from Visual Studio (which is why it says “Press any key to
continue …” after the code output.) Your output after the program is complete might be
different.
Notice that, as we expect, the call to DoSomething on line 11 causes “In the DoSomething
Function!” to be printed once. Then, on line 13, we code a cout statement to just print out
“In main!”. Finally, on line 15, we make another call to DoSomething (causing “In the
DoSomething Function!” to print again), before the return statement on line 17.
Now, I mentioned that there is a better way to work with functions. Although the code
above is syntactically fine (it adheres to C++’s grammar) and the tabbing and formatting is
reasonable and it’s easy to read, there is a coding convention that in a C++ program, the
main function should be the first function in a program file to be defined, that is, to have
its header and body shown.
So, the better way of coding the exact same program is to use a function
prototypebefore main ’s definition. Then, put the definition of the DoSomething
function below main , as follows:
1 #include <iostream>
2 using namespace std;
3
4 void DoSomething();
5
6 int main()
7 {
8 DoSomething();
9
10 cout<<“In main!”<<endl;
11
12 DoSomething();
13
14 return 0;
15 }
16
17 void DoSomething()
18 {
19 cout<<“In the DoSomething Function!”<<endl;
20 }
1 #include <iostream>
2 using namespace std;
3
4 int GetMyNumber();
5
6 int main()
7 {
8
9 int myInteger = GetMyNumber();
10
11 cout<<“My number is: ” <<myInteger<<endl;
12 cout<<“Another call without using a variable: “<<GetMyNumber()<<endl;
13
14 return 0;
15 }
16
17 int GetMyNumber()
18 {
19 int someNum = 5;
20
21 return someNum;
22 }
23
The functions we have seen thus far (in the previous subsection) were parameterless, that
is, they had no parameters between their parentheses. This means that these particular
functions do not require any additional information from the calling function, that is, from
where they were called in the code. For example, the main function
called GetMyNumber function in two locations in the most recent code sample. So, the main
function is called the calling functionwhereas the GetMyNumber function is sometimes
referred to as the called (or invoked) function.
A parameterized function is a function that has parameters, or variable placeholders for
values that are passed in. In other words, a parameterized function requires additional
information from the calling function.
Let’s take a look at a simple example. Consider a function whose purpose is to calculate
the square of an integer that is passed in, and return the squared value as a result.
So in this case, we have a value-returning function that takes a single parameter:
1 #include <iostream>
2 using namespace std;
3
4 int Square(int num);
5
6 int main()
7 {
8
9 cout<<“The Square of 5 is “<<Square(5)<<endl;
10
11 cout<<“The Square of 15 is “<<Square(15)<<endl;
12
13 cout<<“The Square of 2 is “<<Square(2)<<endl;
14
15 return 0;
16 }
17
18 int Square(int num)
19 {
20 return num * num;
21 }
A value-returning function that takes a single parameter, and returns the square of it
Note that the function prototype on line 4 not only has a value ( int ) as its return type, but
it also takes an integer as a parameter. As an aside, the name of the integer parameter in
the function prototype does not need to match the name of the parameter in the definition
(lines 18-21, in our case.) In fact, in the function prototype, the identifier is optional. In
other words, we could have just written:
int Square(int);
The definition however requires the identifier as well as the data type. We actually call
the Square function on lines 9, 11, and 13 in the above code sample. However, notice that
the data we pass into the function is different on each of these lines. For example, the
value 5 is passed in on line 9, so that, as we see in the definition, the parameter
variable num in the header for the definition of the Square function (line 18) will hold that
value while its body is executed. In this case, we only use one line of code, on line 20.
This simply takes the value of num and multiplies it by itself ( num * num ) and returns that
value. As you know, the square of a number is produced by taking the value of that
number and multiplying it by itself.
The output of the code above is as follows:
The Square of 5 is 25
The Square of 15 is 225
The Square of 2 is 4
Press any key to continue …
Note that, given different values being passed in, the results are different as well. Think of
Square as a machine. You feed it some sort of raw material (the parameter), and it
produces something out of that raw material (the return value.)
As a matter of terminology to commit to memory for the purpose of professional
communication (and to sound really smart at parties full of programming nerds), the
actual value (or variable) that you pass in when you call the function is called the
argument. The placeholder for that value, in the definition of the function is what is
actually called the parameter.
For example, we’d say that, in our code from earlier, the values 5, 15, and 2 are the
arguments to the three function calls to Square. The variable num is the parameter.
Sometimes you might hear arguments called actual parameters and what I call
parameters referred to as formal parameters. However, that can cause even more
confusion, so I generally prefer using the terms “argument” and “parameter” instead.
Note that some programmers, professors, and authors use these terms interchangeably
sometimes. But just remember that there is a distinction and that distinction could be
useful in various situations, not the least of which might be a job interview. In fact, I have
had students tell me that they were able to impress their interviewers with how they
naturally and fluidly used terminology related to programming like what we’ve covered
thus far (and will cover.) So, it does matter!
Passing by value vs Passing by reference
Another distinction that must be made when dealing with passing parameters is whether or
not the parameters are pass-by-value or pass-by-reference.
You can think of pass-by-value, which is what we’ve done thus far, as pass-by-copy. A
copy of the variable (or literal value) that you pass in is made, stored in the parameter, and
used in the function. If the value of the parameter changes inside the function, it does not
affect the variable or value that was passed in when the function was called. Think of
making a photocopy of an important assignment and leaving it on the dining room table.
Your little brother crawls up onto one of the dining room chairs and proceeds to practice
his skills as a budding artist using crayons all over your photocopy. This would not affect
the original from which you made the copy – only the copy is modified. Thus it is with
pass-by-value.
Variables all have memory addresses. With pass-by-value (the default mode of parameter
passing), as we discussed in the preceding paragraph, a copy of the variable or value is
made and stored in a variable (the parameter.) The parameter has its own memory
address, as would any variable you passed as an argument have itsown memory address.
For example, given our earlier Square function:
int someValue = 15;
Square(someValue);
//… Later on in the code
int Square(int num)
{
return num * num;
}
In this code sample, the variable someValue is passed as an argument, and num is the
parameter in the function definition. Since it is passed by value, the variable
some V alueand num have different memory addresses. When you modify one, it doesn’t
affect the other.
On the other hand, pass-by-reference can be thought of as pass-by-address. In this case,
the parameter will act as an alias for the variable in the calling function. The result is that
whatever you do to the parameter inside the body of the function to which it was passed
by reference will happen to the argument as well.
To indicate pass-by-reference in C++, you use the & symbol.
Let’s take a look at an example with two functions: one that passes by value and one that
passes by reference.
1 #include <iostream>
2 using namespace std;
3
4 void DoNotChange(int a);
5 void Change(int& a);
6
7 int main()
8 {
9 int myInteger = 20;
10
11 cout<<“Initially, myInteger is “<<myInteger<<endl;
12
13 DoNotChange(myInteger);
14
15 cout<<“After calling DoNotChange, myInteger is “<<myInteger<<endl;
16
17 Change(myInteger);
18
19 cout<<“After calling Change, myInteger is “<<myInteger<<endl;
20
21
22 return 0;
23 }
24
25 void DoNotChange(int a)
26 {
27 a = 150;
28 }
29
30 void Change(int& a)
31 {
32 a = 150;
33 }
Initially, myInteger is 20
After calling DoNotChange, myInteger is 20
After calling Change, myInteger is 150
Press any key to continue …
Notice that even after our call to DoNotChange , the value of the argument ( myInteger ) remains
20. It was not changed to 150 inside the DoNotChange function on line 27. Remember that
in a pass-by-value function like DoNotChange , the parameter (a, in this case) gets its own
memory address and simply gets a copyof the value of whatever was passed in. In this
case, upon entering the function DoNotChange , a hasa value of 20 (copied from
the myInteger variable on line 13 from inside main .) Then, this parameter is assigned the
value 150. However, that parameter’s memory goes out of scope as soon as the function
ends. Therefore, there was never any effect on the argument, myInteger .
However, after the call to Change , the value of myInteger is now 150. This is
because Change ’s parameter a ispass-by-reference. Therefore, the parameter a (of type
integer reference, that is, int& ) is an aliasfor myInteger . So anything that happens to it (such
as its value being changed on line 32) happens to the original argument as well.
3.3 More Examples and other Function Topics
Let’s take a look at a couple more examples just so we can solidify our understanding a
bit.
Multiple Parameters
We aren’t limited to only using one parameter! To use more than one parameter, simply
separate the parameters by commas. You can have many different types, and even
different passing modes (i.e., some parameters can be pass-by-value while others are pass-
by-reference.)
Let’s see an example.
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 void PrintNameAndAge(string name, int age);
6
7 int main()
8 {
9 string name;
10 int age;
11
12 cout<<“Please enter your full name.”<<endl;
13 getline(cin, name); //if a single string, cin>>name; would suffice
14
15 cout<<“Now, enter your age.”<<endl;
16 cin>>age;
17 cout<<endl<<endl; //extra spacing
18
19 PrintNameAndAge(name, age);
20
21 return 0;
22 }
23
24
25 //–––-| Print the Name and Age |––––-//
26 void PrintNameAndAge(string name, int age)
27 {
28 cout<<“Hello, “<<name<<endl;
29 cout<<“You are “<<age<<” years old.”<<endl;
30 }
I have bolded the user input in the above output/user interaction so you can see where the
user typed something from the keyboard.
Function Overloading
In C++, unlike C, you can multiple functions with the same name as long as they have
different signatures. The signature of a function includes its name (identifier) and its
parameter list. It does not include the return type. Having multiple functions with the
same name (but different signatures) is called function overloading.
Let’s see an example with function overloading.
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 int Multiply(int num1, int num2);
6 int Multiply(int num1, int num2, int num3);
7
8
9 int main()
10 {
11 cout<<“2 * 3 = “<<Multiply(2, 3)<<endl;
12
13 cout<<“2 * 3 * 4 = “<<Multiply(2, 3, 4)<<endl;
14
15 return 0;
16 }
17
18 int Multiply(int num1, int num2)
19 {
20 return num1 * num2;
21 }
22
23 int Multiply(int num1, int num2, int num3)
24 {
25 return num1 * num2 * num3;
26 }
The Rectangle.h file is the specification file for our Rectangle class. Notice first on lines
1, 2, and 19 you have some peculiar syntax that doesn’t look too much like C++.
Although not always necessary, it is good programming practice to use these so-called
include guards to prevent file conflict in larger applications – where the preprocessor
attempts to include the same file multiple times, ending with tons of conflicts and other
problems.
So, the general syntax is just to first use an #ifndef (if not defined) followed by some
variable name you make up – by convention, you name it the same as the .h file you’re in
except in all caps and using an underscore instead of a dot (.). So in our case, our special
include guard macro variable is RECTANGLE_H . Then, if it makes it past
the #ifndefRECTANGLE_H (in other words, if there isn’t a symbol with that name), then you
define a symbol with that name with the statement #define RECTANGLE_H . Then, you put the
class specification, and on line 19, you designate the end of the block of code that the
macro, preprocessor ifndef applies to. These are very similar to if statements in C++, but
are read by the preprocessor before the compiler is called on the code. If you don’t totally
understand include guards, don’t sweat it! Just use the syntax verbatim, just remembering
to change the include guard macro variable name to the appropriate name of your own
class. For example, if you had a Dog class, you’d probably want to name the
variable DOG _H.
Now, to the actual C++ code. Let’s start on line 4 where the class header is. You just use
the keyword class, and follow it by the name of the class. In our case, this is Rectangle .
Note that the end of the class declaration, on line 17 ends not only with a curly brace, but
also has a semicolon (;). This is not optional in C++. In fact, if you leave it out, you will
get a compiler error (or errors.)
Zooming in just a little bit, let’s take a look at lines 6 and 14, each of which seem to
designate the beginning of some sort of sectionof code. The keyword public indicates that
anything in that section is visible (accessible) outside the class. The private keyword
indicates that anything in that section is accessible onlyto the class itself. Using the private
keyword to protect data is known as information hiding, which is a big part of one of the
three pillars of object-oriented programming, namely, encapsulation.
On lines 7-12 you see the public member functions. Although data can be public also, it
is most common to have public functions and private data (although, likewise, functions
can be private too!) This is typically considered good programming style and good
software engineering practice.
We’ll come back to line 7 because it’s somewhat special. But lines 8-12 should look
pretty familiar. That’s right! They’re just function prototypes. The only slight difference
being the use of the const keyword after getWidth , getLength , and getArea . In the context of
function headers (and their prototypes) in a class, the const keyword means that the
function will not modify any of the internal data members. In other words, these functions
are for obtaining or accessingdata – they will not modify the data in the class.
E.g., getWidth won’t set the private data member width to another value. If you try to do
something like that, you will get a compiler error. While this might seem restrictive, it is
good programming practice to label any member functions that aren’t supposed to modify
data members as const.
Line 7 is a little peculiar. It looks like a function prototype but is missing a return type and
it has the same name as the class. This type of function, with the same name as the class
(and virtually any choice of parameters) is called a constructor, because it is called
automatically when an instance of the class (also called an object) is created.
Now for the private section. On line 15, I declare the width as an integer. I declare length as
an integer on line 16. These data members (attributes) will be specific to each object. So,
you can create multiple objects of the Rectangle type and they can have different values in
their specific, self-contained private data members.
So, we can see that our class will allow the user to get and set the width and length ,
respectively. Also, it allows the user to obtain the area of the Rectangle object.
You might wonder why we didn’t include area as a data member in the private section.
Typically, if you can derive a value from already-present data members, you don’t need to
(and usually shouldn’t) include that calculated value as another variable. Doing so would
also increase the possibility for forgetting to update the area every time
the length or width was changed, leading to an area that doesn’t match the length and width .
When this occurs, we would say that the variable area contains stale data. So it’s best to
avoid this scenario as much as possible.
So now we know what the class allows us to do with an object of its type. But we’re also
responsible for providing the code telling the class how the functions perform their jobs.
Let’s now move on and take a look at how the Rectangle class is actually implemented.
Rectangle.cpp – the Implementation File
1 #include “Rectangle.h”
2
3 Rectangle::Rectangle(int wid, int len)
4 {
5 width = wid;
6 length = len;
7 }
8
9 void Rectangle::setWidth(int wid)
10 {
11 width = wid;
12 }
13
14 void Rectangle::setLength(int len)
15 {
16 length = len;
17 }
18
19 int Rectangle::getWidth() const
20 {
21 return width;
22 }
23
24 int Rectangle::getLength() const
25 {
26 return length;
27 }
28
29 int Rectangle::getArea() const
30 {
31 return length * width;
32 }
In Rectangle.cpp, on line 1, we include the Rectangle.h file so that our implementation
file has access to the prototypes declared in the header file.
Then, you’ll note some very peculiar syntax that we haven’t really seen before. However,
this syntax is necessary so that the compiler can distinguish between member functions
and other functions (so-called global functions, which we looked at in the last chapter.)
Note that the syntax Rectangle:: is in front of the namesof each of the member functions of
the class. For the member functions that have a specified return type (including void),
the Rectangle:: comes after the return type but before the name. With the constructor (on line
3), there is no return type (not even void), so you just put the Rectangle:: in front of it.
This will always be the name of the class, followed by two colons, called the binary
scope resolution operator. Wow! That’s quite a mouth full. But again, it can help you
communicate effectively on the job and will make you the life of the party, if partying
with programmers is your thing.
The code itself for each of the member functions should be relatively straightforward.
Notice in both setWidth and setLength , a parameter is passed (by value) and then inside these
functions, the internal data members width and length are set to their corresponding
parameters.
For example, on line 11, we have width = wid; This sets the private data member, width , of a
particular object (instance) of the class to whatever value is passed in when
the setWidth function is called. The chosen parameter name in this case is wid .
You may wonder if it’s possible to just use the same name for the private data member and
the parameter for a given function. The answer is yes, but you have to take extra
precautions when doing so.
Let’s consider the following:
void Rectangle::setWidth(int width)
{
width = width;
}
We have a bit of a problem here. What does the variable width refer to in the body of the
code (width = width)? Is it referring to the parameter between the parentheses or the
private data member? It seems ambiguous.
The answer is the parameter shadowsthe name of the private data member. Therefore, the
private data member will not be touched, and this setWidth function would essentially be
made useless. Note that this code will compile. So it’s not a syntax error. It’s valid
syntax in C++.
The code above produces what we call a logic error – an error in which the code compiles
(no syntax/compiler error), the code doesn’t crash (no runtime/execution time error), but
just gives the wrong results – or behaves unexpectedly.
So, it’s hard to come up with new names for parameters when have perfectly good names
that we came up with for the private data member. So is there a way to overcome
shadowing so we can make use of the same name and still change the internal private data
member? The answer is of course, yes!
void Rectangle::setWidth(int width)
{
this->width = width;
}
You can use the this keyword, followed by the arrow member access operator, and the
name of the private data member you’re trying to access and distinguish from the
parameter name. The this keyword is actually a pointer(a variable that contains a memory
address) to whatever object is being acted upon at a given time. So you can read the code
as “this object’s width ( this->width ) should be assigned the value of the parameter
named width ”.
Obviously, you don’t have to go through all this trouble if you use a different name for the
parameter from the private data member. However, many programmers use the this
keyword to avoid having to come up with new names. Some use it even if the names are
different, so they can explicitly indicate that they are referring to a data member.
main.cpp – the Client Code (Driver File)
1 #include <iostream>
2 #include “Rectangle.h”
3 using namespace std;
4
5
6 int main()
7 {
8 Rectangle a(4, 5);
9 Rectangle b(10, 20);
10
11 cout << “Rectangle a’s area is: ” << a.getArea() << endl;
12 cout << “Rectangle b’s area is: ” << b.getArea() << endl;
13
14 cout<<endl;
15 cout<< “Currently, a’s width is: ” << a.getWidth() << endl;
16
17 a.setWidth(10);
18
19 cout<< “a’s new width is: ” << a.getWidth() << endl;
20 cout<< “a’s new area is: “<< a.getArea() <<endl;
21
22 return 0;
23 }
The output of this code is as follows:
You can see in the main.cpp client code that we must include Rectangle.h(you shouldn’t
have to include Rectangle.cpp.) Then, you can create instances of Rectangle such as is done
on lines 8 and 9. Rectangle a hasa width of 4 and height of 5 starting out, because we
wrote the constructor to set the internal width and length to the arguments 4 and 5,
respectively. Rectangle b has width 10 and length 20, as specified on line 9.
Then, we just print out the area of each rectangle on lines 11 and 12.
On line 15, we print out rectangle a ’swidth using the getWidth function. We then change
the internal data for the width using the setWidth function on line 17. Finally, we can see
through the printing done on lines 19 and 20 that the width (and of course, the calculated
area) are different now for a .
Let’s take a look at another example.
Dog class
In this subsection, we’re just going to create a class that represents a dog. I’ll provide less
explanation, but provide mostly just the code. Create the appropriate files just like we did
in the Rectangle class, and run the program.
Dog.h
#ifndef DOG_H
#define DOG_H
#include <string>
using namespace std;
class Dog
{
public:
Dog(); //no-arg / default constructor
Dog(string name, int age, int weight);
void setName(string n);
void setAge(int a);
void setWeight(int w);
string getName() const;
int getAge() const;
int getWeight() const;
private:
string name;
int age;
int weight;
};
#endif
This class has not one but two constructors. Since a constructor is a type of function, and
functions can be overloaded, so can constructors. There is a so-called no-argument
constructor (often just called a no-arg constructor) and a parameterized constructor with
three parameters.
A note about the no-arg constructor: many programmers call a constructor with no
arguments the default constructor. Some other programmers disagree and state that the
default constructor is a no-arg constructor, but is the one that is produced automatically by
the compiler if you don’t include any constructors whatsoever. In other words, if I didn’t
put any constructors, the compiler will provide one automatically. It wouldn’t really do
much of anything as far as setting internal data, but a class must have a constructor if it is
to be instantiated.
Dog.cpp
#include “Dog.h”
//no-arg / default constructor: no parameters
Dog::Dog()
{
name = “Fido”;
age = 3;
weight = 30;
}
Dog::Dog(string name, int age, int weight)
{
this->name = name;
this->age = age;
this->weight = weight;
}
void Dog::setName(string n)
{
name = n;
}
void Dog::setAge(int a)
{
age = a;
}
void Dog::setWeight(int w)
{
weight = w;
}
string Dog::getName() const
{
return name;
}
int Dog::getAge() const
{
return age;
}
int Dog::getWeight() const
{
return weight;
}
main.cpp
#include <iostream>
#include “Dog.h”
using namespace std;
int main()
{
Dog myDog; //calls default constructor
Dog yourDog(“Rover”, 4, 120);
cout << “My dog’s name is ” << myDog.getName() << endl;
myDog.setName(“Beethoven”);
cout << “Now, my dog’s name is ” <<myDog.getName() << endl;
cout << “He weighs ” << myDog.getWeight() << ” lbs and is ”
<< myDog.getAge() << ” years old.” << endl;
cout<<endl<<endl;
cout<<“Your dog’s name is ” << yourDog.getName() << endl;
cout << “He weighs ” << yourDog.getWeight() <<” lbs and is ”
<< yourDog.getAge() << ” years old.” << endl;
return 0;
}
The output is:
Looks pretty good. But what if someone uses your Dog class in an unexpected way. Say
they try (or accidentally try) to set the dog’s age to a negative number. Can you prevent
that? Yes! As the developer of the interface which the client code uses to create and work
with classes, you can prevent the outside world from entering invalid data.
How might you fix the setAge function? Using if-statements is one way.(Note that to
get cerr or cout to work, you have to include iostream at the top of the implementation file.)
void Dog::setAge(int a)
{
if(age > 0)
{
age = a;
}
else
{
cerr << “Error : invalid age attempt!” <<endl;
cerr << “Setting to default of 3” <<endl;
age = 3;
}
}
See if you can make a better Dog class. I’ve fixed one function. See if you can come up
with ways to fix the others, or make everything more robust, that is, less prone to error or
invalid data. In fact, there is a much better way to handle invalid data instead of just
sending errors to the standard output device or the standard error device. You can use
exception handling with try-catch statements. This is outside the scope of this book’s
purpose, but if you are familiar with exception handling, see if you can get your class to
communicate with the “outside world” through exception handling when errors or
problems arise.
4.3 The Three Pillars of Object-Oriented Programming
It is good to at least conceptually understand object-oriented programming (OOP)
concepts even if you’re not going to become an expert, at least with this book alone. You
should commit to memory the names of the three pillars of OOP:
1. Encapsulation
2. Inheritance
3. Polymorphism
Say them over and over in your mind until you start having dreams about the words.
Briefly, we’ve actually seen encapsulation so far. This is the most fundamental building
block of OOP. The class in C++ is how we encapsulate attributes and operations
together. Even the name suggests you’re taking a bunch of “stuff” (attributes and
behaviors) and putting them in a capsule (en-capsule-ating them.) The purpose of
encapsulation is to allow the programmer (yourself or others) to treat a bunch of data and
behaviors as being related, rather than just thrown all over the place and held together with
just naming conventions and wishful hope.
Encapsulation allows you to treat a bunch of “stuff” as one entity. We can treat a width
and a length, along with a bunch of behaviors for getting and setting the data and
calculating the area as a single unit – a Rectangle object. We can take a name, age, and
weight and corresponding operations for setting and getting data (even protecting the data)
and combine them into a single entity – a Dog object.
The second pillar, inheritance, is outside the scope of this book as far as actual
implementation details (although if the demand proves high enough, I will gladly create a
book on OOP in C++.) However, inheritance allows you to take a class that’s already
made that is similar to what you need, and to define another class based on the original
class.
Consider having an Animal class that has things like a name , age , weight , height , and the
ability to make a noise. Then you can take the generic functionality and data of the Animal ,
and inherit from it. Dog s, Cat s, Horse s, Cow s, Penguin s, and Narwhal s are all Animal s. They
will specialize the very general Animal class, but you wouldn’t have to re-implement all the
code that they all have in common. Therefore, we can look at inheritance as a form of
software reuse. Terminology may help you understand other books and articles on the
subject: typically the Animal class would be called the base class, parent class, or
superclass. The Dog , Cat , etc. would be called derived classes, children classes
(singular: child class) or subclasses. Although these terms are interchangeable, you
typically hear the terms “base class” and “derived class” being used in C++ programming
circles (also, C# programming circles.) Java programmers usually use the terms
“superclass” and “subclass” to denote the parent/child relationship. We say that
inheritance denotes an is-arelationship. So, Cat is-an Animal . Dog is-an Animal . Etc.
Arguably the most complex of the pillars is polymorphism. It relies on inheritance in
order to understand it. So it’s even more difficult to describe without going into a great
amount of detail on inheritance first. In C++, it also is used very frequently with pointers,
which we did not cover in this book. However, conceptually you should just understand
that it allows us to dynamically (at runtime) determine which function should be called,
given a variable of a base type.
So, because of the is-arelationship in inheritance, we can actually store an object (or
pointer to / address of an object) of type Cat, for instance, inside an Animal pointer
reference variable. Then, when you call makeNoise on the Animal variable, if you’re using
polymorphism (through use of the virtual keyword), the Cat’s version of makeNoise will be
called instead ofthe Animal ’s version.
Again, this is outside the scope of this book. But stay tuned, and e-mail me if you liked
this book and I will produce more!
4.4 Comments on Classes and OOP
Hopefully, this chapter gave you at least a reasonably solid foundation or a good
review of the concepts related to classes. It was in no means intended to be a full-fledged
chapter with dozens of different classes. It should be just enough to get your feet wet for
the first time, or again.
CLOSING REMARKS
Hopefully this book was useful to you. I encourage you to read it, tell your
friends about it, and e-mail me and let me know what you think.
I haven’t self-published in a while, and I don’t claim that this book will be error free. But
I tried to make it as awesome and clean as possible. I am hoping to publish some more
books on other topics in the near future – so let me know what topics, programming
languages, technologies, or general information you’d like to read from me!
Contact Information
My e-mail: profjpbaugh@gmail.com
My YouTube Channel: https://www.youtube.com/user/profjpbaugh
My Website, where you might just find Errata, Supplemental information, and other
goodies:
http://www.profjpbaugh.com