Commentary: Records
Steven Zeil
Often we have data items that we would like to group together.
-
Arrays can group arbitrary numbers of pieces of data of the same type
-
Records can group a fixed number of pieces of data, fields, of different types
1 Structs and Classes
In C++, a record is created using a struct
or class
.
- The fields of the record are implemented as data members.
In other programming languages, you are most likely to do this with classes.
Now, struct
s and class
es go well beyond the ability to represent records. They can store functions (“function members”) as well as data members. But, in this module, we are only going to focus on the idea of a record as a way of organizing data fields.
Suppose, for example, that we were writing code that needed to work with times, perhaps to allow us to set and manage alarms. A record for “time” would have fields for the hours, minutes, and seconds. In C++, this could be done as:
struct Time {
int hours;
int minutes;
int seconds;
};
or
class Time {
public:
int hours;
int minutes;
int seconds;
};
The struct
in C++ is actually a holdover from C, the parent language of C++. C++ introduced the class
. A class in C++ can provide members that have varying levels of visibility (public, private, and protected) – the reasons for this are outside the scope of this module.
For now, we are only looking at “public” data members. The only difference between a struct
and a class
in C++ is that
- In a
class
, all members are private until we say otherwise (as I have done in the above example by saying “public:
”). - In a
struct
, all members are public until we say otherwise.
2 Working with Records
2.1 Accessing Fields
Fields are accessed by name, via the “.” operator:
struct Time {
int hours;
int minutes;
int seconds;
};
⋮
Time t1;
t1.hours = 12;
t1.minutes = 0;
t1.seconds = 1;
int hourOfDay = t1.hours;
bool t1IsPM = (t1.hours >= 12);
2.2 Advantages
Some advantages of using records:
-
They can simplify functions by grouping parameters or output return values. For example, without our
Time
struct, our alarm program might wind up with functions likevoid setAlarm (int alarmHours, int alarmMinutes, int alarmSeconds); bool doTimesMatch ( int alarmHours, int alarmMinutes, int alarmSeconds, int clockHours, int clockMinutes, int clockSeconds, );
but with our
Time
struct we can havevoid setAlarm (Time alarmTime); bool doTimesMatch (Time alarmTime, Time clockTime);
Similarly, if we wanted to write a function that returned the time of the next upcoming alarm, without our
Time
struct we would would not be able to do so via areturn
statement, because functions can only return a single value, so we would have to write:void getNextAlarm (int& alarmHours, int& alarmMinutes, int& alarmSeconds);
But with our
Time
struct, we have the choice ofvoid getNextAlarm (Time& alarmTime);
or
Time getNextAlarm ();
-
They can simplify array handling.
Suppose that our program uses arrays to store a series of alarms that have been set. Without our
Time
struct, we would need to use parallel arrays:int setAlarmHours[maxAlarms]; int setAlarmMinutes[maxAlarms]; int setAlarmSeconds[maxAlarms];
But with our
Time
struct, we can writeTime setAlarms[maxAlarms];
-
They can simplify copying. Unlike arrays, structures can be copied.
Without our
Time
struct, our set alarm function would probably look something like:void setAlarm (int alarmHours, int alarmMinutes, int alarmSeconds) { setAlarmHours[numberOfAlarms] = alarmHours; setAlarmMinutes[numberOfAlarms] = alarmMinutes; setAlarmSeconds[numberOfAlarms] = alarmSeconds; ++numberOfAlarms; }
With it, we could write:
void setAlarm (Time alarmTime) { setAlarms[numberOfAlarms] = alarmTime; ++numberOfAlarms; }
2.3 Things We Cannot Do With Records
(At least, not by default.)
-
we cannot compare structs or classes
if (alarmTime == clockTime) // error
-
we cannot read/write structs with the usual operators
cout << alarmTime; // error
If we want to do those, we need to craft custom functions
3 Combining Data Structures
We now have two ways to build new data types from existing ones:
-
Create an array of an existing type.
-
Create a struct with existing types for the data members
We can combine structs and arrays with one another.
The challenge in working with these is
- Always be aware of the “outermost” data type.
-
If it’s an array, you can only do array-like things to it, such as
[i]
- Result might be a primitive type, an array, or a struct
-
If it’s a struct, you can only do struct-like things, such as
=
or.
-
3.1 Structs Within Structs
We can use records as fields of other records:
struct Interval {
Time start;
Time stop;
};
⋮
Interval event;
we can chain dot expressions such as
event.stop.hours = event.start.hours + 1;
3.2 Structs Within Arrays
With a declaration like
Time setAlarms[maxAlarms];
int nextAlarm = ...;
we can combine indexing and dot selection, e.g.,
int nextHour = setAlarms[nextAlarm].hours;
Because setAlarms
is an array of Time
, the “outer” data structure is the array. So we can only do array-things to setAlarms
, such as indexing into it with [nextAlarm]
.
That indexing operation gives us a single value of type Time
. So we can then apply record-things, such as dot selection (“.hours
”), to that.
3.3 Arrays Within Structs
The same principle applies if we want to use an array as a data member of a struct
:
struct AlarmSystem {
Time setAlarms[MaxAlarms];
int numAlarms;
Time clockTime;
};
⋮
AlarmSystem system;
This would allow us to write expressions like system.setAlarms[k].minutes
If we start with system
, we have a struct. So we can only do struct-things to it, such as .setAlarms
.
system.setAlarms
is an array. So we can only do array-things to it, such as [k]
.
system.setAlarms[k]
is a struct. So we can only do struct-things to it, such as .minutes
.