You are on page 1of 35

ECNG 

1009: Introduction to Programming 
Lab 2: Testing Programs, More Loops, Arrays, Vectors, 
Strings and Files 
Most of the material for this lab has been sourced from http://www.learncpp.com 

1. Testing Your Program 
Software testing is any activity aimed at evaluating an attribute or capability of program or system (and 
its components) to determine whether or not it meets its required results. Software testing is 
performed with the intent of finding software bugs; we execute a system in order to identify gaps, 
errors or missing requirements. ANSI/IEEE 1059 standards define testing as “a process of analyzing a 
software item to detect differences between existing and required conditions (i.e. defects, errors, bugs) 
and to evaluate the features of the software item”. Testing can also be stated as the process of 
validating and verifying that a software program, application or product meets the business and 
technical requirements for which it was designed and developed, and that it works as expected. 

Why perform software testing? As humans, we make mistakes all the time. Some of these mistakes are 
unimportant, but some may be expensive and dangerous, depending on the application. We need to 
check everything and anything we produce because things can always go wrong. As mentioned 
previously, software testing is really required to point out the defects and errors that were made during 
the development phases. It is necessary to ensure that the application should not result in any failures 
because they can be very expensive in the future or in the later stages of development. 

Testing is crucial to software quality; it is important to ensure quality of a product (quality assurance 
(QA)). It is also necessary for the verification and validation or reliability estimation. It is essential for the 
customer’s satisfaction and required for ensuring effective performance of software applications or 
products. Software testing is a trade‐off between budget, time and quality. 

1.1 Types of Testing and Testing Methods 
During the software development life cycle, we may wish to perform different types of testing on our 
product. At the highest level, testing can be classified into manual and automation testing. 

1 | P a g e  
 
Manual testing, as the name implies, refers to testing software manually, i.e. without using an 
automated tool or script. This is the type of testing with which we are familiar and which we will focus 
on. The tester takes over the role of an end‐user and tests the software to identify any unexpected 
behavior of bugs. Testers use test plans, test cases or test scenarios to test a software to ensure the 
completeness of testing. The different stages for manual testing include: unit testing, integration 
testing, system testing and user acceptance testing. 

Automation testing, also known as Test Automation, is when the tester writes scripts and uses another 
software to test the product. This process involves the automation of a manual process. 

Automation testing is used to re‐run the test scenarios that were performed during manual testing, 
quickly and repeatedly. We shall see this in the form of component drivers. Test automation is typically 
used in large, critical projects, in projects that require testing the same areas frequently, in projects 
where the requirements do not change frequently and when availability of time is a critical factor. There 
are a number of commercial software testing tools that can be used for automation testing. 

Testing can also be classified as static or dynamic. Static testing involves reviews, code walkthroughs and 
inspections. Static testing finds defects without executing code and starts early in the life cycle and 
performed during the verification stage. It is performed without the use of a computer, i.e. we do not 
use a compiler nor do we build the program. This may involve identifying syntax errors and data flow 
errors. 

Dynamic testing however involves the execution of the code to demonstrate the result of running tests. 
This is performed during the validation process and requires the computer for testing. Examples involve 
unit testing, integration testing and system testing. Dynamic testing may begin before the program is 
100% complete in order to test particular sections of code. Typical techniques include the use of stubs 
and drivers or execution from a debugger environment. 

1.2 Component Testing 
The use of component drivers falls under the heading of component testing. Component testing is a 
method where testing of each component in an application is done separately. It is also known as 
module testing and may be done in isolation from the rest of the system. In such cases, stubs and 
drivers are used to replace the missing software and simulate the interface between the software 
components in a simple manner. 

2 | P a g e  
 
Consider the following example: suppose there is an application consisting of two modules, say function 
A and function B. The developer has developed function A and wants to test it. But in order to test 
function A completely, a few of its functionalities are dependent on function B, specifically function A is 
supposed to call function B. But function B has not yet been developed. In such a case, to test function A 
completely, function B can be replaced by a stub. A stub is called from the software component to be 
tested. 

Say now the developer had developed function B and not function A, but he wants to test function B. 
Since function B can only be called from function A (which has not yet been created), in order to run 
function B, function A can be replaced by a driver. A driver calls the component to be tested. These two 
cases are illustrated in the following diagram: 

1.2.1 The Box Approach 
The box approach of software testing are the two most widespread testing methods used in the 
software engineering community. Testing methods are traditionally divided into white and black 
box testing and differ in the point of view that a test engineer takes when designing test cases. 

White Box Testing: 

 Also known as clear box testing, open box testing or glass box testing 
 A software testing in which the internal structure/design/implementation is known to the tester 
 Examines program structure, as opposed to functionality, and derives test data from program 
logic/code. Goes beyond the user interface. 
 Tester chooses inputs to exercise paths through the code and determines appropriate outputs 
 Techniques include: statement coverage, branch coverage and path coverage 

3 | P a g e  
 
 Can be applied to unit, integration and system testing levels. Typically most used for unit testing. 
Advantages: 

 Because tester has knowledge of source code, it is easy to find out which type of data can help 
in testing application effectively 
 Helps in optimizing code 
 Extra lines of code can be removed which can bring hidden defects (dead code) – better 
programming practice 
Disadvantages: 

 Expensive – both time (it is an exhaustive process) and money (skilled testers are needed) are 
spent to perform white box testing 
 Impossible to look through code to find EVERY hidden error – many paths will go untested 
 Difficult to maintain white box testing as it requires specialized tools like code analysers and 
debuggers 
 

Black Box Testing 

 Also known as specifications based testing 
 Unlike white box testing, this is a technique of testing without having any knowledge of the 
interior workings of the application. 
 Tester is oblivious to the system architecture and does not have access to source code. 
 Tester interacts with the system’s user interface by providing inputs and examining outputs 
without knowing how and where the inputs are worked upon 
 Tester examines functionality of an application based on specifications 
 Applied at unit, integration, system and acceptance testing 
 Techniques include: equivalence class, boundary value analysis, domain tests, etc. 
 

Advantages: 

 Well suited and efficient for large code segments 
 Code access not required 
 Separates user’s perspective from developer’s perspective through visibly defined roles 

4 | P a g e  
 
Disadvantages: 

 Limited coverage, since only a selected number of test scenarios are performed 
 Inefficient testing since tester only has limited knowledge about application 
 Blind coverage 
 Test cases difficult to design 
 

