dot operator
member
structure
structure assignment operator
structure pointer operator
This unit teaches you how to build your own data types! In addition to the regular data types such as int, float, and double, you can now have data types called George, Paul, and Ringo if you want.
The most important reason for defining your own data types is to group other data types together. As a matter of fact, in a nutshell, that's what this unit is all about. Visual C++ gives you primary data types from which you can represent virtually any
low-level value such as a single number, character, or string (through an array). After you finish this unit, you'll be able to define data types that are aggregate collections of Visual C++'s fundamental data types.
Sometimes, defining your own data types eliminates the need to program using parallel arrays. Instead of a parallel array that represents several types of data, you can define an array of your own data types that represents data. However, your excursion through parallel arrays was not in vain. Advanced Visual C++ programmers often create parallel arrays of their own data types to perform high-level data access.
Use the struct statement to define your own data types. struct tells Visual C++ to treat a collection of fundamental data types as a single data type from which you can define variables and arrays of variables.
Structures are a very important part of C++. In C, structures provided a way to represent a collection of data. In C++, structures are far more important because they are a fundamental building block to representing an object. A structure represents the
data needed to describe an object. In the next lesson, you will explore the concepts behind object-oriented programming. But first, you will explore the mechanics of structures.
Arrays (and also pointers to data lists) are a powerful method of grouping a lot of data together, but they have one drawback: All data elements must be of the same data type. You can't create a 10-element array in which half the elements are integers
and half are floating-point values.
There might be times, however, when such a mixed data-typed array would come in handy. A local television station wants to track its weekday broadcast audience (an int or long int) and the cost of each average airtime hour per day (a float). You'd need
five integer values and five floating-point values, and you would pair two at a time for each day of the week. Two parallel arrays are the only means that you know of right now for achieving such a database.
Such combined data almost always works better and makes for easier coding when you store it in structures instead of parallel arrays. One of the easiest ways to begin thinking about structure data is to picture the 3x5 cardfiles you've seen or used.
Such cardfiles usually contain names of contact people, their phone numbers, possibly their ages, and all sorts of other related but differently formatted data.
definition
A structure is a programmer-defined collection of other data types.
Structures are often called records in other programming languages and in database systems.
Another good example of the perfect data for Visual C++ structures is a stack of rental applications for an apartment. Each application contains the same format and collection of information: the prospective tenant's name, address, income, Social
Security number, and so on. Each application contains completely different facts and figures, but they all have the same format.
When you define a structure, you define the format (similar to a blank form) of the data you want the structure to hold. When you ask Visual C++ to define a structure variable, Visual C++ makes a place in memory that combines all the data types in each
structure member. The members in the structure correspond to each blank on a form. Through assignment statements, user input, and file input, programs initialize structure variables with unique data in each of the structure variable's fields.
Members are often called fields in other programming languages and in database systems.
Members of structures should have something in common with each other; they always should describe the same thing or object. This might be a real object or something imaginary. If you have several things you need to hold data for in your program,
each of these things should have its own structure. If you own a car rental company and you want to have records describing your cars and your customers, you should have a structure for a car and a structure for your customer. If you had one structure,
thinking that only one customer can rent a car at a time, you would soon run into trouble when a customer rented two cars. You would need to have the information about the customer in two places at once. What would you do with structures for cars that
weren't rented? Hey, this isn't a data analysis course! Let's get on with structures. Just remember that a structure is more than just a convenient way of putting several data items together.
When you define a structure, you tell Visual C++ exactly what format you want that structure to take. For example, suppose that your company currently keeps its inventory on 3x5 cards that look like the one in Figure 17.1. It would be your job to
convert that cardfile system to a Visual C++ program.
Figure 17.1. Each cardfile contains the same structure.
definition
A member is an individual data value within a structure.
Each card in the cardfile has the same format. In other words, each card has a place for a product description, a retail price, and so on. Each card shares a uniform format, or structure, with the other cards. Each card contains the same details.
Therefore, each card could be considered a structure variable, and each data item on each card would be a member of that structure variable. Your job is to convert the card into a structure, convert the card's items into members, and convert each card's
facts and figures into data for the structure variables.
The first thing you must do to convert the cardfile to a program using Visual C++ structures is to determine the data types for each of the members. Table 17.1 lists good data type suggestions for the card inventory. If a data type is char *, that can
mean either a character array holding a string or a character pointer pointing to a string.
You must decide in advance how much space to allow for each text member that will hold string data. You'll store each string member in an array, so you must decide in advance how long that array will be. Make the array long enough to hold the longest
but most reasonable data value you expect. In other words, don't make the array too short, because you won't be able to hold needed descriptions, but at the same time, don't allow for a lot of unused space by making the array too large.
Member Name | Data Type | Length of String | Description |
partID | char * | 4 | Unique part ID. No duplicates are allowed. |
descrip | char * | 15 | Description of the item. |
quant | int | Quantity in inventory currently. | |
retPrice | float | Retail price. | |
whoPrice | float | Wholesale price. | |
reorder | int | Reorder quantity. When the quantity gets to this level, the item should be reordered. |
If you were defining the data in Table 17.1 in separate variables, you might do so like this:
char partID[5]; // Leave room for the terminator char descrip[16]; int quant; float retPrice; float whoPrice; int reorder;
Instead of using separate variables, it would be nice to be able to refer to these items as a whole. When you pick up one of the 3x5 inventory cards, you're manipulating all the data at once. If you were to define a structure variable from these items,
you could manipulate the single structure variable and treat the collection as a single entity instead of as six separate variables.
The struct statement defines structures. As mentioned earlier, when you define a structure, you don't actually define variables at that time. (Technically, there's a way to define both the structure and variables at once, but it's rarely done.) Defining
structure variables requires two steps:
definition
A structure name is a name you can assign to a structure's format.
Here is the format of the struct statement that you must use when defining the format of data:
struct [structureName] { member definition; member definition; // One or more member definitions can follow member definition; }; // Remember the required semicolon!
Putting the inventory items in a structure definition is easy. All you have to do is take the separate variable definitions shown earlier and surround them with the opening and closing lines of the struct statement. You don't have to supply a structure
name. The brackets around structureName in the format indicate that the name is optional. However, in C++ it is normal to use a name, and a name is required if you define the structure format in one place and a structure variable in another. You can think
of a structure name as a data type name from now on. In C, structure names are called tags, so you might see this terminology elsewhere.
Here is an example of a structure definition for the inventory described earlier:
struct Invent // Defines a new data type named invent { char partID[5]; // A part ID code. Leave room for the null zero. char descrip[16]; // A description of the item int quant; // The number of items in the inventory float retPrice; // Retail price float whoPrice; // Wholesale price int reorder; // Level reached before reordering }; // The semicolon is required
Remember that this struct statement defines only the format of the structure, not the structure variable. After this struct statement executes, the program can define int variables, float variables, and also Invent variables. Before this struct, Visual
C++ knew about the built-in data types, but it had no idea what an Invent data type was.
Put comments to the right of members to describe the data each member holds.
One thing to note about the struct statement is that it looks quite like a function definition without any code. However, a struct declaration is a statement in its own right, and it must have that final semicolon that all C++ statements have. Structure
data types have their first letter capitalized to help distinguish them from variables.
Now that Visual C++ recognizes the Invent data type, you can use the structure tag Invent to define variables. The following statement defines three inventory structure variables:
Invent item, item2, item3;
When you define an integer variable, you precede the variable name with int like this:
int i;
When you define a structure variable, you precede the variable name with structureName, as done for the three inventory parts. Figure 17.2 shows what the variable item1 looks like. The boxes next to the members represent how many bytes of memory
each member consumes using the Visual C++ compiler.
Figure 17.2. The format of the item1 structure variable.
What does Visual C++ put in the structure variable's members? The answer is the same as for any other kind of variable: Visual C++ doesn't put anything in the structure automatically. The structure will contain garbage. If you want data in the structure
variable members, you have to put data in the structure variable, as described in the next section.
One nice advantage that C++ offers over C is that you don't have to use the struct keyword when defining structure variables. The following C-like definition is identical to the previous one:
struct Invent item, item2, item3;
Listing 17.1 doesn't contain a complete program, but it demonstrates how to define a structure for a radio station's listener database. Instead of defining individual, separately named structure variables, this program defines an
array of structure variables.
The struct statement defines a structure and gives the structure a name used for defining variables from structures.
1: // Filename: RADIOST.CPP 2: // Defines a structure for a radio station listener 3: // database and defines an array of structure 4: // variables for that database 5: 6: // Before defining structure variables, 7: // you must define the structure's format 8: struct RadioList 9: { 10: char listName[25]; // Listener name 11: char dateFirst[9]; // Date first tuned in (i.e., 11/02/93) 12: int age; 13: char sex; 14: int progSegment; // Favorite daily program segment number 15: }; 16: 17: void main() 18: { 19: struct RadioList listeners[100]; // Define 100 variables 20: RadioList *owner; // A pointer to a structure 21: // variable 22: // Rest of program would follow
There is no output because this program is incomplete.
Analysis
You'll see the pattern in Listing 17.1 throughout your career as a Visual C++ programmer. Most Visual C++ programmers define their structures globally before the main() function. Remember that the structure definition doesn't define variables, so you
won't be defining global variables just because the structure definition appears before main().
If you use the same structure definitions often, put them in their own header files and include them at the top of whatever programs need to define structure variables. If you must make a change to the structure definition, you can do so in one place without changing all the source code files that use the structures.
Line 19 uses the structure definition to define 100 occurrences of the listener variables in an array. (Of course, you could define 100 separately named variables, but that wouldn't be useful, as you already know.) Keep in mind, however, that line 19
defines a lot of data! Not only does line 19 define 100 variables, but each variable is actually a collection of five other member variables, two of which are also arrays. A single element of the listeners array, such as listeners[16], takes approximately
39 bytes of data!
You can't predict by adding individual data sizes exactly how much memory a structure variable will consume. Visual C++ might add hidden padding between members to help with memory organization. There is always only one way to determine the size of any data in Visual C++by using the sizeof operator.
Figure 17.3 shows what the listeners array looks like. Each element in the array contains an individual set of five members that look like the RadioList structure's format.
Figure 17.3. The organization of the listeners array.
Line 20 of Listing 17.1 defines a pointer to a structure. Not only can you define pointers to the built-in data types, but you can also define pointers to the data types that you define. (You must first define the structure before defining a pointer to
a structure that is not built-in.) You won't see the full advantage of using pointers to structures in this unit, but in the next unit you'll fully understand how pointers to your structures can help you manage memory effectively. If line 20 included the
struct keyword, the results would be the same.
A pointer to a structure can point only to data with that particular format. In other words, if Listing 17.1 defined a second structure in addition to the listener structure, owner couldn't point to variables defined from that second structure; owner
could point only to variables defined from the listeners structure.
The dot operator and structure pointer operator access data of the members of structure variables.
There are two places in a program that you can initialize structure variables. You can initialize structure variables when you define them, and also in the body of the program through the usual mechanisms of assignment, user input, and file input.
Rarely will you initialize structures when you define them, but for completeness, you should know how to put data in structure variables at definition time. You'll see a pattern here, because you initialize structure variables the same way you
initialize array datausing braces. The following struct defines a simple structure that contains a float, an int, and a char array:
struct S { float f; int i; char name[15]; }; // Define the structure
To define a structure variable without initializing the data, you already know that this will work:
S aVar;
To assign and initialize at the same time you define the variable, do this:
S aVar = {10.5, 14, "Paul Jones"};
The order of the data in the braces must match the order of the members. This kind of assignment is available only at the time you define the structure variable, just as assigning a list of values to arrays is possible only when you define arrays.
If you want to define and initialize an array of structures, list the data values consecutively:
S aVars[3] = { {10.5, 14, "Paul Jones"}, {73.4, 8, "Kim London"}, {19.5, 56, "William Meck"} };
Each group of inner braces initializes a new structure variable.
You can't initialize structure variables directly using the literal braces once you've defined the variables.
Inside the program's body, assigning data to structures takes just a little more effort. You must learn about two new operators before you put data in structure variables. They are the dot operator and the structure pointer operator. Table 17.2
describes each of these operators.
Operator | Description |
. | Accesses data in a member of an individual structure variable. |
-> | Accesses data in a member of a structure pointed to by a pointer. |
The dot operator and the structure pointer operator access data one member at a time. Therefore, if the structure variable contains 15 members, you'll need 15 statements
to assign data to the entire structure variable.
Here is the format of the dot operator's usage:
structureVariableName.memberName
Here is the format of the structure pointer operator's usage:
pointerToStructure->memberName
You'll never see a structure variable on the left of the -> operator, only a pointer to a structure. That's how you know which to use. If you want to store data in a specific structure variable's member, use the dot operator. If you want to store
data in a member of a structure that's pointed to by a structure pointer, use the -> operator.
Newcomers to Visual C++ programming often feel that the dot operator is easier to understand than the structure pointer operator. Part of the reason they feel this way is that they don't see the need for pointers to structures until they learn about
dynamic memory allocation (which you'll learn about in the next unit). Until you get to the next unit, leave the -> alone and concentrate on the dot operator.
Here is the S structure definition:
struct S { float f; int i; char name[15]; }; // Define the structure
Given this definition, if you were to define three nonarray variables like
S aVar1, aVar2, aVar3; // Define three nonarray variables
you then could put data in the members of aVar1 like this:
aVar1.f = 12.34; // Fills up the first member aVar1.i = 23; // Fills up the second member strcpy(aVar1.name, "Sally Lake"); // Fills up the third member
When you grab the correct structure variable by putting it on the left side of the dot operator, the right side of the dot operator tells Visual C++ exactly which member from that particular operator is to be assigned. As usual, if you want to store
data in character arrays, you'll have to use the strcpy() function, because you can't assign to arrays directly.
Here's some code that would fill the other two variables:
aVar2.f = 84.5; aVar2.i = 3; strcpy(aVar2.name, "Tim Deer"); aVar3.f = 56.3; aVar3.i = 16; aVar3.name[0] = 'A'; aVar3.name[1] = 'n'; aVar3.name[2] = 'n'; aVar3.name[3] = ' '; aVar3.name[4] = 'H'; aVar3.name[5] = 'u'; aVar3.name[6] = 'f'; aVar3.name[7] = 'f'; aVar3.name[8] = '\0';
Do you understand the last few assignments? You don't have to assign strings to character arrays using strcpy(). If you like, you can store one character at a time. Just because the character array is part of a structure variable, that doesn't affect
what you can do with the array. The existence of the structure variable simply affects how to get to the data, because you must preface the data with the name of the structure variable using the dot operator.
If an array of S variables was defined instead of separately named nonarray variables, the dot operator would work exactly as it does with nonarray variables. The only thing you must be sure of is to put the variable's subscript to the left of the dot,
because the subscript goes with the structure variable and not with the member. For example, the following definition defines an array of three S variables:
S aVar[3]; // An array of three variables
The following code assigns each of these array elements the same data just assigned to the individual variables. However, the subscripts determine which structure variable is being assigned:
aVar[0].f = 12.34; // Fills up the first member aVar[0].i = 23; // Fills up the second member strcpy(aVar[0].name, "Sally Lake"); // Fills up the third member aVar[1].f = 84.5; aVar[1].i = 3; strcpy(aVar[1].name, "Tim Deer"); aVar[2].f = 56.3; aVar[2].i = 16; aVar[2].name[0] = 'A'; aVar[2].name[1] = 'n'; aVar[2].name[2] = 'n'; aVar[2].name[3] = ' '; aVar[2].name[4] = 'H'; aVar[2].name[5] = 'u'; aVar[2].name[6] = 'f'; aVar[2].name[7] = 'f'; aVar[2].name[8] = '\0';
Yikes! The last few assignments show subscripts on both sides of the dot operator. Nothing is really new here. The left side of the dot indicates which of the array structure variables is being assigned to, and the subscript on the right of the dot operator determines the member's individual element you're assigning to.
Just in case you're following this, I'll now confuse you further! There is nothing stopping you from having a structure that contains another structure in C++. You have already seen that a structure name can be thought of as a data type. Consider the
following code:
struct Point { int x; int y; }; struct Rectangle { Point topLeft; Point bottomRight; }; Rectangle rect = {{10,10},{100,200}}; int left = rect.topLeft.x; rect.bottomRight.y = 300; rect.topLeft = rect.bottomRight; // !!! What does this do?
To access each level, you use the dot operator. So the first assignment reads "Using the variable rect, get me the member topLeft, and then using that member, get me the member x." In real programs, you might even have arrays of structures.
The important thing to note is that you perform each member access in a right-to-left fashion, evaluating the result before going to the next level of member access. Note the braces in the initialization of the rect variable. Each level of structure can be
initialized as a separate unit.
The dot operator lets you access the data of individual members of structure variables.
Structures can be assigned to other structures of the same type.
We have talked about structures being used as data types. You can do all sorts of operations with standard Visual C++ data types. With structures, you can see that you can't add and subtract them sensibly. Visual C++ does allow you to use the assignment
operator (=) with them. So the following is sensible and acceptable:
struct S { char name[30]; int number; } ... S iAmNotANumber = {"The Prisoner",6}; S numberOne; numberOne = iAmNotANumber;
However, the following code is not allowed:
if (numberOne == iAmNotANumber)
Visual C++ always creates a default assignment operator if you do not create one (defining operators is beyond the scope of this book). On the other hand, Visual C++ does not create an equality operator for you. In fact, most operations are not allowed
on structures by default; only assignment and copying (which are not quite the same thing in C++) are allowed. As far as you are concerned, the default assignment operator works by assigning each member one by one. For simple data this is just what you
need. Sounds like there is a catch, right? There is! What happens if you assign a structure that contains a pointer? The pointer gets copied, not the data that the pointer points to. This means that you have two structures with a pointer pointing to the
same data. In the next unit, you will explore some code that shows the problem.
Structures can be passed as parameters in the same way as any other data type.
A structure can be passed by value, by address, or by reference:
struct S { int x; int y; }; void AValueFunction(S s); void AnAddressFunction(S *s); void AReferenceFunction(S &s); ... void main() { S sArg = {1,2}; AValueFunction(sArg); AnAddressFunction(&sArg); // address of AReferenceFunction(sArg);
This code fragment shows that the parameters work just like any other standard data type. It is important to remember that a structure can hold a lot of data. Recall that pass by value creates a copy of the variable passed so that the
called function can't change the original value. Creating a copy of a large structure will be inefficient. In fact, C++ invents a special operation to copy a structure. (By the way, I'm being pedantic here; there is a subtle difference between the
assignment operator that I mentioned earlier and the copy function. You'll learn about how C++ does the copying in Lesson 10.) As you can see, if you have a structure with a pointer in a structure, the data accessed by the pointer can be changed,
even when passed by value.
To avoid processing large amounts of data on the parameter list, you should always pass a structure by reference. Remember that by reference means that the called function is given the actual data of the argument, not a copy. Similarly, you can
use the address operator. Lazy C++ programmers don't like typing all those pointers rather than dots, so they often prefer by reference. Once passed, the data would be accessed as follows for each of the previous functions:
void AValueFunction(S s) { int x = s.x; } void AnAddressFunction(S *s) { int x = s->x; } void AReferenceFunction(S &s) { int x = s.x; }
Remember that you can use the const keyword to ensure that reference or address parameters are not changed in the called function.
Those examples show you something else about scope. Note that C++ can tell the difference between the local variable x in the called functions and the x that belongs to the structure. The structure x's belong only to the structure and can't be accessed
without reference to the structure name. C++ calls this new scope class scope because a structure is a special sort of a C++ class, which you will learn about in the next lesson.
Listing 17.2 contains a simple example of capturing data into a record and then displaying it, which explores the joys of structure passing and access.
1:// Filename: CONTACT.CPP 2:// Store and show contacts 3:// 4:#include <iostream.h> 5: 6:struct Contact 7: { 8: char name[50]; 9: int age; 10: char phoneNo[15]; 11: }; 12: 13:// Maximum number of contacts 14:const int MAXCONTACTS = 10; 15: 16:// Prototypes 17:int AddContact(Contact& contact); 18:void DisplayContact(const Contact& contact); 19:void DisplayAllContacts(const Contact contacts[MAXCONTACTS],int count); 20: 21:void main() 22: { 23: Contact contacts[MAXCONTACTS]; 24: int count = 0; 25: while( count < MAXCONTACTS )// while more room 26: { 27: if (AddContact(contacts[count])) 28: count++; 29: else 30: break; 31: } 32: DisplayAllContacts(contacts,count); 33: } 34:// 35:// DisplayContact displays a single name 36:// 37:void DisplayContact(const Contact& contact) 38: { 39: cout << endl; 40: cout << "Name : " << contact.name << endl; 41: cout << "Age : " << contact.age << endl; 42: cout << "phone no: " << contact.phoneNo << endl; 43: cout << endl; 44: } 45:// 46:// DisplayAllContacts calls DisplayContact to show 47:// all entered names 48:void DisplayAllContacts(const Contact contacts[MAXCONTACTS], 49: int count) 50: { 51: for (int i = 0; i < count; i++) 52: { 53: DisplayContact(contacts[i]); 54: } 55: } 56:// 57:// Add contact asks for one contact 58:// 59:int AddContact(Contact& contact) 60: { 61: char answer; 62: cout << "Do you want to add a contact [Y]es/[N]o: "; 63: cin >> answer; 64: cin.ignore(80,'\n'); // skip rubbish 65: 66: if (answer == 'y' || answer == 'Y') 67: { 68: cout << "Name: "; 69: cin.getline(contact.name,30); 70: cout << "Phone no: "; 71: cin.getline(contact.phoneNo,15); 72: cout << "Age "; 73: cin >> contact.age; 74: cin.ignore(80,'\n'); 75: return 1; // Added name ok 76: } 77: else 78: return 0; // Did not add name 79: }
Output
Do you want to add a contact [Y]es/[N]o: y Name: Stanley B. Lippman Phone no: 0-201-54848-8 Age 20 Do you want to add a contact [Y]es/[N]o: y Name: Robert Arnson Phone no: 0-679-7921-3 Age 70 Do you want to add a contact [Y]es/[N]o: y Name: Matthias Hansen Phone no: +44 123 4567 Age 30 Do you want to add a contact [Y]es/[N]o: n Name : Stanley B. Lippman Age : 20 phone no: 0-201-54848-8 Name : Robert Arnson Age : 70 phone no: 0-679-7921-3 Name : Matthias Hansen Age : 30 phone no: +44 123 4567
Analysis
This program consists of a few very simple routines. For a start, notice how the structure makes it easy to pass around large amounts of data. The Contact structure defined in lines 6 through 11 contains three variables, but the parameter list of
AddContact in line 17 only needs a single parameter. Structures do make it easy to handle large amounts of information.
The main program in lines 21 through 33 simply asks for as many contacts as possible, up to the maximum it can store. When the user has entered the maximum, or the user has entered all the data, the loop stops and the information is displayed. Note that
line 27 uses the return value of AddContact (which only adds a single contact) to decide whether to increment the stored contact's count. It is very important to keep counts and arrays in synchronization. The clever part of C++ is that a reference can
refer to an individual array member as well as a single declared value. This means that the code in AddContact in lines 68 to 73 that accesses the members does not need to know that there is an array of contacts. The routine would work well however you
chose to store the contactseven if it was part of another structure.
DisplayAllContacts passes the maximum number of items to be shown. With arrays, you must be careful to keep track of which members are valid. C++ cannot tell which elements have entries and which do not by default. You could put an indicator to show
which entries are present and check each one. Again, in line 53, the routine takes advantage of the reference parameter to pass an individual contact to the DisplayContact function. By breaking the function up in this way, the code for displaying is much
simplified and could be reused if you changed your mind about how the contact data was to be held.
AddContact and DisplayContact are good examples of simple object-oriented functions. They understand about the one objectthe contactand know how to perform a task with that object. In the next lesson, you'll see how you can associate
data not only with structures, but also with functions.
struct S { int i; char * c; char c2[100]; float x; long int l; };
struct Astruct { char c[10]; char c2; int i; float x; }; AStruct aVar = {"abc", 'X', 4, 34.3};
struct Book { char title[25]; char author[16]; int quant; float retPrice; float whoPrice; }
Book myBook;
strcpy(title.myBook, "A Thousand Tales");