Charles E. Oyibo
In Ch9: Arrays and Strings, we discussed how to group values of the same type by using arrays. We will now discuss how to group related values that are of different types. C++ provides a structured data type, called a struct (some languages use the term "record") to group items of different types. An array is a homogenous data structure; a struct is typically a heterogenous data structure.
We discuss another structured data type, called a class, in Ch12.
Suppose we want to write a program to process student data. A student record consists of, among other things, the student's name, student ID, GPA, and grades. However, these components are of different types, and as such, we cannot use an array to group all of the items associated with a student. C++ provides a structured data type called struct to group items of different types.
A struct is a collection of a fixed number of components in which the components are accessed by name. The components are called the members of the struct, and may be of different types. The general syntax for defining a struct in C++ is:
struct structName
{
dataType1 identifier1;
dataType2 identifier2;
...
dataTypen identifiern;
};
Note that the semicolon at the end of the struct definition is a part of the syntax. Like any type definition, a struct is a definition, not a declaration. That is, it only defines a data type; no memory is allocated. Once a data type is defined, we can then declare variable of that type.
Consider:
struct studentType
{
string fName;
string lname;
char grade
int testScore
int progScore
double GPA;
};
studentType nmacin1;
Here we define the struct studentType, and subsequently declare the struct variable nmacin1, of the type studentType. The memory allocated is large enough to store fName, lname, grade, testScore, progScore, and GPA. We could also declare a struct variable within the same statement as the definition of a struct:
struct studentType
{
string fName;
string lname;
char grade
int testScore
int progScore
double GPA;
} nmacin1;
Because a struct is typically defined before the definitions of all the functions in the program, declaring a struct variable as a part of the definition of the struct makes that struct variable a global variable. Since we are aware of the side effects of using global variables rather than local ones, we should first define a struct and then declare the struct variable locally.
In arrays we access a component by using the array name together with the relative position (index) of the component. To access a structure member (component), we use the struct variable name together with the member name, separated by a dot (period). The syntax:
structVariableName.memberName
as in:
nmacin1.fname = "Nathan";
nmacin1.lname = "McIntosh";
...
cin >> nmacin1.GPA;
cin >> nmacin1.testScore >> nmacin1.progScore;
...
int score;
score = (nmacin1.testScore + nmacin1.progScore) / 2;
In C++, the dot (.) is an operator, called the member access operator.
We can assign the value of one struct variable to another struct variable of the same type by using the assignment statement. Consider:
studentType arbStudent; //L1
arbStudent = nmacin1; //L2
L1 declares a struct variable of type studentType and L2 copies the content of nmacin1 into arbStudent. The statement in L2 is equivalent to copying each member of the struct studentType member-by-member:
arbStudent.fName = nmacin1.fName;
arbStudent.lName = nmacin1.lName;
// etc...
We compare struct variables member-wise. As with arrays, no aggregate relational operations can be performed on structs. Suppose we want to see whether nmacin1 and arbStudent refer to the same student, we compare their values member-wise, as follows:
if (nmacin1.fName == arbStudent.fName
&& nmacin1.lName == arbStudent.lName)
...
Although we can use an assignment statement to copy the content of one struct into another struct of the same type, we cannot use the relational operator on struct variables. Thus the following would be illegal:
if (nmacin1 == arbStudent) //illegal
...
No aggregate input/output operators are allowed on a struct variable. Data in a struct variable must be read one member at a time. Similarly, the content of a struct variable must be written one member at a time.
Recall that arrays are passed by reference only, and that a function cannot return a value of the type array. However,
| Aggregate Operation | Array | struct |
|---|---|---|
| Arithmetic | No | No |
| Assignment | No | Yes |
| Input/Output | No (except strings) | No |
| Comparison | No | No |
| Parameter Passing | By reference only | By value or by reference |
| Function returning a value | No | Yes |
A list is a set of elements of the same type. Hence, a list has two things associated with it: the values (that is, elements), and the lenght. Because the values and the lenght are both related to a list, we can define a struct containing both items.
const arraySize = 1000;
struct listType
{
int listElem[arraySize]; //
array containing the list
int listLength; //
lenght of the list
};
listType intList; // declares intList to be a
struct variable
of the type listType
Observe what happens here: We define listType as a struct variable with two members, listElem, an array of 1000 components of the type int, and listLenght, of the type int. Moreover, intList.ListElem accesses the member listElem and intList.listLength accesses the member listLength. So, we could have the following statements:
intList.listLength = 0;
intList.listElem[0] = 12;
intList.listLength++;
intList.listElem[1] = 37;
intList.listLenght++;
Recall that when we pass variables by value, the corresponding formal parameter receives a copy of that value. If, say a struct, has several data members and, say, some of those members are arrays, passing a parameter by value might require, in addition to large amounts of storage space, a considerable amount of computer time to copy the value of the actual parameter into the formal parameter. In this case, an efficient way to pass a variable as a parameter is by reference.
Suppose our company has 50 full-time employees, and we need to print their monthly paychecks and keep track of how much money has been paid to each employee in the year-to-date. We could first define an employee record in a struct:
struct employeeType
{
string fName;
string lName;
int personID;
... // etc.
};
Now, each employee has the following records: first name, last name, personal ID, etc., and because we have 50 employees, and the data type of each struct member within each record is the same, we can use an array of 50 employees to process the employees' data. Hence:
employeeType employees[50];
declares an array employees of 50 components and type employeeType. That is, every element of the array employees is a struct. We can now process each component of each array (each "component" being a struct):
int counter;
ifstream infile;
for (counter = 0; counter < 50; counter ++)
{
infile >> employees[counter].fName
>> employees[counter].lName
>> employees[counter].personID;
// etc.
}
We now look at situations where it is beneficial to organize data in a struct by using another struct. Let's say we have a struct called employeeType that holds the entire range of employee record from first- and last name to address to hire date, department, cell phone number, salary, ...
We immediately see that we could do better with organizing employee record. So instead of one all-encompassing struct (employeeType), we could subcategorize employeeType by defining the following structs: nameType, addressType, dateType, and contactType.
struct nameType
{
string first;
string middle;
string last;
{;
struct addressType
{
string address1;
string address2;
string city;
string state;
string zip;
{;
struct dateType
{
int month;
int day;
int year;
{;
struct contactType
{
string phone;
string cellphone;
string fax;
string pager;
string email;
{;
Let's rebuild the employee's record as follows:
struct employeeType
{
nameType name;
addressType address;
dateType hiredate;
dateType quitDate;
contactType contact;
double salary;
{;
The information in the new employee struct is easier to manage than the previous one. Some of this struct can be reused to build another struct. Next let us declare a variable of the type employeeType and discuss how to access its members. Consider:
employeeType newEmployee;
newEmployee.salary = 45678.00;
newEmployee.name.first = "Mary";
newEmployee.name.middle = "Ann";
newEmployee.name.last = "Shields";
Note that newEmployee has a member called name. We access this member via newEmployee.name. Note also that newEmployee.name is a struct and has three members. We apply the member access criteria to access the member first of the struct newEmployee.name. So newEmployee.name.first is the member where we want to store the first name.
The following statement declares employees to be an array of 100 components, wherein each component is of the type employeeType. The subsequent for loop reads and stores the names of 100 employees in the array employees. Because employees is an array, we use the index to access the components; and because employee[index] is a struct, we apply the member access criteria to select a particular member:
employeeType employees[100];
for (int index = 0; index < 100; index++)
cin >> employee[index].name.first
>>
employee[index].name.middle
>>
employee[index].name.last;
Page Last Updated: Saturday February 12, 2005 10:21 AM