1.3 Errors 
Now that we have been exposed to an overview of testing, let us now look into the different types or 
errors that testing determines. As we said earlier, humans make mistakes all the time – no matter how 
careful you are, errors are your constant companion. With practice, you can get better at not making 
errors, and even better at finding and correcting them. In programming, there are three kinds of errors: 
syntax errors, logic errors and runtime errors. 

 Syntax errors – errors in spelling and “grammar”. Spelling errors in code occur when keywords 
are spelt incorrectly and when variables previously declared are misspelt later on. Grammatical 
errors in code refer to such things as omitting a semicolon at the end of a line, using a variable 
that was not declared and incorrectly nested braces. Syntax errors are the easiest to find and 
correct and may be done via code walkthroughs or using a compiler. The compiler tells you 
where it got into trouble and the program will not execute until these errors are corrected. 
 Logic errors – these occur when your program compiles and runs but does the wrong thing. The 
compiler fails to detect logic errors – you do not get error messages and the program need not 
necessarily crash. The only clue to the existence of logic errors is the production of wrong 
solutions. These can be rectified by using a debugger to step through your program and watch 
what it does. 
 Runtime errors – if there are no syntax errors, errors may be encountered while the program is 
running. These errors are denoted by error messages during runtime and program crashes, e.g. 
stack overflows or incorrectly indexing an array. Runtime errors are intermediate in difficulty 
since you are told where the program goes wrong, but you need to trace back from there to 
figure out where the problem originated. 

   

5 | P a g e  
 
1.4 White Box Testing 
Typically, white box testing is done within the code implementation i.e. writing a driver or stub function 
to conduct the testing procedure. The purpose of white box testing is to test individual functions within 
the code by calling them inside the driver or stub function with different assorted input parameters. The 
choice of these input parameters is determined by the necessary test cases to be conducted. 

Consider the code snippet below where the area of the rectangle can be calculated using the area 
function. If white box testing was to be conducted instead using multiple different inputs for the width 
and height, a driver function could be written to conduct the area function for each one of them. The 
driver function would then be called within the main function. The code snippet for the driver function 
can be seen below: 

int width(5), height(5);

double area(int width, int height){


int area = width * height;
return area;
}

void Driver(){
std::cout << area(1,2) << std::endl;
std::cout << area(2,3) << std::endl;
std::cout << area(3,4) << std::endl;
}

int main(){
Driver();
return 0;
}

The example shows white box testing for one function using different inputs. Consider the scenario if 
multiple shapes required their area to be calculated. White box testing would allow for testing the area 
of each shape, as well as any other functions written for each shape. Furthermore, white box testing is 

6 | P a g e  
 
not limited to just one function. Multiple different driver functions could be written for white box 
testing. 

1.5 Black Box Testing 
Black box testing is done when outside of the code’s implementation since the tester does not have 
access to any of the source code. In such an event, the only thing that the tester can do is write a batch 
script native to the operating system’s command prompt to automate testing of the overall program 
itself. 

However, using a batch script automation process for black box testing requires an assumption to be 
validated. The assumption is such that the program is written to accept input parameters from the 
command line. If this assumption is violated i.e. it is not true, black box testing must be done manually. 

In C++, it is possible to accept command line arguments but this means the main() function must contain 
a special input argument list which can look like either one of the following forms. Please note that both 
forms are equivalent and are entirely dependent on the programmer’s choice: 

int main(int argc, char* argv[])


int main(int argc, char** argv)
 

The first argument argc is the number of elements within the array which is the second argument argv. 
The second argument is always a pointer to a character array because any arguments passed from the 
command line are considered to be character arrays and can only be passed using pointers. Spaces 
between clusters of characters on the command line indicates that each character cluster is treated as a 
separate array argument. Consider the following code snippet below: 

int main(int argc, char* argv[]) {


cout << "argc = " << argc << endl;
for(int i = 0; i < argc; i++)
cout << "argv[" << i << "] = " << argv[i] << endl;
return 0;
}

7 | P a g e  
 
It can be noticed that argv[0] is the  path and name of the compiled program itself. Any other command 
line arguments would start from argv[1] and so forth with the numerical index increasing for the 
number of arguments. 

As stated before, all input command line parameters are considered as character arrays. If they must be 
treated as some other data type such as an integer, the data type must be converted. The cstdlib must 
be included to facilitate these conversions and the functions used to transform these characters would 
be atoi(), atoll() and atof() which converts ASCII character ararys to data types int, long and double 
respectively. There are other conversion functions within the cstdlib that can be utilized. 

Consider the code snippet below that shows an example of how the atoi() function works: 

#include <iostream>
#include <cstdlib>
using namespace std;
int main(int argc, char* argv[]) {
for(int i = 1; i < argc; i++)
cout << atoi(argv[i]) << endl;
return 0;
}

Attempt to use the program by passing any number of arguments on the command line, be it integers, 
decimals or random characters. Observe the effect as to how the compiled program treats each case. 

Since the program is written using the special input parameters for the main() function, it is possible to 
write a batch script in the Windows operating system to automate any testing procedures. For instance, 
if the program is named test_program and the code snippet above is under test, consider the batch 
script below that shows an example of how a batch script can be written using Notepad: 

test_program 1 2 3
test_program 1.23 4.56 7.8901
test_program 1 1.23 a
 

Please note that when saving this file in Notepad, it must be saved with the .bat extension and that the 
location of the batch file must be in the same directory of the compiled program. Run the batch file by 
calling it in the command prompt and observe the effects as to how the compiled program operates. 

8 | P a g e  
 
To summarize what is learnt in this section, using this programming technique would allow for faster 
automation black box testing than using a manual method. It is also possible to write the results of the 
black box testing procedure by modifying the source code to write the results to a file instead of using 
the cout command to print to the command prompt. An attempt of this should be made outside of the 
lab time. 

1.6 Handling Errors: Exception Handling 
An exception is a problem that arises during the execution of a program (i.e. during runtime). A C++ 
exception is a response to an exceptional circumstance (like runtime errors) that arises while a program 
is running, such as an attempt to divide by zero. Why, then, do we use exceptions as opposed to, say, if‐
statements? Using exceptions for error handling makes code simpler, cleaner and less likely to miss 
errors. With if‐statements, your error handling and your normal code are closely intertwined. Your code 
gets messy and it becomes hard to ensure that you have dealt with all errors. Exceptions provide a way 
to transfer control from one part of a program to special functions called handlers. C++ exception 
handling is built upon three keywords: try, catch and throw. 

 throw – a program throws an exception when a problem shows up. This is done using the throw 
