Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 26

Accessing Struct Members: Dot vs. Arrow Operator

Recommended resources

Introduction

In our last lesson, we laid the groundwork for organizing our game's data by learning to define and initialize structs. You now know how to bundle related information, like a player's name and health, into a single unit and access its members directly using the dot (.) operator.

Today, we build on that foundation by exploring a more powerful and common way of working with structs: through pointers. In C programming, especially for applications like games where we need to manage memory efficiently, we frequently handle objects not by their value, but by their address in memory. This lesson will teach you how to access a struct's members when all you have is a pointer to it. You will master the essential arrow operator (->), a piece of C syntax designed specifically for this task, and understand the clear distinction between when to use the dot and when to use the arrow.


From Direct Access to Pointer Access

Let's quickly recap what we know. When you have a struct variable directly, you use the dot operator (.) to access its members:

struct Player {
    char name[50];
    int health;
};

// 'p1' is a struct variable
struct Player p1 = {"Ryu", 100};

// Access members using the dot operator
printf("Player: %s, Health: %d\n", p1.name, p1.health);

Now, let's introduce a pointer. Instead of holding the struct itself, a pointer variable holds the memory address of the struct.

// 'p1_ptr' is a POINTER to a struct Player
// It holds the memory address of 'p1'
struct Player* p1_ptr = &p1;

How do we get to p1's health if we only have p1_ptr? Trying p1_ptr.health will cause a compiler error because p1_ptr isn't a struct Player—it's a pointer. We need a way to follow the pointer to the struct and then access the member. The following image illustrates this relationship.

This diagram shows a pointer variable `ptr` that contains a memory address (1080). This address "points to" the location where the actual `struct Student` data (Name, Roll, Mark) is stored.

C gives us two ways to solve this.

Method 1: Dereference and Dot

The first method is a two-step logical process.

  1. Dereference the pointer: Use the * operator on the pointer (*p1_ptr) to "get the value it points to," which is the entire struct p1.
  2. Use the dot operator: Now that you have the struct, you can use the familiar . operator to access the member.

Because the dot operator has higher precedence than the dereference operator, you must wrap the dereference operation in parentheses:

// Correct: First dereference p1_ptr, then access health
int current_health = (*p1_ptr).health;

// Incorrect: This tries to access a 'health' member of the pointer itself
// int current_health = *p1_ptr.health; // COMPILE ERROR!

This (*p1_ptr).health syntax works, but it's cumbersome to type and read.

Method 2: The Arrow Operator

Since accessing members via a pointer is extremely common in C, the language provides a more elegant solution: the arrow operator (->). It combines the dereferencing and dot access into a single, clean operation.

The expression p1_ptr->health is exactly equivalent to (*p1_ptr).health. It means "follow the pointer p1_ptr to the struct it points to, and then access its health member."

This leads to a simple, unbreakable rule:

  • If the variable is a struct itself, use the dot operator (.).
  • If the variable is a pointer to a struct, use the arrow operator (->).

The following video provides an excellent explanation of this distinction, clearly showing why the arrow operator exists and how it simplifies your code.

When to use the Arrow Operator in C and C++

This video, "When to use the Arrow Operator in C and C++" from Jacob Sorber's channel, methodically walks through the problem and its solution.

Please watch from this segment. The video first shows how to access members with a pointer the "ugly way" using dereferencing and parentheses. It then introduces the arrow operator as the clean, "syntactic sugar" alternative and finishes by stating the simple rule for when to use dot versus arrow.


Applying Pointers and Arrows in a Game Context

Now, let's see this in a more practical scenario for our game development goal. Often, we won't know how many players or enemies we need ahead of time. Instead of creating them as local variables (on the stack), we'll allocate memory for them dynamically as needed (on the heap). The function for this, malloc, returns a pointer to the newly allocated memory block. This is a primary use case for the arrow operator.

The following resource walks through an example of building a simple role-playing game where player characters are created dynamically.

Learning C With Game Concepts/Designing A Roleplaying ...

This Wikibooks article demonstrates building a simple RPG in C. It's a great example of using struct pointers to manage game entities.

First, read the section that introduces dynamic allocation for players. Start from the explanation of dynamic allocation and stop right before the code block. This part explains why we use pointers and introduces the arrow operator syntax. Next, study the code that follows, starting from the NewPlayer function prototype. Notice how malloc is used to create a player, and then the -> operator is used to set the name, health, and mana of this new character. Continue reading through the code in the section dynamicPlayers.c to see how these pointers are passed to functions like DisplayStats, which also uses the -> operator to read the data.

This example from Wikibooks is a direct preview of how we will manage entities in our own text-based strategy game. You can see that once you're working with dynamically created objects, the arrow operator becomes your primary tool for interacting with them.


Code Example: Tying It All Together

Let's solidify this with a complete, runnable example that shows both direct and pointer access in one program.




#include <stdio.h>
#include <string.h>

// Our familiar Player struct
typedef struct {
    char name[50];
    int score;
} Player;

int main() {
    // --- Direct Access ---
    // 'p1' is a struct variable, allocated on the stack.
    Player p1;
    strcpy(p1.name, "Chun-Li");
    p1.score = 50000;

    printf("Direct access with (.): %s's score is %d\n", p1.name, p1.score);

    // --- Pointer Access ---
    // 'p1_ptr' is a POINTER to a Player struct.
    // It stores the memory address of 'p1'.
    Player* p1_ptr = &p1;

    // We can now access p1's members through the pointer.
    // Let's change the score using the arrow operator.
    p1_ptr->score = 55000; 

    printf("Pointer access with (->): %s's new score is %d\n", p1_ptr->name, p1_ptr->score);

    // This also proves that p1 and p1_ptr are referring to the same data.
    // The change made via the pointer is reflected in the original struct.
    printf("Original variable after pointer modification: p1.score is now %d\n", p1.score);

    return 0;
}

This code explicitly demonstrates the core concept: p1 and p1_ptr are different variables, but p1_ptr gives you a path to modify the data inside p1.

Here is a summary table for easy reference:

Your Variable Type of Variable How to Access Member x Example
p1 Player (a struct) Dot operator (.) p1.score
p1_ptr Player* (a pointer) Arrow operator (->) p1_ptr->score

Conclusion

You have now added a crucial tool to your C programming toolkit. You not only know how to define custom data types with struct, but you can also interact with them through pointers—the way they are most often handled in complex programs.

Key Takeaways:

  • There are two operators for accessing struct members: the dot (.) and the arrow (->).
  • Use the dot (.) operator when you have the struct variable directly.
  • Use the arrow (->) operator when you have a pointer to the struct.
  • The arrow operator ptr->member is simply convenient "syntactic sugar" for the more cumbersome (*ptr).member.
  • Pointers to structs are essential for dynamic memory allocation (malloc) and for efficiently passing data to functions.

In our next lesson, we will focus on that last point. We will explore how to pass structs to functions, comparing the trade-offs of passing them by value (which copies the entire struct) versus passing them by pointer (which is faster and allows the function to modify the original data). Your understanding of the arrow operator will be essential.