Search

S.4.7 — Structs

There are many instances in programming where we need more than one variable in order to represent an object. For example, to represent yourself, you might want to store your name, your birthday, your height, your weight, or any other number of characteristics about yourself. You could do so like this:

However, you now have 6 independent variables that are not grouped in any way. If you wanted to pass information about yourself to a function, you’d have to pass each variable individually. Furthermore, if you wanted to store information about someone else, you’d have to declare 6 more variables for each additional person! As you can see, this can quickly get out of control.

Fortunately, C++ allows us to create our own user-defined aggregate data types. An aggregate data type is a data type that groups multiple individual variables together. One of the simplest aggregate data types is the struct. A struct (short for structure) allows us to group variables of mixed data types together into a single unit.

Declaring and defining structs

Because structs are user-defined, we first have to tell the compiler what our struct looks like before we can begin using it. To do this, we declare our struct using the struct keyword. Here is an example of a struct declaration:

This tells the compiler that we are defining a struct named Employee. The Employee struct contains 3 variables inside of it: a short named id, an int named age, and a double named wage. These variables that are part of the struct are called members (or fields). Keep in mind that Employee is just a declaration -- even though we are telling the compiler that the struct will have member variables, no memory is allocated at this time. By convention, struct names start with a capital letter to distinguish them from variable names.

Warning: One of the easiest mistakes to make in C++ is to forget the semicolon at the end of a struct declaration. This will cause a compiler error on the next line of code. Modern compilers like Visual Studio 2010 will give you an indication that you may have forgotten a semicolon, but older or less sophisticated compilers may not, which can make the actual error hard to find.

In order to use the Employee struct, we simply declare a variable of type Employee:

This defines a variable of type Employee named joe. As with normal variables, defining a struct variable allocates memory for that variable.

It is possible to define multiple variables of the same struct type:

Accessing struct members

When we define a variable such as Employee joe, joe refers to the entire struct (which contains the member variables). In order to access the individual members, we use the member selection operator (which is a period). Here is an example of using the member selection operator to initialize each member variable:

As with normal variables, struct member variables are not initialized, and will typically contain junk. We must initialize them manually.

In the above example, it is very easy to tell which member variables belong to Joe and which belong to Frank. This provides a much higher level of organization than individual variables would. Furthermore, because Joe’s and Frank’s members have the same names, this provides consistency across multiple variables of the same struct type.

Struct member variables act just like normal variables, so it is possible to do normal operations on them:

Initializing structs

Initializing structs by assigning values member by member is a little cumbersome, so C++ supports a faster way to initialize structs using an initializer list. This allows you to initialize some or all the members of a struct at declaration time.

If the initializer list does not contain an initializer for some elements, those elements are initialized to a default value (that generally corresponds to the zero state for that type). In the above example, we see that frank.wage gets default initialized to 0.0 because we did not specify an explicit initialization value for it.

C++11/14: Non-static member initialization

Starting with C++11, it’s possible to give non-static (normal) struct members a default value:

Unfortunately, in C++11, the non-static member initialization syntax is incompatible with the initializer list and uniform initialization syntax. For example, in C++11, the following program won’t compile:

Consequently, in C++11, you’ll have to decide whether you want to use non-static member initialization or uniform initialization. Uniform initialization is more flexible, so we recommend sticking with that one.

However, in C++14, this restriction was lifted and both can be used. If both are provided, the initializer list/uniform initialization syntax takes precedence. In the above example, Rectangle x would be initialized with length and width 2.0. In C++14, using both should be preferred, as it allows you to declare a struct with or without initialization parameters and ensure the members are initialized.

We talk about what static members are in chapter 8. For now, don’t worry about them.

Assigning to structs

Prior to C++11, if we wanted to assign values to the members of structs, we had to do so individually:

This is a pain, particularly for structs with many members. In C++11, you can now assign values to structs members using an initializer list:

Structs and functions

A big advantage of using structs over individual variables is that we can pass the entire struct to a function that needs to work with the members:

In the above example, we pass an entire Employee struct to printInformation() (by value, meaning the argument is copied into the parameter). This prevents us from having to pass each variable individually. Furthermore, if we ever decide to add new members to our Employee struct, we will not have to change the function declaration or function call!

The above program outputs:

ID:   14
Age:  32
Wage: 24.15

ID:   15
Age:  28
Wage: 18.27

A function can also return a struct, which is one of the few ways to have a function return multiple variables.

This prints:

The point is zero

Nested structs

Structs can contain other structs. For example:

In this case, if we wanted to know what the CEO’s salary was, we simply use the member selection operator twice: myCompany.CEO.wage;

This selects the CEO member from myCompany, and then selects the wage member from within CEO.

You can use nested initializer lists for nested structs:

Struct size and data structure alignment

Typically, the size of a struct is the sum of the size of all its members, but not always!

Consider the Employee struct. On many platforms, a short is 2 bytes, an int is 4 bytes, and a double is 8 bytes, so we’d expect Employee to be 2 + 4 + 8 = 14 bytes. To find out the exact size of Employee, we can use the sizeof operator:

On the author’s machine, this prints:

The size of Employee is 16

It turns out, we can only say that the size of a struct will be at least as large as the size of all the variables it contains. But it could be larger! For performance reasons, the compiler will sometimes add gaps into structures (this is called padding).

In the Employee struct above, the compiler is invisibly adding 2 bytes of padding after member id, making the size of the structure 16 bytes instead of 14. The reason it does this is beyond the scope of this tutorial, but readers who want to learn more can read about data structure alignment on Wikipedia. This is optional reading and not required to understand structures or C++!

Accessing structs across multiple files

Because struct declarations do not take any memory, if you want to share a struct declaration across multiple files (so you can instantiate variables of that struct type in multiple files), put the struct declaration in a header file, and #include that header file anywhere you need it.

Struct variables are subject to the same rules as normal variables. Consequently, to make a struct variable accessible across multiple files, you can use the extern keyword in the declaration in the header and define the variable in a source file.

Final notes on structs

Structs are very important in C++, as understanding structs is the first major step towards object-oriented programming! Later on in these tutorials, you’ll learn about another aggregate data type called a class, which is built on top of structs. Understanding structs well will help make the transition to classes that much easier.

The structs introduced in this lesson are sometimes called plain old data structs (or POD structs) since the members are all data (variable) members. In the future (when we discuss classes) we’ll talk about other kinds of members.

Quiz

1) You are running a website, and you are trying to keep track of how much money you make per day from advertising. Declare an advertising struct that keeps track of how many ads you’ve shown to readers, what percentage of ads were clicked on by users, and how much you earned on average from each ad that was clicked. Read in values for each of these fields from the user. Pass the advertising struct to a function that prints each of the values, and then calculates how much you made for that day (multiply all 3 fields together).

2) Create a struct to hold a fraction. The struct should have an integer numerator and an integer denominator member. Declare 2 fraction variables and read them in from the user. Write a function called multiply that takes both fractions, multiplies them together, and prints the result out as a decimal number. You do not need to reduce the fraction to its lowest terms.

Quiz Answers

1) Show Solution

2) Show Solution


S.7.x -- Chapter 7 summary and quiz
Index
S.4.5a -- Enum classes