keyword 
 catch – A program catches an exception with an exception handler at the place in the program 
where you want to handle the problem. The catch keyword indicates the catching of an 
exception. The catch block is the exception handler itself. 
 try – a try block identifies a block of code for which particular exceptions will be activated. The 
try block is used to place a portion of code under “exception inspection”. It is followed by one or 
more catch blocks. 
Assuming a block will raise an exception, a method catches an exception using a combination of try and 
catch keywords. A try/catch block is placed around the code that might generate an exception. Code 
within a try/catch block is referred to as protected code. The following snippet shows the syntax for 
using try/catch: 

try
{
// protected code

9 | P a g e  
 
} catch( ExceptionName e1 )
{
// catch block
} catch( ExceptionName e2 )
{
// catch block
} catch( ExceptionName eN )
{
// catch block
}
 

You can list multiple catch statements to catch different types of exceptions in case your try block raises 
more than one exception in different situations. The catch block following the try block catches any 
exception. You can specify what type of exception you want to catch and this is determined by the 
exception declaration that appears in brackets following the keyword catch. In the code above, we catch 
exceptions e1, e2 and eN of type ExceptionName. If you want to specify a catch block that should handle 
any type of exception that is thrown in a try block, you must put ellipsis (…) between the parentheses. 
For example: 

try 

// protected code 
} catch(...) 

// code to handle any exception 

 

Exceptions can be thrown anywhere within a block of code using throw statements. The operand of the 
throw statements determines a type for the exception and can be any expression and the type of the 
result of the expression determines the type of exception thrown. Consider a divide by zero condition: 

10 | P a g e  
 
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
 

Since these three statements for exception handling are used together, let us look at an example which 
throws a division by zero exception and catches it in a catch block. 

#include <iostream>
using namespace std;

double division(int a, int b)


{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
 
 
int main () 

  int x = 50; 
  int y = 0; 
  double z = 0; 
 
  try { 

11 | P a g e  
 
      z = division(x, y); 
      cout << z << endl; 
  } catch (const char* msg) { 
      cerr << msg << endl; 
  } 
  return 0; 

 

So the typical order of exception handling is: when the exception arises in the try block, an exception is 
thrown that transfers control to the exception handler, which is the catch block. If no exception is 
thrown, the code continues normally and all handlers are ignored. The exception can also be thrown 
from inside the try block, as shown below: 

int main () {
try
{
throw 20;
}
catch (int e)
{
cout << "An exception occurred. Exception Nr. " << e << '\n';
}
return 0;
}

   

12 | P a g e  
 
2. More Loops 
The previous lab introduced the topic of loops, with the for loop being a highlight. We will now 
investigate while and do‐while loops in particular. 

2.1 While Loops 
The while statement is the simplest of the loops that C++ provides, and it has a definition very similar to 
that of an if statement. The definition for the while statement can be seen below: 

while (expression) 

  statement; 

A while statement is declared using the while keyword. When a while statement is executed, the 
expression is evaluated. If the expression evaluates to true (non‐zero), the statement executes. 

However, unlike an if statement, once the statement has finished executing, control returns to the top 
of the while statement and the process is repeated. Let’s take a look at a simple while loop in action 
within the code below: 

#include <iostream> 
  
int main() 

    int count = 0; 
    while (count < 10) 
        { 
        std::cout << count << " "; 
        ++count; 
        } 
    std::cout << "done!"; 
  
    return 0; 

 
First, count is initialized to 0. We can see that “0 < 10” evaluates to true, so the statement block 
executes. The first statement prints 0, and the second increments the count to 1. Control then returns 
back to the top of the while statement. We now see that “1 < 10” evaluates to true, so the code block is 
executed again. The code block will repeatedly execute until count is 10, at which point “10 < 10” 
evaluates to false, and the loop will exit. 

It is possible that a while statement can execute zero times. Consider the code below: 

#include <iostream> 

13 | P a g e  
 
  
int main() 

    int count = 15; 
    while (count < 10) 
        { 
        std::cout << count << " "; 
        ++count; 
        } 
    std::cout << "done!"; 
  
    return 0; 

 

The condition “15 < 10” immediately evaluates to false, so the while statement is skipped. The only 
thing that the program prints is “done!” 

On the other hand, if the expression always evaluates to true, the while loop will execute forever. This is 
referred to as an infinite loop. The code below shows an example of this: 

#include <iostream> 
  
int main() 

    int count = 0; 
    while (count < 10) // this condition will never be false 
        std::cout << count << " "; // so this line will repeatedly execute 
  
    return 0; // this line will never execute 

 

Count is never incremented in this program, so “count < 10” will always be true. The only way to exit an 
infinite loop is through a return statement, a break statement, an exit statement or the user killing the 
program. 

Often, we want a loop to execute a certain number of times. To do this, it is common to use a loop 
variable, often called a counter. A loop variable is an integer variable that is declared for the sole 
purpose of counting how many times a loop has executed. In the examples above, the variable “count” 
is a loop variable. 

Loop variables are often given simple names, such as “i”, “j” or “k”. However, naming variables like this 
has one major problem. If you want to know where in your program a loop variable is used, and you use 
the search function on “i”, “j” or “k”, all the instances of these characters would be returned. A better 
idea is to use “iii”, “jjj” or “kkk” as your loop variable names. These names are more unique and it makes 

14 | P a g e  
 
searching for your loop variables easier. An even better idea is to use “real” variable names such as 
“count”, or a name that gives more detail about what you are counting. 

Each time a loop executes, it is called an iteration. Since the loop body is typically a block, and because 
that block is entered and exited with each iteration, any variables declared inside the loop are created 
and then destroyed with each iteration. The code below shows that the variable ‘x’ is created and 
destroyed 5 times: 

#include <iostream> 
  
int main() 

    int count = 1; 
    int sum = 0; // sum is declared up here because we need it later (beyond the loop) 
  
    while (count <= 5) // iterate 5 times 
    { 
        int x; // x is created here with each iteration 
  
        std::cout << "Enter integer #" << count << ':'; 
        std::cin >> x; 
  
        sum += x; 
  
        // increment the loop counter 
        ++count; 
    } // x is destroyed here with each iteration 
  
    std::cout << "The sum of all numbers entered is: " << sum; 
  
    return 0; 

 

For fundamental variables, this is fine. For non‐fundamental variables, which you will meet later on, this 
causes performance issues. You may want to consider defining non‐fundamental variables before the 
loop. This is another one of the cases where you might declare a variable well before its first actual use. 

Note that the variable “count” is declared outside the loop. This is necessary because we need the value 
to persist across all iterations, not to be destroyed with each iteration. 

It is also possible to nest loops inside of other loops. In the following example, the inner and outer loops 
each have their own counters. However, note that the loop expression for the inner loop makes use of 
the counter of the outer loop as well. 

#include <iostream> 
  

15 | P a g e  
 
// Loop between 1 and 5 
int main() 

    int outer = 1; 
    while (outer <= 5) 
    { 
        // loop between 1 and outer 
        int inner = 1; 
        while (inner <= outer) 
            std::cout << inner++ << " "; 
  
        // print a newline at the end of each row 
        std::cout << "\n"; 
        ++outer; 
    } 
  
    return 0; 

 

2.2 Do‐While Loops 
One interesting thing about the while loop is that if the loop condition is false, the while loop will not 
execute at all. It is sometimes the case that we know we want a loop to execute at least once, such as 
when displaying a menu. To facilitate this, C++ offers the do‐while loop. Its definition can be seen below: 

do 

  statement; 

while (condition); 

The statement in a do‐while loop always executes at least once. After the statement has been executed, 
the do‐while loop checks the condition. If the condition is true, the path of execution jumps back to the 
top of the do‐while loop and executes it again. The code below shows an example of using a do‐while 
loop to display a menu to the user and wait for the user to make a valid choice: 

 
#include <iostream> 
  
int main() 

    // selection must be declared outside do/while loop 
    int selection; 
  
    do 
    { 
        std::cout << "Please make a selection: \n"; 
        std::cout << "1) Addition\n"; 
        std::cout << "2) Subtraction\n"; 

16 | P a g e  
 
        std::cout << "3) Multiplication\n"; 
        std::cout << "4) Division\n"; 
        std::cin >> selection; 
    } 
    while (selection != 1 && selection != 2 && 
        selection != 3 && selection != 4); 
  
    // do something with selection here 
    // such as a switch statement 
  
    std::cout << "You selected option #" << selection << "\n"; 
  
    return 0; 

 

One interesting thing about the above example is that the selection variable must be declared outside of 
the do block. If the selection variable was to be declared inside the do block, it would be destroyed 
when the do block terminates, which happens before the while conditional is executed. We need the 
variable to be used in the while conditional. Consequently, the selection variable must be declared 
outside the do block. 

Now, given the code below, investigate what is wrong with the loop: 

#include <iostream> 
 
int main(){ 
  int counter = 0; 
  while {counter > 100} 
  if (counter % 2 == 1) 
    std::cout << counter << " is odd." << std::endl; 
  else 
    std::cout << counter << " is odd." << std::endl; 
  ++counter; 
  int counter = 0; 
  while {counter > 100} 
  if (counter % 2 == 1) 
    std::cout << counter << " is odd." << std::endl; 
  else 
    std::cout << counter << " is odd." << std::endl; 
  ++counter; // same as: counter = counter + 1; 
  return 0; 

 

2.3 Challenge Questions 
Write code to do the following: 

1) Repeatedly print the value of variable “xValue”, decreasing it by 0.5 each time, as long as 
“xValue” remains positive. The user should specify the initial value of “xValue”. 

17 | P a g e  
 
2) Print the square root of the first 25 odd numbers. 
3) Calculate “n!”, where “n” is supplied by the user 

