Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Deciphering Object-Oriented Programming with C++
Deciphering Object-Oriented Programming with C++

Deciphering Object-Oriented Programming with C++: A practical, in-depth guide to implementing object-oriented design principles to create robust code

By Dorothy R. Kirk
$37.99 $25.99
Book Sep 2022 594 pages 1st Edition
eBook
$37.99 $25.99
Print
$46.99
Subscription
$15.99 Monthly
eBook
$37.99 $25.99
Print
$46.99
Subscription
$15.99 Monthly

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Sep 23, 2022
Length 594 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781804613900
Category :
Table of content icon View table of contents Preview book icon Preview Book

Deciphering Object-Oriented Programming with C++

Understanding Basic C++ Assumptions

This chapter will briefly identify the basic language syntax, constructs, and features of C++, which you are assumed to have from familiarity either with the basic syntax of C++, C, Java, or similar languages. These core language features will be reviewed concisely. If these basic syntax skills are not familiar to you after completing this chapter, please first take the time to explore a more basic syntax-driven C++ text before continuing with this book. The goal of this chapter is not to teach each of the assumed skills in detail but to briefly provide a synopsis of each basic language feature to allow you to quickly recall a skill that should already be in your programming repertoire.

In this chapter, we will cover the following main topics:

  • Basic language syntax
  • Basic I/O
  • Control structures, statements, and looping
  • Operators
  • Function basics
  • User defined type basics
  • Namespace basics

By the end of this chapter, you’ll have a succinct review of the very basic C++ language skills in which you’re assumed to be proficient. These skills will be necessary in order to move forward with the next chapter successfully. Because most of these features do not utilize OO features of C++, I will refrain from using OO terminology (as much as possible) and will instead introduce appropriate OO terminology when we move into the OO sections of this book.

Technical requirements

Please ensure that you have a current C++ compiler available; you’ll want to try many of the online code examples. Minimally, please download g++ from https://gcc.gnu.org.

Online code for full program examples can be found in the following GitHub URL: https://github.com/PacktPublishing/Deciphering-Object-Oriented-Programming-with-CPP/tree/main/Chapter01. Each full program example can be found in the GitHub under the appropriate chapter heading (subdirectory) in a file that corresponds to the chapter number, followed by a dash, followed by the example number in the chapter at hand. For example, the first full program in Chapter 1, Understanding Basic C++ Assumptions, can be found in the subdirectory Chapter01 in a file named Chp1-Ex1.cpp under the aforementioned GitHub directory.

The Code in Action (CiA) video for this chapter can be viewed at: https://bit.ly/3PtOYjf

Reviewing basic C++ language syntax

In this section, we will briefly review basic C++ syntax. We’ll assume that you are either a C++ programmer with non-OO programming skills, or that you’ve programmed in C, Java, or a similar strongly typed checked language with related syntax. You may also be a long-standing professional programmer who is able to pick up another language’s basics quickly. Let’s begin our brief review.

Comment styles

Two styles of comments are available in C++:

  • The /*   */ style provides for comments spanning multiple lines of code. This style may not be nested with other comments of this same style.
  • The // style of comment provides for a simple comment to the end of the current line.

Using the two comment styles together can allow for nested comments, which can be useful when debugging code.

Variable declarations and standard data types

Variables may be of any length, and may consist of letters, digits, and underscores. Variables are case sensitive and must begin with a letter or an underscore. Standard data types in C++ include the following:

  • int: To store whole numbers
  • float: To store floating point values
  • double: To store double precision floating point values
  • char: To store a single character
  • bool: For boolean values of true or false

Here are a few straightforward examples using the aforementioned standard data types:

int x = 5;
int a = x;
float y = 9.87; 
float y2 = 10.76f;  // optional 'f' suffix on float literal
float b = y;
double yy = 123456.78;
double c = yy;
char z = 'Z';
char d = z;
bool test = true;
bool e = test;
bool f = !test;

Reviewing the previous fragment of code, note that a variable can be assigned a literal value, such as int x = 5; or that a variable may be assigned the value or contents of another variable, such as int a = x;. These examples illustrate this ability with various standard data types. Note that for the bool type, the value can be set to true or false, or to the opposite of one of those values using ! (not).

Variables and array basics

Arrays can be declared of any data type. The array name represents the starting address of the contiguous memory associated with the array’s contents. Arrays are zero-based in C++, meaning they are indexed starting with array element[0] rather than array element[1]. Most importantly, range checking is not performed on arrays in C++; if you access an element outside the size of an array, you are accessing memory belonging to another variable, and your code will likely fault very soon.

Let’s review some simple array declarations (some with initialization), and an assignment:

char name[10] = "Dorothy"; // size is larger than needed
float grades[20];  // array is not initialized; caution!
grades[0] = 4.0;  // assign a value to one element of array
float scores[] = {3.3, 4.3, 4.0, 3.7}; // initialized array

Notice that the first array, name, contains 10 char elements, which are initialized to the seven characters in the string literal "Dorothy", followed by the null character ('\0'). The array currently has two unused elements at the end. The elements in the array can be accessed individually using name[0] through name[9], as arrays in C++ are zero-based. Similarly, the array above, which is identified by the variable grades, has 20 elements, none of which are initialized. Any array value accessed prior to initialization or assignment can contain any value; this is true for any uninitialized variable. Notice that just after the array grades is declared, its 0th element is assigned a value of 4.0. Finally, notice that the array of float, scores, is declared and initialized with values. Though we could have specified an array size within the [] pair, we did not – the compiler is able to calculate the size based upon the number of elements in our initialization. Initializing an array when possible (even using zeros), is always the safest style to utilize.

Arrays of characters are often conceptualized as strings. Many standard string functions exist in libraries such as <cstring>. Arrays of characters should be null-terminated if they are to be treated as strings. When arrays of characters are initialized with a string of characters, the null character is added automatically. However, if characters are added one by one to the array via assignment, it would then be the programmer’s job to add the null character ('\0') as the final element in the array.

In addition to strings implemented using arrays of characters (or a pointer to characters), there is a safer data type from the C++ Standard Library, std::string. We will understand the details of this type once we master classes in Chapter 5, Exploring Classes in Detail; however, let us introduce string now as an easier and less error-prone way to create strings of characters. You will need to understand both representations; the array of char (and pointer to char) implementations will inevitably appear in C++ library and other existing code. Yet you may prefer string in new code for its ease and safety.

Let’s see some basic examples:

// size of array can be calculated by initializer
char book1[] = "C++ Programming"; 
char book2[25];  // this string is uninitialized; caution!
// use caution as to not overflow destination (book2)
strcpy(book2, "OO Programming with C++"); 
strcmp(book1, book2);
length = strlen(book2);
string book3 = "Advanced C++ Programming";  // safer usage
string book4("OOP with C++"); // alt. way to init. string
string book5(book4); // create book5 using book4 as a basis

Here, the first variable book1 is declared and initialized to a string literal of "C++ Programming"; the size of the array will be calculated by the length of the quoted string value plus one for the null character ('\0'). Next, variable book2 is declared to be an array of 25 characters in length, but is not initialized with a value. Next, the function strcpy() from <cstring> is used to copy the string literal "OO Programming with C++" into the variable book2. Note that strcpy() will automatically add the null-terminating character to the destination string. On the next line, strcmp(), also from <cstring>, is used to lexicographically compare the contents of variables book1 and book2. This function returns an integer value, which can be captured in another variable or used in a comparison. Lastly, the function strlen() is used to count the number of characters in book2 (excluding the null character).

Lastly, notice that book3 and book4 are each of type string, illustrating two different manners to initialize a string. Also notice that book5 is initialized using book4 as a basis. As we will soon discover, there are many safety features built into the string class to promote safe string usage. Though we have reviewed examples featuring two of several manners to represent strings (a native array of characters versus the string class), we will most often utilize std::string for its safety. Nonetheless, we have now seen various functions, such as strcpy() and strlen(), that operate on native C++ strings (as we will inevitably come across them in existing code). It is important to note that the C++ community is moving away from native C++ strings – that is, those implemented using an array of (or pointer to) characters.

Now that we have successfully reviewed basic C++ language features such as comment styles, variable declarations, standard data types, and array basics, let’s move forward to recap another fundamental language feature of C++: basic keyboard input and output using the <iostream> library.

Recapping basic I/O

In this section, we’ll briefly review simple character-based input and output with the keyboard and monitor. Simple manipulators will also be reviewed to both explain the underlying mechanics of I/O buffers and to provide basic enhancements and formatting.

The iostream library

One of the easiest mechanisms for input and output in C++ is the use of the <iostream> library. The header file <iostream> contains definitions of data types istream and ostream. Instances of these data types, cin, cout, cerr, and clog, are incorporated by including the std namespace. The <iostream> library facilitates simple I/O and can be used as follows:

  • cin can be used in conjunction with the extraction operator >> for buffered input
  • cout can be used in conjunction with the insertion operator << for buffered output
  • cerr (unbuffered) and clog (buffered) can also be used in conjunction with the insertion operator, but for errors

Let’s review an example showcasing simple I/O:

#include <iostream>
using namespace std;  // we'll limit the namespace shortly
int main()
{
    char name[20];  // caution, uninitialized array of char
    int age = 0;
    cout << "Please enter a name and an age: ";
    cin >> name >> age; // caution, may overflow name var.
    cout << "Hello " << name;
    cout << ". You are " << age << " years old." << endl;
    return 0;
}

