Search

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.

In C++11, we can also use uniform initialization:

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(). 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 to do so.

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

4.8 -- The auto keyword
Index
4.6 -- Typedefs and type aliases

333 comments to 4.7 — Structs

  • Arun Gandhi

    #include <iostream>

    using namespace std;
    struct Fraction
    {
        int numerator;
        int denominator;
    }f1, f2;

    void multiply(Fraction f1, Fraction f2)
    {
        

        double result = static_cast<double>(f1.numerator * f2.numerator) / (f1.denominator * f2.denominator);
        //Static cast to change integer vlues to floats for decimal result.
        cout << "The result of the two fractions multiplied together is: " << result << "\n"; //Show result to user
    }

    int main()
    {
        
        cout << "Please enter numerator for fraction 1: ";
        cin >> f1.numerator;
        cout << "please enter denominator for fraction 1: ";
        cin >> f1.denominator;

        
        cout << "Please enter numerator for fraction 2: ";
        cin >> f2.numerator;
        cout << "Please enter denominator for fraction 2: ";
        cin >> f2.denominator;
        multiply(f1, f2);

        return 0;
    }

  • Arun Gandhi

    #include <iostream>

    using namespace std;

    struct advertisement{
      int adsshown;
      double clickratepercentage;
      double averageearningsperclick;
        
    }ad;

    void totalearnings(advertisement ad){
        cout<<"howmany ads were shown"<<endl;
        cin>>ad.adsshown;
        cout<<"total adds shown"<<" "<<ad.adsshown<<endl;
        cout<<"what is the click percentage"<<endl;
        cin>>ad.clickratepercentage;
        cout<<"click rate percentage"<<" "<<ad.clickratepercentage<<endl;
        cout<<"what is the average earning"<<endl;
        cin>>ad.averageearningsperclick;
        cout<<"average earning per click"<<" "<<ad.averageearningsperclick<<endl;
        
        cout<<"total earnings"<<" "<<(ad.adsshown * ad.clickratepercentage / 100 * ad.averageearningsperclick);
        
    }

    int main(){
        
        totalearnings(ad);
        return 0;
    }

  • dsg vrghsefggf aegfr

    This is the first one i did without looking at answers! i feel very satisfied.

  • Mark

    Let me clarify my previous comments about writing out numbers. Mathematical expressions, line numbers, and programming variable values would be exceptions to the rule, as it would be far less confusing to use figures (e.g., iTotal = 2 + 3 or exit(0)), but in a sentence such as “C++ provides 3 types of loops…” (as you write in lesson 5.1), should instead have been written “C++ provides three types of loops….”

    Now, I turn to what’s confusing me. When assigning values to struct members using an initializer list, I found that the equal sign operator before the opening brace is not necessary (at least in Code::Blocks 17.12). Sometimes you include it; sometimes you don’t. Why is that? The only thing I can think of is that the operator may be necessary on older IDE versions and you have either added new snippets of code examples to your lessons or updated some program snippets and not others.

    Also, I’ve found that using direct initialization (the use of parentheses) does not work with assigning values to struct members. Aside from uniform initialization of variables being faster and type safe, is this another reason (consistency with the syntax for structs) for using braces versus parentheses?

    • Alex

      > When assigning values to struct members using an initializer list, I found that the equal sign operator before the opening brace is not necessary (at least in Code::Blocks 17.12). Sometimes you include it; sometimes you don’t. Why is that?

      The version with the equals sign was how aggregate (array, struct, or class) initialization was done prior to C++11. The version without the equals sign was introduced in C++11 and is now the preferred method for initializing aggregates. Many of these examples were written pre-C++11. They're not in

      > Aside from uniform initialization of variables being faster and type safe, is this another reason (consistency with the syntax for structs) for using braces versus parentheses?

      Only in cases where uniform initialization isn't supported (e.g. constructor initialization lists). The braces were added in C++11 to make initialization easier and more consistent across a wide variety of types, and they should be used wherever possible.

  • Mark

    According to the Harbrace College Handbook, where numbers are used frequently, the general practice is to spell out numbers from one to ten and to use figures for all others. Admittedly, a minor point, but one worth mentioning since you otherwise do such an excellent job of instruction.

    Now, I suspect I’m opening a can of worms here, but I tried implementing a program using the snippet of six independent variables you used as an example right up top in this lesson. Some experimentation showed that with Code::Blocks 17.12, I did not have to include the preprocessor directive #include <string> for this program to work even though I am using std::string and std::getline in my program. When I selected “Find declaration of: ‘string’”, it opened a header file called stringfwd.h. When I added the aforementioned preprocessor directive to the program and then selected “Open #include file: ‘string’”, I found on line 45:

    #include <bits/localefwd.h>    // For operators >>, <<, and getline.

    So, I then selected “Open #include file: ‘bits/localefwd.h’”, and at this point I suspect I could be doing this ad nauseam all the while having no idea what’s going on. Interpreting this will undoubtedly require a much more complete understanding of the language (as well as all of the associated library functions), but I feel this is the most frustrating thing about learning C++. Basically, I am not sure what I am reading here, or how I am supposed to determine whether or not I need to include the #include <string> directive. Is this a case where I just have to be patient until I get to the point in this tutorial that will allow me to see the light? Right now I feel as if I am in a tunnel with no light in sight, and I am afraid of tunnels…and the things that haunt them.

    • Alex

      > the general practice is to spell out numbers from one to ten and to use figures for all others

      I wasn't aware of that. I'll try to keep that in mind as I continue to update the lessons.

      The answer to your other question is actually simple: If you use std::string or std::getline, include the string header. More generally, if you use some functionality from a library, you should include the header containing that functionality.

      What's likely happening in your case is that you're including iostream, which is including some other library that's including some other library that eventually includes string. Thus, string is getting included into your program even though you're not doing it explicitly. Unfortunately, I'm not sure there's a good way to detect that this is happening. Perhaps some readers have some ideas?

  • Hi!

    I am having trouble with question 1. Here is my program:

    I always get this output for the income: -2.14748e+09
    If anyone could help me with this...

  • Piotr

    I've extended the quiz and wrote a function also for fractional multiplication (still yet without reducing to lowest terms).

  • DAT

    Question 1
    #include"stdafx.h"
    #include<iostream>

    struct Fraction
    {
        int numerator;
        int denominator;
    };

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

    double multiplyFraction(Fraction temp1,Fraction temp2)
    {
        double fraction1 = static_cast<double>(temp1.numerator) / (temp2.denominator);
        double fraction2 = static_cast<double>(temp2.numerator) / (temp2.denominator);
        return fraction1 / fraction2;
    }

    int main()
    {
        Fraction first = getUserInput();
        Fraction second = getUserInput();
        std::cout << multiplyFraction(first, second) << "\n";
        return 0;

    }

  • Kio

    Hi Alex,

    For Quiz 1.

    Getting a user input, it's a great candidate for creating an input function:

    It's nothing new, and it's sum of all learned lessons until now.

    And for Quiz 2.

    It seems like a more natural "code" for me.

  • Sam

    Hey, thanks so much again for these tutorials, I have been making notes on all the lessons and completing all the tasks. Decided to post my solutions for these tasks, so please let me know of anything that could be better if anyone spots something:

    Question 1:

    Question 2:

    • Hi Sam!

      * Try limiting your lines to 80 characters in length for readability
      * (1) Line 16: Use double literals when calculating with doubles (100.0 instead of 100)
      * (1) Line 6-8 or 21, (2) Line 6, 7 or 12: Initialize your variables with uniform initialization
      * You're using the same name style for functions and variables. This can get confusing.

  • tyro

    When I took the quiz (#1), I was proud of myself for remembering to use

    but noticed it was not included in your solution. I was wondering if there was any reason that this is unnecessary in this particular context?

    Thanks in advance! This website has been an amazing resource!

  • Nguyen

    Hi Alex & nascardriver,

    After reading "A function can also return a struct, which is one of the few ways to have a function return multiple variables.", I just made a small program to see if a function can also return an array.  Unlike struct, it does not seem to do the same job or there might be some bugs in my program?  Please check my program.  Although I strongly believe a function can't return an array, I really wish to see it to be mentioned somewhere in the lesson in the future.

    Thanks, Have a great day.

    • Hi Nguyen!

      Since you've used content from lessons that haven't been covered yet, I'll do the same to partially answer your question.

      Please read compiler errors and warnings

      @tempt is an int*, @y returns an int, that doesn't work.

      Fixing that gives you

      in line 12. Arrays cannot be assigned other arrays. You need to copy the elements in a loop, or declare @x int*.

      Done that, you'll get a warning

      This happens, because @tempt will be destroyed at the end of @y, @x will be a dangling pointer.
      This can be fixed by either passing an existing array to @y, which is then filled, or dynamically allocating the array in @y and returning that.
      Let's choose the first option, because it's easier for now, note however that we're no longer using a return to return the array.

      This code produces the expected output

      Ok, cool, problem solved, but you can only return multiple values if they have the same type and the values aren't named, just indexed, which makes it harder to understand what's happening.

  • Aditi

    My solutions
    Q1.

    Q2.

    • Q1.
      * "Pass the advertising struct to a function that prints each of the values" Missing
      * Line 6-8 or 13: Initialize your variables with uniform initialization. If you don't understand this re-read lesson 2.1
      * Line 20: Use double numbers when calculating with doubles (100.0 instead of 100)

      Q2.
      * Line 6-7 or 17,23: Uniform initialization
      * Line 11: Uniform initialization
      * Line 11: Only use float when you're concerned about memory usage, use double or long double otherwise.

  • Wow a bit of a head scratcher - had to think back to over 30 years ago when I last did fraction maths in school.  However, my final solution is thus:

    You mentioned OOP will this include forms and form controls or is that something I will need to get a book on?

    • Hi Nigel!

      "prints the result out as a decimal number"
      You used a float. If you want precision, use a double or long double, computers have enough memory and processing power these days.

      * Line 15: Initialize your variables with uniform initialization. You already know which value @result is supposed to have in line 15, initialize it.
      * Line 25, 31 or 9, 10: Initialize your variables with uniform initialization. @std::cin won't read the values and it will override them, but knowing the values of all variables at any point in time makes debugging a whole lot easier.

  • Nguyen

    Hi,

    In order to use the Employee struct, we simply declare a variable of type Employee:
    Employee joe; // struct Employee is capitalized, variable joe is not

    I am wondering "Employee struct, type Employee, and struct Employee" in above lines have the same meaning?.  Sorry, my English is bad, they confuse me.

    Thank, Have a great day.

  • Aditi

    Hi ! I have a question about initializing variables with a type whose name is the name of a struct. If we do that , does it mean that , that particular variable will have parameters defined by variables that are defined in the structonly ? For example , in the first programme under the heading ‘Structs and Functions’ , a variable employee of the type Employee was declared . Does that mean that employee will have parameters defined under the employee struct ?

  • Will

    Hi, I have a question about the static_cast function. While I working on question 2, my code would not print the fraction in decimal form even though I thought I used the static_cast correctly. My code looked like this:

    After looking at the solution to the question, I found out when I removed the outer parentheses in the equation I got the code to print out the fraction in decimal.

    I'm just wondering why this happens. Wouldn't the equation make sense to the compiler with the extra parentheses? Is there some hidden rule that I forgot while making this code?

    • nascardriver

      Hi Will!

      In your code, the division is performed before the cast. You're dividing int by int, resulting in an int, which you then cast to a float.
      In Alex' code there is no int by int division. He divides a float by an int, which results in a float.

  • Harsh

    Can we take inputs for structs, if yes then tell my how .. please by example

    errors:- expected primary-expression before '-' token
             expected primary-expression before '-' token
         expected primary-expression before '-' token

    • nascardriver

      Hi Harsh!

      You need to instantiate an object of type @Calculate_Income.

  • ayush

    shall i proceed for lesson 5

  • ayush

    #include<iostream>
    using namespace std;
    int advertise(int ads,int ads_clicked)
    {
    return (ads+ads_clicked)*3;

    }
    struct adv
    {
        int ads;
        int ads_clicked;
        
    };
    int main()
    {
    adv ads={100};
    adv ads_clicked={1111};
    cout<<" total encome";
    (ads+ads_clicked)*3;    
        return 0;    
    }

    im getting output but that is not what i expected plz help me

    • nascardriver

      Hi ayush!

      You're not outputting the result of your calculation.

      You also didn't understand structs, give the lesson another read and, if you're still having trouble, have a peek at the solution.

  • derbutzemann

    I have tried something different to be sure about the new things before I go to next chapter.

    • nascardriver

      Hi derbutzemann!

      Use double numbers for doubles (0.0 instead of 0). I wouldn't have used the using directives for @std::cout and @std::cin but I guess it's controlled enough to be tolerated.
      You're ready for chapter 5.

  • DecSco

    Hey, if you find the time, do you have suggestions for improvement for the following code (Question 2)?

    • nascardriver

      Hi DecSco!

      @long is rarely used, at least I haven't seen it a lot, have a look at @<cstdint>, the data types in there have a guaranteed size.

      Initialize @num and @den in @getFractionFromUser, it won't change anything if you're using C++11 or later, but you shouldn't trust any function with uninitialized variables.

      References
      * <cstdint>: http://www.cplusplus.com/reference/cstdint/

      • DecSco

        Thank you! So you suggest to use, say, int32_t instead of long?
        And if I did a validity check on the user input, can I then skip initialisation (because I'd assign a value anyway if the user input is invalid) or what is the precise reason for not trusting a function with uninitialised variables?

        • nascardriver

          I thought you use @long the have a larger data type. If you don't want anything special go with @int. If you want a larger type use @std::int64_t.

          > can I then skip initialisation
          You could, but I'd still initialize the variable. A program should be deterministic, you should know it's state at any given point, with uninitialized variables this isn't the case.

          > what is the precise reason for not trusting a function with uninitialised variables?
          You don't know how the function works internally. You don't know if it uses the value for anything before overriding it. Even if you do, not every reader of your code knows which functions can be passed undefined values and which cannot.
          Uninitialized variables rarely cause trouble, but if they do, it's big trouble.

  • _RryanT

    I'm feeling like a bad programer

    My attempt at Quiz 1:

    Quiz 2:

    Thank you for those tutorials, I'm learning a lot. I just need more praticing xD

    • nascardriver

      Hi Ryan!

      Suggestions:
      * Initialize your variables with uniform initialization.
      * Quiz 1, Line 16: You're dividing int by int, this causes you to lose precision.
      * Quiz 2: Unnecessary forward declaration of @multiply. Move @multiply above @main.
      * Inconsistent use of '\n' and @std::endl. Pick one and stick to it unless you have a reason to mix them.

  • Serial Seth

    Anything I could do better?
    My attempt at question 1:

    • Serial Seth

      Lol, just saw the solution. Missed the part about user inputs to populate the struct... oh well.

    • nascardriver

      Hi Seth!

      Ignoring that you've missing the user input part, here are some suggestions:

      * You're using three different kinds of initialization, stick with uniform initialization.
      * Your @multiply function is pretty pointless, do the calculation in-line.
      * You're supposed to pass @march to a function which does the printing and calculations.
      * Which compiler are you using? '0()' isn't valid C++ and should cause an error.

      • Serial Seth

        Visual Studio Community compiled it. I don't know why I didn't get an error, I usually don't do the () on return values. I see the print and calculations function as an easy fix, so I won't post the update. I would like to know what you mean by 3 different types of initialization.

        Thank you for your input; been doing this only for a week. I will be posting more of my code in further lessons to make sure I am not developing bad habits. Again, it is nice to find a community here willing to help me in the next stage of my career.

        I plan on developing SCADA clients in the future with c,c++ controls and storage and java HMI. If anyone has any advice on subjects to make sure I master, please let me know.

        • nascardriver

          References
          Lesson 2.1 - Fundamental variable definition, initialization, and assignment

  • Samira Ferdi

    Can I input something (by using std::cin) to struct?

    • nascardriver

      Hi Samira!

      You'd need to either fill it manually

      or define an operator

  • Tony

    Hey Alex, since you said we'd better avoid the "casts", I've done my program like this (using double instead of ints, and just (numerator /  denominator) * (numerator1 / denominator2).
    It gives the total in decimal but I'm not sure if this is good practice?

    • Alex

      It's not good practice. When you're creating structs (or classes) the members should use the data type that best represents the thing you're modelling. For a fraction, the numerator and denominator should be integer values, and thus they should use int.

      Also, it's totally fine to use casts! Just use the C++ style casts (e.g. static_cast), not the C-style ones.

Leave a Comment

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