C++ Tutorial – Getting Started

  • Post author:
  • Post last modified:September 17, 2024
  • Reading time:34 mins read

C++ is a high-level, general purpose programming language providing facilities for data abstraction, object-oriented and generic programming. C++ was designed by Bjarne Stroustrup and the first release came in 1985. C++ is compatible with the C programming language, as C is a subset of C++. C++ has been standardized by the International Organization for Standardization (ISO); the first ISO standard on C++ was published in 1998 (C++98), and there was a major update in 2011 (C++11). C++ has been a top programming language since the 1990s.

1. Establishing a development environment

The best way to learn a programming language is to write programs in it. And, for that, we need a development environment. We need the means to write the code and, then, compile and build the executable file. The development environment depends on the operating system of the computer and also the personal preferences of the programmer. You can use a text editor like vim, or an Integrated Development Environment (IDE), like Eclipse, or VS Code. For building the executable file, g++ compiler can be used. We can install the g++ compiler in Ubuntu Linux using the commands,

$ sudo apt-get update
$ sudo apt-get install build-essential

Also, the make utility should, preferably, be used for building the executable file.

2. Hello, World!

The first program in a programming language is Hello, World! Here is a Hello World! program in C++.

// helloworld.cpp

#include <iostream>

int main ()
{
    std::cout << "Hello, World!\n";
}

The first line starting with a double slash is a comment. It is for human use and is ignored by the compiler. Next, we have the include for the iostream system header file, which is for the standard Input/Output stream facilities. It provides stream classes and objects for I/O operations. There are three standard stream objects provided by iostream. The first is cin, the standard input stream, which by default, is the keyboard. The second is cout, the standard output stream, which by default, is the video display. And, the third is cerr, the standard error stream. This is also, by default, the video display. The operator << puts “Hello, World!\n” on the standard output stream, cout.

We can compile and run our first Hello, World! program as below.

$ g++ helloworld.cpp -o helloworld
$ ./helloworld
Hello, World!

Consider the line,

    std::cout << "Hello, World!\n";

in the above program. It says, “put Hello, World!\n“, on cout, which is a part of the std, standard library namespace. Rather than qualifying the names from std for each use, we can specify “using namespace std” once, and the names from std become visible in the entire scope. And, our Hello, World program is modified as below.

// helloworld.cpp

#include <iostream>
using namespace std;

int main ()
{
    cout << "Hello, World!\n";
}    

We need to compile our modified Hello, World! program and it's time to use the make program. So, we can just say, “make helloworld”, and the helloworld executable is built.

$ make helloworld
g++     helloworld.cpp   -o helloworld
$ ls -ls
total 20
16 -rwxrwxr-x 1 user1 user1 16384 Aug 22 21:09 helloworld
 4 -rw-rw-r-- 1 user1 user1   111 Aug 22 21:05 helloworld.cpp

make has some implicit rules. When we say, “make helloworld”, it looks for a source file with the stem, “helloworld’ in the current directory. It finds “helloworld.cpp”, which has the “.cpp” extension. So, make figures out that it has a C++ source file to compile and it chooses the g++ compiler. And, make builds the helloworld executable file.

In general, programs have multiple source files and might need special libraries to generate the final executable file. A .cpp file is compiled into an object file using the make implicit rule,

$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) foo.cpp

The value of $(CXX) is g++. $(CXXFLAGS) are extra flags for the C++ compiler and these, if required, can be set in the makefile. If set, these are used automatically by the implicit compilation command. Similarly, $(CPPFLAGS) are extra flags for the C preprocessor.

After all the source files are compiled into objects files, the object files need to be linked with relevant libraries to generate the final executable file. The command for generating the executable file (the recipe, as it called) is like,

$(CXX) $(LDFLAGS) $(OBJECTS) $(LOADLIBES) $(LDLIBS) -o foo

$(LDFLAGS) are extra flags for the compiler for invoking the linker. $(LDLIBS) indicate the libraries. $(LOADLIBES) is also for the libraries, but is deprecated. $(OBJECTS) are the object file names separated by spaces. Our makefile for building helloworld is,