First, we include the <iostream> library and indicate that we’re using the std namespace to gain usage of cin and cout (more on namespaces later in this chapter). Next, we introduce the main() function, which is the entry point in our application. Here, we declare two variables, name and age, neither of which is initialized. Next, we prompt the user for input by placing the string "Please enter a name and an age: " in the buffer associated with cout. When the buffer associated with cout is flushed, the user will see this prompt on the screen.

The keyboard input string is then placed in the buffer associated with cout using the extraction operator <<. Conveniently, one mechanism that automatically flushes the buffer associated with cout is the use of cin to read keyboard input into variables, such as seen on the next line, where we read the user input into the variables name and age, respectively.

Next, we print out a greeting of "Hello" to the user, followed by the name entered, followed by an indication of their age, gathered from the second piece of user input. The endl at the end of this line both places a newline character '\n' into the output buffer and ensures that the output buffer is flushed – more of that next. The return 0; declaration simply returns a program exit status to the programming shell, in this case, the value 0. Notice that the main() function indicates an int for a return value to ensure this is possible.

Basic iostream manipulators

Often, it is desirable to be able to manipulate the contents of the buffers associated with cin, cout, and cerr. Manipulators allow the internal state of these objects to be modified, which affects how their associated buffers are formatted and manipulated. Manipulators are defined in the <iomanip> header file. Common manipulator examples include the following:

  • endl: Places a newline character ('\n') in the buffer associated with cout then flushes the buffer
  • flush: Clears the contents of the output stream
  • setprecision(int): Defines the precision (number of digits) used to output floating point numbers
  • setw(int): Sets the width for input and output
  • ws: Removes whitespace characters from the buffer

Let’s see a simple example:

#include <iostream>
#include <iomanip>
using namespace std;   // we'll limit the namespace shortly
int main()
{
    char name[20];     // caution; uninitialized array
    float gpa = 0.0;   // grade point average
    cout << "Please enter a name and a gpa: "; 
    cin >> setw(20) >> name >> gpa;  // won't overflow name
    cout << "Hello " << name << flush;
    cout << ". GPA is: " << setprecision(3) << gpa << endl;
    return 0;
}

In this example, first, notice the inclusion of the <iomanip> header file. Also, notice that setw(20) is used to ensure that we do not overflow the name variable, which is only 20 characters long; setw() will automatically deduct one from the size provided to ensure there is room for the null character. Notice that flush is used on the second output line – it’s not necessary here to flush the output buffer; this manipulator merely demonstrates how a flush may be applied. On the last output line with cout, notice that setprecision(3) is used to print the floating point gpa. Three points of precision account for the decimal point plus two places to the right of the decimal. Finally, notice that we add the endl manipulator to the buffer associated with cout. The endl manipulator will first insert a newline character ('\n') into the buffer and then flush the buffer. For performance, if you don’t need a buffer flush to immediately see the output, using a newline character alone is more efficient.

Now that we have reviewed simple input and output using the <iostream> library, let’s move forward by briefly reviewing control structures, statements, and looping constructs.

Revisiting control structures, statements, and looping

C++ has a variety of control structures and looping constructs that allow for non-sequential program flow. Each can be coupled with simple or compound statements. Simple statements end with a semicolon; more compound statements are enclosed in a block of code using a pair of brackets {}. In this section, we will be revisiting various types of control structures (if, else if, and else), and looping constructs (while, do while, and for) to recap simple methods for non-sequential program flow within our code.

Control structures – if, else if, and else

Conditional statements using if, else if, and else can be used with simple statements or a block of statements. Note that an if clause can be used without a following else if or else clause. Actually, else if is really a condensed version of an else clause with a nested if clause inside of it. Practically speaking, developers flatten the nested use into else if format for readability and to save excess indenting. Let’s see an example:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
int main()
{
    int x = 0;
    cout << "Enter an integer: ";
    cin >> x;
    if (x == 0) 
        cout << "x is 0" << endl;
    else if (x < 0)
        cout << "x is negative" << endl;
    else
    {
        cout << "x is positive";
        cout << "and ten times x is: " << x * 10 << endl;
    }  
    return 0;
}

Notice that in the preceding else clause, multiple statements are bundled into a block of code, whereas in the if and else if conditions, only a single statement follows each condition. As a side note, in C++, any non-zero value is considered to be true. So, for example, testing if (x) would imply that x is not equal to zero – it would not be necessary to write if (x !=0), except possibly for readability.