3. Arrays 
An array is an aggregate data type that lets us access many variables of the same type though a single 
identifier. 

Consider the case where you want to record the test scores for 30 students in a class. Without arrays, 
you would have to allocate 30 almost identical variables. Arrays give us an easier way to do this. The 
following array definition is essentially equivalent: 

int testScore[30]; // allocate 30 integer variables in a fixed array 
 

In an array variable declaration, we use square brackets, ‘[‘ and ‘]’, to tell the compiler both that this is 
an array variable (instead of a normal variable), as well as how many variables to allocate (referred to as 
the array length). 

The above line of code declares a fixed array named “testScore”, with a length of 30. A fixed array, also 
called a fixed length array or fixed array size, is an array where the length is known as compile time. 
When “testScore” is initialized, the compiler will allocate 30 integers. 

Each of the variables in an array is called an element. Elements do not have their own unique names. 
Instead, to access individual elements of an array, we use the array name, along with the subscript 
operator [], and a parameter called the subscript or index, that tells the compiler which element we 
want. This process is referred to as subscripting or indexing the array. 

The first element in our array is testScore[0]. The second is testScore[1]. The tenth is testScore[9]. The 
last element in our testScore array is testScore[29]. This is great because we no longer need to keep 
track of a bunch of different (but related) names; we can just vary the subscript to access different 
elements. 

It should be noted at this point that C++ arrays always count starting from zero. Hence why the first 
element is at position 0 and the last element is at position 29. For an array of length N, the array 
elements are numbered 0 through N‐1. This is referred to as the range of an array. 

The code below illustrates the definition and indexing of an array: 

18 | P a g e  
 
#include <iostream> 
  
int main() 

    int prime[5]; // hold the first 5 prime numbers 
    prime[0] = 2; // The first element has index 0 
    prime[1] = 3; 
    prime[2] = 5; 
    prime[3] = 7; 
    prime[4] = 11; // The last element has index 4 (array length‐1) 
  
    std::cout << "The lowest prime number is: " << prime[0] << "\n"; 
    std::cout << "The sum of the first 5 primes is: " << prime[0] + prime[1] + prime[2] + 
prime[3] + prime[4] << "\n"; 
  
    return 0; 

 

Arrays can be made from any data type. The following code example shows how we declare an array of 
doubles: 

#include <iostream> 
  
int main() 

    double array[3]; // allocate 3 doubles 
    array[0] = 2.0; 
    array[1] = 3.0; 
    array[2] = 4.3; 
  
    cout << "The average is " << (array[0] + array[1] + array[2]) / 3 << "\n"; 
  
    return 0; 