# 
# Makefile
#

.PHONY: clean

LDLIBS :=
LDFLAGS :=
OBJECTS := helloworld.o

helloworld: $(OBJECTS)
        $(CXX) $(LDFLAGS) $(OBJECTS) $(LDLIBS) -o helloworld

clean:
        rm -f *.o
        rm -f helloworld

In makefiles, the first character in lines containing commands must be the tab character. So, the lines “$(CXX) $(LDFLAGS) $(OBJECTS) $(LDLIBS) -o helloworld” and the ones containing the rm command must start with the (invisible) tab character. Otherwise, make aborts processing and gives an error. Using the above makefile, we can build helloworld as below.

$ make clean
rm -f *.o
rm -f helloworld
$
$ ls -ls
total 8
4 -rw-rw-r-- 1 user1 user1 111 Aug 22 21:05 helloworld.cpp
4 -rw-rw-r-- 1 user1 user1 193 Aug 22 23:09 Makefile
$
$ make
g++    -c -o helloworld.o helloworld.cpp
g++  helloworld.o  -o helloworld
$
$ ./helloworld
Hello, World!

3. Types and Variables

The basic types in C++ are char, bool, int and double. A type tells about the representation of an object of its type in memory and the operations that can be done with it. A variable is a named object. The type char is the most basic type in a computer. It represents a character. The bool type represents boolean, and can hold either a false, or a true value. Similarly, the types, int and double represent integers and double precision floating point numbers. The following program prints the sizes of objects of different types.

#include <iostream>
using namespace std;

int main ()
{
    cout << "Size of char is " << sizeof (char) << " byte.\n";
    cout << "Size of bool is " << sizeof (bool) << " byte.\n";
    cout << "Size of short is " << sizeof (short) << " bytes. \n";
    cout << "Size of int is " << sizeof (int) << " bytes.\n";
    cout << "Size of long is " << sizeof (long) << " bytes.\n";
    cout << "Size of long long is " << sizeof (long long) << " bytes.\n";
    cout << "Size of float is " << sizeof (float) << " bytes.\n";
    cout << "Size of double is " << sizeof (double) << " bytes.\n";
    cout << "Size of long double is " << sizeof (long double) << " bytes.\n";
}

The above program gives the following result on a x86_64 based system.

$ make sizes
g++     sizes.cpp   -o sizes
$ ./sizes
Size of char is 1 byte.
Size of bool is 1 byte.
Size of short is 2 bytes. 
Size of int is 4 bytes.
Size of long is 8 bytes.
Size of long long is 8 bytes.
Size of float is 4 bytes.
Size of double is 8 bytes.
Size of long double is 16 bytes.

The types short, long and long long are variations of the basic type int. Similarly, float and long double are variations of the basic type double.

Variables are introduced in a program via a declaration like,

extern int x;

It tells the compiler that there is an integer variable named x, and x becomes available for use in statements in the scope. There are two similar sounding terms, declaration, and definition for variables. A declaration just tells about a variable, as in the case of x, above and it becomes available for use in statements. Definition does more than that. Consider the definition of a variable y.

int y;

Here, y is defined as an integer. An object y is created, and space is allocated. Since we have not initialized it, y gets the default initialization. All the global and static variables are initialized to zero. Local variables are not initialized; they contain junk values. However, it is considered a good practice to initialize variables explicitly at the time of definition. So, we could have written the definition of y as,

int y = 0; // y is initialized to 0

Instead of assignment, we can use braces around the values for initialization, as in the examples below.

int p {7};
double q {2.33};
char a {'c'};
bool b {true};

And, instead of specifying the type in the definition, we can say auto, and the compiler makes a guess about the type of the variable from the value of initialization. The auto type can be used when the type of a variable is not important.

auto p = 7;

4. Arithmetic and Relational Operators

C++ provides plus (+), minus (-), multiply (*), divide (/) arithmetic operators. It provides remainder, or modulus (%) operator for integers. It also provides unary plus and unary minus operators. For example,

