Fundamentals (part 2)

Structures

A good deal of computer programming is concerned with organizing data efficiently

It is often beneficial to organize and store a variety of different data types together

As we will see later, C++ classes (like Java) are ideal mechanisms for such organization

In C/C++, we can use a somewhat similar mechanism called a structure to organize information

Structures are used to organize and store different variable types under a common type

Structures can be thought of as simply another variable type (like an int or float)

The difference, however, is that this variable type is declared by the developer (or code author)

Consider the following example…

Suppose you are writing an application to simulate how small particles of dust move around in an environment. To describe each particle, you might need a location in three-dimensional space (x, y, z) and perhaps an identifying id (you might even want information associated with each particle to describe its weight, size, and even chemical composition, but we’ll simply use a location and id for this example).

 

You might begin by declaring variables to store this information for a single particle (id and pos for location or position)

int id;

float pos[3];

 

This would be fine for a single particle, but what if we had multiple particles? We could either declare separate variables for each particle

int id1, id2, ...

float pos1[3], pos2[3], ...

 

or we could declare arrays for each piece of information

int id[NUM_PARTICLES];

float pos[NUM_PARTICLES * 3];

 

Although both methods would work, it is not the most efficient or intuitive way of organizing such information. Instead, we could declare a new structure variable type to store information for a single particle

struct Particle

{

    int id;

    float pos[3];

};

 

We could then declare a variable of this new type to store a particle

main()

{

      struct Particle p;

      ...

}

 

We access the variables within the structure using a structure member operator (.) as follows

main()

{

struct Particle p;

 

      p.id = 1;

      p.pos[0] = 0.0;

      p.pos[1] = 1.0;

      p.pos[2] = 1.0;

      printf( "particle %d, position: %f %f %f\n",

p.id, p.pos[0], p.pos[1], p.pos[2] );

}

 

Structure variable types can also be declared outside the main program using a typedef statement and referred to with a simpler name as in the example below (structures in code examples in the study notes use this method):

typedef struct

{

  int id;

  float pos[3];

} Particle;

 

main()

{

      Particle p;

      p.id = 1;

      p.pos[0] = 0.0;

      p.pos[1] = 1.0;

      p.pos[2] = 1.0;

      printf( "particle %d, position: %f %f %f\n",

p.id, p.pos[0], p.pos[1], p.pos[2] );

 

}

 

To make things even easier, C++ does not require the type definition:

#include <iostream>

 

using namespace std;

 

struct Particle

{

    int id;

    float pos[3];

};

 

main()

{

      Particle p;

      p.id = 1;

      p.pos[0] = 0.0;

      p.pos[1] = 1.0;

      p.pos[2] = 1.0;

      cout << “particle “ << p.id << “ position: “:

      cout << p.pos[0] << “, “ << p.pos[1] << “, “ << p.pos[2] << endl;

 

}

 

Pointers

In part 1, we looked at how parameters were passed by value to functions

Recall that the function worked on a copy of the parameter value on the stack

We asked the following question

What if we wanted to change the parameter variable contents in the calling program from within the function?

One method of accomplishing this is to use pointers

Let’s look closely at this very important concept…

 

Definition - what is a pointer?

-       A pointer is a variable containing the address of another variable

-       The address of a variable is the location in memory the variable is occupying

-       By containing an address, a pointer variable "points to" another variable

-       In the diagram below, we can visualize memory as rectangular blocks

-       Each memory block stores information

-       Each memory block is designated with a number to represent its location

-       This location value is referred to as an address (just like we can be located where we live by an address)

-       For example, the memory at address 1001 below is storing the value 10

-       The memory at address 1004, however, is storing another address!

-       This value at 1004 contains the address 1001

-       In this way the value at 1004 “points to” the value at address 1001

-       The value at 1004 is referred to as a pointer

 

diagram1

 

 

Declaration – how do we declare a pointer?

-       We can designate a variable to store a memory address

-       We refer to such a variable as a pointer

-       The type of the pointer indicates the type at the address it is pointing to

-       We declare and notate a pointer variable (var) as follows:

type *var;

or

type* var;

where type can be any data type (int, char, float, ...) or a struct
 

Example 1

-       Two pointers are declared

-       One pointer is designated to store the address of an integer (int *iptr)

-       The other pointer is designated to store the address of a float (float* fptr)

-       Note that the asterix (*) can either be after the type or before the variable name

 

main()
{
    int *iptr;
    float* fptr;

    .....

}


Example 2

-       A structure, named Particle, is first declared to store an integer and a float array

-       A pointer is designated (Particle *p) to store the address of such a structure

 

struct Particle
{
    int id;
    float pos[3];
};

main()
{
    Particle *p;

    ....
}


 
 

 

Variable Address – how do we specify an address of a variable?

-       Unary operator & is used to represent the address of variable named var

-       Used before a variable name (not the type) returns the address of the variable

 

&var
 


 

 

Assigning pointers - how do we assign a pointer variable?
 

Example 1

main()
{
    int *iptr;
    int ivalue = 77;

    iptr = &ivalue;

    printf( "iptr contains ivalue's address: %d\n", iptr );

}

