C++ Structures

C++ Structs

Structures allow programmers to define their own type of data by grouping together several types of variables. They are extremely useful when you need to aggregate different variables of different types to represent a composite object.

Therefore, the most important point for you to understand is that...
A structure is not a variable but a type of variable.

toc_collapse=0;
Contents 

Structs by example: Implementing a Person

For example, let's consider you're writing a program that manages lots of persons. You would need to implement a Person data type so that you can easily manipulate persons individually or altogether. We will implement the Person data type as a structure.

A person has many attributes. Therefore, we're going to group several C++ variables to better define a person. A person has a name, so we'll use an std::string to store it, a person has an age, so we'll use an unsigned int to store it, and so on. Please read the following code and comments carefully.

#include <iostream>
#include <string>

//      Notice the declaration syntax: struct [Name] { [variables] };
struct Person {
        std::string name;       //      A person has a name
        unsigned int age;       //      A person has an age
        unsigned int weight;    //      A person works out or doesn't work out :P
        bool isMan;             //      A person can be a man or a woman
        //      If you feel like adding other attributes continue the variable list
};

//      We now create some persons...

//      You can simply initialize structures like this (order is relevant)
Person aLiNuSh = {"aLiNuSh", 18, 165, true};

//      This structure is not initialized, we're gonna set its members manually
Person bush;

//      You can also have pointers to structures.
//      After all, a Person is a type of variable so why
//      not have a pointer to a Person?
Person * saddam;

int main ()
{
        //      The syntax to set a structure's members
        //      We're using the '.' operator to refer to a structure's members
        bush.name = "I need sunglasses";
        bush.age = 14;
        bush.isMan = true;

        //      You can also read the values from the keyboard or show them on screen like this...
        std::cout << "How much do you think bush weighs in kilograms? ";
        std::cin >> bush.weight;
        std::cout << bush.weight << " kilograms seems about right..." << std::endl;

        //      Respawn Saddam >:)
        //      Make our person pointer point to newly allocated memory
        saddam = new Person;

        //      The syntax for pointers is a little different
        //      We're using the '->' operator to dereference the pointer
        //      and refer to its members
        saddam->name = "The evil dictator";
        saddam->age = 0;
        //      It's good to know that the -> operator is just shorthand for...
        (*saddam).age = 0;

        //      You can do the same things to "saddam" just like with "bush"... (cin and cout)
        std::cout << "Saddam has a new name: " << saddam->name << std::endl;
        std::cout << "What do you think Saddam's age is?\n";
        std::cin >> saddam->age;
       
        //      Saddam dies again... the world is a better place.
        delete saddam;
       
        //      You can obviously set structure pointers to point to other structures alike
        //      The new saddam who needs sunglasses :P
        saddam = &bush;

        return 0;
}

As you've seen structures are pretty easy. Now let's look into some particularities of structs.

The size of a struct

You're probably well aware that variables take up a certain amount of space. For instance, depending on your compiler an int could take up 2 or 4 bytes of memory. In C++, you can easily find this out by using the sizeof operator.

//      You can use the sizeof operator with a type...
cout << "The size of an integer is: " << sizeof(int) << " bytes." << endl;

//      ...or with a variable
int anInteger = 1490;
cout << "The size of an integer is: " << sizeof(anInteger) << " bytes." << endl;

Naturally, the size of a structure is the sum of the sizes of all its constituent parts. Look at the following example.

#include <iostream>
using namespace std;

//      A structure that defines a car.
struct Car
{
        unsigned int nWheels;           //      Number of wheels.
        unsigned int nWindows;          //      Number of windows.
        unsigned int nDoors;            //      Number of doors.
};

int main()
{
        //      The size of three unsigned integers.
        cout << "3 * sizeof(unsigned int) == "
                << 3 * sizeof(unsigned int) << endl;
       
        //      The size of a structure that's made up of three unsigned integers.
        cout << "sizeof(Car) == " << sizeof(Car) << endl;
       
        return 0;
}

On most machines this should output 12 for the first cout statement and 12 again for the second cout statement.
Pretty obvious, right? Well, there is one special case where "The whole is greater than the sum of its parts."
Consider the following scenario where we add a boolean variable to our Car structure.

#include <iostream>
#include <string>

using namespace std;

//      A structure that defines a car
struct Car {
        unsigned int nWheels;  
        unsigned int nWindows; 
        unsigned int nDoors;   
        bool isConvertible;             //      Let's add a boolean variable to the mix.
};

