CS107 Study Guide

Site: Saylor Academy
Course: CS107: C++ Programming
Book: CS107 Study Guide
Printed by: Guest user
Date: Friday, April 26, 2024, 1:41 PM

Navigating this Study Guide

Study Guide Structure

In this study guide, the sections in each unit (1a., 1b., etc.) are the learning outcomes of that unit.

Beneath each learning outcome are:

  • questions for you to answer independently;
  • a brief summary of the learning outcome topic; 
  • and resources related to the learning outcome.

At the end of each unit, there is also a list of suggested vocabulary words.


How to Use this Study Guide

  1. Review the entire course by reading the learning outcome summaries and suggested resources.
  2. Test your understanding of the course information by answering questions related to each unit learning outcome and defining and memorizing the vocabulary words at the end of each unit.

By clicking on the gear button on the top right of the screen, you can print the study guide. Then you can make notes, highlight, and underline as you work.

Through reviewing and completing the study guide, you should gain a deeper understanding of each learning outcome in the course and be better prepared for the final exam!

Unit 1: Introduction and Setup

1a. Describe the basic history of C++

  • What is C++?
  • How did C++ emerge as a programming language?
  • What are some distinguishing features of the C++ programming language?

The history of computers is generally traced back to Charles Babbage who designed a mechanical computer called the Analytical Engine around 1837 (although it was never actually finished). It had its own set of computer instructions and the first known computer program was written for this system. The history of modern programming languages dates back to the 1940s when the first electronic digital computer, ENIAC was constructed. Over the next decade computation gained popularity and the first commercially available high level computer language FORTRAN was developed in 1956. Many programming paradigms such as procedural programming and object oriented programming also arose as computation became an integral part of society. Procedural programming uses procedures and functions in order to accomplish a given task. In object oriented programming, classes are defined using data attributes and methods that describe aspects of the problem being solved. Objects that apply class methods from the class definitions are then instantiated within a program.

C++ is a general-purpose programming language that supports procedural, object-oriented, and functional programming paradigms. Bjarne Stroustrup developed C++ during the 1980s to enhance the C programming language which was originally designed in order to develop the Unix operating system. Specifically, the C programming is a high level language that can interact with a computer system's hardware. Bjarne Stroustrup was inspired by the object oriented aspects of the language Simula. He therefore created C++ by adding object oriented capabilities similar to Simula such as class definitions, methods, object instantiation, and operator overloading to the C programming language.

C++ retains a lot of syntax from the C programming language and therefore can be used to interact at the low level with system hardware. On the object oriented side of programming, C++ has much in common with the Java programming language. This makes C++ a very versatile programming language with a range of applications from low level hardware implementations to higher level software application development.

To review, see Introduction to C++.


1b. Set up and create a simple C++ project using Eclipse CDT

  • What is Eclipse?
  • What is the most common form of a filename for a C++ source file?
  • What does it mean to build a project?

The Eclipse C++ Development Tool (CDT) is an integrated development environment (IDE) for writing, compiling, linking, building, and running C++ programs. The first level to Eclipse is simply a text editor that allows you to type C++ instructions into a source file. This text file is most commonly named with the file extension '.cpp' to indicate it is a C++ program. Within the IDE, you construct a project which houses C++ source code file(s) relevant to your project.

Once you are confident that you have completed writing the program within your source file, you can use Eclipse to compile the text file to low level object code that your computer can recognize. If there are several components to your project, Eclipse can link them together to form a complete executable file. This process of compiling and linking project components together to form an executable program is called 'building'. Once the build process is successful, the program can be run within the Eclipse CDT.

To review, see


1c. Explain the meaning of simple C++ commands

  • What does the include directive accomplish?
  • What is a namespace?
  • What is main?

Certain features are found in most C++ programs and will be applied over and over again. For instance, there are directives that tell a compiler where to find certain components referred to, but not contained, within your program. Under these circumstances the '#include' directive refers the compiler to libraries outside of your program. When invoked, the compiler will take code from the library and place it directly into your program. For example,

#include <iostream>

refers to a component of the C++ standard library necessary for basic input and output. Namespaces are used in C++ to avoid confusion when referring to functions with the same name from different sources. The using command then helps you to avoid having to use a namespace prefix to refer to a component of a library each time you want to apply the component. For example,

using namespace std;

