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:
https://github.com/PacktPublishing/Deciphering-Object-Oriented-Programming-with-CPP/blob/main/Chapter01/Chp1-Ex8.cpp
#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
:
https://github.com/PacktPublishing/Deciphering-Object-Oriented-Programming-with-CPP/blob/main/Chapter01/Chp1-Ex9.cpp
#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:
https://github.com/PacktPublishing/Deciphering-Object-Oriented-Programming-with-CPP/blob/main/Chapter01/Chp1-Ex10.cpp
#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
.