It is worth mentioning that in C++, it is wise to adopt a set of consistent coding conventions and practices (as do many teams and organizations). As a straightforward example, the placement of brackets may be specified in a coding standard (such as starting the { on the same line as the keyword else, or on the line below the keyword else with the number of spaces it should be indented). Another convention may be that even a single statement following an else keyword be included in a block using brackets. Following a consistent set of coding conventions will allow your code to be more easily read and maintained by others.

Looping constructs – while, do while, and for loops

C++ has several looping constructs. Let’s take a moment to review a brief example for each style, starting with the while and do while loop constructs:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
int main()
{
    int i = 0;
    while (i < 10)
    {
        cout << i << endl;
        i++;
    }
    i = 0;
    do 
    {
        cout << i << endl;
        i++;
    } while (i < 10);
    return 0;
}

With the while loop, the condition to enter the loop must evaluate to true prior to each entry of the loop body. However, with the do while loop, the first entry to the loop body is guaranteed – the condition is then evaluated before another iteration through the loop body. In the preceding example, both the while and do while loops are executed 10 times, each printing values 0-9 for variable i.

Next, let’s review a typical for loop. The for loop has three parts within the (). First, there is a statement that is executed exactly once and is often used to initialize a loop control variable. Next, separated on both sides by semicolons in the center of the () is an expression. This expression is evaluated each time before entering the body of the loop. The body of the loop is only entered if this expression evaluates to true. Lastly, the third part within the () is a second statement. This statement is executed immediately after executing the body of the loop and is often used to modify a loop control variable. Following this second statement, the center expression is re-evaluated. Here is an example:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
int main()
{
    // though we'll prefer to declare i within the loop
    // construct, let's understand scope in both scenarios
    int i; 
    for (i = 0; i < 10; i++) 
        cout << i << endl;
    for (int j = 0; j < 10; j++)   // preferred declaration
        cout << j << endl;      // of loop control variable
    return 0;
}

Here, we have two for loops. Prior to the first loop, variable i is declared. Variable i is then initialized with a value of 0 in statement 1 between the loop parentheses (). The loop condition is tested, and if true, the loop body is then entered and executed, followed by statement 2 being executed prior to the loop condition being retested. This loop is executed 10 times for i values 0 through 9. The second for loop is similar, with the only difference being variable j is both declared and initialized within statement 1 of the loop construct. Note that variable j only has scope for the for loop itself, whereas variable i has scope of the entire block in which it is declared, from its declaration point forward.

Let’s quickly see an example using nested loops. The looping constructs can be of any type, but in the following, we’ll review nested for loops:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
int main()
{
    for (int i = 0; i < 10; i++) 
    {
        cout << i << endl;
        for (int j = 0; j < 10; j++)
            cout << j << endl;
        cout << "\n";
    }
    return 0;
}

Here, the outer loop will execute ten times with i values of 0 through 9. For each value of i, the inner loop will execute ten times, with j values of 0 through 9. Remember, with for loops, the loop control variable is automatically incremented with the i++ or j++ within the loop construct. Had a while loop been used, the programmer would need to remember to increment the loop control variable in the last line of the body of each such loop.

Now that we have reviewed control structures, statements, and looping constructs in C++, we can move onward by briefly recalling C++’s operators.

Reviewing C++ operators

Unary, binary, and ternary operators exist in C++. C++ allows operators to have different meanings based on the context of usage. C++ also allows programmers to redefine the meaning of selected operators when used in the context of at least one user defined type. The operators are listed in the following concise list. We’ll see examples of these operators throughout the remainder of this section and throughout the course. Here is a synopsis of the binary, unary, and ternary operators in C++:

Figure 1.1 – C++ operators

Figure 1.1 – C++ operators

In the aforementioned binary operator list, notice how many of the operators have “shortcut” versions when paired with the assignment operator =. For example, a = a * b can be written equivalently using a shortcut operator a *= b. Let’s take a look at an example that incorporates an assortment of operators, including the usage of a shortcut operator:

score += 5;
score++;
if (score == 100)
    cout << "You have a perfect score!" << endl;
else
    cout << "Your score is: " << score << endl;
// equivalent to if - else above, but using ?: operator
(score == 100)? cout << "You have a perfect score" << endl:
                cout << "Your score is: " << score << endl; 

In the previous code fragment, notice the use of the shortcut operator +=. Here, the statement score += 5; is equivalent to score = score + 5;. Next, the unary increment operator ++ is used to increment score by 1. Then we see the equality operator == to compare score with a value of 100. Finally, we see an example of the ternary operator ?: to replace a simple if - else statement. It is instructive to note that ?: is not preferred by some programmers, yet it is always interesting to review an example of its use.

Now that we have very briefly recapped the operators in C++, let’s revisit function basics.

Revisiting function basics

A function identifier must begin with a letter or underscore and may also contain digits. The function’s return type, argument list, and return value are optional. The basic form of a C++ function is as follows:

<return type> FunctionName (<argumentType argument1, …>)
{
    expression 1…N;
    <return value/expression;>
}

Let’s review a simple function:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
int Minimum(int a, int b)
{
    if (a < b)
        return a;
    else
        return b;
}
int main()
{
    int x = 0, y = 0;
    cout << "Enter two integers: ";
    cin >> x >> y;
    cout << "The minimum is: " << Minimum(x, y) << endl;
    return 0;
}

In the preceding simple example, first, a function Minimum() is defined. It has a return type of int and it takes two integer arguments: formal parameters a and b. In the main() function, Minimum() is called with actual parameters x and y. The call to Minimum() is permitted within the cout statement because Minimum() returns an integer value; this value is passed along to the extraction operator (<<) in conjunction with printing. In fact, the string "The minimum is: " is first placed into the buffer associated with cout, followed by the return value from calling function Minimum(). The output buffer is then flushed by endl (which first places a newline character in the buffer before flushing).

Notice that the function is first defined in the file and then called later in the file in the main() function. Strong type checking is performed on the call to the function by comparing the parameter types and their usage in the call to the function’s definition. What happens, however, when the function call precedes its definition? Or if the call to the function is in a separate file from its definition?

In these cases, the default action is for the compiler to assume a certain signature to the function, such as an integer return type, and that the formal parameters will match the types of arguments in the function call. Often, the default assumptions are incorrect; when the compiler then encounters the function definition later in the file (or when another file is linked in), an error will be raised indicating that the function call and definition do not match.

These issues have historically been solved with a forward declaration of a function included at the top of a file where the function will be called. Forward declarations consist of the function return type, function name and types, and the number of parameters. In C++, a forward declaration has been improved upon and is instead known as a function prototype. Since there are many interesting details surrounding function prototyping, this topic will be covered in reasonable detail in the next chapter.

Important note

The specifier [[nodiscard]] can optionally be added to precede the return type of a function. This specifier is used to indicate that the return value from a function must not be ignored – that is, it must be captured in a variable or utilized in an expression. Should the function’s return value consequently be ignored, a compiler warning will be issued. Note that the nodiscard qualifier can be added to the function prototype and optionally to the definition (or required in a definition if there is no prototype). Ideally, nodiscard should appear in both locations.

As we move to the object-oriented sections in this book (Chapter 5, Exploring Classes in Detail, and beyond), we will learn that there are many more details and quite interesting features relating to functions. Nonetheless, we have sufficiently recalled the basics needed to move forward. Next, let’s continue our C++ language review with user defined types.

Reviewing user defined type basics

C++ offers several mechanisms to create user defined types. Bundling together like characteristics into one data type (later, we’ll also add relevant behaviors) will form the basis for an object-oriented concept known as encapsulation explored in a later section of this text. For now, let’s review the basic mechanisms to bundle together only data in struct, class, and typedef (to a lesser extent). We will also review enumerated types to represent lists of integers more meaningfully.

struct

A C++ structure in its simplest form can be used to collect common data elements together in a single unit. Variables may then be declared of the composite data type. The dot operator is used to access specific members of each structure variable. Here is a structure used in its most simple fashion:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
struct student
{
    string name;
    float semesterGrades[5];
    float gpa;
};
int main()
{
    student s1;
    s1.name = "George Katz";
    s1.semesterGrades[0] = 3.0;
    s1.semesterGrades[1] = 4.0;
    s1.gpa = 3.5;
    cout << s1.name << " has GPA: " << s1.gpa << endl;
    return 0;        
}

Stylistically, type names are typically in lowercase when using structs. In the preceding example, we declare the user defined type student using a struct. Type student has three fields or data members: name, semesterGrades, and gpa. In the main() function, a variable s1 of type student is declared; the dot operator is used to access each of the variable’s data members. Since structs are typically not used for OO programming in C++, we’re not going to yet introduce significant OO terminology relating to their use. It’s worthy to note that in C++, the tag student also becomes the type name (unlike in C, where a variable declaration would need the word struct to precede the type).

typedef and “using” alias declaration

A typedef declaration can be used to provide a more mnemonic representation for data types. In C++, the relative need for a typedef has been eliminated in usage with a struct. Historically, a typedef in C allowed the bundling together of the keyword struct and the structure tag to create a user defined type. However, in C++, as the structure tag automatically becomes the type, a typedef then becomes wholly unnecessary for a struct. Typedefs can still be used with standard types for enhanced readability in code, but in this fashion, the typedef is not being used to bundle together like data elements, such as with a struct. As a related historical note, #define (a preprocessor directive and macro replacement) was once used to create more mnemonic types, but typedef (and using) are certainly preferred. It’s worthy to note when viewing older code.

A using statement can be used as an alternative to a simple typedef to create an alias for a type, known as an alias-declaration. The using statement can also be used to simplify more complex types (such as providing an alias for complex declarations when using the Standard Template Library or declaring function pointers). The current trend is to favor a using alias-declaration to a typedef.

Let’s take a look at a simple typedef compared to a simple using alias-declaration:

typedef float dollars; 
using money = float;

In the previous declaration, the new type dollars can be used interchangeably with the type float. Likewise, the new alias money can also be used interchangeably with type float. It is not productive to demonstrate the archaic use of typedef with a structure, so let’s move on to the most used user defined type in C++, the class.

class

A class in its simplest form can be used nearly like a struct to bundle together related data into a single data type. In Chapter 5, Exploring Classes in Detail, we’ll see that a class is typically also used to bundle related functions together with the new data type. Grouping together data and behaviors relevant to that data is the basis of encapsulation. For now, let’s see a class in its simplest form, much like a struct:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
class Student
{
public:
    string name;
    float semesterGrades[5];
    float gpa;
};
int main()
{
    Student s1;
    s1.name = "George Katz";
    s1.semesterGrades[0] = 3.0;
    s1.semesterGrades[1] = 4.0;
    s1.gpa = 3.5;
    cout << s1.name << " has GPA: " << s1.gpa << endl;
    return 0;      
}

Notice that the previous code is very similar to that used in the struct example. The main difference is the keyword class instead of the keyword struct and the addition of the access label public: at the beginning of the class definition (more on that in Chapter 5, Exploring Classes in Detail). Stylistically, the capitalization of the first letter in the data type, such as Student, is typical for classes. We’ll see that classes have a wealth more features and are the building blocks for OO programming. We’ll introduce new terminology such as instance, to be used rather than variable. However, this section is only a review of skills assumed, so we’ll need to wait to get to the exciting OO features of the language. Spoiler alert: all the wonderful things classes will be able to do also applies to structs; however, we’ll see that structs stylistically won’t be used to exemplify OO programming.

enum and strongly-typed enum

Traditional enumerated types may be used to mnemonically represent lists of integers. Unless otherwise initialized, integer values in the enumeration begin with zero and increase by one throughout the list. Two enumerated types may not utilize the same enumerator names.

Strongly-typed enumerated types improve upon traditional enumerated types. Strongly-typed enums default to represent lists of integers, but may be used to represent any integral type, such as int, short int, long int, char, or bool. The enumerators are not exported to the surrounding scope, so enumerators may be reused between types. Strongly-typed enums allow forward declarations of their type (allowing such uses as these types as arguments to functions before the enumerator declaration).

Let’s now see an example of traditional enums and strongly-typed enums:

#include <iostream>
using namespace std;   // we'll limit the namespace shortly
// traditional enumerated types
enum day {Sunday, Monday, Tuesday, Wednesday, Thursday,
          Friday, Saturday};
enum workDay {Mon = 1, Tues, Wed, Thurs, Fri};
// strongly-typed enumerated types can be a struct or class
enum struct WinterHoliday {Diwali, Hanukkah, ThreeKings,
  WinterSolstice, StLucia, StNicholas, Christmas, Kwanzaa};
enum class Holiday : short int {NewYear = 1, MLK, Memorial,
  Independence, Labor, Thanksgiving};
int main()
{
    day birthday = Monday;
    workDay payday = Fri;
    WinterHoliday myTradition = WinterHoliday::StNicholas;
    Holiday favorite = Holiday::NewYear;
    cout << "Birthday is " << birthday << endl;
    cout << "Payday is " << payday << endl;
    cout << "Traditional Winter holiday is " << 
             static_cast<int> (myTradition) << endl;
    cout << "Favorite holiday is " << 
             static_cast<short int> (favorite) << endl;
    return 0;      
}

In the previous example, the traditional enumerated type day has values of 0 through 6, starting with Sunday. The traditional enumerated type workDay has values of 1 through 5, starting with Mon. Notice the explicit use of Mon = 1 as the first item in the enumerated type has been used to override the default starting value of 0. Interestingly, we may not repeat enumerators between two enumerated types. For that reason, you will notice that Mon is used as an enumerator in workDay because Monday has already been used in the enumerated type day. Now, when we create variables such as birthday or payday, we can use meaningful enumerated types to initialize or assign values, such as Monday or Fri. As meaningful as the enumerators may be within the code, please note that the values when manipulated or printed will be their corresponding integer values.

Moving forward to consider the strongly-typed enumerated types in the previous example, the enum for WinterHoliday is defined using a struct. Default values for the enumerators are integers, starting with the value of 0 (as with the traditional enums). However, notice that the enum for Holiday specifies the enumerators to be of type short int. Additionally, we choose to start the first item in the enumerated type with value 1, rather than 0. Notice when we print out the strongly-typed enumerators that we must cast the type using a static_cast to the type of the enumerator. This is because the insertion operator knows how to handle selected types, but these types do not include strongly-typed enums; therefore, we cast our enumerated type to a type understood by the insertion operator.

Now that we have revisited simple user defined types in C++, including struct, typedef (and using an alias), class, and enum, we are ready to move onward to reviewing our next language necessity, the namespace.

Recapping namespace basics

The namespace utility was added to C++ to add a scoping level beyond global scope to applications. This feature can be used to allow two or more libraries to be utilized without concern that they may contain duplicative data types, functions, or identifiers. The programmer needs to activate the desired namespace in each relevant portion of their application with the keyword using. Programmers can also create their own namespaces (usually for creating reusable library code) and activate each namespace as applicable. In the previous examples, we’ve seen the simple use of the std namespace to include cin and cout, which are instances of istream and ostream (whose definitions are found in <iostream>). Let’s review how we can create namespaces ourselves:

#include <iostream>
// using namespace std; // Do not open entire std namespace
using std::cout;   // Instead, activate individual elements
using std::endl;   // within the namespace as needed
namespace DataTypes
{
    int total;
    class LinkList
    {  // full class definition … 
    };
    class Stack
    {  // full class definition …
    };
};
namespace AbstractDataTypes
{
    class Stack
    {  // full class definition …
    };
    class Queue
    {  // full class description …
    };
};
// Add entries to the AbstractDataTypes namespace
namespace AbstractDataTypes   
{
    int total;
    class Tree
    {  // full class definition …
    };
};
int main()
{
    using namespace AbstractDataTypes; //activate namespace
    using DataTypes::LinkList;    // activate only LinkList 
    LinkList list1;     // LinkList is found in DataTypes
    Stack stack1;    // Stack is found in AbstractDataTypes
    total = 5;       // total from active AbstractDataTypes
    DataTypes::total = 85;// specify non-active mbr., total
    cout << "total " << total << "\n";
    cout << "DataTypes::total " << DataTypes::total;
    cout << endl;
    return 0;        
}

In the second line of the preceding code (which is commented out), we notice the keyword using applied to indicate that we’d like to use or activate the entire std namespace. Preferably, on the following two lines of code, we can instead activate only the elements in the standard namespace that we will be needing, such as std::cout or std::endl. We can utilize using to open existing libraries (or individual elements within those libraries) that may contain useful classes; the keyword using activates the namespace to which a given library may belong. Next in the code, a user specified namespace is created called DataTypes, using the namespace keyword. Within this namespace exists a variable total, and two class definitions: LinkList and Stack. Following this namespace, a second namespace, AbstractDataTypes, is created and includes two class definitions: Stack and Queue. Additionally, the namespace AbstractDataTypes is augmented by a second occurrence of the namespace definition in which a variable total and a class definition for Tree are added.

In the main() function, first, the AbstractDataTypes namespace is opened with the using keyword. This activates all names in this namespace. Next, the keyword using is combined with the scope resolution operator (::) to only activate the LinkList class definition from the DataTypes namespace. Had there also been a LinkList class within the AbstractDataType namespace, the initial visible LinkList would now be hidden by the activation of DataTypes::LinkList.

Next, a variable of type LinkList is declared, whose definition comes from the DataTypes namespace. A variable of type Stack is next declared; though both namespaces have a Stack class definition, there is no ambiguity since only one Stack has been activated. Next, we use cin to read into total, which is active from the AbstractDataTypes namespace. Lastly, we use the scope resolution operator to explicitly read into DataTypes::total, a variable that would otherwise be hidden. One caveat to note: should two or more namespaces contain the same identifier, the one last opened will preside, hiding all previous occurrences.

It is considered good practice to activate only the elements of a namespace we wish to utilize. From the aforementioned example, we can see potential ambiguity that can otherwise arise.

Summary

In this chapter, we reviewed core C++ syntax and non-OO language features to refresh your existing skill set. These features include basic language syntax, basic I/O with <iostream>, control structures/statements/looping, operator basics, function basics, simple user defined types, and namespaces. Most importantly, you are now ready to move to the next chapter, in which we will expand on some of these ideas with additional language necessities such as const qualified variables, understanding and using prototypes (including with default values), and function overloading.

The ideas in the next chapter begin to move us closer to our goal for OO programming, as many of these aggregate skills are used often and matter of factly as we move deeper into the language. It is important to remember that in C++, you can do anything, whether you mean to do so or not. There is great power in the language, and having a solid base for its many nuances and features is crucial. Over the next couple of chapters, the solid groundwork will be laid with an arsenal of non-OO C++ skills, so that we may realistically engage OO programming in C++ with a high level of understanding and success.

Questions

  1. Describe a situation in which flush, rather than endl, may be useful for clearing the contents of the buffer associated with cout.
  2. The unary operator ++ can be used as a pre- or post-increment operator, such as i++ or ++i. Can you describe a situation in which choosing a pre- versus post-increment for ++ would have different consequences in the code?
  3. Create a simple program using a struct or class to make a user defined type for Book. Add data members for title, author, and number of pages. Create two variables of type Book and use the dot operator . to fill in the data members for each such instance. Use iostreams to both prompt the user for input values, and to print each Book instance when complete. Use only features covered in this chapter.
Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Apply object-oriented design concepts in C++ using direct language features and refined programming techniques
  • Discover sophisticated programming solutions with nuances to become an efficient programmer
  • Explore design patterns as proven solutions for writing scalable and maintainable C++ software

Description

Even though object-oriented software design enables more easily maintainable code, companies choose C++ as an OO language for its speed. Object-oriented programming in C++ is not automatic – it is crucial to understand OO concepts and how they map to both C++ language features and OOP techniques. Distinguishing your code by utilizing well-tested, creative solutions, which can be found in popular design patterns, is crucial in today’s marketplace. This book will help you to harness OOP in C++ to write better code. Starting with the essential C++ features, which serve as building blocks for the key chapters, this book focuses on explaining fundamental object-oriented concepts and shows you how to implement them in C++. With the help of practical code examples and diagrams, you’ll learn how and why things work. The book’s coverage furthers your C++ repertoire by including templates, exceptions, operator overloading, STL, and OO component testing. You’ll discover popular design patterns with in-depth examples and understand how to use them as effective programming solutions to solve recurring OOP problems. By the end of this book, you’ll be able to employ essential and advanced OOP concepts to create enduring and robust software.

What you will learn

Quickly learn core C++ programming skills to develop a base for essential OOP features in C++ Implement OO designs using C++ language features and proven programming techniques Understand how well-designed, encapsulated code helps make more easily maintainable software Write robust C++ code that can handle programming exceptions Design extensible and generic code using templates Apply operator overloading, utilize STL, and perform OO component testing Examine popular design patterns to provide creative solutions for typical OO problems

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Sep 23, 2022
Length 594 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781804613900
Category :

Table of Contents

30 Chapters
Preface Chevron down icon Chevron up icon
Part 1: C++ Building Block Essentials Chevron down icon Chevron up icon
Chapter 1: Understanding Basic C++ Assumptions Chevron down icon Chevron up icon
Chapter 2: Adding Language Necessities Chevron down icon Chevron up icon
Chapter 3: Indirect Addressing – Pointers Chevron down icon Chevron up icon
Chapter 4: Indirect Addressing – References Chevron down icon Chevron up icon
Part 2: Implementing Object-Oriented Concepts in C++ Chevron down icon Chevron up icon
Chapter 5: Exploring Classes in Detail Chevron down icon Chevron up icon
Chapter 6: Implementing Hierarchies with Single Inheritance Chevron down icon Chevron up icon
Chapter 7: Utilizing Dynamic Binding through Polymorphism Chevron down icon Chevron up icon
Chapter 8: Mastering Abstract Classes Chevron down icon Chevron up icon
Chapter 9: Exploring Multiple Inheritance Chevron down icon Chevron up icon
Chapter 10: Implementing Association, Aggregation, and Composition Chevron down icon Chevron up icon
Part 3: Expanding Your C++ Programming Repertoire Chevron down icon Chevron up icon
Chapter 11: Handling Exceptions Chevron down icon Chevron up icon
Chapter 12: Friends and Operator Overloading Chevron down icon Chevron up icon
Chapter 13: Working with Templates Chevron down icon Chevron up icon
Chapter 14: Understanding STL Basics Chevron down icon Chevron up icon
Chapter 15: Testing Classes and Components Chevron down icon Chevron up icon
Part 4: Design Patterns and Idioms in C++ Chevron down icon Chevron up icon
Chapter 16: Using the Observer Pattern Chevron down icon Chevron up icon
Chapter 17: Applying the Factory Pattern Chevron down icon Chevron up icon
Chapter 18: Applying the Adapter Pattern Chevron down icon Chevron up icon
Chapter 19: Using the Singleton Pattern Chevron down icon Chevron up icon
Chapter 20: Removing Implementation Details Using the pImpl Pattern Chevron down icon Chevron up icon
Part 5: Considerations for Safer Programming in C++ Chevron down icon Chevron up icon
Chapter 21: Making C++ Safer Chevron down icon Chevron up icon
Assessments Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Filter icon Filter
Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Full star icon 5
(1 Ratings)
5 star 100%
4 star 0%
3 star 0%
2 star 0%
1 star 0%

Filter reviews by


N/A Feb 21, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Feefo Verified review Feefo image
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.