avoids having to constantly use the prefix std::cout from the standard library when using the cout instruction for screen output.

The starting point of all C++ programs is the main function, a special function in all C++ programs. This function is called when a program is run. The execution of all C++ programs begins with the main function, independent of its location within the code. Typical syntax one sees for starting a C++ program might look like

int main()
{
#your program code goes here
}

The syntax for functions is covered in Unit 2. For now, you should just understand what syntax should be in place when starting to write a C++ program. Pay close attention to curly brackets { } which are used to enclose blocks of code.

To review, see Understanding the "Hello World" Program.


1d. Use cout and cin objects effectively

  • Why are the cout and cin commands important?
  • What syntax is used for invoking cout?
  • What syntax is used for invoking cin?

Fundamental to every programming language is the ability to perform input and output. The most basic form of input is keyboard input. Output usually comes in the form of screen output. In order to allow for keyboard input and screen output, the computer system must provide for interactions between various levels of hardware and the operating system. A high level language must simplify all these interactions for the user. C++ does this through the cout and cin commands.

With iostream included and the std namespace invoked, the syntax for input and output is greatly simplified. Screen output can be accomplished with a command such as

cout << "hello" ;

Keyboard input can be accomplished with a command such as

int age;
cin >> age ;

Given the vastness and object oriented nature of the standard library, there are many variations on this theme explored throughout the course.

To review, see Taking User Input.


1e. Declare and use variables

  • What is a variable?
  • What are some common variable types?
  • What is the difference between variable declaration and variable initialization?

A variable in a programming language holds and remembers a piece of data intended to be used within a program. The variable value can be changed and updated to a new value anytime a programmer chooses to do so. C++ requires that all variables be declared as a specific type so that the compiler knows how much memory to allocate to hold the variable data. Some common variable types applied at the introductory level are int (integer), float (single precision floating point), double (double precision floating point), and char (character data).

A variable can be declared, but not initialized to a specific value OR can be simultaneously declared and initialized. For example, the following commands separately declare and then initialize the variable x as an integer

int x;
x=5;

On the other hand, for the sake of brevity and flexibility, it is also possible to perform these operations with one command

int x=5;

The declaration tells the compiler to set aside the correct number of bytes to hold an integer. The initialization step places data defined by the programmer into the variable.

To review, see C++ Variables and Data Types.


1f. Use C++ operators

  • List some common C++ arithmetic operators.
  • How are arithmetic, relational and logical operators used?
  • What is the effect of the assignment operator?

One of the most basic applications of a computer is to perform arithmetic. All programming languages provide syntax similar to that found on most calculators. The most fundamental arithmetic operations are addition (+), subtraction (-), multiplication (*), and division (/). In addition, the modulus operator (%) is used for computing the remainder after integer division.

Upon making the following declarations/initializations:

float a=5.0;
float b=10.0;
int c = 5;
int d = 10;
float e,f;
int g;

You can immediately perform calculations similar to those of a simple calculator such as:

e = b/a;
f = a*b;
g = d % c;

The assignment operator (=) assigns whatever is computed on the right side of the equal sign to the variable on the left side of the equal sign. It is not an arithmetic operator, but it is used in tandem with arithmetic operators in order to assign results to a variable.

Relational and logical operators are of the utmost importance when applying programming control structures discussed in Unit 2. It is imperative to understand relational operator syntax (==, !=, >, >=, <, <=) in order to allow for comparisons between values. In addition, logical boolean operations (!, &&, ||) are necessary for constructing more complex logical expressions. The logical and (&&) between two boolean expressions can only evaluate to a value of 1 (true) when both expressions are true. The logical or (||) between two boolean expressions can only evaluate to a value of 0 (false) when both expressions are true. These two rules are instrumental when evaluating logical expressions containing several terms. For example, executing the following lines of code:

#include <iostream>
int main() {
int a=1;
int b=2;
int c=3;
int d=4;
bool j = (a>c) || (d<b);
std::cout << j << "\n";
}

would result in a screen output of 0. It is a good idea to practice with code structures like this one and try to predict the output when various relational and logical operators are mixed together.

To review, see Arithmetic Operators in C and C++ and C operators.


Unit 1 Vocabulary

This vocabulary list includes terms that students need to know to successfully complete the final exam for the course.

  • arithmetic operators
  • C++
  • high-level language
  • object-oriented programming
  • procedural programming
  • variable
  • char
  • cin
  • cout
  • float
  • include
  • int
  • main
  • namespace