int main()
{
        //      The size of the constituent parts...
        cout << "sizeof(bool) + 3 * sizeof(unsigned int) == "
                << sizeof(bool) << " + " << 3 * sizeof(unsigned int) << " == "
                << sizeof(bool) + 3 * sizeof(unsigned int) << endl;
       
        //      ...and the size of the whole, might just be different.
        cout << "sizeof(Car) == " << sizeof(Car) << endl;
       
        return 0;
}

In this case the size of a boolean variable is one byte and the size of the three integers is 12 so you would expect the size of the structure to be 13. You'll be surprised to find out that will not be the case on some compilers, unless some special flags are used. In my case the size of a Car is 16 bytes as opposed to just 13.

I'll oversimplify this. This happens because computers work faster when they're dealing with a certain "number" of bytes. They don't like dealing with odd numbers of bytes for one thing. They also prefer powers of two a lot and multiples of four. So what the compiler does in this case is it pads the structure with dummy bytes until it gets a size that's a multiple of four. This will make everything flow faster on the memory bus.

Structures and functions

Now that you understand how "big" a structure is you should think of the consequences. An important one comes to mind: passing and returning structures from functions.

Passing a structure by value to a function is a really bad idea. Chances are your structure will be pretty big, I mean that's why you're using it in the first place, to pack a lot of stuff together (otherwise you might be missing the point). Therefore, it is a significantly better idea to use pass by address or pass by reference. Since this is a C++ tutorial, I'll demonstrate pass by reference using the Person structure in our first example.

/**
*       This function takes in a Person and prints it to the screen.
*
*       @param  person  The person that will be printed to the screen
*/

void printPerson(const Person& person)
{
        cout << "Name: " << person.name << endl;
        cout << "Age: " << person.age << endl;
        cout << "Weight: " << person.weight << endl;
        cout << "Sex: " << person.isMan ? "Male" : "Female" << endl;
}

Pass by reference saved us a lot of time. If we had passed the person by value all the data would've had to be copied to a temporary Person variable just so we can print it to the screen and then dump it. That's not too efficient, especially when dealing with structures that contain std::string's for instance.

The other interesting topic is returning a structure from a function. Depending on what kind of project you're working on you could either return a locally declared structure or a dynamically allocated one. I'll demonstrate both.

/**
*       This function returns a new Person initialized according to the parameters.
*
*       @param  name    The new person's name
*       @param  age             The new person's age
*
*       @return A person structure constructed accordingly
*/

Person createPerson(const char * name, unsigned int age)
{
        Person person;
       
        person.name = name;
        person.age = age;
        person.weight = 0;
        person.isMan = false;
       
        return person;
}

/**
*       The same function as the above, except it returns a pointer
*       to a dinamically allocated Person structure.
*/

Person * createPerson(const char * name, unsigned int age)
{
        Person * person = new Person;
       
        person->name = name;
        person->age = age;
        person->weight = 0;
        person->isMan = false;
       
        return person;
}

Both of these method are correct and will work fine, but depending on what you're aiming for, one might be better than the other.

The first one has the advantage of not using dynamic memory allocation, which can be very expensive and hard to deal with, but it's inefficient because it'll copy the data twice. The first time, when the person is initialized and the second time, when the return value of the function is stored into a person variable.

//      A lot of copying being done here.
//      Might be better than a lot of memory allocation though.
Person somePerson = createPerson("Billy", 34);

The second method is efficient in that it only copies the data once (when everything is initialized). Also, you'll find that when working with structures you'll be mostly using pointers or references to them since they're "light" and easy to deal with. If your structures are not that big though you might find that dynamic memory allocation is overkill.

The final topic is modifying structures inside functions. The best way to do this is to have the function take the structure by reference and just modify it inside the function. The worst way of doing this would be to pass the structure by value to the function and then also return the new modified structure by value.

//      Notice that we're not using a constant reference anymore
//      Since we need to modify the data referenced to
void modifyPersonSomehow(Person& person)
{
        //      Mess with the person a little bit...
        person.name.append(" can has cheezburger?");
        person.age += 54;
        person.weight *= 2;
        person.isMan = !person.isMan;
}

Conclusion

Congratulations, you made it through this long tutorial.
This is what I expect you to understand:

  • Structures can be used to pack a lot of variables together. This is useful when you're trying to represent a compound object.
  • The size of a structure is the sum of the sizes of its constituent parts. Unless it needs padding.
  • Always pass structures by address or by reference to functions. It avoids making useless copies that waste performance.

Sahara's picture

Excellent explanation, funny

Excellent explanation, funny variable names too hehehe.

Anonymous's picture

I wanna ask what`s the

I wanna ask what`s the differences between a class type & a structure type.

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.