Structures


Objects generally are too complex to be described by a single variable. An array can store multiple values, but all the values in that array must be of the same data type.

The restriction that all values must be of the same data type usually is not workable for objects. For example, each of us as human beings shares common characteristics such as a name and height. The value of each may be stored in a variable. However, these variables have different data types. Height may be stored in an integer or other numeric variable, but a name would be stored in a C-string or a C++ string class variable.

The name and height variables are related in the sense that they both describe different characteristics of the same person. However, if you declare them as follows , they do not belong together, but instead simply are two separate, independent variables:

 string name;  int height; 

C++ enables you to package related variables together into a structure. A structure may contain multiple variables of different data types, permitting the program to more faithfully emulate the complexity of a real-world object.

A structure, in reality, is a data type. However, it is not a data type built into C++, such as an int or a C-string. Instead, it is a programmer-defined data type.

Declaring a Structure

Since a structure is a programmer-defined data type, you must declare it so the compiler will understand what it is. The following code fragment declares a structure representing a person with two characteristics, name and height:

 struct Person {  string name;   int height; }; 

The declaration commences with struct, which is a keyword indicating that a structure is being declared. Person is the name I gave for this structure. I could have used another name. As with naming variables, you should give the structure a name which indicates what it represents.

The open and close curly braces define the body of the structure. Structures, like functions, have a body, enclosed in open and close curly braces. However, unlike functions, the close curly brace must be followed by a semicolon.

Note  

Forgetting the semicolon after the close curly brace is a common rookie mistake. You may not experience a compiler error, but instead a run-time error, particularly on multiple file projects, with the error message providing little or no clue that the real reason for the error is that you forgot the semicolon.

The variables that are related to each other are declared in the body of the structure. Each such variable is referred to as a member variable. A structure may have many member variables. Indeed, as discussed in a later section in this chapter, Nesting Structures, a variable of one structure may be a member variable of another structure.

A structure may be declared pretty much anywhere in your code. However, by convention, a structure usually is declared just below the preprocessor directives and above main, as in the following code fragment:

 #include <iostream> #include <string> using namespace std; struct Person {  string name;   int height; }; int main () {  // code  return 0; } 

You may be wondering: Wait a minute! He is declaring a structure the same place a global variable would be declared and he taught us not to use global variables unless absolutely necessary.

Don t worry, this is not a case of Do what I say, not what I do. When we declare a structure, we are not declaring a variable. Instead, we are declaring a data type. By declaring the data type globally, we will be able to use that data type throughout the program. The reasons for not making variables global don t apply to declaring a data type. For example, you can t assign a value to a Person structure any more than you can assign a value to an int. Instead, you need to declare variables of the structure. That issue is discussed next .

Declaring a Structure Variable

Late on a Saturday evening, I drove to a party to pick up my teenage daughter . The party was noisy , packed with perhaps one hundred teenagers, each in programming parlance an instance of a Person structure, with their own name and height. Some also had nose rings, but I won t talk about that variable. Anyway, I was looking for just one particular person, my daughter. When I went up to the parent hosting the party, I did not ask if they knew where a generic Person was. Instead, I asked if they knew where I could find a particular person, identifying her specifically .

A generic person is the Person structure that in the previous section I showed you how to declare. However, each particular person is an instance of the Person structure, and each such instance needs to be declared as a Person structure variable.

You declare a structure variable essentially the same as you declare a variable of a built-in data type. The following code declares a Person variable:

 Person p1; 

As with the declaration of a variable of a built-in data type, the declaration of a structure variable starts with the data type, then a variable name, and closes with a semicolon to indicate to the compiler the end of the statement.

You also can declare multiple Person variables, such as:

 Person p1, p2, p3; 

Indeed, for the mob scene at the party where I went to pick up my daughter, you might want to declare an array of Person variables:

 Person p[100]; 

You may declare a structure variable essentially anywhere in your program; the same scope and lifetime rules apply just the way they do for integer, float, and other types of variables.

Accessing Structure Member Variables