Unit 2: Structuring Program Code

2a. Use conditional and iteration structures in C++

  • Why is the flow of control important in programming?
  • How are if-else statements applied in the flow of control?
  • Describe some features of constructing for, while and do-while loops.

A computer is different from a simple calculator because it can control the flow of a program. Computers can solve complex problems precisely because computer languages can make decisions. Flow control statements allow for decision making within a program and come in several basic forms such as if-else blocks and loops. if-else blocks are conditional structures and loops are iterative structures.

if-else blocks test expressions that depend upon logical conditions and relational comparisons. This is one major reason why you must review and master the Unit 1 material involving relational and logical operators. The switch statement is similar to a series of if-else clauses; however, the test condition is based upon a single value and test cases are chosen in response to the test value. A major characteristic of if-else and switch blocks is that only the first block of code encountered that satisfies the test condition will execute. This means that there will be blocks of code in a program that will not execute if a given condition is not satisfied. Such code blocks will be disregarded and program control will jump to the first instruction directly following the if-else clause.

Iteration is the process of performing a task over and over until some stopping condition is reached. Loops allow for a task to be performed several times (i.e. iterated) based upon a set of conditions. There is syntax for defining three types of loops in C++: the for loop, the while loop and the do-while loop. The for loop is often applied when one knows how many times a task must be performed; however, it can be used with logical constraints. The governing syntax involves a loop variable and requires three pieces of information:

  • A = Initial condition for the loop variable
  • B = Continue the loop based upon a condition involving the loop variable
  • C = How to update the loop variable after the completion of an iteration

and is of the form:

for (A; B; C)

The while loop tests a boolean condition at the beginning of a loop. If the condition evaluates to a value of true, another iteration will take place. Iterations will continue until the boolean condition at the beginning of the loop evaluates to false. Once the test fails, program control will jump to the instruction directly following the while loop. The do-while loop is similar to the while loop except that the boolean condition is checked at the end of the loop, and the loop is executed at least once.

To review, see 


2b. Explain the purpose of different features of the debugging tool

  • Why are debuggers necessary?
  • What are some basic operations needed for a useful debugger?
  • How does Eclipse implement basic debugging operations?

Program debugging skills are fundamental and indispensable to any programmer's repertoire or skillset. At the beginner level, you should never attempt to write a long program without testing small sections of code to ensure proper operation. This way, you can easily isolate program errors.

Debugger tools help programmers isolate program errors through a series of basic operations that allow the user to run and test single instructions as well as blocks of code. When testing a block of code, it is common to define a breakpoint in order to stop the debugger at a specific point in the program. In this way, variable values can be checked to ensure correct program execution has occurred. Debuggers can therefore be instructed to operate in different modes that make the debugging process convenient for the user. Common debugger modes allow for important tasks such as (1) single stepping through a set of instructions, (2) stepping into a block of code, (3) stepping over a block of code, (4) running blocks of code by defining a breakpoint for code execution, and (5) allowing a programmer to edit/update/modify variable values in order to test different conditions. These are tasks that should be mastered within any debugger.

Operations and tasks within a debugger are done using various directives, menu choices, control keys, and function keys. For example, using Eclipse, to enter the debugging mode you would choose the debug as mode (as opposed to run as mode). Using the detail formatter, it is possible to choose which and how variables are to be displayed in order to inspect their values. A function key such as F6 will allow you to step over a line (as opposed to F5 which will step into a line). The Step Return feature is also useful in the case where you want to execute a function/method, but do not want to step through the function/method. Finally, be aware of the meaning of debugger user interface graphics that indicate important markings such as breakpoints.

To review, see Debugging in Eclipse.


2c. Define test cases and coverage analysis

  • What is a 'Test Development Responsibility' flow diagram?
  • What is coverage analysis?
  • What are some strategies for testing software?

An often overlooked subject at the introductory level involves software engineering practices for testing and verification. However, it is important to incorporate these practices in the early stages, in order to objectively and quantitatively define when your program 'works'. In the field of software engineering as well as through the creation of industry standards and best practices, much effort has gone into rigorously defining how testing of small units should build up and be integrated in larger units. The generation, testing, and verification of large pieces of software must follow a well-defined pattern that can be objectively applied over and over again. When large units must be integrated to form a final product, it is important to review the components of a test development responsibility flow diagram. It allows you to know at what point each piece of the software design puzzle will fit together.