x + y   // x plus y
+x      // unary plus x
x - y   // x minus y
-x      // unary minus x
x * y   // x multiplied by y
x / y   // x divided by y
x % y   // if x and y are integers, remainder of x divided by y

There are increment (++) and decrement (--) operators. The increment operator adds 1 to the operand and the decrement operator subtracts 1 from the operand. Both the increment and decrement operators can be used as prefix or postfix to the operand. Prefix usage increments or decrements the operand before use, whereas in the case of postfix, the operand is first used in the expression and then the increment or decrement operation is done. For example,

++i; // add 1 to i, prefix
i--; // subtract 1 from i, postfix
x = a [++i]; // prefix: First add 1 to i and then assign a [i] to x
x = *ptr++;  // postfix: First, assign *ptr to x and then increment ptr

The relational operators are less than (<), greater than (>), less than equal to (<=), greater than equal to (>=), equal to (==) and not equal to (!=). The precedence of first four operators is higher than the last two and the precedence of relational operators is less than the binary arithmetic operators. For example,

x < y  // x is less than y
x > y  // x is greater than y
x <= y // x is less than or equal to y
x >= y // x is greater than or equal to y
x + y == a + b // x plus y is equal to a + b
x != -1   // x is not equal to -1

A single equal to sign (=) is the assignment operator. The arithmetic operators can be combined with the assignment operator, like,

<variable> <arithmetic-operator>= <expression>

It is a concise notation for this.

<variable> = <variable> <arithmetic-operator> <expression>

For example,

p = a + b; // compute a + b and assign the result to p
p += a + b; // compute a + b and add the result to p
p -= a + b; // compute a + b and subtract the result from p
p *= a + b; // compute a + b and multiply the result to p
p /= a + b; // compute a + b and divide the result into p
p %= a + b; // If p, a and b are integers, compute a + b 
            // and set p to remainder after dividing (a + b) into p

5. Constants

A constant is something that does not change. There are two types of constants in C++. First, there are constant variables. The qualifier const is prefixed to a variable definition and the variable must be initialized. However, a constant variable is evaluated at runtime. The const qualifier means that this variable is constant and, once defined, cannot be modified during its lifetime.

const int day_hours = 24;     // day_hours is a constant
double r = 2.303;             // r is not a constant
const double q = r;           // q is a constant, evaluation is at runtime

Constant variables are often used in function interfaces. The parameters are passed to a function, as pointers or reference and we do not want the called function to modify data in the calling function. So the parameters are qualified as constant variables. Also, the function return value, especially if it is a pointer or reference and should not be modified, can be qualified as const.

C++ also provides the constexpr keyword. A variable with qualifier constexpr must be initialized and evaluated at compile-time. For example,

#include <iostream>
#include <cmath>

using namespace std;

constexpr double cube (const double x)
{
    return pow (x, 3);
}

int main ()
{
    constexpr double p = 2.3;
    double r = 2.303;

    constexpr double q = p;   // OK, p is constexpr
    constexpr double s = 1.55 * cube (q); //OK, since cube returns constexpr and q is also constexpr
    cout << "s = " << s << '\n';

    cout << "r = " << r << " cube (r) = " << cube (r) << '\n';
}

The variable s is constexpr; it is evaluated at compile-time. Also, note that although cube returns a constexpr, it has been used with non-constant argument r. We can use a constexpr function with non-constant arguments in non-constant contexts. Had it not been this way, we would have to define the same function twice; one returning a constexpr and the other one returning a non-constexpr value.

6. Arrays and Pointers

C++ provides arrays and pointers. An array is a sequence of objects of a base type called elements, with the elements occupying consecutive memory locations. The number of elements in an array is prefixed at compile-time. An array is defined as,