561 comments to S.4.7 — Structs

  • Imagine

    Although this is VERY long I tried to make my program and split the functions as much as I could and also tried to follow all best practices, is this okay?

  • ops

    Everywhere you used the word "declaration" should be replaced by the word "definition", in this lesson.

    Structure forward declaration would look like this:

    Structure definition:

    Also, if I don't put ";" at the end of a structure, then compiler gives this error: "expected ; after structure definition".

    • ops

      Edit: Since every definition is also a declaration, then using "declaration" is fine, I guess. So, nothing was wrong.

      Just be clear:

      is a definition and also a declaration ( also, it's not forward declaration or pure declaration!)

  • Uyph

    Hi. As for quiz #1, i want to ask if placing temp inside the parentheses as a parameter like this

    would make any difference to the way it works. I guess it would because i had no idea how to make

    work with my suggestion.

  • Fusiooon

    Is there a reason why we shouldn't define the struct members (numerator and denominator) as doubles instead of integers?
    Am I right that this would take away the need to static_cast? Or would this lead to an unnecessarily big memory allocation?

    • nascardriver

      Your numerator and denominator never are floating point numbers, they're always integers, that's how fractions work. Using `double`s is unnecessary (More memory use and might be slower depending on your CPU).

  • bastenko

    I don't know where the problem is.
    If i type  5/6 for the first fraction and 6/5 for the seconds, the result is 0, not 1. (quiz #2)

  • Hi, my answer to question 2 is this:

    I get error C2106 on lines 22 and 23: '=': left operand must be l-value. Why am I getting this error?
    Also, I don't see why you need to static_cast in your answer, why is division necessary?

    Thanks in advance!

  • Tony

    My god, Question 2 surely gave me a big headache!

    This is my result:

    Unlike Question1 (which I didn't get correctly), I tried to (again) use a paper and type down my ideas. It worked thanksfully!

    The most difficult part on Question 2 (atleast for me) was the return value of

    , I kept on using static_cast<double> but my result was always 0... After 100 checks I realized I actually was trying to static_cast the whole Fraction type, which wasn't possible (cough). I'm so happy I managed to print the correct result, although my solution isn't equal to yours!

    Once again, thanks for these questions. They really help us improve our code :-)

    • nascardriver

      Congrats on finishing the quiz :)

      Remember to use single quotation marks for characters. '/' is a character, "/" is a more expensive string.

  • Zolee

    Hi, I came up with this solution for the first Quiz, but I am not sure if this is okay or not. could you please have a look?

  • Suresh

    This was mentioned in the enum lesson as well, but what is meant by "struct declarations do not take any memory" and why does this allow the programmer to put enum and struct declarations in header files? I mention this specifically in contrast to functions which cannot be declared in a header file then #include'd into multiple code file.

    • nascardriver

      When you create a variable, it takes up some memory. A struct declaration does not, it's only a declaration.
      A declaration can be placed in a header file, because it doesn't do anything. If you define something in a header file, it would be defined multiple times.

      When you build a hut for your doggo, you need wood. But to draw the hut on a paper, you don't need wood.
      You can draw the hut as many times as you like, as long as it always looks the same. If you built the hut multiple times, you don't have any space to put them.

      • Suresh

        I appreciate your memory analogy about the declaration; it's really good. To discuss more concretely, are you saying that the declarations essentially serve as blueprints? That, at compile-time, the compiler uses these blueprints to structure the data according to these declarations, but the actual byte code would have no specific reference to say an Employee struct. The compiler would essentially organize each variable of struct type or enum type as defined in its declaration then place them into the byte code without the byte code itself having a specific section dedicated to these struct and enum types. I ask just to make sure I understand how this principle works in practice.

  • Good post. I study something more challenging on different blogs everyday. It'll always be stimulating to read content from different writers and observe a little something from their store. Idesire to make use of some with the content on my blog whether you don't mind. Natually I'll offer you a hyperlink on your internet blog. Thanks for sharing.

  • Thanks for one's marvelous posting! I quite enjoyed reading it, you might be a great author.I will be sure to bookmark your blog and may come back very soon. I want to encourage you continue your great job, have a nice weekend!

  • Joshua

    At what point in this tutorial would it be a good idea to maybe break away and do a medium-sized side project to solidify what I've learned? Also, does anyone have any recommendations for projects?

  • Swaraj

    is there any way to access struct members from a different namespace
    line 20 causes a compile error:

    • nascardriver

      You cannot have assignments outside of functions. Initialize `PIG` instead.

  • Jacob_Crowford

    Hey,

    Is it a good idea to return a local variable inside a function below to the main function?

  • AE35_Unit

    Thanks for help on #1, got the code all cleaned up and it looks and runs well.  #2 is here but it's been a bit of a day, so not sure how solid it is.  Thanks all, for the help.

  • Yung

    >>Understanding structs well will help make the transition to classes that much easier.

    I am guessing 'that' is extra!

  • Gowtham

    Isn't 'definition' instead of 'declaration'  a better word of choice?

  • sami

    Would you please help me to figure out which function call and which function definition is more efficient and better?

    • nascardriver

      That's what it should be. `printInformation` doesn't take ownership of the employee, so it should be an r-value reference. It also doesn't modify the employee, so it should be a const reference.

  • sami

    "This tells the compiler that we are defining a struct named Employee."

    1) Shouldn't 'defining' above be 'declaring'?

    "the non-static member initialization syntax is incompatible with the initializer list AND uniform initialization syntax."

    2) Shouldn't it be 'OR' instead of 'AND' as I think both are the same just have different names?

  • AE35_Unit

    I got quiz 1 completed, I think.

    • nascardriver

      - Initialize variables with brace initialization to avoid uninitialized variables and for higher type-safety.
      - Use double literals for doubles to prevent integer arithmetic.
      - Avoid abbreviations, they make your code harder to understand.
      - You don't need `temp`, you can `return { numAds, pctClicks };`. This also allows to compiler to do more optimizations.

  • salah

    Hi, is the date stored Consecutively when the object is created?

    and what about accessing the object name, say we have 'Rectangle rec'
    I tried to print the value of rec using 'printf' function and I got the first variable which is the length.

    so what does rec represent here? is it like array name which represents the address of the first element??

    • nascardriver

      Hi

      > is the date stored Consecutively when the object is created?
      I'm not sure I understand your question. There can be gaps between member variables.

      > what does rec represent here?
      `rec` is a structure. The first member of a `struct` has the same address as the instance of the `struct`. Here, that's `rec` and `rec.length`. Because `printf` is not aware of types, it doesn't care that you want to print an integer but are giving it a `Rectangle`. It treats the `Rectangle` as an integer. Because the first member is `length`, that's what `printf` prints.

      • SALAH

        At the university the teacher made a Card struct that contains char type, and char value, and made some instances of that struct, after that he shuffled them by using swapping method just like when you swap normal variable,so are we changing the the addresses of the first variable which is, in turn, change the struct instance address too ?

        • nascardriver

          Addresses don't change. Once a variable has been created at an address, it stays there until it dies. Instances of structs are normal variables, you can swap them like everything else.

  • Innervate

    For question 2 of the quiz I had the following:

    Running the code causes the multiply function to return 0. Changing the code the the following seems to resolve the issue:

    Should we not contain the whole expression using parenthesis when static casting?
    ie. (n1 * n2)/(d1 * d2) vs ((n1 * n2) / (d1 * d2)).

    Thanks!

    • nascardriver

      You're performing an integer division, then casting the result (Which is an int) to a double. The precision has been lost at that point already. By having one of the operands be a double, the division will be performed with doubles rather than ints.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]