When it comes to testing and verification, coverage analysis involves analyzing the design of test cases covering all possible potential sources of error (e.g. user input, unacceptable variable ranges, program termination dependencies) as well as expected output. The main purpose of coverage analysis is to verify the comprehensiveness of a set of test cases. Coverage analysis does this by executing tests which record the complete execution path through the code and then applying metrics showing the coverage achieved during execution.

Common software components for designing test cases involve the use of if-else clauses and switch statements that attempt to encompass all possibilities for a given set of variables. In addition, you can use tools from the LSST project libraries such as scons to perform coverage analysis in C++ that produce coverage reports. For instance, gcov is included with GNU C/C++ compilers. Other options for generating reports include ggcov (which uses its own analysis engine within a GTK) and tggcov (a non-GUI version of ggcov).

To review, see Software Unit Test Policy and Coverage Analysis.


2d. Use simple functions

  • What is the syntax for defining and calling a function?
  • What is pass by value?
  • What is pass by reference?

Functions are useful for defining sections of code that are intended to be reused throughout a program. Rather than reproduce a set of instructions over and over again, it is more sensible to define a function containing those instructions that can be called within the program. The syntax for defining a function follows the pattern:

output variable type function name(input variable data){ function code}

Exactly one variable can be output and returned from the function. The output variable type must be specified and can be any valid type such as void, int, float, struct, a pointer, an array or a class. Obviously, the function name must be specified. When the function definition (rather than a function prototype) is used, the input variables are declared as the input variable data separated by commas. The input variable list contains variable declarations which must include both the type and the variable name having local scope within the function. On the calling side, the function name is used to call the function. Input variables must have the same order as specified in the function definition. In addition, input variables types must match those specified in the function definition or a compile error will occur.

When data is passed to a function, there are two basic possibilities. Either the function is given the power to modify an input variable sent from the calling program (pass by reference) or it is not (pass by value). Basic variable types such as int, float, and char are, by default, passed by value. This means the data input to the function can be used and modified; however, data modifications will not be seen by the calling program. In this case, effectively, a copy of the data is sent to the function so it can be operated upon.

If you want the data modified by the function to be seen by the calling program, it must be sent as pass by reference. This means that the address of the data must be sent to the function as an input so that the function can access this specific address. The most basic way of accomplishing this is to use the address operator &. Therefore, if you wanted to input some variable x of type int to a function using pass by reference, the function definition would include a declaration such as int &y to correspond to the input variable and the function call would include an input &x (as opposed to simply x for pass by reference). On the other hand, arrays, by default, are passed by reference. The reason for this is that pass by value creates a copy of the input data. Obviously, this could lead to issues if the array size contained billions of elements. Under these circumstances, it is more sensible to send the address of the 0th element of the array.

To review, see Functions in C++ and Passing Arguments by Value and by Reference.


2e. Use character arrays and the functions of the string class

  • What is a character array?
  • What is a string object?
  • What are some important string methods?
  • How can string output formatting be performed in C++?

In general, an array is a data structure that can house elements of the same data type. Therefore, a character array is an array that can hold elements with data type char. For example,

char a[ ] = { 'a', 'b', 'c', 'd'};

is an array of characters. Often, you use a string of characters with the cout method. In order for cout to determine the end of a string, a 'null' character '\0' must be placed at the end. To understand the need for the null character, you should compare what happens when the following instructions are executed within main.

char a[ ] = {'a', 'b', 'c', 'd'};
cout << a << "\n";
char b[ ] = {'a', 'b', 'c', 'd', '\0'};
cout << b << "\n";

Another, simpler way to create a character array containing a string is to use the syntax:

char c[ ] = "abcd";

Using double quotes tells the compiler that the null character should be automatically inserted at the end of the string.

When you want to operate on null character terminated character arrays (i.e. character arrays containing a string), you should invoke the compiler directive:

#include <cstring>

This directive refers to the C library used to operate on character arrays terminated with the null character. Some useful functions from this library include toupper (make all characters uppercase), strncmp (compare two strings), and strspn (length of the maximum initial segment that matches the source string).