int v [10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

Array of 10 integers

Here, v is an array of 10 integers, which are the elements of the array. An element in an array can be accessed as array-name [index]. The array index starts from zero. So, the array v has 10 elements, v [0], v [1], ..., v [9], and v [7] is the eighth element of the array.

A pointer variable points to an object of a base type. It stores the address of an object; so it is said to be pointing to that object. Using a pointer variable, we can access the object it points to. For example, a pointer to an integer is defined as,

int *ptr;     // ptr is a pointer to an int

[] and * are declaration operators. [] means array of, and * means pointer to.

The unary & operator, in an expression, gives the address of a variable. So, we can assign the address of v [5] as below.

ptr = &v[5];

ptr points to v [5]

Now, ptr points to v [5]. We can access the object a pointer points using the star operator. So, in this case, we can now access v [5] as *ptr.

The unary & is a declaration operator. It gives a reference to a variable, which is a new name for a variable. It is like a pointer, but you do not need to put the star (*) operator to use the object. Also, once a name refers to an object, it is bound to that object and it can not be made to refer to another object.

int i = 77;

int& j = i;

cout << "j = " << j << '\n';  // prints "j = 77"

7. Loops

Like C, C++ provides the while, for, and do-while loops. Let's look at an example of using the for loop. We use the for loop for finding the sum of the elements of an array storing ten integers.

#include <iostream>

using namespace std;

int main ()
{
    int v [10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sum {0};

    for (auto i = 0; i < 10; i++) {
        cout << v [i] << '\n';
        sum += v [i];
    }

    cout << "The sum is " << sum << '\n';
}

It prints the array elements, one per line, and, then, the sum. In C++, we use an alternate form of for statement, called the range-for statement, which is like,

for (type elem : range-expression) 
    ...

The range-expression is a sequence like an array or a vector, or a list of comma separated values in braces. For each iteration of the for loop, the next element of the sequence specified by the range-expression is copied in elem. It is preferable to use type of elem as auto.

#include <iostream>

using namespace std;

int main ()
{
    int v [] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sum {0};

    for (auto x : v) {
        cout << x << '\n';
        sum += x;
    }

    cout << "The sum is " << sum << '\n';
}

Instead of using a copy of each element of the sequence in the loop control variable, elem, we can use the reference, as in the example below.

#include <iostream>

using namespace std;

int main ()
{
    int v [] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sum {0};

    for (auto& x : v) {
        cout << x << '\n';
        sum += x;
    }

    cout << "The sum is " << sum << '\n';
}

8. User-Defined Types

We have seen the basic built-in types - char, bool, int and double. These reflect the basic capabilities of the underlying hardware. These help in writing expressions in a program. In addition to these, C++ provides abstraction mechanisms for making higher level user-defined types that can capture and represent the attributes and behavior of real-life entities. The user-defined types are structures, unions, classes and enumerations.

8.1 Structures

A structure is a collection of data of for an entity. It is similar to records used in languages like Pascal and COBOL. The fields, or members, of a structure, can have different types. For example, the following struct employee has the relevant data fields about employees.

struct employee {
    string empId;
    string name;
    string address;
    string dob;
    int age;
    string doj;
};

The above structure has six fields about an employee. We might gather these data elements for all employees of an organization, and store them as structures, one per employee, in a file on the secondary storage.

8.2 Unions

Unions are like structures, but there is a major difference. Structures contain multiple members; all of them occupy memory and data is stored in all members. Unions also have multiple members, but the memory required for the largest member is allocated for the entire union. At any time, only one member of the union is "active" and the data for that member is stored in the union. Usually there is an additional variable somewhere which tells which member of the union is active at that time. As an example, consider the union dobOrAge and struct employee,

union dobOrAge{
    string dob;
    int approxAge;
};

struct employee {
    string empId;
    string name;
    string address;
    union dobOrAge;
    bool dob;
    string doj;
};

For some employees, the exact date of birth might not be available. For them, we store the approximate age. At any time, the union dobOrAge contains either date of birth, or the age. We might also keep a flag dob indicating whether date of birth or the approximate age of the employee is stored.

8.3 Classes

The structures help in organizing the data for an entity. There is a strong connection between the data for an entity and the operations that can be done using the data. In fact, the data for an entity, and the operations that can be done by the entity make the user defined type, class. The concept of class is central to object oriented programming and provides a fundamental abstraction for developing software systems. For example, consider the class Queue.

class Queue {
    public:
        Queue ();
        void push (const std::string&);
        std::string pop ();
        std::string peekHead () const;
        std::string peekTail () const;
        bool isEmpty () const;
        void printQ () const;
    private:
        struct element* tail;
};

A class comprises of members, which can be data, or attributes, and functions, which are also called methods. The members are classified as public and private. The public members are for the users of the class, and can be accessed from outside the class. The private members are internal to the class; these cannot be accessed from outside the class. The public members define the class for the users. The users of the class do not have to worry about the implementation of the class. Then can just use the class as per the public members.

A queue is a first-in, first-out data structure. It has two ends, called the head and the tail. The elements enter queue from the tail end, progress towards the head, and eventually exit the queue from the head end. The elements progress and exit the queue in the order of entrance to the queue, making the queue a first-in, first-out data structure.

There are multiple ways to implement the queue data structure. One common and effective way to implement the queue is using a linked list of elements. The linked list is managed using two pointers to the elements, the head and the tail. There are two major operations that can be done on a queue and these are push and pop. An element enters the queue at the tail using the push operation and it leaves the queue at the head when there is a pop operation done on the queue. Then, there are auxiliary operations, peek for the head and tail of queue, which tell elements that are currently at the head and tail of the queue respectively. We also want to know whether the queue is empty and, also, we wish to print the queue at any time. We will look at the program for implementing the class Queue in the later part of this post.

8.4 Enumerations

C++ provides enumeration types, and there are two of them, the "plain" enum's and the enum class. A "plain" enum type defines a set of values which a variable of that type can take. For example,

enum PrimaryColors { Red, Green, Blue };

PrimaryColors is a "plain" enum type whose variables can take values from the set { Red, Green, Blue}. However, the names "Red", "Green" and "Blue" are "unscoped"; they are visible in the entire scope where enum PrimaryColors is declared. So, we cannot declare another enum that has the same names.

enum PrimaryColors { Red, Green, Blue };
enum ArtColors { Red, Yellow, Green };    // Error

A better way is to use enum class instead of plain enum's. Then the values are available only in the scope of the class. You cannot use unqualified values like "Red" or "Green"; you have use something like "ArtColors::Red". Now you get the benefit of type safety. The value PrimaryColors::Red is completely different from ArtColors::Red. For example,

enum class PrimaryColors { Red, Green, Blue };
enum class ArtColors { Red, Yellow, Green };
...
PrimaryColors pcolor = Red; // error, "Red" is not available in this scope
PrimaryColors pcolor = ArtColors::Red; // error, pcolor is of type PrimaryColors,
                                       // not ArtColors
PrimaryColors pcolor = PrimaryColors::Red; // OK

The default operators for enum class are assignment, initialization and comparison. We can use static_cast to convert an enum class to an int.

int i = pcolor; //error, cannot convert PrimaryColors to int
int i  = static_cast<int> (pcolor);  // OK

9. Namespaces

Real world programs tend to be large; they need be broken down into logical entities so that the development can be managed. Also, we need to use libraries in our programs, the most common case being the standard library, std. The parts are compiled separately, but they need to be linked together to make the final executable file. Somehow, we need to ensure that the global names do not clash. To this effect, C++ has the mechanism of namespaces. A namespace defines a scope where classes, functions, enumerations, etc, which belong together can be put. After that, we can access a name by qualifying it with a namespace, like std::cout. Or, we could declare "using namespace std;" to access the names in the std namespace.

10. Error Handling

A function does processing based on inputs and an algorithm. While executing the algorithm, it can encounter certain error conditions. Like, it might realize that the divisor in a certain calculation is zero. The errors have categorized like "runtime_error", "out_of_range", "invalid_argument", etc. A function tries to do its work and, if everything goes well, returns the "return-value", or no value in case of void. But if something goes wrong, a function might throw exceptions. So, there are two kinds of functions. First, those which are expected to work without any errors. For these, the calling function just has a variable to copy the return value via assignment. Second, for the functions which might encounter errors, the calling function does a "try", and in case the function throws any error, the calling function catches the errors, and does the error handling work. For example, the queue handling pop () function, removes the element at the head of the queue. But, if the queue is empty, it throws a "runtime_error". The calling function, "catches" the error, and in this case, prints an error message.

try {
    name = q.pop ();
    cout << name << " exited the queue.\n";
}
catch (runtime_error) {
    cerr << "The queue is empty!\n";
}

11. Example Program: An implementation of Queue using a linked list

As an example for the concepts introduced so far, we have a program for queue, implemented using a linked-list. The program is organized in three files Queue.h, Queue.cpp, and userq.cpp. Queue.h is the header file, which needs to be included by any program that wishes to use our queue. It contains the declarations for the Queue class, and the structure used by the elements in the queue. Queue.cpp is the program implementing the queue as a linked-list. There is a pointer, called tail, which points to the last element in the queue. The last element's next pointer points to the first element of the queue. So, the head of the queue is tail -> next, and we use that expression, instead of a separate variable, for the head of the queue. Finally, the userq.cpp is a user program, using the queue. The schematic of the queue looks like this.

Schematic representation of queue implementation

The source code for the header file Queue.h is,

//   Queue.h

struct element {
    std::string *name;
    struct element* next;
};

class Queue {
    public:
        Queue ();
        ~Queue ();
        void push (const std::string&);
        std::string pop ();
        std::string peekHead () const;
        std::string peekTail () const;
        bool isEmpty () const;
        void printQ () const;
    private:
        struct element* tail;
};

The Queue.cpp file is as follows.

// Queue.cpp

#include <iostream>
#include <string>

#include "Queue.h"

using namespace std;

Queue::Queue ()
    : tail { nullptr } 
{
}

Queue::~Queue ()
{
    // delete the existing queue
    while (tail != nullptr) {
        struct element* ptr = tail -> next; // head

	if (ptr == tail)
            tail = nullptr;
	else
	    tail -> next = ptr -> next;

	cout << "Deleting " << *(ptr -> name) << endl;
	delete (ptr -> name);
	delete (ptr);
    }
}

void Queue::push (const string& s)
{
    // Put at the end of queue
    string *s1 = new string (s);
    struct element* ptr = new (struct element);
    ptr -> name = s1;

    if (tail == nullptr)
	ptr -> next = ptr;
    else
    {
	ptr -> next = tail -> next;
	tail -> next = ptr;
    }
    tail = ptr;
    cout << "Added " << *s1 << endl;
}

string Queue::pop ()
{
     // pop the head of the queue
    if (tail == nullptr)
        throw runtime_error ("Queue is empty");
    struct element *ptr = tail -> next; // head of the queue
    string s = *(ptr -> name);

    if (ptr == tail)
	tail = nullptr;
    else
	tail -> next = ptr -> next;
    delete (ptr -> name);
    delete (ptr);
    return s;
}

string Queue::peekHead () const
{
    string s;

    if (tail == nullptr)
        throw runtime_error ("Queue is empty!");
    else
    {
	s = *((tail -> next) -> name);
	return s;
    }
}

string Queue::peekTail () const
{
    string s;
    if (tail == nullptr)
        throw runtime_error ("Queue is empty!");
    else
    {
	s = *(tail -> name);
	return s;
    }
}

bool Queue::isEmpty () const
{
    // returns true if the queue is empty
    return tail == nullptr;
}

void Queue::printQ () const
{
    // print persons in the queue
    
    if (isEmpty ()) {
        cout << "Queue is empty" << endl;
	return;
    }

    int num = 0;
    struct element* ptr = tail -> next; // head

    while (true) {
	cout << ++num << ". " << *(ptr -> name) << endl;
	if (ptr == tail)
	    break;
	else
	    ptr = ptr -> next;
    } 
}

And the user program, userq.cpp is,

// userq.cpp     

#include <iostream>
#include <string>

#include "Queue.h"

using namespace std;

enum class Command {
    Push = 1,
    Pop,
    PeekHead,
    PeekTail,
    IsQueueEmpty,
    PrintQ,
    Quit = 9
};

int main ()
{
    Queue q;

    int input = 0;
    Command command;
    bool over = false;
    string s;

    while (!over) {
        cout << "\nQueue Management\n\n";
        cout << "1. Push: Add to queue\n";
        cout << "2. Pop: Delete head of queue\n";
        cout << "3. Peek: Head of queue\n";
        cout << "4. Peek: Tail of queue\n";
        cout << "5. Is the queue empty?\n";
        cout << "6. Print queue\n";
        cout << "9. QUIT\n\n";
        cout << "Your option: ";
	getline (cin, s);
        try {
            input = stoi (s);  // Convert the string to an integer
        } catch (std::invalid_argument& e) {
            cerr << "Invalid input: Not a number! Press ENTER to continue ";
	    getline (cin, s);
	    continue;
        } catch (std::out_of_range& e) {
            cerr << "Invalid input: Number out of range!  Press ENTER to continue ";
	    getline (cin, s);
	    continue;
        }

	if (input < 1 || input > 9 || input == 7 or input == 8) {
            cerr << "Error in input. Please enter value 1-6 or 9 for Quit. Press ENTER to continue ";
	    getline (cin, s);
	    continue;
	}

        command = static_cast<Command> (input);
        string name;

	switch (command) {
		case Command::Push : 
                    cout << "Enter name: ";
	            getline (cin, name);
                    q.push (name);
		    break;

		case Command::Pop : 
                    try {
                        name = q.pop ();
		        cout << name << " exited the queue.\n";
                    }
                    catch (runtime_error) {
                        cerr << "The queue is empty!\n";
                    }
		    break;

		case Command::PeekHead : 
                    try {
                        name = q.peekHead ();
			cout << name << " is at the beginning of queue.\n";
                    }
                    catch (runtime_error) {
                        cerr << "The queue is empty!\n";
                    }
		    break;

		case Command::PeekTail : 
                    try {
                        name = q.peekTail ();
		        cout << name << " is at the end of the queue.\n";
                    }
                    catch (runtime_error) {
                        cerr << "The queue is empty!\n";
                    }
		    break;

		case Command::IsQueueEmpty : 
                    cout << "Is the queue empty? " << ((q.isEmpty ()) ? "Yes." : "No.") << '\n';
		    break;

		case Command::PrintQ : 
                    cout << "The queue is: \n\n";
                    q.printQ ();
		    break;

		case Command::Quit : over = true;
		    break;
	}
	if (!over) {
	    cout << "Press ENTER to continue ";
	    getline (cin, s);
	}
    }

    cout << "\nEnding the program...\n";
    return 0;
}

The Makefile is,

# 
# Makefile
#

.PHONY: clean

LDLIBS :=
LDFLAGS :=
OBJECTS := Queue.o userq.o

userq: $(OBJECTS)
        $(CXX) $(LDFLAGS) $(OBJECTS) $(LDLIBS) -o  userq

Queue.o: Queue.cpp Queue.h

userq.o: userq.cpp Queue.h

clean:
        rm -f *.o
        rm -f userq

We can compile and build the above program,

$ make
g++    -c -o Queue.o Queue.cpp
g++    -c -o userq.o userq.cpp
g++  Queue.o userq.o  -o  userq
$ 
$ ./userq

Queue Management

1. Push: Add to queue
2. Pop: Delete head of queue
3. Peek: Head of queue
4. Peek: Tail of queue
5. Is the queue empty?
6. Print queue
9. QUIT

Your option: 1
Enter name: Jane Doe
Added Jane Doe
Press ENTER to continue 
...

12. See also

Karunesh Johri

Software developer, working with C and Linux.