In C++, arrays subscripts must always be an integral type (char, short, int, long, bool). These subscripts 
can either be a constant or non‐constant value. We have seen so far that the array subscripts used are 
integers. This is the most commonly used data type for the array subscript. 

When declaring a fixed array, the length of the array (between the square brackets) must be a compile‐
time constant. This is because the length of a fixed array must be known at compile time. This 
introduces two limitations: 

 Fixed arrays cannot have a length based on either user input or some other value calculated at 
runtime. 
 Fixed arrays have a fixed length that cannot be changed. 

19 | P a g e  
 
3.1 Initializing an Array 
Array elements are treated just like normal variables, and as of such, they are not initialized when 
created. One way to initialize an array is to do it element by element as we have seen in the previous 
code examples. However, this is a pain, especially as the array gets larger. 

Fortunately, C++ provides a more convenient way to initialize entire arrays via use of an initializer list. 
The following example is equivalent to the initialization of the “prime” array: 

int prime[5] = { 2, 3, 5, 7, 11 }; // use initializer list to initialize the fixed array 
 

If there are more initializers in the list than the array can hold, the compiler will generate an error. 
However, if there are less initializers in the list than the array can hold, the remaining elements are 
initialized to zero. The following code example illustrates this: 

#include <iostream> 
  
int main() 

    int array[5] = { 7, 4, 5 }; // only initialize first 3 elements 
  
    std::cout << array[0] << '\n'; 
    std::cout << array[1] << '\n'; 
    std::cout << array[2] << '\n'; 
    std::cout << array[3] << '\n'; 
    std::cout << array[4] << '\n'; 
  
    return 0; 

 

Consequently, to initialize all the elements of an array to zero, you can do this: 

// Initialize all elements to 0 
int array[5] = { }; 
 

If you are initializing a fixed array of elements using an initializer list, the compiler can figure out the 
length of the array for you, and you can omit explicitly declaring the length of the array. The following 
two lines of code are equivalent: 

int array[5] = { 0, 1, 2, 3, 4 }; // explicitly define length of the array 
int array[] = { 0, 1, 2, 3, 4 }; // let initializer list set length of the array 
 

20 | P a g e  
 
This not only saves typing, it also means you do not have to update the array length if you add or 
remove elements later. 

3.2 Arrays and Functions 
Passing an array to a function at first glance looks just like passing a normal variable. However, C++ 
treats arrays differently. 

When a normal variable is passed by value, C++ copies the value of the argument into the function 
parameter. Since the parameter is a copy, changing the value of the parameter does not change the 
value of the original argument. 

However, because copying large arrays can be very expensive, C++ does not copy an array when it is 
passed into a function. Instead, the actual array is passed. This has the side effect of allowing functions 
to directly change the value of array elements. The following code illustrates this point: 

#include <iostream> 
  
void passValue(int value) // value is a copy of the argument 

    value = 99; // so changing it here won't change the value of the argument 

  
void passArray(int prime[5]) // prime is the actual array 

    prime[0] = 11; // so changing it here will change the original argument! 
    prime[1] = 7; 
    prime[2] = 5; 
    prime[3] = 3; 
    prime[4] = 2; 

  
int main() 

    int value = 1; 
    std::cout << "before passValue: " << value << "\n"; 
    passValue(value); 
    std::cout << "after passValue: " << value << "\n"; 
  
    int prime[5] = { 2, 3, 5, 7, 11 }; 
    std::cout << "before passArray: " << prime[0] << " " << prime[1] << " " << prime[2] 
<< " " << prime[3] << " " << prime[4] << "\n"; 
    passArray(prime); 
    std::cout << "after passArray: " << prime[0] << " " << prime[1] << " " << prime[2] << 
" " << prime[3] << " " << prime[4] << "\n"; 
  
    return 0; 

 

21 | P a g e  
 
In the above example, “value” is not changed in main(), because the parameter value in function 
passValue() was a copy of variable “value” in function main(), not the actual variable. However, because 
the parameter array in function passArray() is the actual array, passArray() is able to directly change the 
value of the elements. 

If you want to ensure a function does not modify the array elements passed into it, you can make the 
array constant using the keyword const. The code below illustrates this: 

// even though prime is the actual array, within this function it should be treated as a 
constant 
void passArray(const int prime[5]) 

    // so each of these lines will cause a compile error! 
    prime[0] = 11; 
    prime[1] = 7; 
    prime[2] = 5; 
    prime[3] = 3; 
    prime[4] = 2; 

 

The sizeof operator can be used on arrays, and it will return the total size of the array (array length 
multiplied by element size). This will not work properly for arrays that have been passed into functions. 
The code below gives an example of this: 

void printSize(int array[]) 

    std::cout << sizeof(array) << '\n'; // prints the size of a pointer, not the size of 
the array! 

  
int main() 

    int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 }; 
    std::cout << sizeof(array) << '\n'; // will print the size of the array 
    printSize(array); 
  
    return 0; 

 

It is possible to determine the length of a fixed array by dividing the size of the entire array by the size of 
an array element. The code below illustrates this: 

int main() 

    int array[] = { 1, 1, 2, 3, 5, 8, 13, 21 }; 
    std::cout << "The array has: " << sizeof(array) / sizeof(array[0]) << "elements\n"; 
  

22 | P a g e  
 
    return 0; 

 

Note that the size of the entire array is equal to the length of the array multiplied by the size of an 
element. 

Recall that an array of length N has array elements 0 through N‐1. What happens if you try to access an 
array with a subscript outside of that range? Consider the code below: 

int main() 

    int prime[5]; // hold the first 5 prime numbers 
    prime[5] = 13; 
  
    return 0; 

 

In this program, the array is of length 5, but an attempt is made to write a test score into the 6th element 
(index 5). 

C++ does not check to make sure that your indices are valid for the length of your array. In the above 
example, the value of 13 will be inserted into memory where the 6th element would have been had it 
existed. When this happens, you will get undefined behavior, meaning your program will crash. 

3.3 Arrays and Loops 
It is possible to use a loop variable as an array index to loop through all of the elements of an array and 
perform some type of calculation on them. The loop variable in this case would be the array subscript. 
This is common wherever you find arrays, you will most certainly find loops used for them. When a loop 
is used to access each array element in turn, this is often called iterating through the array. The code 
below gives an example of this: 

 
 
 
#include <iostream> 
  
