This tutorial introduces you to structures and pointers in C. This tutorial is a variant of the original CSSE1000 C Programming Tutorial 5.
It is assumed that you are familiar with the material covered in previous tutorials and the associated readings.
The following textbook sections are relevant:
You may wish to create a new directory for this tutorial. You may type (cut and paste) each program in a separate C file or you may just use one C file and just overwrite your previous program each time (except if you're writing a program that spans multiple files).
A structure is a collection of one or more variables - possibly of different types. These variables are group together under a single name and form a user-defined data type.
Some example structure types are shown below:
struct student_record { char name[24]; unsigned long id; int yearlevel; float gpa; };
struct point { int x; int y; };
struct weather_reading { float temperature; int relative_humidity; int wind_direction; /* degrees */ float wind_speed; };
These structure definitions don't define variables - they define types that we can use elsewhere e.g. in structure variable declarations as well as in in other structure definitions:
/* declare pt to be a point */ struct point pt; /* declare an array of 100 student records - records[0] to records[99] */ struct student_record records[100]; /* define a structure type to hold a rectangle - and ** declare variables named rect1 and rect2 to hold rectangles */ /* This shows that the structure definition and declaration can be combined */ struct rectangle { struct point lower_left; struct point upper_right; } rect1, rect2;
Members of a structure variable can be accessed using the "." operator, e.g. (using the definitions and declarations above):
pt.x = 1; pt.y = 10; /* Print the GPA of all first year students */ int i; for (i=0; i<100; i++) { if(records[i].yearlevel == 1) { printf("%g\n", records[i].gpa); } }
The following program illustrates the use of functions with structures (as well as many other concepts from previous weeks). Note that like other primitive types, structures are passed by value (i.e. they're copied when passed to and from functions). The program also illustrates typedef declarations - declaring a type name. This lets us use the named type without having to use the full struct name. Try running the program to see what the output is. Make sure you can follow why the output is what it is.
#include <stdio.h> #define PI 3.14159265 /* Define a structure named point - and define a type named "point" which ** is the same as "struct point" */ typedef struct point { int x; int y; } point; /* We don't need to name the structure when using a typedef */ typedef struct { point ll; /* lower left */ point ur; /* upper right */ } rectangle; struct circ { point centre; int radius; }; /* The typedef can be separated from the structure definition and need not have the same name*/ typedef struct circ circle; /* Function prototypes */ point make_point(int x, int y); rectangle make_rectangle(point lower_left, point upper_right); int rectangle_area(rectangle r); double circle_area(circle c); int main() { /* It is possible to initialise structures when they are created (in a similar fashion to the ** way arrays can be initialised). We can refer to points as "point" or "struct point" */ point a = {1, 1}; struct point b = {5, 3}; point p; rectangle r1 = { {2, 2}, {5,6} }; rectangle r2; circle c1; p = make_point(-1, -1); c1.centre = p; /* Structures can be copied */ c1.radius = 4; r2 = make_rectangle(a, b); printf("Rectangle 1: (%d,%d) to (%d,%d). Area is %d\n", r1.ll.x, r1.ll.y, r1.ur.x, r1.ur.y, rectangle_area(r1)); printf("Rectangle 2: (%d,%d) to (%d,%d). Area is %d\n", r2.ll.x, r2.ll.y, r2.ur.x, r2.ur.y, rectangle_area(r2)); printf("Circle: centred at (%d,%d), radius %d. Area is %g\n", c1.centre.x, c1.centre.y, c1.radius, circle_area(c1)); } point make_point(int x, int y) { point p; p.x = x; p.y = y; return p; } rectangle make_rectangle(point ll, point ur) { rectangle r; r.ll = ll; r.ur = ur; return r; } int rectangle_area(rectangle r) { return (r.ur.y - r.ll.y) * (r.ur.x - r.ll.x); } double circle_area(circle c) { /* Note that because we multiply integers by a double, the result will be a double */ return (PI * c.radius * c.radius); }
1. Write a function that determines whether a rectangle is a square. It should have the function prototype
int is_square(rectangle r);
and return true (i.e. non-zero) if the given rectangle is a square, false (i.e. zero) otherwise. Incorporate the function into the program above and test it.
If a large structure is to be passed to a function, it is generally more efficient to pass a pointer than copy the whole structure. A pointer to a variable (of any type) is just the memory address of that variable. The address of a variable can be found using the "&" (address-of) operator. A pointer variable can be declared using the following syntax:
type *p;
This declares p to be a pointer to something of type "type". For example:
int a; int *p; p = &a; rectangle r; rectangle* r_ptr; r_ptr = &r;
This code declares p to be a pointer to an integer and makes it point to a (i.e. p will hold the address of a). r_ptr is declared to be a pointer to a rectangle and is set to point to rectangle r.
Note that the following declarations are identical and declare a to be an integer and p to be a pointer to an integer:
int a, *p; int* p, a; int *p, a;
This means that the "*" is associated with the variable name - not the type. For this reason, the second of these forms is NOT preferred - it looks as if a would be a pointer to an integer, but this is NOT the case.
A pointer can be dereferenced (i.e. the thing being pointed to can be accessed) using the "*" operator (known as the indirection or dereferencing operator). The following (artificial) example shows how a pointer can be dereferenced and how pointers don't always have to point to the same thing.
int x = 1; int y = 2; int z = 3; int *ip; ip = &x; /* ip points to x */ y = *ip; /* y will take on the value of the integer pointed to by ip */ ip = &z; /* ip now points to z */ *ip = 0; /* The integer pointed to by ip will take on the value 0 */
Dereferencing a pointer to a structure with the "*" operator can be quite cumbersome:
If pp is a pointer to a point structure, e.g.
struct point *pp;
then the members of the structure can be accessed using the syntax: (*pp).x and (*pp).y. Note that the parentheses are necessary here because the "." operator has a higher precedence than the "*" operators. *pp.x would mean *(pp.x) which is illegal because x is not a pointer. Because (*pp).x is quite cumbersome, C allows an alternative syntax: pp->x.
If p is a pointer to a structure, then the members can be accessed using
p->member-of-structure
Consider the following program (type this in and run it and make sure you understand why you get the result you do)
#include <stdio.h> typedef struct point { int x; int y; } point; /* It is sometimes useful to define pointer types */ typedef point* point_ptr; int main() { point a = {1, 2}; point b = {5, 3}; point_ptr p1, p2; /* Line above is the same as "point *p1, *p2;" */ printf("BEFORE: a is (%d,%d), b is (%d,%d)\n", a.x, a.y, b.x, b.y); p1 = &a; p2 = &b; p1->x = p2->x; p2 = &a; p2->x = p1->y; p1 = &b; p1->y = p2->y; printf(" AFTER: a is (%d,%d), b is (%d,%d)\n", a.x, a.y, b.x, b.y); }
Pointers are often used for accessing arrays, in fact the name of an array can be treated as a pointer to the first element on the array. Consider the following code:
/* Declare a to be an array of 10 integers and p to be a pointer to a integer */ int a[10]; int *p; /* Set p to point to the first element in the array */ p = &a[0]; /* We could have written p = a; here */ /* Set the first element in the array to be 1 */ *p = 1; /* Increment our pointer by 1 - this means p now points to the next integer */ p++; /* Set the next element in the array to be 2 */ *p = 2;
Note that we use pointer arithmetic in this example (by adding 1 to p). Arithmetic on pointers is treated specially - adding i to a pointer means the pointer will now point to the i-th object of that type beyond the pointer. (It does not add i to the memory address - it will add i *sizeof(type) where type is the type being pointed to.)
This also means an array reference
a[i]
can also be written
*(a+i)
The two forms are equivalent. It also follows that
&a[i]
is the same as
a+i
which is the reason we can replace
p = &a[0];
with
p = a;
if we wanted to. Note however that an array name is not a variable. We can not write a++ to advance the array position.
Note that strings are often referred to as type char* rather than char[].
All arguments to functions are passed by value - i.e. the value of the variable passed is copied for use in the function. (We saw in an earlier tutorial that arrays were an exception to this. This is because an array name is effectively just a pointer to the memory where the array is stored - we don't copy the whole array.) By passing pointers to functions, we enable the function access the actual variable/structure rather than a copy. (The function is passed a copy of the pointer - i.e. we can't change the value of the pointer in the calling function, but we can dereference the pointer and change what it points to.)
There are two circumstances in which we would want access to the original variable/structure rather than a copy. First, if the structure is large, it is cheaper (i.e. less CPU time and memory) to pass a pointer to the structure rather than copy it. Second, we may want to modify the original structure within a function.
Consider the function make_point() we saw above:
point make_point(int x, int y) { point p; p.x = x; p.y = y; return p; }
This was called as
p = make_point(-1, -1);
Instead of returning a point structure (which must be copied when the function returns), it is possible to pass a pointer to the point structure and have the function directly populate that structure:
void make_point(point* p, int x, int y) { p->x = x; p->y = y; }
which could be called as
make_point(&p, -1, -1);
Consider a function which attempts to swap two variable values, being called with
swap(a,b);
void swap(int x, int y) { /* WRONG */ int temp; temp = x; x = y; y = temp; }
This function does not swap the variable values - it just swaps copies of a and b. By using pointers to a and b, however, we can make a swap function that swaps the values of two variables:
void swap(int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; }
This function would be called as swap(&a, &b); rather than swap(a, b);
2. Modify the make_point() and make_rectangle() functions above so that instead of returning a structure, they accept a pointer to a structure argument and modify that structure. (This saves the structure being copied when these functions return.) The function prototypes should become:
void make_point(point* p, int x, int y);
void make_rectangle(rectangle* r, point lower_left, point upper_right);