Declaring a structure variable does not assign values to its member variables. You can access a member variable by the name of a structure variable, a dot operator, which looks like a period, and the name of the member variable. The following code assigns the value Emily Kent to the name member variable of the Person variable p1 :

 p1.name = "Emily Kent"; 

Similarly, you could output the value of the member variable using the same syntax:

 cout << "The name of p1 is " << p1.name; 

Don t make the beginner s mistake of using the name of the structure, rather than the name of the structure variable, as in the following example:

 Person.name = "Emily Kent"; // won't work! 

The following program declares a three-element Person array, assigns values to the member variables of each element, and then outputs their values:

 #include <iostream> #include <string> using namespace std; const int MAX = 3; struct Person {  string name;   int height; }; int main () {  Person p[MAX];  for (int x = 0; x < MAX; x++)  {  cout << "Enter person's name: ";  getline(cin, p[x].name);  cout << "Enter height in inches: ";  cin >> p[x].height;  cin.ignore();   }  cout << "Outputting person data\n";  cout << "======================\n";  for (x = 0; x < MAX; x++)  cout << "Person #" << x + 1 << "'s name is "  << p[x].name << " and height is "  << p[x].height << endl;  return 0; } 

Some sample input and output could be

 Enter person's name: Genghis Khent Enter height in inches: 78 Enter person's name: Jeff Kent Enter height in inches: 72 Enter person's name: Dante Kent Enter height in inches: 10 Outputting person data ====================== Person #1's name is Genghis Khent and height is 78 Person #2's name is Jeff Kent and height is 72 Person #3's name is Dante Kent and height is 10 

Initializing a Structure

There are two ways you may initialize a structure. The first way is to use an initialization list. The second way is to use a constructor.

Initialization Lists

The following code fragment demonstrates how you can initialize a structure with an initialization list:

 Person p1 = {"Jeff Kent", 72}; 

This may seem like d j   vu, since an array is similarly initialized with an initialization list. However, there is an important difference. While all of the elements of an array share the same data type, the member variables of a structure may have different data types. This makes the order of the values in the initialization list particularly important. For example, the following code will result in a compiler error because the first member variable of the structure is a C-string and you cannot assign an integer to a C-string:

 Person p1 = {72, "Jeff Kent"}; // won't work 

Constructors

The constructor was discussed in Chapter 13 in connection with file stream objects. The constructor is a function that is automatically called when you attempt to create an instance of an object.

Default Constructors

You do not need to write a constructor. Indeed, we did not write a constructor in the previous program that declared a three-element Person array, assigned values to the member variables of each element, and then outputted their values.

If you do not write a constructor, then a default constructor is called. The term default means the constructor is supplied by default since you did not write one.

The following program demonstrates the use of the default constructor:

 #include <iostream> #include <string> using namespace std; const int MAX = 3; struct Person {  string name;   int height; }; int main () {  Person p1;  cout << "The person's name is "  << p1.name << " and height is "  << p1.height << endl;  return 0; } 

The output is

 The person's name is and height is -858993460 

The default constructor was called by the following statement, which created a Person instance:

 Person p1; 

The result was that a Person instance was created by the default constructor. However, the member variables were not assigned values. Consequently, the value of the name member variable is an empty string and the value of the height member variable is a garbage value.

No-Argument Constructors

You can write a no-argument constructor that, unlike the default constructor, assigns default values to the member variables. The following code shows the addition of a no-argument constructor inside the body of the declaration of the Person structure:

 struct Person {  string name;   int height;  Person()  {  name = "No name assigned";  height = -1;  } }; 

The constructor itself reads

 Person()  {  name = "No name assigned";  height = -1;  } 

The name of the constructor is always the same as the name of the structure itself; no exceptions. Additionally, the constructor has no return value; again, no exceptions. Indeed, some compilers will object if you put a void return value in the function header.

Modify the program by adding the no-argument constructor to the declaration of the Person structure. The program now reads

 #include <iostream> #include <string> using namespace std; const int MAX = 3; struct Person {  string name;  int height;  Person()  {  name = "No name assigned";  height = -1;  } }; int main () {  Person p1;   cout << "The person's name is "  << p1.name << " and height is "  << p1.height << endl;  return 0; } 

