CS107 Study Guide

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