C++: The Basics - eps1.7_Struct
Hello and welcome. Didn’t the past source code examples have enough context for you? No problem, because now we bring structure to our coding skills.
In the last episode we learned about the first user-defined data type, the enumeration, and used it to define our own type. Today we’ll look at another way to create custom data types.
Structures
Like so many other components, the structure has been inherited from the C programming language into C++. With this element logically related data can be combined in one object. They do not have to be of the same data type. And exactly that is the intention, if structures are used. To form a new one from different data types.
Thus, structures show an early approach to Object Oriented Programming (OOP). They are the precursor to classes, but have more limitations. Structures in C can only contain data. That’s why you can find the name ‘Data Structures’ from time to time. Your self-defined enumerations are also data types and can be an element of a structure as well.
In C++ it is possible to define functions in a structure, but even if the compiler allows it, you should rather use a class in this situation. The original idea behind structures is to have a simple object for better memory management. When you instantiate your custom data type, the compiler reserves an area for all elements in memory.
Let’s look at the syntax.
struct TypName
{
ListOfElements;
}ListOfVariables;
// listing1: structs syntax
As you can see in Listing1, the syntax of structures and enumerations is very similar. The keyword struct
introduces the declaration. This is followed by your name for the structure. Inside the curly braces are all the elements. An element consists like a variable of data type and name and ends with a semicolon ;
. If you want you can create first variables of your new datatype after the closing brace. It is important that you tell the compiler the end of the structure with a semicolon.
Why use a struct?
The use of structures is useful when you want to put a group of variables or elements into a certain context. This can be, for example, the description of an object. The elements of the structure define the properties of the object.
Let’s create a pixel in the monitor as a structure. From this we can directly knit a complex example. The properties of a pixel are its position and color. The position on the monitor matrix is defined by the int x
and int y
coordinates. The color is a bit more extensive.
A pixel consists of three subpixels
. These represent the three basic colors
red
, green
and blue
. From the sum of their intensities
all colors of the pixel can be mixed. If no subpixel is lit, the pixel is black. If all subpixels have the maximum intensity, we get white. So much for the phenomenon of additive color mixing from optics.
#include <iostream>
enum class colorRGB
{
red, // equivalent to 0
green, // equivalent to 1
blue // equivalent to 2
};
struct subPixel
{
int intensity;
colorRGB color;
};
struct pixel
{
int x;
int y;
subPixel redSubPixel;
subPixel greenSubPixel;
subPixel blueSubPixel;
}aPixel;
int main()
{
aPixel.x = 24;
aPixel.y = 201;
aPixel.redSubPixel.intensity = 136;
aPixel.redSubPixel.color = colorRGB::red;
aPixel.greenSubPixel.intensity = 76;
aPixel.greenSubPixel.color = colorRGB::green;
aPixel.blueSubPixel.intensity = 198;
aPixel.blueSubPixel.color = colorRGB::blue;
std::cout << "Position x: " << aPixel.x << " y: " << aPixel.y << std::endl;
std::cout << "Red subpixel intesity: " << aPixel.redSubPixel.intensity
<< " color: " << static_cast<int>(aPixel.redSubPixel.color) << std::endl;
std::cout << "Green subpixel intesity: " << aPixel.greenSubPixel.intensity
<< " color: " << static_cast<int>(aPixel.greenSubPixel.color) << std::endl;
std::cout << "Blue subpixel intesity: " << aPixel.blueSubPixel.intensity
<< " color: " << static_cast<int>(aPixel.blueSubPixel.color) << std::endl;
return 0;
}
// listing2: example structure pixel
In Listing, the pixel
structure and all its elements are implemented. For the basic colors colorRGB
we have created an enumeration with the enumerators red
, green
and blue
. subPixel
are themselves a structure and consist of the elements int intensity
and the variable color
with our enumeration as data type.
Each element of a structure is a separate entity. You can see this very well in the enumeration colorRGB
. All elements together form a parent entity, the structure. No matter how many subpixels there are, the shape remains identical.
Now we come to the declaration of the pixel structure. pixel has the elements
int x and
int y for the coordinates. In addition we have the three subpixels
redSubPixel,
greenSubPixel and
blueSubPixel of the
subPixel data type. Finally, the first variable
aPixel` of our new structure is listed.
Important when creating variables of a user defined datatype is that the datatype has been declared before. So you have to write the declaration of the structure above the declaration of the variable. This is the prerequisite for the use. Otherwise the compiler will complain.
Working with structure
When you work with structures, you must distinguish between the form and the content. The form is all the elements, their symbolic names and data types, and arrangement within the structure. The content is the data held by the elements.
The form of our pixel is described, now we fill the structure with content. In the main()
function from Listing2 we assign values to the elements of pixel
. To access the elements according to the syntax, write the name of the object, put a dot .
and add the identifier of the desired element, e.g. aPixel.x
. The dot has the technical term access operator. Then you can assign a value with the operator =
. Pay attention to the correct data type.
In the case of subpixels we want to access an element of an element. To do this, you simply have to line up the names of object, element and element of the element and separate them with a dot, e.g. aPixel.redSubPixel.intensity
. Often you will find in the literature concatenate as a technical term for concatenate.
You have to specify the name of the structure variable and the access operator each time you want to access the element, but otherwise they behave like ordinary variables. You can work with them as usual. So nothing unknown for you.
If you want to output the x
coordinate of the pixel to the console, you can simply use the std::cout
stream, as with any integer variable. If you want to display the color of the subpixel, then you must first execute a cast to type integer, e.g. static_cast<int>(aPixel.redSubPixel.color)
, as required for all enumerations.
Size++ enum / struct
Do you remember we talked about the memory requirements of primitive datatypes? The choice of data type is not only noticeable by the accepted values, the range of values and the set of operators, but also by the occupied space in memory. With the function sizeof()
we have determined the size in bytes of the primitive data types.
How big are actually our user-defined data types?
#include <iostream>
enum class colorRGB
{
red, // equivalent to 0
green, // equivalent to 1
blue // equivalent to 2
} myColor;
struct subPixel
{
int intensity;
colorRGB color;
} aSubpixel;
int main()
{
std::cout << "enum" << std::endl;
std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl; // output = 4 bytes
std::cout << "Size of enum : " << sizeof(myColor) << " bytes" << std::endl; // output = 4 bytes
std::cout << std::endl << "struct" << std::endl;
std::cout << "Size of int element: " << sizeof(aSubpixel.intensity) << " bytes" << std::endl; // output = 4 bytes
std::cout << "Size of enum element: " << sizeof(aSubpixel.color) << " bytes" << std::endl; // output = 4 bytes
std::cout << "Size of struct : " << sizeof(aSubpixel) << " bytes" << std::endl; // output = 8 bytes
return 0;
}
// listing3: size of enum colorRGB and struct subPixel
If you execute the code from Listing3 you get the size of the variable in bytes. As you can see, a variable of the enumeration data type has the size of an integer. This is not surprising, because enumerators are also mapped to integers.
More interesting is the structure. Its elements can have different primitive or more complex data types. Our example structure subPixel
has an integer element and an enumeration element. The two have the size of 4 bytes each. It is almost predictable that our structure has as size 8 bytes and thus the sum of the sizes of its elements.
Unfortunately, this is not the rule you can remember. Let’s look at other structures and their sizes.
#include <iostream>
struct structA
{
short anInt;
float aFloat;
char aChar;
};
struct structB
{
float aFloat;
char aChar;
short anInt;
};
struct structC
{
char aChar;
short anInt;
float aFloat;
};
int main()
{
std::cout << "Size of short: " << sizeof(short) << " bytes" << std::endl // output = 2 bytes
<< "Size of char: " << sizeof(char) << " bytes" << std::endl // output = 1 bytes
<< "Size of float: " << sizeof(float) << " bytes\n" << std::endl; // output = 4 bytes
std::cout << "Size of structA: " << sizeof(structA) << " bytes" << std::endl; // output = 12 bytes
std::cout << "Size of structB : " << sizeof(structB) << " bytes" << std::endl; // output = 8 bytes
std::cout << "Size of structC : " << sizeof(structC) << " bytes" << std::endl; // output = 8 bytes
return 0;
}
// listing4: padding of structs
In Listing4 you can see that the size of a structure is not always equal to the sum of the size of each element. This is due to the padding of the compiler. It adds padding bytes to avoid alignment problems. Padding bytes neither inserted only when an element is followed by another with more memory requirements.
This is too theoretical for me. How can I visualize this?
In structA
we first have a short
variable with 2 bytes. This is followed by a float
variable with 4 bytes. For this reason the compiler reserves 4 byte memory blocks for the elements of the structure. But since the first variable needs only 2 bytes the remaining space is filled up. It does the same with the third char
variable with 1 byte. So we get a total of 12 bytes for the whole structure.
The padding occupies unnecessary memory and therefore the compiler tries to use as few padding bytes as possible. In structA
this does not work better due to the order of the variables.
The order of the variables in structB
is much more suitable. The float
varaible sets the memory block size to 4 bytes. The compiler sees that the two following variables together have less than 4 bytes and writes them into a memory block. Thus the structure needs one block less and has only 8 bytes.
The compiler behaves similarly with structC
. Here it can also save a memory block by merging.
The compiler tries to use memory optimally, but it cannot swap the order of the elements in a structure. But you are able to do this. So think about a reasonable sequence to keep the size of your structure minimal.
Unfortunately, you can’t remember this as a rule either, because each compiler follows its own guidelines for alignment.
That sticks
We got to know our second user-defined data type, the structure. This was taken from the C programming language and already showed the first approach of Object Oriented Programming. But much less complex compared to classes.
You know that with structures you can concatenate several variables of different data types to a new one. In an example we showed that it is even possible to nest structures and declare a structure variable as an element of another structure. You also know the syntax and how to work with structures.
We have looked at how much memory a structure uses and found that the order of the variables within the declaration has a non-negligible influence.
Our portfolio of data types is growing.
I wish you maximum success!
Sources
- [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
- [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
- [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017
- [4] cplusplus.com, ‘Data structures’, 2000-2017. [Online]. Available: http://www.cplusplus.com/doc/tutorial/structures/. [Accessed: 19-Jul-2019].