Search

8.4 — 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: an int 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.

Non-static member initialization

It’s possible to give non-static (normal) struct members a default value:

If both non-static member initializer and list-initialization are provided, the list-initialization takes precedence.

In the above example, Rectangle x would be initialized with length and width 2.0.

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

Assigning to structs

If we wanted to assign values to the members of structs, we can to do so individually:

If you want to assign a new value to all members, this is a pain, particularly for structs with many members. You can also 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, but with fixed-size integers and `id` being half the size of `age`. On many platforms, 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 a double is 8
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 time

Question #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).

Show Solution

Question #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 returns the result as a decimal number. You do not need to reduce the fraction to its lowest terms. Print the result of the multiplication of the 2 fraction variables.

Show Solution


8.5 -- Random number generation
Index
8.3 -- Enum classes

611 comments to 8.4 — Structs

  • yeokaiwei

    I don't know where I went wrong with this.

    I am unable to get a decimal point even with static_cast<double>.

    Could you help?

    • nascardriver

      `std::cout` doesn't print decimal points unless it would print a non-zero value after the decimal point. If everything after the decimal point would be 0, `std::cout` uses the same style as for integers.
      You can use `std::fixed` and `std::setprecision` to force `std::cout` to print the decimal point.

      • yeokaiwei

        1. For input, I tried using 22/7 and 1/1 to simulate pi.
        2. #include <cmath> and #include <iomanip>
        3. I changed

        to

        My result is still 3.14286

        Did I misunderstand something?

        • nascardriver

          Ah, you did the calculation different, which is what's causing you problems. The code you posted doesn't output 3.14286 with 22/7 and 1/1, you must have updated your code after posting it. Can you post your updated code without `std::cin` (Hardcode values) to make sure we're talking about the same thing?

          • yeokaiwei

            • nascardriver

              This code can't produce the output you stated, please remove the `std::cin >> /*...*/` and set constant values so I don't have to guess what you entered.
              You're performing integer division twice in line 25 (Only casting the result (Which is already an integer) of the first division to a `double`).

              • yeokaiwei

                Unhandled exception at 0x0082373C in Chapter7FractionStruct.exe: 0xC0000094: Integer division by zero.

                Errr... I think something weird occured...

                • nascardriver

                  You only need to update these 2 lines

                  For example

                  With these values, the output should be
                  3.0000000000

                  • yeokaiwei

                    Yes, I got the same answer but it's not correct.

                    • nascardriver

                      Yes, because "You're performing integer division twice in line 25 (Only casting the result (Which is already an integer) of the first division to a `double`)."

                      You need to cast at least 1 operand to `double` _before_ doing to division. The solution gets away with 1 cast, because it's only performing 1 division.

                  • yeokaiwei

                    Ah, I finally figured out what you were saying.

                    It works now.

                    It's due to my bracket placement.

                    I did a static_cast<double(numerator)twice to make doubly sure.

  • yeokaiwei

    Just wanted to share some feedback on how I got confused. I got confused by thinking that I had intialized a variable when I didn't. I got around it by changing the naming from "ad" to "local" and it reminded me that it was just a local variable. You could use any word in place for "local". This prevented massive confusion due to "Ad" and "ad".

  • yeokaiwei

    How do we get the correct answer for sizeof(Employee)?

    • nascardriver

      What do you mean, what is the "correct answer"?

      • yeokaiwei

        Apologies, I should have been clearer.
        I have an issue with my code.
        All the employee information prints out nicely.

        However,"std::cout << "The size of Employee is " << sizeof(Employee) << '\n';", prints out 72 and I'm not sure why.

        • nascardriver

          Types can have different sizes on different systems. An `int` on your desktop might be 32 bits wide but only 16 bits on your raspberry pi. The same goes for all other types that don't have a fixed width.
          Whenever the lesson says "On the author’s machine", it means that the output can differ on your machine.

          • yeokaiwei

            What is setting the different sizes?

            Is there a way to standardize?

            Did I set the configurations wrongly?

            • nascardriver

              Your compiler decides which sizes the fundamental types have (The standard restricts the possible sizes).
              There is no wrong or right size.

              • yeokaiwei

                Ah, I understand a little bit more now.

                If I compiled in x86 or x64, the pointer size is different and thus the final answer is different too.

                I think it is very important to state in which mode it is compiled in the tutorial.

  • ExitFailure

    Wouldn't be more efficient if we omit variables?

    Also isn't multiply doing more then it says,that is, converting to decimal and printing?

    • nascardriver

      The order of the `getFraction()` calls in

      is unspecified. The user wouldn't know if they enter the left or right fraction first. For multiplication it doesn't matter, but it can break your code if you're doing something else, eg. subtraction.

      > isn't multiply doing more then it says
      Yes, lesson updated. Thanks for pointing it out!

  • Zephirus

    Hi, I was working on question 2. When I ran my code, I got the error: "error C2397: conversion from 'int' to 'double' requires a narrowing conversion". This came up on lines 29 and 30. I static_casted all the int values to doubles just before, so I don't understand why my code caused this error. Would you be able to help? Thanks,
    Zephi.

    • nascardriver

      The type of a variable can not change. `static_cast` returns the value of the variable with the new type. You're not using the return value of `static_cast`. See lesson 6.16.

  • Nir3us

    What do you think about my answears to quiz?

    and second one

    • nascardriver

      You're casting types to the same type they already were, eg.

      `ad.adsShown` is already an `int`, the cast doesn't do anything.

      Use double literals for floating point calculations to avoid accidental integer arithmetic.

      `Fraction` and `FractionTwo` are identical. You don't need 1 type per variable, you can use `Fraction` as many times as you like.

  • srt1104

    For the two quiz questions, I tried to follow all the best practices you have recommended so far.
    I'm posting their source code below. Any advice regarding corrections/improvements would mean a lot. Thank you!

    Question 1:
    ad.h

    ad.cpp

    main.cpp

    Question 2:
    fraction.h

    fraction.cpp

    main.cpp

  • Quiz !

    include <iostream>

    // First we need to define our Advertising struct
    struct Advertising
    {
        int adsShown;
        double clickThroughRatePercentage;
        double averageEarningsPerClick;
    };

    Advertising getAdvertising()
    {
        Advertising temp;
        std::cout << "How many ads were shown today? ";
        std::cin >> temp.adsShown;
        std::cout << "What percentage of ads were clicked on by users? ";
        std::cin >> temp.clickThroughRatePercentage;
        std::cout << "What was the average earnings per click? ";
        std::cin >> temp.averageEarningsPerClick;
        return temp;
    }

    void printAdvertising(Advertising ad)
    {
        std::cout << "Number of ads shown: " << ad.adsShown << '\n';
        std::cout << "Click through rate: " << ad.clickThroughRatePercentage << '\n';
        std::cout << "Average earnings per click: $" << ad.averageEarningsPerClick << '\n';

        // The following line is split up to reduce the length
        // We need to divide ad.clickThroughRatePercentage by 100 because it's a percent of 100, not a multiplier
        std::cout << "Total Earnings: $" <<
            (ad.adsShown * ad.clickThroughRatePercentage / 100 * ad.averageEarningsPerClick) << '\n';
    }

    int main()
    {
        // Declare an Adverti struct variable
        Advertising ad{ getAdvertising() };
        printAdvertising(ad);

        return 0;

    Quiz 2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #include <iostream>

    struct Fraction
    {
        int numerator;
        int denominator;
    };

    Fraction getFraction()
    {
        Fraction temp;
        std::cout << "Enter a value for numerator: ";
        std::cin >> temp.numerator;
        std::cout << "Enter a value for denominator: ";
        std::cin >> temp.denominator;
        std::cout << '\n';
        return temp;
    }

    void multiply(Fraction f1, Fraction f2)
    {
        // Don't forget the static cast, otherwise the compiler will do integer division!
        std::cout << static_cast<double>(f1.numerator * f2.numerator) /
            (f1.denominator * f2.denominator) << '\n';
    }

    int main()
    {
        // Allocate our first fraction
        Fraction f1{ getFraction() };
        Fraction f2{ getFraction() };

        multiply(f1, f2);

        return 0;
    }

  • 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]