int main() 

    int scores[] = { 84, 92, 76, 81, 56 }; 
    const int numStudents = sizeof(scores) / sizeof(scores[0]); 
  
    int maxScore = 0; // keep track of our largest score 
    for (int student = 0; student < numStudents; ++student) 
        if (scores[student] > maxScore) 

23 | P a g e  
 
            maxScore = scores[student]; 
  
    std::cout << "The best score was " << maxScore << '\n'; 
  
    return 0; 

 

In the code above, we use a non‐loop variable called “maxScore” to keep track of the highest score we 
have seen. “maxScore” is initialized to zero to represent that we have not seen any scores yet. We then 
iterate through each element of the array, and if we find a score that is higher than any we have seen 
before, we set “maxScore” to that value. Thus, “maxScore” always represents the highest score out of all 
the elements we have searched so far. By the time we have reached the end of the array, “maxScore” 
holds the highest score in the entire array. 

Loops are typically used with arrays to do one of three things: 

1. Calculate a value (e.g. average, total) 
2. Search for a value (e.g. highest, lowest) 
3. Reorganize the array (e.g. ascending, descending) 

When calculating a value, a variable is typically used to hold an intermediate result that is used to 
calculate the final value. 

When searching for a value, a variable is used to hold the best candidate value seen so far (or the array 
index of the best candidate). 

Sorting an array is trickier, as it typically involves nested loops. 

One of the trickiest part of using loops with arrays is making sure that the loop iterates the proper 
number of times. Off‐by‐one errors are easy to make, and trying to access an element that is larger than 
the length of the array can have dire consequences. Consider the code below: 

 
 
int scores[] = { 84, 92, 76, 81, 56 }; 
const int numStudents = sizeof(scores) / sizeof(scores[0]); 
  
int maxScore = 0; // keep track of our largest score 
for (int student = 0; student <= numStudents; ++student) 
    if (scores[student] > maxScore) 
        maxScore = scores[student]; 
  
std::cout << "The best score was " << maxScore << '\n'; 

24 | P a g e  
 
 

The problem with the code is that the conditional in the for loop is wrong. The array declared has 5 
elements, indexed from 0 to 4. However, this array loops from 0 to 5. Consequently, on the last 
iteration, the array will try to access “scores[5]” but this is undefined. You may end up with a garbage 
value which will mean that the value of “maxScore” would be wrong. 

Image what would happen if we inadvertently assigned a value to “scores[5]”. We might overwrite 
another variable (or part of it), or perhaps corrupt some other data. 

When using loops with arrays, always double‐check your loop conditions to make sure you do not 
introduce off‐by‐one errors. 

3. Vectors 
The previous section showed how static arrays can be implemented. This section introduces the usage 
of the vector library, which allows for the creation and manipulation of dynamic arrays. 

The std::vector datatype provides dynamic array functionality that handles its own memory 
management. This means that arrays can be created with their own lengths at runtime, without having 
to explicitly allocate memory on compile time. The std::vector datatype lives in the <vector> header. 

Consider the code snippet below: 

#include <vector> 
 
// no need to specify length at initialization 
std::vector<int> array; 
std::vector<int> array2 = { 9, 7, 5, 3, 1 }; // use initializer list to initialize array 
std::vector<int> array3{ 9, 7, 5, 3, 1 }; // use uniform initialization to initialize 
array (C++11 onward) 

Note that both in the uninitialized and initialized cases, there is no need to include the array length at 
compile time. The std::vector datatype will dynamically allocate memory for its contents as requested. 

Just like any typical array, accessing the elements of the array can be done using the [] operator (which 
does no bounds checking). In addition, the at() function can be used (which does bounds checking). An 
example is given in the code snippet below: 

array[6] = 2; // no bounds checking 
array.at(7) = 3; // does bounds checking 

25 | P a g e  
 
In either case, if an element is requested that is off the end of the array, the vector will not 
automatically resize itself. 

When a vector variable goes out of scope, it automatically deallocates the memory it controls, if 
necessary. This prevents memory leaks in your program. 

Vectors also keep track of their own length using the size() function. An example is given in the code 
below: 

#include <vector> 
#include <iostream> 
 
int main() 

  std::vector<int> array{ 9, 7, 5, 3, 1 }; 
  std::cout << "The length is: " << array.size() << '\n'; 
 
  return 0; 

It is impossible to resize a static array, since its size is set at compile time. However, a vector can be 
resized at any point during runtime, using the resize() function. Consider the code below: 

#include <vector> 
#include <iostream> 
 
int main() 

  std::vector<int> array{ 0, 1, 2 }; 
  array.resize(5); // set size to 5 
 
  std::cout << "The length is: " << array.size() << '\n'; 
 
  return 0; 

The code snippet below illustrates some of the functionality of vectors that can be used: 

// C++ program to illustrate the  
// capacity function in vector  
#include <iostream>  
#include <vector>  
 
using namespace std; 
 
int main() 

  vector<int> g1; 
 
  for (int i = 1; i <= 5; i++) 
    g1.push_back(i); 

26 | P a g e  
 
 
  cout << "Size : " << g1.size(); 
  cout << "\nCapacity : " << g1.capacity(); 
  cout << "\nMax_Size : " << g1.max_size(); 
 
  // resizes the vector size to 4  
  g1.resize(4); 
 
  // prints the vector size after resize()  
  cout << "\nSize : " << g1.size(); 
 
  // checks if the vector is empty or not  
  if (g1.empty() == false) 
    cout << "\nVector is not empty"; 
  else 
    cout << "\nVector is empty"; 
 
  // Shrinks the vector  
  g1.shrink_to_fit(); 
  cout << "\nVector elements are: "; 
  for (auto it = g1.begin(); it != g1.end(); it++) 
    cout << *it << " "; 
 
  return 0; 

 

There are other functions that are useful for vectors, especially modifier functions. A list of these 
functions can be found at http://www.cplusplus.com/reference/vector/vector/ 

4. Strings 
A collection of sequential characters is what we refer to as a string. In C++, we use strings to represent 
text such as name, addresses, words and sentences. String literals, such as “Hello World!” are placed 
between double quotes to identify them as a string. 

