3.1. Header Files – The Basics

I promised I’d come back to header files and this is the section where they finally get the brief treatment they deserve at this stage. Basically, you can place function prototypes in header files rather than at the top of your source file, and then include them with the pre-processor directive #include. Unsurprisingly, this includes the contents of the header file in your source file. All you need to know at this stage is that the standard header files which come with the compiler should be enclosed in <> in the #include command to tell the compiler that they are located in one of its standard directories; any header files that you create should simply be enclosed in “”.

3.2. Decision-Making – The Basics

Often we need to make decisions based on the value of a particular variable. This section is not going to cover many aspects of decision-making, but it will give you as much information as you need for the moment. As an example, say we had an integer variable num, and we wanted to check whether it’s value was between 0 and 4, or between 5 and 10. If it was between 0 and 5, we would display, “num is between 0 and 4” and likewise for the other case. We would express this as follows:

if ((num >= 0) && (num <= 4))
printf("num is between 0 and 4");
else if ((num >= 5) && (num <= 10))
printf("num is between 5 and 10");

The only complicated thing about this is to determine what the && operator does. In this case it means “and,” so what we are really saying is if the first condition and the second condition are true then the whole expression is true, and if the whole expression is true then execute the following statement.

3.3. Passing Pointers as Parameters to Functions

Up till now, we have simply been writing functions which return a value. What happens if we wanted to change the value passed into the function without specifically returning a value? As an example, you might think we could simply write the following to increment num by increment:

void IncrementWrong(int num, int increment)
{
num+=increment;
}

Now, there is something wrong with this which you may not be able to spot unless you have some previous programming knowledge. When values are passed to functions, a copy of them is first made on what is known as the stack and it is this copy which is then passed to the function (this is known as passing by value). Although you change the copy within the function, the original remains unchanged so the function doesn’t work as expected. Instead, you need to pass a pointer to the variable you want to change, and change it indirectly within the function. That way, although a copy gets made of the pointer (which obviously you can’t modify within the function), its contents remain the same so we’re all good and can access the variable it points to:

void Increment(int *num, int increment)
{
*num+=increment;
}

Of course, when calling the function, we need to pass in the address of the integer we want to change, otherwise we end up trying to change an address somewhere else in memory (often jovially known as writing to la-la land).

Ever since C++ came along, there has been a better way to do this, which closely models other languages’ ability to pass values by reference instead of by value. This effectively means that you pass the variable as a pointer, but without the inherently time-consuming nature of constantly dereferencing parameters. This fantastic new method is called a reference. Basically this is another variable which you can define to be an alias of a variable. It works like this:

int num;
int &ref_num = num;

Two important restrictions are that a) references such as ref_num must be initialised as soon as they are declared and that b) once initialised they cannot be changed to point to another variable, they are fixed. Any change now to ref_num will change num itself. So, how is this useful for passing variables to functions? Look at this:

void Increment(int &num, int increment)
{
num+=increment;
}

As you can see, num is now an alias to any variable being passed in; it is the equivalent of a pointer managed internally by the compiler. Notice that we don’t need to dereference it this time.

3.4. C-Style Structures

Imagine we want to model an address book. We have lots of entries, each consisting of the person’s name and their address (and maybe even their telephone number). We could make a whole two arrays, one called Names and one called Addresses, but wouldn’t it be better to group the two together? They are, after all, related in our example. The way we do this is by using a structure as follows:

struct entry
{
char name[32];
char address[128];
};

Now is the time to do something cunning. We have a whole lot of entries in an address book, but we don’t have an address book. Let’s create an address book structure to hold our entries:

struct address_book
{
entry entries[32];
};

As you can see, this address_book structure can hold 32 entries. It is often better to define a value somewhere else and use it in place of an arbitrary value (“magic number”) chosen at random. For example:

// somewhere else in the program
#define MAX_ENTRIES 32

// back in the structure
entry entries[MAX_ENTRIES];

This makes it easier for readers of our program to understand what exactly our intention is. Now, say we wanted to immediately create a variable of the type of the structure we have just created (called an instance of the structure). How would we go about doing this? We could keep it simple:

// straight after structure definition
address_book AddressBook;

Or we could use the more compact approach:

struct address_book
{
entry entries[MAX_ENTRIES];
} AddressBook;

So, we have an address book and some entries, but how do we go about accessing them? Say we wanted to access the name associated with the first entry in the address book, we would use the following:

AddressBook.entries[0].name

The . (dot) operator is used to access members of structures. We can declare pointers to structures too, as shown by the following code:

address_book *AddressBook_ptr = new address_book;

Now, however, we appear to have a problem. How do we access members of a structure through a pointer? Well, those of you who are more perceptive may have spotted that:

(*AddressBook_ptr).entries[0].szName

would work as expected. However, this is far too much of a pain to use on a regular basis, so the creators of C decided that it would be much simpler to create a new operator (the structure pointer dereference operator or so I am reliably informed ). The operator -> is used for this purpose. Hence:

Structure_ptr->member == (*Structure_ptr).member

Note that the equality operator is ==, not simply = (which is the assignment operator). If you write = when you mean == in for example an if statement, the compiler will happily change the value of the variable for you despite the fact that you only wanted to compare the two:

if (i = 1)
{
// Oh dear, it’s 1...
}

What you meant to write here was:

if (i == 1)
{
// Comparison happens as expected.
}

It is a good idea to turn the warning level on your compiler up to the highest possible level: that way the compiler will warn you of the possibility that you’ve made an error when writing a statement like the above. Returning to our original theme, if we want to free the memory associated with the above address book, we would simply write:

delete AddressBook_ptr;

Notice that this time we must not use the square brackets since AddressBook_ptr does not point to an array.

Tags: ,

Leave a Reply

You must be logged in to post a comment.