Recall that C++ elevated and augmented the C programming language to become object oriented. Therefore, a string library exists that adheres to object oriented syntax for calling string methods. The string library can be invoked using the compiler directive:

#include <string>

Under these circumstances, a host of methods exist for operating upon string objects. Three major topics to be mastered are how to declare a string object, how to invoke method calls, and which string methods should be focused on at the beginner level.

There are different ways of instantiating a string object:

string t1 ("hello");
string t2 = "hello";

Once the string object is instantiated, you have access to the complete set of methods in the string library. Important methods to be reviewed are size,length, capacity, max_size, append, find, replace, and swap. For example, given the above declarations, this:

t1.size()

calls the size method to operate on the object t1. Finally, the + operator can be used to concatenate, or link, string objects together. In this case, + does not mean arithmetic addition, but rather the + operator has been overloaded to make sense when string objects are used.

The printf command is useful for formatting strings in a more detailed way than cout. For instance, when you want numerical data to be output, it is possible to control the number of digits on both sides of a decimal point. In this case, syntax applying the format specifier % along with its various options (e.g. f for floating point in decimal format, s for string) should be reviewed. The sprintf command is similar to the printf command except that it will copy (or 'scan') formatted data into a character array.

To review, see 


Unit 2 Vocabulary

This vocabulary list includes terms that students need to know to successfully complete the final exam for the course.

  • address operator 
  • character array
  • conditional
  • coverage analysis
  • debugger
  • functions
  • iteration
  • loop
  • methods
  • pass by reference
  • pass by value
  • string
  • test development responsibility
  • address operator &
  • Do-while
  • Else
  • If
  • While

Unit 3: Working with Simple Data Structures

3a. Use arrays

  • What is an array?
  • How are loops useful when processing arrays?
  • What is a multidimensional array?

An array is a collection of elements of the same data type and those elements can be referenced using an index. Just like all variables in C++, arrays must be declared. For example,

int a[5];

declares an array that will contain 5 integers. Arrays can be simultaneously declared and initialized using notation such as:

int a[ ] = {2,3,4,-4,-7};

Elements within an array are referenced using an index starting with the integer 0. Loops are often useful tools for referencing elements where the loop variable can be used to form an index. For instance, code such as:

void main() {
float a[ ]={7,5,6,-7,-8,};
float s=0;
for (int i=0; i<5; i++)
s+=a[i];
}

will sum the elements contained in the array. Additionally, invoking the compiler directive:

#include <array>

will allow use of the array library that contains several life-simplifying methods such as size, empty, and swap.

Multidimensional arrays (also referred to as multi-subscript arrays) refer to array data that is distributed across several dimensions. The simplest extension is from the single subscript (one dimensional) array to a double subscript (two dimensional) array which you can imagine as having rows and columns. Each row can be thought of and treated syntactically as a one dimensional array. Therefore, two subscripts are required to access the array elements: the first index refers to the row and the second index refers to the column. For example:

int a[3][4] = { {1,2,3,4}, {3,4,5,6}, {4,5,6,7}}

initializes a two dimensional array with 3 rows and 4 columns resulting in a total of 12 elements contained within the array. Multidimensional arrays are often used with nested loops. For example, if you wanted to sum all the elements of the above array, you could run something like:

int s=0;
int i,j;
for (i=0; i<3; i++)
for (j=0; j<4; j++)
s += a[i][j];

To review, see Arrays and Multidimensional Arrays.


3b. Use struct, unions, and enumerations

  • What is a structure?
  • How are unions used?
  • When are enumerations useful?

Structures (or struct) originated in the C programming language and are the intermediate step on the way to class definition syntax for C++ object oriented programming. Whereas arrays can hold exactly one data type, structures allow you to include members of any data type. Therefore, it is important to review the use of the keyword struct in order to define a structure and understand how to declare a structure variable. Once a structure variable is declared, its members are referred to using dot notation. For example, a given structure variable v with member t would be referred to as v.t (assuming t is a single variable). Syntax for creating a structure generally looks like:

struct name {
variable declarations
};

where name is the name of the structure.

Unions are similar to structures in terms of their definition, syntax, and variable references; however, only one member of the union can be accessed at any time. This is because only one block of memory is allocated to the union as the size of its largest member. Unions are useful for organizing related data members into the same family where exactly one member will be assigned a value at any given time. Syntax for creating a union generally looks like:

union name {
    variable declarations
};

where name is the name of the union.