4.1 C‐Style Strings 
Before we learn about C++ strings, we need to know about C‐style strings. A C‐style string is simply an 
array of characters that uses a null terminator. A null terminator is a special character (‘\0’, ASCII Code 
0) used to indicate the end of the string. More generically, a C‐style string is called a null‐terminated 
string. 

In order to define a C‐style string, simply declare a char array and initialize it with a string literal: 

char myString[] = "string"; 
 

27 | P a g e  
 
Although “string” only has 6 letters, C++ automatically adds a null terminator to the end of the string for 
us. Consequently, “myString” is actually an array of length 7. 

When declaring strings in this manner, since they are character arrays, it is a good idea to use [] and let 
the compiler calculate the length of the array. That way if you change the string later, you will not have 
to manually adjust the array length. 

C‐style strings follow all the same rules as arrays. You can initialize the string upon creation, but you 
cannot assign values to it using the assignment operator after that. Since C‐style strings are arrays, you 
can use the [] operator to change individual characters in the string. The code below gives an example of 
this: 

#include <iostream> 
  
int main() 

    char myString[] = "string"; 
    myString[1] = 'p'; 
    std::cout << myString; 
  
    return 0; 

 

When printing a C‐styled string, cout prints characters until it encounter a null terminator. If you 
accidentally overwrite the null terminator in a string, you will not only get all the characters in the string, 
but cout will just keep printing everything in adjacent memory slots until it happens to hit a zero. 

There are many cases when we do not know in advance how long our string is going to be. For example, 
consider the problem of writing a program where we need to ask the user to enter their name. How 
long is their name? We do not know until they enter it. It is possible to declare an array larger that we 
need. Consider the code below: 

#include <iostream> 
  
int main() 

    char name[255]; // declare array large enough to hold 255 characters 
    std::cout << "Enter your name: "; 
    std::cin >> name; 
    std::cout << "You entered: " << name << '\n'; 
  
    return 0; 

 

28 | P a g e  
 
Since we have allocated an array of 255 characters to “name”, we assume that the user will not enter 
more than 255 characters. However, this is poor programming practice, because nothing is stopping the 
user from entering more than 255 characters, be it by accident or maliciously. 

The code below shows the recommended way of reading strings using cin: 

#include <iostream> 
int main() 

    char name[255]; // declare array large enough to hold 255 characters 
    std::cout << "Enter your name: "; 
    std::cin.getline(name, 255); 
    std::cout << "You entered: " << name << '\n'; 
  
    return 0; 

 

This call to cin.getline() will read up to 254 characters into name, leaving room for the null terminator. 
Any excess characters will be discarded. This guarantees that the array will not overflow. 

4.2 C++ Strings 
In order to use strings in C++, we first need to #include the <string> header to bring in the declarations 
for std::string. Once that is done, we can define variable of type std::string. Consider the code below: 

#include <string> 
  
std::string myName; 
std::string myName("Alex"); // initialize myName with string literal "Alex" 
myName = "John"; // assign variable myName the string literal "John" 
std::string myID("45"); // "45" is not the same as integer 45! 
 
 

Just like normal variables, you can initialize or assign values to strings as you would expect. Strings can 
hold numbers as well. In string form, numbers are treated as text, not numbers, and thus they cannot be 
manipulated as numbers (you cannot do any math with them). C++ will not automatically convert string 
numbers into integers or floating point values. 

Strings can be output as expected using cout. Consider the code below: 

#include <string> 
#include <iostream> 
  
int main() 

    std::string myName("Alex"); 

29 | P a g e  
 
    std::cout << "My name is: " << myName; 
  
    return 0; 

 

However, using strings with cin may yield some surprises. Consider the code below: 

#include <string> 
#include <iostream> 
  
int main() 

    std::cout << "Enter your full name: "; 
    std::string name; 
    std::cin >> name; // this won't work as expected since std::cin breaks on whitespace 
  
    std::cout << "Enter your age: "; 
    std::string age; 
    std::cin >> age; 
  
    std::cout << "Your name is " << name << " and your age is " << age; 

 

This produces an unexpected result. When using operator “>>” to extract a string from cin, operator 
“>>” only returns characters up to the first whitespace it encounters. Any other characters are left inside 
cin, waiting for the next extraction. 

To read a full line of input into a string, you must use the std::getline() function instead. This function for 
C++ strings takes two parameters: the first is std::cin and the second is your string variable. Consider the 
code below: 

#include <string> 
#include <iostream> 
  
int main() 

    std::cout << "Enter your full name: "; 
    std::string name; 
    std::getline(std::cin, name); // read a full line of text into name 
  
    std::cout << "Enter your age: "; 
    std::string age; 
    std::getline(std::cin, age); // read a full line of text into age 
  
    std::cout << "Your name is " << name << " and your age is " << age; 

Reading inputs with both std::cin and std::getline may cause some unexpected behavior. Consider the 
code below: 

30 | P a g e  
 
#include <string> 
#include <iostream> 
  
int main() 

    std::cout << "Pick 1 or 2: "; 
    int choice; 
    std::cin >> choice; 
  
    std::cout << "Now enter your name: "; 
    std::string name; 
    std::getline(std::cin, name); 
  
    std::cout << "Hello, " << name << ", you picked " << choice << '\n'; 
  
    return 0; 

 

You will notice when the program asks you to enter your name, it will skip waiting for you to enter your 
name. It turns out, when you enter a numeric value using cin, cin not only captures the numeric value, it 
always captures the newline. So when we enter ‘2’, cin actually gets the string “2\n”. It then extracts the 
2 to variable “choice”, leaving the newline stuck in the input stream. Then, when std::getline goes to 
read the name, it sees “\n” is already in the stream, and figures we must have entered an empty string. 

A good rule of thumb is that after reading a numeric value with std::cin, remove the newline from the 
stream using the std::cin.ignore() function. The corrected code can be seen below: 

#include <string> 
#include <iostream> 
  
int main() 

    std::cout << "Pick 1 or 2: "; 
    int choice; 
    std::cin >> choice; 
  std::cin.ignore(32767, '\n'); // ignore up to 32767 characters until a \n is 
removed 
  
    std::cout << "Now enter your name: "; 
    std::string name; 
    std::getline(std::cin, name); 
  
    std::cout << "Hello, " << name << ", you picked " << choice << '\n'; 
  
    return 0; 

 