The output now reflects the default values:

 The person's name is No name assigned and height is -1 
Note  

This no-argument constructor, like the default constructor, is called by the statement Person p1 . Though a no argument constructor is called, there are no empty parentheses following p1 since it is not a function call but instead a variable declaration.

Constructors with Arguments

Declaring a no-argument constructor is an improvement over the default constructor since now the member variables have default values. However, it would be even better if we could truly initialize the member variables with values supplied by the user when the program is running. This is possible if we add arguments to the constructor, each argument being the value used to initialize a member variable. Accordingly, add the following two-argument constructor to the definition of the Person structure:

 Person(string s, int h)  {  name = s;  height = h;  } 

The program now reads

 #include <iostream> #include <string> using namespace std; const int MAX = 3; struct Person {  string name;  int height;  Person()  {  name = "No name assigned";  height = -1;  }  Person(string s, int h)  {  name = s;  height = h;  } }; int main () {  int inches;  string strName;  cout << "Enter person's name: ";  getline(cin, strName);  cout << "Enter height in inches: ";  cin >> inches;  cin.ignore();   Person p1(strName, inches);   cout << "The person's name is "  << p1.name << " and height is "  << p1.height << endl;  return 0; } 

The sample input and output could be

 Enter person's name: Jeff Kent Enter height in inches: 72 The person's name is Jeff Kent and height is 72 

The two-argument constructor is called by the following declaration of a Person instance:

 Person p1(strName, inches); 

The parentheses are necessary because there are arguments. The arguments must be in the order the constructor is expecting. If the constructor expects the first argument to be a string and the second to be an integer, a compiler error will result if you declare the Person instance with the first argument being an integer and the second a string.

Separating the Constructor Prototype and Implementation

The following code modifies the previous program by separating the prototype of the constructors from the implementation of the constructors. The prototypes are inside the declaration of the structure. However, the implementations are outside the declaration of the structure:

 #include <iostream> #include <string> using namespace std; const int MAX = 3; struct Person {  string name;  int height;  Person();  Person(string, int); };  Person::Person()  {  name = "No name assigned";  height = -1;  }  Person::Person(string s, int h)  {  name = s;  height = h;  } int main () {  int inches;  string strName;  cout << "Enter person's name: ";  getline(cin, strName);  cout << "Enter height in inches: ";  cin >> inches;  cin.ignore();   Person p1(strName, inches);   cout << "The person's name is "  << p1.name << " and height is "  << p1.height << endl;  return 0; } 

In the function header of each of the constructors, the function name is preceded by the class name and the scope resolution operator (::):

 Person::Person()  Person::Person(string s, int h) 

The reason is that since the function implementation is outside of the class, preceding the function name with the structure name and the scope resolution operator (::) is necessary to tell the compiler that the function belongs to the structure rather than being just another standalone function like the ones you created in Chapter 9.

While the preceding discussion should explain how you can separate the prototype and implementation of the constructors, the question thus far unanswered is why. The reason is in OOP ”one principle is to separate what a function does from how it does it. This enables programmers to later improve how a function does its job without affecting the function s signature (arguments and return value) on which existing programs using the function depend.

Passing Structures as Function Arguments

The following program passes a Person structure instance as an argument to two functions. The setValues function assigns values to the member variables of the structure instance, whereas the getValues function outputs the values of the member variables of the structure instance:

 #include <iostream> #include <string> using namespace std; struct Person {  string name;   int height; }; void setValues(Person&); void getValues(const Person&); int main () {  Person p1;  setValues(p1);   cout << "Outputting person data\n";  cout << "======================\n";  getValues(p1);  return 0; } void setValues(Person& pers) {  cout << "Enter person's name: ";  getline(cin, pers.name);  cout << "Enter height in inches: ";  cin >> pers.height;   cin.ignore(); } void getValues(const Person& pers) {  cout << "Person's name is " << pers.name   << " and height is " << pers.height << endl;  } 

The following is some sample input and output:

 Enter person's name: Genghis Khent Enter height in inches: 78 Outputting person data ====================== Person's name: Genghis Khent Person's height in inches is: 78 