Enumerations are useful when one knows a priori a spectrum of values that can be referenced (i.e. the 'enumerators'). The keyword enum should be reviewed in order to understand the syntax. Enumerators are often used in tandem with switch statements where the case statements correspond to the enumerator values. Syntax for creating an enumeration generally looks like:

enum name {
enumerator list
};

where name is the name of the enumeration.

For a review see Structs, Unions, and Enumerations.


Unit 3 Vocabulary

This vocabulary list includes terms that students need to know to successfully complete the final exam for the course.

  • array
  • element
  • enumerations
  • index
  • multidimensional arrays
  • structures
  • unions
  • enum
  • struct
  • union

Unit 4: Object-Oriented Programming

4a. Define and use constructors and deconstructors

  • What is a constructor?
  • What is a destructor?
  • How are constructors and destructors executed?

A constructor is a method that executes automatically when an object from a class is instantiated within a program. A constructor must have the same name as the class, this is how the compiler identifies constructor methods within a class definition. Constructor methods do not have a return type, their sole purpose is to instantiate class objects. The most basic type of constructor is the default constructor which takes no input arguments. If no constructor is included in the class definition, a default constructor will always run in order to instantiate an object. User-defined constructors can have input values if needed and are used to initialize an object's data attributes at the time an object is instantiated. Copy constructors create a new object which is a copy of an existing object, a very convenient feature when it comes to cloning objects.

Constructors can be overloaded allowing a class definition to have several constructors if desired. C++ distinguishes between different constructors based upon the order and type of input variable (i.e. according to the rules of function overloading which should be reviewed). This allows for flexibility in the use of a class.

Destructors are automatically executed when an object is destroyed. For example, a destructor will run when an object is deleted (e.g. using the del command) or when a process involving an object terminates (e.g. when main ends). Similar to a constructor, a destructor has the same name as the class; but, additionally, it is preceded by the symbol ~ in order to distinguish it from a constructor. Typical kinds of tasks for destructors involve clean-up operations such as memory deallocation.

To review, see Classes and Objects and Function Overloading.


4b. Create overloading operators

  • What is operator overloading?
  • Which operators can/cannot be overloaded?
  • What is the syntax for overloading?

Operator overloading allows the programmer to create a different meaning for a C++ operator depending upon how it is used. A list of operators that can be overloaded should be reviewed, but, as a general rule, arithmetic, relational and logical operators can be overloaded. In addition, increment/decrement operations such ++ or -- can be overloaded. On the other hand, higher level operations such as :: (scope resolution) and . (object reference) cannot be overloaded. It should be pointed out that operator overloading does not imply the creation of new operators, but, rather, it refers to a different use of an existing operator.

Operator overloading syntax should be reviewed as there is much detail surrounding this subject. At the most basic level, reference must be made to the class name so that objects from the class can apply the overloaded operator. The operator keyword followed by the overloaded operator is also fundamental to overloading syntax. As an example of a case involving a nonmember function for a class named Examp to overload the + operator, the definition would follow the pattern:

Examp operator+(input arguments){ C++ code for implementation}

Other factors such as access to private or public members will dictate syntactical additions necessary for complete implementation across the class definition.

To review, see Operator Overloading.


4c. Define and use the keyword "this" and use the static members appropriately

  • What is the keyword this used for?
  • What is the syntax for using this?
  • What is a static attribute?

The keyword this is used to refer to the current object of a class. It can be used to refer to class data members whose name conflicts with an input variable name used in a member function. Syntax for using this involves the -> operator. Its typical use might look something like:

this->a =a;

where the variable a has been input to a member function and a has also been used as the name of a data member within the associated class. The -> notation arose to make a clear delineation between a pointer to a structure and a pointer to a data member within a structure. It naturally carries over to refer to class objects.

Static attributes refer to data members whose value will persist beyond the instantiation of any object. In this way, instantiated objects from the same class will all see the same value for the static member. This first time an object is instantiated, the static data member will be set by the class definition or within an associated member definition declaration. After that, if another object is instantiated, the data member can retain its value (hence, the name static) from any previous instantiation.

To review, see C++ Programming/Classes/this and Static Attributes.


4d. Design and appropriately use friend functions and classes

  • Why is the friend keyword useful?
  • What is a friend function?
  • What is a friend class?