It is possible to use the ‘+’ operator to concatenate two strings together, or the ‘+=’ operator to append 
one string to another. The code below illustrates this: 

31 | P a g e  
 
#include <string> 
#include <iostream> 
  
int main() 

    std::string a("45"); 
    std::string b("11"); 
  
    std::cout << a + b << "\n"; // a and b will be appended, not added 
    a += " volts"; 
    std::cout << a; 
  
    return 0; 

 

Note that the ‘+’ operator concatenated the strings “45” and “11” into “4511”. 

If we want to know how long a string is, we can ask the string for its length. The syntax for doing this is 
different than you have seen before, but it is pretty straightforward: 

#include <string> 
#include <iostream> 
int main() 
  

    std::string myName("Alex"); 
    std::cout << myName << " has " << myName.length() << " characters\n"; 
    return 0; 

 

The length function is not a normal standalone function like we have used previously. It is a special type 
of function that belongs to std::string, called a member function. 

There are many other member functions of strings that can be found here: 
http://www.cplusplus.com/reference/string/string/ 

Most of these member functions can come in handy when dealing with strings. You are expected to 
know which of these member functions are to be useful, as well as their operation, for both strings and 
vectors. 

5. Files 
File I/O in C++ works very similarly to normal I/O, with a few minor added complexities. There are 3 
basic file I/O classes in C++: 

32 | P a g e  
 
 ifstream: File input 
 ofstream: File output 
 fstream: Both input and output 

To use the file I/O classes, you will need to #include the <fstream> header. 

Unlike the cout and cin streams, which are already ready for use, file streams have to be explicitly set up 
by the programmer. However, this is extremely simple: to open a file for reading and/or writing, simply 
instantiate an object of the appropriate file I/O class, with the name of the file as a parameter. Then use 
the insertion (<<) or extraction (>>) operator to read/write to the file. Once you are done, there are 
several ways to close a file: explicitly call the close() function, or just let the file I/O variable go out of 
scope. 

Consider the following code below which illustrates how to read from a file (file input): 

#include <iostream> 
#include <fstream> //Needed to use the ifstream & ofstream functionalities 
 
int main(){ 
  std::ifstream infile; //input file stream operator used to handle all file‐in 
functions 
  int num, sum=0; 
  infile.open("name.txt"); //opens the specified file “name.txt” 
   
  if (!infile.is_open()) //Verify whether the file exists 
    std::cout<<"Cannot find file"<<std::endl; 
  else{ //The file exists 
    std::cout<<"File found"<<std::endl; 
    while(!infile.eof()){ //execute this block until we reach the end of file 
      infile>>num; //read in a number from the file 
      sum+=num; //add the num to the sum 
      std::cout<<num<<std::endl; //print the number we read in from the 
    } 
    std::cout<<"Sum is "<<sum<<std::endl; //output the sum 
    infile.close(); //close the file 
  } 
 
  return 0; 

 

We instructed the compiler to include the <fstream> library, which would give us use of the “ifstream” 
and “ofstream” functionalities. 

33 | P a g e  
 
We declared a variable named “infile” of type “ifstream” to show that we want to read from a particular 
file. We open the specific file that we want, namely “name.txt”. We expect to be reading integers from 
the file. Hence, “num” and “sum” are of the integer data type. 

Error handling plays an important role in programming. As programmers, there are times you are unable 
to predict the behavior of your programs for specific inputs. Rather than having your program crash 
unexpectedly, you can instruct your program to terminate using your own code. 

If a proper file is not supplied, we do not want the program to crash. We instead inform the user of the 
error that occurred and then terminate the program. We verify whether or not the file exists, using the 
“infile.is_open()” function, when returns a Boolean value. If the file exists, it returns true. Otherwise, it 
returns false. 

A loop is utilized because we do not know how many lines there are within the file. The function 
“infile.eof()” searches for the end of the file, such that the while loop will keep reading the contents of 
the file until the end is reached. 

We then use the “infile.close()” function to close the file stream. 

Consider the following code below which illustrates how to read from a file (file input) and write to a file 
(file output): 

#include <iostream> 
#include <fstream> 
 
int main(){ 
  std::ifstream infile; 
  std::ofstream outfile; //output file stream operator; handles all file‐out 
functions 
  int num, sum=0; 
  infile.open("name.txt"); 
  outfile.open("name_appended.txt"); 
 
  if (!infile.is_open()) //Error detection & Handling 
    std::cout<<"Cannot find file"<<std::endl; 
  else{ 
    std::cout<<"File found"<<std::endl; 
    while(!infile.eof()){ 
      infile>>num; 
      sum+=num; 
      std::cout<<num<<std::endl; 
    } 
    std::cout<<"Sum is "<<sum<<std::endl; 
    outfile<<"Sum is "<<sum; 
    infile.close(); 
    outfile.close();//Close output file 

34 | P a g e  
 
  } 
 
  return 0; 

 

If you search your directory, you will notice that a file “names_appended.txt” has appeared. The 
function “output.open()” searches for the file named in its function parameters, within the directory. If 
the file does not exist, the function creates the file. 

What happens if we write to write to a file that already exists? Normally, the original file is completely 
overwritten each time the program is run. What if, instead, we wanted to append some more data to 
the end of the file? There are parameters that allow you to specify information about how a file should 
be opened. The only parameter we are concerned with is the append parameter. Consider the code 
below: 

#include <iostream> 
#include <fstream> 
  
int main() 

  
    // We'll pass the ios:app flag to tell the ofstream to append 
    // rather than rewrite the file.  We do not need to pass in ios::out 
    // because ofstream defaults to ios::out 
    std::ofstream outf("Sample.txt", std::ios::app); 
  
    // If we couldn't open the output file stream for writing 
    if (!outf) 
    { 
        // Print an error and exit 
        std::cout << "Uh oh, Sample.dat could not be opened for writing!" << std::endl; 
        return ‐1; 
    } 
  
    outf << "This is line 3" << std::endl; 
    outf << "This is line 4" << std::endl; 
     
  return 0; 

 

Note that in creating “outf”, we now pass a second parameter which is “std::ios::app”. This second 
parameter states that any new contents that are being written to the file are to be appended to the end 
of the file, if it already exists. 

35 | P a g e  
 

You might also like