On Structs

A struct is a quintessential example of an abstraction born out of engineering necessity — it was the demand that chose the struct, not the struct that created the demand.


1. What Is a Struct?

Wikipedia’s struct article describes it as follows:

In C, a struct (structure) is a data structure that falls under the category of composite (aggregate) data types.

In plain language, a struct is a composite data type created by combining basic data types as needed.

2. What Are Structs Good For?

In the early days of C, there was no struct. When the language needed to handle composite data types composed of multiple coupled data types, programmers had to use separate arrays of different types and figure out how to manage them. This was neither elegant nor easy to understand, so structs were born.

Engineers love encapsulation at every layer — making the abstract concrete, or rather making the concrete abstract — and structs are one of the most typical forms of encapsulation. For any large-scale project, basic data types alone are insufficient and cannot accommodate every requirement. Structs, with their flexibility and power, instantly provided a solution to these problems.

We can use structs to combine multiple variables to describe complex objects. We can bundle a set of related data together and pass it into a function or return it as a function’s return value, rather than handling scattered data pieces individually.

For example, if you were writing a product management program for a supermarket, a struct is far more concise than writing a pile of arrays to store the data:

1
2
3
4
5
6
struct goods  //Product
{
    char name[20];  //Product name
    float price;  //Unit price
    float weight;  //Weight
};

C was originally created as a tool for the UNIX operating system. In UNIX, UNIX v3 introduced a compiler with struct support, and it was immediately put to use in subsequent versions.

As you can see, the birth of structs was no accident — it was an inevitability under specific conditions. Essentially, large-scale engineering projects meant that programmers had a strong need for abstraction and encapsulation, which led them to create structs. It was the demand that chose the struct, not the struct that created the demand.

3. Things to Note When Using Structs

  • Structs are not exactly the same in C and C++. Here are some examples to illustrate.

    • Defining and declaring a struct in C:
    1
    2
    3
    4
    5
    6
    7
    8
    
    struct goods  //Product
    {
        char name[20];  //Product name
        float price;  //Unit price
        float weight;  //Weight
    };
    
    struct goods var_goods;  // struct goods is the type name, var_goods is the variable name
    

    Alternatively, use the typedef keyword to define a type alias:

    1
    2
    3
    4
    5
    6
    7
    8
    
    typedef struct goods  //Product
    {
      char name[20];  //Product name
      float price;  //Unit price
      float weight;  //Weight
    } Good;
    
    Good var_goods;  // "Good" is the type alias, var_goods is the variable name; using "Good" is similar to using "int"
    
    • Defining and declaring a struct in C++:
    1
    2
    3
    4
    5
    6
    7
    8
    
    struct goods  //Product
    {
        char name[20];  //Product name
        float price;  //Unit price
        float weight;  //Weight
    };
    
    goods var_goods;  // goods is the type name, var_goods is the variable name
    

    In C++, both struct and class are essentially classes, so they are used similarly and typedef is not needed.

  • The difference between -> and . with structs

    To quote Lion Yang from Zhihu:

    Simply put, one is a shortcut, and the other is syntactic sugar.

    Let’s illustrate with code:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    #include <iostream>
    using namespace std;
    
    struct goods  //Product
    {
        char name[20];  //Product name
        float price;  //Unit price
        float weight;  //Weight
    };
    
    int main(void) {
        goods s;  //Struct instance
        goods* p;  //Struct pointer
        p = new goods;
        s.price = 10;
        p->price = 10;
        if ((&s)->price == (*p).price) {
            cout << "Prices are equal!" << endl;
        }
        delete p;
    }
    

    As you can see, we used s.price and p->price for assignment, and we could also use (&s)->price and (*p).price for reading. Fundamentally, there is not much difference — in memory, both work by offsetting from the struct’s base address to reach the desired memory location for reading or writing data. However, there is a subtle distinction in usage: the left operand of -> must be a struct pointer variable, while the left operand of . must be a struct variable.

    The output of the above program is:

    1
    
    Prices are equal!
    

    These two operators are leftovers from history. Back then, programming was mostly done from the machine’s perspective, and the code was closer to machine logic than to human intuition. If machines could think, (*p).i would be like counting i characters forward from p on a number line; whereas p.i would require finding p’s position first before counting. Machines exist to serve humans, so the -> operator was introduced as a more intuitive alternative. Using -> lets us express (*p).i concisely as p->i, which became the pragmatic compromise.

References