Output

iptr contains ivalue's address: 2147430016
 

Notes

The statement

iptr = &ivalue

 

assigns the address of the integer ivalue to the pointer variable iptr

Since the pointer variable contains the address, we can print it out to see the actual machine memory address (this will differ depending on the architecture and machine used)
 

Example 2

main()
{
    Particle p;
    Particle *pptr;

    pptr = &p;

    printf( "pptr contains p's address: %d\n", pptr );
}

Output

pptr contains p's address: 2147430008
 

Notes

pptr = &p
assigns the address of the structure p to the pointer variable pptr


 

 

Access - how do we access the variable a pointer is pointing to?

-       unary operator * is used to access the value the pointer ptr is pointing to

-       This is also called dereferencing or indirection

*ptr
 

Example 1

main()
{
    float value = 23.0;
    float *valuePtr;

    valuePtr = &value;

    printf( "Value: %f  Value pointer points to: %f\n", value, *valuePtr );
}

Output

Value: 23.000000  Value pointer points to: 23.000000
 
 

Example 2

main()
{
    Particle p;
    Particle *pptr = &p;

    p.id = 45;
    p.pos[0] = 1.0;
    p.pos[1] = 2.0;
    p.pos[2] = 3.0;

    printf( "pptr points to structure with contents:\n" );
    printf( "  id: %d\n", pptr->id );
    printf( "  pos: %f %f %f\n", pptr->pos[0],
                                 pptr->pos[1], pptr->pos[2]);
}

Output

pptr points to structure with contents:
  id: 45
  pos: 1.000000 2.000000 3.000000

Notes

We assigned the pointer variable before the structure elements were assigned

Recall that -> means "the element in the structure pointed to by...."

We could have also dereferenced structure elements as follows:

printf( "  id: %d\n", (*pptr).id );
printf( "  pos: %f %f %f\n", (*pptr).pos[0],
                             (*pptr).pos[1],(*pptr).pos[2]);

Recall that because the structure member operator . has a higher order of precedence than the indirection operator *, we need to dereference the pointer variable before we access the structure member

We can substitute the more readable -> for (*var)

 

 

 

Pointers as function parameters
   

Let’s use our knowledge of pointers to answer our original question about function parameters

What if we wanted to change the parameter variable contents in the calling program from within the function?

Instead of passing a variable, we can pass a variable's address to a function

We've reviewed that a variable's address can be stored in a pointer variable

Let's look more closely at what happens if we pass a pointer to a function

When a function is called with a pointer parameter...

Temporary memory space is setup to contain the pointer parameters

This temporary space in memory is called the stack

The actual value of the pointer (the address of some variable) is then copied into this temporary space

By dereferencing this copy, the function accesses the original variable value in it's original location

The function CAN actually modify the original value of the variable "pointed to" by the pointer!

This method of passing the address of parameters is also termed "call by reference"

Compare this method with call by value from part 1 of the notes

Note how the value at 1001 is changed by dereferencing the pointer in the function

diagram3

Example

void addToPtr( int *, int );

main()
{
    int ivalue = 10;
    int *iptr = &ivalue;

    printf( "ivalue before function is called: %d\n", ivalue );

    addToPtr( iptr, 10 );

    printf( "ivalue after function is called: %d\n", ivalue );
}

void addToPtr( int *valuePtr, int x )
{
    *valuePtr += x;
}

Output

ivalue before function is called: 10
ivalue after function is called: 20
 

Notes

Note that we must explicitly state we're passing a pointer variable in the function declaration and definition

void addToPtr( int *, int );

 


 

Arrays as function parameters

   When we pass arrays as parameters to functions, we are using this same method of "call by reference"

    When the name of an array is used as a parameter, the address of the array (or first element) is passed

int array[3];

someFunction( array ) and someFunction( &array[0] ) are identical


    The function can then reference, access, and alter original array values by subscripting ( i.e. [...])
 

Example

void addToArray( int [3], int );

main()
{
    int iarray[3] = { 10, 20, 30 };

    printf( "iarray values before function is called: %d %d %d\n",
                iarray[0],iarray[1],iarray[2] );

    addToArray( iarray, 10 );

    printf( "iarray values after function is called: %d %d %d\n",
                iarray[0],iarray[1],iarray[2] );
}

void addToArray( int iarray[3], int x )
{
    iarray[0] += x;
    iarray[1] += x;
    iarray[2] += x;
}

Output

iarray values before function is called: 10 20 30
iarray values after function is called: 20 30 40
 

Note

Because the array address is passed (technically a pointer), we could have also used the following notation for the function declaration and definition

void addToArray( int *, int );

void addToArray( int *iarray, int x )
{
    iarray[0] += x;
    iarray[1] += x;
    iarray[2] += x;
}

OR

void addToArray( int *iarray, int x )
{
    *(iarray + 0) += x;
    *(iarray + 1) += x;
    *(iarray + 2) += x;
} 

The two functions do the same thing.  Both are accessing sequential addresses in memory then dereferencing them to access the original variable values!