Private members of a class are inaccessible and cannot be modified (except through accessors and mutators). Aside from class children, protected members are also inaccessible. The friend keyword is used to enable the sharing of class information that might otherwise be inaccessible (such as private or protected members).

Within a class definition, friend functions are declared using the friend keyword. In this way, a class can make its data accessible to a specific function. The function is said to be a friend of the class. On a larger scale, using the friend keyword, one class can declare another class to be a friend. In doing so, one class can make all of its members available to the friend class. For example, if A is a class and wants to make class B a friend, then within A's class definition the following syntax would be used:

friend class B;

Under these circumstances B would now be a friend of A and can access all of its members.

It is important to note that friendship is not symmetric. If A is to be a friend of B, then

friend class A;

would have to be declared within B's class definition.


4e. Use class inheritance to design better code

  • What is inheritance?
  • What members can be accessed by a child class?
  • What is the syntax defining a parent child relationship?

Inheritance means that it is possible to define a parent child relationship between classes so that a child class will inherit all the attributes of its parent class (also referred to as the base class). Additionally, the child class can have its own data attributes and methods. Inheritance can be applied whenever the child has an is-a relationship to the parent class. For example, a square is a rectangle, a human is an animal, a car is a vehicle. Hence, a rectangle base class can be constructed where a square child class could inherit the rectangle class attributes.

The question of which members of a parent class can be accessed by a child class is a bit involved and its depth should be held off until the sections dealing with encapsulation and access modifiers are mastered. However, the most basic invocation of a child class B derived from a parent class A would look like:

class B: public A {class B definitions go here}

This is an example of public inheritance. Using this syntax, public and protected members will be accessible to the child class (base class private members, by definition and intentionally, are not accessible). In addition, it is extremely important to recognize that, if there are friend functions or friend classes, these are not inherited. Another point to note is that base class constructors and destructors are not automatically inherited.

Finally, when a base class has a single child, this is referred to as single inheritance. When a child class is derived from two or more base classes then it is called multiple inheritance. When the inheritance tree involves both single and multiple inheritance, this is referred to as hybrid inheritance.

To review, see Basics of Inheritance in C++ With Examples and Encapsulation, Inheritance, and Polymorphism In C++.


4f. Explain how polymorphism is achieved through C++ code

  • What is polymorphism?
  • How are polymorphisms used in C++?
  • What are some important concepts regarding polymorphism implementation?

Polymorphism means that different member function calls can be executed depending upon the type of object that calls the method. Polymorphisms typically arise when there is a hierarchy of classes related by inheritance. Under these circumstances, child classes can have separate implementations of a method having the same name or purpose. The purpose is often phrased as virtual function within a base class.

A classic type of example is to have a shape class to be considered as an abstract base class. The intent of an abstract class is not for instantiating objects, but rather to define overarching properties that will be inherited by child classes. For example, shapes such as a rectangle and a triangle could be child classes. All shapes might want to invoke an area method; however, each class would have its own method for doing so. The child class inheriting the area property would then be polymorphic. Furthermore, it is often appropriate for the abstract base class to contain a virtual method for the area to define its properties.

Lastly, a common technique for invoking the parent object is to create a pointer to the object. Using this technique, a major consequence of class inheritance is that a pointer to a derived class is type-compatible with a pointer to its base class. The feature is exactly why polymorphism works.

To review, see Encapsulation, Inheritance, and Polymorphism In C++ and Polymorphism.


4g. Create accessors, mutators, and access modifiers

  • What is an accessor?
  • What is a mutator?
  • What is an access modifier?

One major goal of encapsulation in object oriented programming is to define levels of attribute accessibility within a class definition. Access modifiers (also known as access specifiers) such as public, protected and private are used for this purpose. Public means that class objects can access a public member. Protected means that base class and inherited class objects can access a protected member. Private means that only class members can access a private member. Any attempt by an object to access a private method or data attribute will result in an error.

If you want an object to see private data, you need an accessor method (also known as a getter) within the class definition. An accessor generally consists of a single instruction that returns the data attribute. If you want an object to set a private data attribute, you need to include a mutator method (also known as a setter) within the class definition. Mutators return nothing (i.e. have void return type) and consist of a single instruction that assigns an input value to the data attribute. As a convention, the name of a getter usually begins with 'get' and the name of a setter usually begins with 'set'.