Unlike an array name, a structure s value is not an address. Therefore, to change the values of its member variables when passing a structure as a function argument, the structure needs to be passed by reference or by address. Therefore, in the setValues function, which changes the member variables of the structure passed to it, the structure is passed by reference.

However, the structure also is passed by reference to the getValues function even though that function does not change the value of its member variables. The reason is that less memory is required to pass the address of an object than the object itself, which may take up a lot of bytes. However, here the structure instance in the getValues function s argument list is preceded with the const keyword to prevent the function from inadvertently changing the values inside the structure instance.

Nesting Structures

In previous chapters, we have nested if statements within if statements, and loops within loops . You also may nest a structure within another structure.

Of course, your mother may have told you (or at least mine told me) Just because you can do something doesn t mean you should do it. Here, however, nesting structures is a good idea.

Using our Person structure example, every person has a birthday. A birthday is a date. A date, in turn , may be defined by a structure that contains three member variables, all integers, which represent the month, day, and year of the particular date. The Date structure could be declared as follows:

 struct Date {  int month;  int day;  int year; }; 

The Person structure declaration then would be modified to add a member variable, of the structure Date, named bDay, to represent the person s birthday:

 struct Person {  string name;   int height;  Date bDay; }; 

The following code modifies the previous one by adding the Date structure and the bDay Date member variable to the Person structure, as well as modifying the setValues function to also assign a value to the bDay Date member variable and the getValues function to output the value of that member variable:

 #include <iostream> #include <string> using namespace std; struct Date {  int month;  int day;  int year; }; struct Person {  string name;   int height;  Date bDay; }; void setValues(Person&); void getValues(const Person&); int main () {  Person p1;  setValues(p1);   cout << "Outputting person data\n";  cout << "======================\n";  getValues(p1);  return 0; } void setValues(Person& pers) {  cout << "Enter person's name: ";  getline(cin, pers.name);  cout << "Enter height in inches: ";  cin >> pers.height;   cin.ignore();  cout << "Enter month, day and year of birthday separated by spaces: "  cin >> pers.bDay.month >> pers.bDay.day >> pers.bDay.year;  cin.ignore(); } void getValues(const Person& pers) {  cout << "Person's name: " << pers.name << endl;   cout << "Person's height in inches is: " << pers.height << endl;  cout << "Person's birthday in mm/dd/yyyy format is: "   << pers.bDay.month << "/" << pers.bDay.day   << "/" << pers.bDay.year << endl; } 

The following is some sample input and output:

 Enter person's name: Genghis Khent Enter height in inches: 78 Enter month, day and year of birthday separated by spaces: 3 4 1211 Outputting person data ====================== Person's name: Genghis Khent Person's height in inches is: 78 Person's birthday in mm/dd/yyyy format is: 3/4/1211 

The Date structure must be declared before the Person structure. Otherwise, the compiler would not know what Date was in the declaration of the bDay member variable of the Person structure.

The setValues function sets the value of the person s birthday. It cannot do so by:

 cin >> pers.bDay 

The reason is that bDay is not an integer that can be assigned user input of an integer. Instead, it itself is also a structure. Therefore, it is necessary to drill down further into the member variables of bDay , month , day, and year , as in the following statement:

 cin >> pers.bDay.month >> pers.bDay.day >> pers.bDay.year; 

Similarly, the getValues function cannot output the person s birthday with the statement:

 cout << pers.bDay; 

Instead, it must also drill down further into the member variables of bDay , month , day, and year , as in the following statement:

 cout << "Person's birthday in mm/dd/yyyy format is: "   << pers.bDay.month << "/" << pers.bDay.day   << "/" << pers.bDay.year << endl; 

The nesting of a Date structure variable in a Person structure is an example of containership (discussed previously in this chapter in the section Object-Oriented Programming) in that a person has a birthday.




C++ Demystified(c) A Self-Teaching Guide
C++ Demystified(c) A Self-Teaching Guide
ISBN: 72253703
EAN: N/A
Year: 2006
Pages: 148

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net