To review, see Accessors and Modifiers and Encapsulation, Inheritance, and Polymorphism In C++.


Unit 4 Vocabulary

This vocabulary list includes terms that students need to know to successfully complete the final exam for the course.

  • accessor method
  • base class
  • child class
  • constructor
  • derived class
  • destructor
  • encapsulation 
  • friend functions
  • getter
  • inheritance
  • mutator method
  • mutators 
  • operator overloading
  • parent class
  • pointer
  • polymorphism
  • private member
  • protected member
  • public inheritance 
  • setter
  • static attributes
  • static member
  • virtual function

Unit 5: Advanced Concepts

5a. Write class and function templates

  • What is a template?
  • How are templates used?
  • What is the syntax for invoking a template?

Templates allow you to write rules so that functions and classes can operate on different data types without having to be rewritten for each separate case. So, for example, rather than overloading several different functions, it is possible to create a template that can take various cases into account. Then, at compile-time, the code associated with the template rules for the specific case at hand is inserted into the program. As an example, consider the following code snippet:

template <typename T>
void Print(T printval)
{
  cout << printval << "\n";
}
int main() {
  Print<int>(3);
  Prin<float>(3.3);
  Print<string>("three");
}

The template instruction indicates that some yet-to-be specified data type T will be used as an input to the Print function. The true data type will not be known until compile-time. At that point, the code in main will resolve the data type based upon the function call and the function code will be generated. Some loosely refer to this process as 'on-demand compilation'.

You can use any name you like for the typename (T and S are often chosen). In addition,

template <class T>

is also acceptable syntax in the above example. Similar syntax holds for template use within. Once template <typename T> is invoked, you can use T as a variable type to declare any data attribute within the class. Again, details about the usage are worked out at compile-time based upon declarations within the class definition and the class method call. Finally, there may be cases where a template must be specialized for a given data type (even though many uses are still possible). Template specialization is indicated using the syntax template <>.

For a review see C++ Templates and Introduction to C++ Templates.


5b. Code with a class that manipulates files

  • What are the basic steps required for handling a file?
  • What methods accomplish these basic steps?
  • What is the syntax for reading from and writing to a file?

Part of the goal of file handling syntax is to make standard input/output similar to file input/output. This can be accomplished using

#include <fstream>

which will include the file handling library for both reading (input) and writing (output). Alternatively, the ofstream library can be used for file output and ifstream can be used for file input. Once this is done, the syntax for file I/O will appear quite similar to using cin and cout from the standard library.

There are three basic steps to handling a file: open, perform operations, and close. Opening a file makes a request to the operating system so that the file can be found. In addition, the operation to be performed (e.g. "r" implies reading, "w" implies writing) must be specified. The desired operations are then performed while the file is open. Lastly, the file must be closed. Not surprisingly, the methods involved in opening and closing files are named open and close. For the operations to read or write the data, similar in syntax and usage to printf and sprintf is the command fprintf. The fscanf command as an extension of the scanf command should also be reviewed. Lastly, it is possible to invoke file objects using ios::mode where modes such as trunc, in, out, app, etc should be reviewed.

To review, see Reading File Input in C++, Output File Streams in C++, and Input and Output.


5c. Use exceptions in C++ code

  • What is an exception?
  • How are exceptions handled?
  • What are some common exception types?

During the execution of a program, there is potential for unexpected runtime errors and abnormalities that can interrupt normal program flow (i.e. exceptions). Exception handling is the process of dealing with such program interruptions in order to allow for continued execution in light of some abnormality or to allow for a nonvolatile program termination. The exception is said to be thrown at the place in the program where the exception occurs. To anticipate the exception to be thrown, an exception handler should be written to deal with the condition that has caused the exception.

At the basic level, three keywords used to handle exceptions are try, catch and throw. The try block is a block of code which could cause an exception. An exception is thrown by using the throw keyword from inside the try block. The try block is immediately followed by one or many catch blocks each designed to catch and then handle an exception thrown by the try block.Exception types such as range_error, overflow_error, underflow_error, bad_alloc, length_error, and domain_error from the standard library should be reviewed.

To review, see Exception Handling.


Unit 5 Vocabulary

This vocabulary list includes terms that students need to know to successfully complete the final exam for the course.

  • catch
  • close
  • exceptions
  • file input/output
  • open
  • reading
  • template
  • thrown
  • try
  • writing